<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Meiglyph</title>
    <description>The latest articles on DEV Community by Meiglyph (@meiglyph).</description>
    <link>https://dev.to/meiglyph</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4060126%2F05cec334-122b-4e7e-8fe9-e50748ea703a.png</url>
      <title>DEV Community: Meiglyph</title>
      <link>https://dev.to/meiglyph</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/meiglyph"/>
    <language>en</language>
    <item>
      <title>Automating Team Expense Reports with Python</title>
      <dc:creator>Meiglyph</dc:creator>
      <pubDate>Tue, 08 Sep 2026 11:16:40 +0000</pubDate>
      <link>https://dev.to/meiglyph/automating-team-expense-reports-with-python-30ik</link>
      <guid>https://dev.to/meiglyph/automating-team-expense-reports-with-python-30ik</guid>
      <description>&lt;p&gt;In many small teams, each employee submits their monthly expenses as a separate Excel file. Someone — usually whoever handles approvals — then has to open every single file and manually copy the rows into one master sheet before anything can be reviewed.&lt;/p&gt;

&lt;p&gt;It's slow, and it doesn't scale past a handful of people.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;expense-tool&lt;/strong&gt;, a small Python CLI that automates this: point it at a folder of expense files, and it produces one clean, aggregated summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this problem
&lt;/h2&gt;

&lt;p&gt;The usual alternatives don't quite fit for a lot of teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Excel macros&lt;/strong&gt; work, but require someone comfortable writing VBA to set up and maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Power Query&lt;/strong&gt; is powerful, but the setup isn't intuitive if you're not already familiar with Excel's data tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No-code automation platforms&lt;/strong&gt; (Zapier, Make) are great for connecting services, but are often overkill — and an extra subscription — for what is fundamentally a "read some files, sum some numbers" task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Meanwhile, the actual manual process (open each file, copy rows, paste into a master sheet) is exactly the kind of repetitive task that's easy to automate with a short script, once the shape of the problem is well-defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;expense-tool runs as a single CLI command and does three things in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Combine&lt;/strong&gt; — reads every Excel file in a folder and merges them into one table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate&lt;/strong&gt; — sums expense amounts by employee and category.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Format&lt;/strong&gt; — writes the result to a new Excel file with a bold header row, and highlights any row above a configurable amount threshold.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python expense_tool.py sample_data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summary written to expense_summary.xlsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Employee&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;john&lt;/td&gt;
&lt;td&gt;Meals&lt;/td&gt;
&lt;td&gt;68.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;john&lt;/td&gt;
&lt;td&gt;Office Supplies&lt;/td&gt;
&lt;td&gt;15.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;john&lt;/td&gt;
&lt;td&gt;Travel&lt;/td&gt;
&lt;td&gt;42.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sarah&lt;/td&gt;
&lt;td&gt;Meals&lt;/td&gt;
&lt;td&gt;145.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sarah&lt;/td&gt;
&lt;td&gt;Software&lt;/td&gt;
&lt;td&gt;29.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sarah&lt;/td&gt;
&lt;td&gt;Travel&lt;/td&gt;
&lt;td&gt;320.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rows above the threshold (sarah's Meals and Travel entries here) are highlighted in the output file, so whoever reviews the summary can spot larger expenses at a glance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design decisions
&lt;/h2&gt;

&lt;p&gt;A few choices shaped how the tool is put together — and why.&lt;/p&gt;

&lt;h3&gt;
  
  
  One file, three functions
&lt;/h3&gt;

&lt;p&gt;At this scale — three features — splitting the project across multiple files would have added structure without adding much clarity. Instead, &lt;code&gt;expense_tool.py&lt;/code&gt; has one function per feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;load_expense_files&lt;/code&gt; — reads and merges the input files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aggregate_expenses&lt;/code&gt; — groups and sums&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;write_formatted_excel&lt;/code&gt; — writes the styled output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single Typer command (&lt;code&gt;run&lt;/code&gt;) calls the three in order: load → aggregate → write. If the tool grows — more input formats, more output options — splitting into modules becomes worth the added structure. For now, one file stays easy to read top to bottom.&lt;/p&gt;

&lt;h3&gt;
  
  
  Employee name comes from the filename, not a column
&lt;/h3&gt;

&lt;p&gt;Since each file represents one employee's report, adding an "Employee" column inside every file would just be repeating the same value on every row. Instead, &lt;code&gt;load_expense_files&lt;/code&gt; derives the employee name from each file's name (&lt;code&gt;john.xlsx&lt;/code&gt; → &lt;code&gt;"john"&lt;/code&gt;) and adds it as a column only after merging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;folder_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.xlsx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;employee_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stem&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Employee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;employee_name&lt;/span&gt;
    &lt;span class="n"&gt;dataframes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps each individual file simple — no extra column employees have to fill in correctly — while still giving the merged table everything it needs for aggregation.&lt;/p&gt;

&lt;h3&gt;
  
  
  pandas for data, openpyxl for style
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;write_formatted_excel&lt;/code&gt; uses &lt;code&gt;pd.ExcelWriter&lt;/code&gt; with &lt;code&gt;engine="openpyxl"&lt;/code&gt;, so pandas handles writing the data and openpyxl handles the formatting on the same file afterward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ExcelWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openpyxl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sheet_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;worksheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sheets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# openpyxl styling happens here, on the same worksheet
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the data-processing logic (grouping, summing) written the simple pandas way, and treats formatting as a separate, later step — rather than mixing styling logic into the aggregation code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Highlighting is a plain loop over rows
&lt;/h3&gt;

&lt;p&gt;For highlighting rows above the threshold, there's no need for anything more elaborate than checking each row's &lt;code&gt;Amount&lt;/code&gt; and applying a &lt;code&gt;PatternFill&lt;/code&gt; when it exceeds the limit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;highlight_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;col_index&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;worksheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;row_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;col_index&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;highlight_fill&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;highlight_threshold&lt;/code&gt; is a parameter with a default value, so it's easy to adjust for a team with a different sense of what counts as a "large" expense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/meiglyph/expense-tool.git
&lt;span class="nb"&gt;cd &lt;/span&gt;expense-tool
pip &lt;span class="nb"&gt;install &lt;/span&gt;pandas openpyxl typer
python expense_tool.py &amp;lt;input_folder&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each input file just needs the columns: &lt;code&gt;Date&lt;/code&gt;, &lt;code&gt;Category&lt;/code&gt;, &lt;code&gt;Description&lt;/code&gt;, &lt;code&gt;Amount&lt;/code&gt;, &lt;code&gt;Payee&lt;/code&gt;, &lt;code&gt;Notes&lt;/code&gt;. Full details are in the &lt;a href="https://github.com/meiglyph/expense-tool" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The current version assumes every input file shares the same columns — a reasonable assumption inside a single team with a shared template, but not something to take for granted more broadly. A natural next step would be adding an optional department-level breakdown, or making the highlight threshold configurable from the command line instead of the function signature.&lt;/p&gt;

&lt;p&gt;For now, the core loop — combine, aggregate, format — does exactly what it needs to.&lt;/p&gt;

</description>
      <category>python</category>
      <category>excel</category>
      <category>automation</category>
      <category>cli</category>
    </item>
    <item>
      <title>I Built BuildNext: A Tiny Tracker That Won't Let My Side Projects Stall</title>
      <dc:creator>Meiglyph</dc:creator>
      <pubDate>Wed, 19 Aug 2026 08:58:26 +0000</pubDate>
      <link>https://dev.to/meiglyph/i-built-buildnext-a-tiny-tracker-that-wont-let-my-side-projects-stall-3k7c</link>
      <guid>https://dev.to/meiglyph/i-built-buildnext-a-tiny-tracker-that-wont-let-my-side-projects-stall-3k7c</guid>
      <description>&lt;p&gt;I have a problem a lot of indie devs have: too many side projects, and no idea&lt;br&gt;
what to actually &lt;em&gt;do&lt;/em&gt; next on any of them. Every project had a mental backlog&lt;br&gt;
a mile long, which meant every project felt overwhelming — so I'd avoid all of them.&lt;/p&gt;

&lt;p&gt;So I built BuildNext: a small Flask app with one rule baked into the data model&lt;br&gt;
itself — &lt;strong&gt;every project gets exactly one next action.&lt;/strong&gt; Not a todo list. One thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add projects and move them through five stages: Idea → Planning → Building →
Testing → Published (or Paused)&lt;/li&gt;
&lt;li&gt;Each project has exactly one "next action" — with an optional due date that
flags when it's due today or overdue&lt;/li&gt;
&lt;li&gt;"Mark as done" logs the completed action to that project's history, so I can
see momentum over time&lt;/li&gt;
&lt;li&gt;Optional blocker note + GitHub link per project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabk2iyqrezphxljju96l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabk2iyqrezphxljju96l.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;Flask + Flask-SQLAlchemy + SQLite, server-rendered Jinja templates, no JS&lt;br&gt;
framework. Deliberately boring — this is a tool, not a demo of a framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Right now it's a personal tool I run locally. No accounts, no cloud — just a&lt;br&gt;
GitHub repo I can point people to. If enough people want to actually use it&lt;br&gt;
rather than just read the code, a real deploy is the obvious next step.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/meiglyph/build-next" rel="noopener noreferrer"&gt;https://github.com/meiglyph/build-next&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>python</category>
      <category>buildinpublic</category>
      <category>flask</category>
    </item>
    <item>
      <title>Why I Kept My CLI Tool's Tags as Plain Text</title>
      <dc:creator>Meiglyph</dc:creator>
      <pubDate>Sun, 09 Aug 2026 05:59:43 +0000</pubDate>
      <link>https://dev.to/meiglyph/why-i-kept-my-cli-tools-tags-as-plain-text-eoa</link>
      <guid>https://dev.to/meiglyph/why-i-kept-my-cli-tools-tags-as-plain-text-eoa</guid>
      <description>&lt;h1&gt;
  
  
  Keeping Tags as Plain Text — Adding Features to brain-cli Without Losing the Point
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Why I Built This
&lt;/h2&gt;

&lt;p&gt;brain-cli started as a tiny CLI with exactly four commands: &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;show&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;. Under the hood, it just appends lines to Markdown files. No database, no index — a thin wrapper around plain text.&lt;/p&gt;

&lt;p&gt;After using it for a while, one thing started to bug me. The more notes piled up, the harder it got to find the one I wanted. &lt;code&gt;search&lt;/code&gt; only does substring matching, so there was no way to say "just show me the work-related notes" or "just the study log." So I added tagging.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;First, tagging on &lt;code&gt;add&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brain add &lt;span class="s2"&gt;"Built my first CLI command."&lt;/span&gt; &lt;span class="nt"&gt;--tag&lt;/span&gt; learning &lt;span class="nt"&gt;--tag&lt;/span&gt; cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--tag&lt;/code&gt; (short form &lt;code&gt;-t&lt;/code&gt;) can be repeated, and internally each tag just gets turned into a &lt;code&gt;#tag&lt;/code&gt; string appended to the line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; Built my first CLI command. #learning #cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, filtering &lt;code&gt;search&lt;/code&gt; by tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brain search SSH &lt;span class="nt"&gt;--tag&lt;/span&gt; work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a simple AND condition — only lines that match both the query and the tag get shown.&lt;/p&gt;

&lt;p&gt;Finally, a &lt;code&gt;tags&lt;/code&gt; command to see everything you've been using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brain tags
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Tags
┏━━━━━━━━━┓
┃ Tag     ┃
┡━━━━━━━━━┩
│ #python │
│ #study  │
└─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It scans every note, collects words starting with &lt;code&gt;#&lt;/code&gt;, dedupes them, and prints them alphabetically. That's the whole implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Tags Stay Plain Text
&lt;/h2&gt;

&lt;p&gt;This is the part I actually spent time thinking about. When implementing tags, there were two real options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store tags as structured data (YAML frontmatter, JSON metadata, etc.)&lt;/li&gt;
&lt;li&gt;Keep tags as literal &lt;code&gt;#tag&lt;/code&gt; strings inside the line&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I went with option 2, without much hesitation. The core value of brain-cli, as I see it, is that the storage is transparent plain text. You can open &lt;code&gt;~/.brain-cli/notes/*.md&lt;/code&gt; in any editor, with or without the CLI, and it just works. You can hand-write &lt;code&gt;#tag&lt;/code&gt; directly in your editor and it behaves exactly the same as if you'd used &lt;code&gt;--tag&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If I'd made tags structured metadata, brain-cli would have become a tool that "breaks" the moment you bypass the CLI. That would contradict the simplicity the tool was built around in the first place. When adding a feature, keeping the existing design intact mattered more than making the feature technically "cleaner."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Deliberately Didn't Build
&lt;/h2&gt;

&lt;p&gt;A couple of other commands came up as candidates alongside tagging. I considered both and skipped them — here's why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;brain rm&lt;/code&gt; (delete a note)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I decided this wasn't worth building. Since storage is just Markdown files, you can already delete one with &lt;code&gt;rm ~/.brain-cli/notes/2026-08-09.md&lt;/code&gt;. A dedicated command would mostly add complexity — confirmation prompts, safety checks — for very little actual benefit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;brain edit&lt;/code&gt; (open a note in $EDITOR)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the one feature I still think might be worth adding later. Fixing a typo or adding a follow-up thought isn't something you can do by just re-running &lt;code&gt;add&lt;/code&gt; — that's a real, recurring friction point. Since it would just delegate to &lt;code&gt;$EDITOR&lt;/code&gt;, the implementation itself would stay simple. If there's a "next feature," this is probably it.&lt;/p&gt;

&lt;p&gt;The general rule I used: bias toward not adding things, but let real, recurring friction override that bias.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Actually Built It
&lt;/h2&gt;

&lt;p&gt;Each feature went through the same cycle: implement, write tests, update the README, commit. Instead of building everything at once, I finished one feature completely before starting the next. By the end, there were 10 passing tests and two commits pushed to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;The part that actually took time wasn't writing the code — it was deciding how much to structure, and what not to build. The more a tool grows, the harder it gets to keep it simple. This time, I think that trade-off worked out.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>cli</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Hello, DEV Community!</title>
      <dc:creator>Meiglyph</dc:creator>
      <pubDate>Tue, 04 Aug 2026 02:49:19 +0000</pubDate>
      <link>https://dev.to/meiglyph/hello-dev-community-map</link>
      <guid>https://dev.to/meiglyph/hello-dev-community-map</guid>
      <description>&lt;p&gt;Hi, I’m Meiglyph.&lt;/p&gt;

&lt;p&gt;I have a background in design, and I’m currently learning Python and exploring software development.&lt;/p&gt;

&lt;p&gt;As I build small tools and web applications, I often discover useful lessons about programming, debugging, user experience, and the development process itself.&lt;/p&gt;

&lt;p&gt;On DEV, I plan to share:&lt;/p&gt;

&lt;p&gt;・What I learn while studying Python&lt;br&gt;
・Lessons and mistakes from my projects&lt;br&gt;
・Practical tips I discover during development&lt;br&gt;
・Thoughts on combining design and programming&lt;/p&gt;

&lt;p&gt;I’m still learning, so this blog will also be a record of my progress.&lt;/p&gt;

&lt;p&gt;I hope my experiences can be useful to other beginners and people who are building their own projects.&lt;/p&gt;

&lt;p&gt;Thanks for reading. I’m looking forward to learning and sharing with the DEV community!&lt;/p&gt;

</description>
      <category>introduction</category>
      <category>python</category>
      <category>design</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
