<?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: Nicholas Drone</title>
    <description>The latest articles on DEV Community by Nicholas Drone (@ndrone).</description>
    <link>https://dev.to/ndrone</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%2F6521%2F0e139613-3893-4891-96f7-384fe6c1856e.jpg</url>
      <title>DEV Community: Nicholas Drone</title>
      <link>https://dev.to/ndrone</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ndrone"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Nicholas Drone</dc:creator>
      <pubDate>Tue, 23 Jun 2026 13:21:05 +0000</pubDate>
      <link>https://dev.to/ndrone/-3c17</link>
      <guid>https://dev.to/ndrone/-3c17</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/ndrone/building-a-custom-status-line-for-claude-code-5822" class="crayons-story__hidden-navigation-link"&gt;Building a Custom Status Line for Claude Code&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/ndrone" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F6521%2F0e139613-3893-4891-96f7-384fe6c1856e.jpg" alt="ndrone profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/ndrone" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nicholas Drone
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nicholas Drone
                
              
              &lt;div id="story-author-preview-content-3971073" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/ndrone" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F6521%2F0e139613-3893-4891-96f7-384fe6c1856e.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nicholas Drone&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/ndrone/building-a-custom-status-line-for-claude-code-5822" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 23&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/ndrone/building-a-custom-status-line-for-claude-code-5822" id="article-link-3971073"&gt;
          Building a Custom Status Line for Claude Code
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/claude"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;claude&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/ndrone/building-a-custom-status-line-for-claude-code-5822" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/ndrone/building-a-custom-status-line-for-claude-code-5822#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              1&lt;span class="hidden s:inline"&gt;&amp;nbsp;comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Building a Custom Status Line for Claude Code</title>
      <dc:creator>Nicholas Drone</dc:creator>
      <pubDate>Tue, 23 Jun 2026 13:20:37 +0000</pubDate>
      <link>https://dev.to/ndrone/building-a-custom-status-line-for-claude-code-5822</link>
      <guid>https://dev.to/ndrone/building-a-custom-status-line-for-claude-code-5822</guid>
      <description>&lt;p&gt;I spend most of my day in Claude Code. It's my pair programmer, code reviewer, and rubber duck all rolled into one. After a few days of using it, I realized I had no idea how many tokens I'd burned, how much the session was costing, or how close I was to filling the context window.&lt;/p&gt;

&lt;p&gt;Every once in a while I'd run &lt;code&gt;/cost&lt;/code&gt;, think "that's higher than I expected," and go back to work.&lt;/p&gt;

&lt;p&gt;That lasted about a day.&lt;/p&gt;

&lt;p&gt;I like dashboards. If I'm consuming resources or spending money, I want to see it happening in real time. I don't want another command I have to remember to run. I want to glance at the bottom of my terminal and immediately know what's going on.&lt;/p&gt;

&lt;p&gt;So I built a custom status line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why
&lt;/h2&gt;

&lt;p&gt;There were three things I wanted to see.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;p&gt;Claude Opus isn't free, and long sessions can get surprisingly expensive. Having a running total visible all the time changes how I work. It's the difference between checking your bank account once a month and having your balance on your phone's home screen.&lt;/p&gt;

&lt;p&gt;When I see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;session:$1.47
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I immediately know whether I'm still in "keep going" territory or whether it's time to wrap up the current task and start fresh.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context
&lt;/h3&gt;

&lt;p&gt;Claude Code automatically compacts conversations when you get close to the context limit, but I'd rather make that decision myself.&lt;/p&gt;

&lt;p&gt;Watching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ctx:81% left
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;slowly become&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ctx:32% left
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;during a long refactor tells me it's probably time to commit what I have and open a new session before I lose momentum.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The context percentage reported by the status line is the raw value Claude Code provides. In my experience, Claude will start warning that the context window is getting low when the status line still shows roughly &lt;strong&gt;28% remaining&lt;/strong&gt;. The warning itself says about &lt;strong&gt;14%&lt;/strong&gt; is left. My guess is Claude is intentionally conservative and reserves part of the context window for compaction and generating responses, so it alerts you well before you actually hit the limit. I haven't found official documentation confirming this behavior, but it's been consistent enough that I plan around the status line value rather than waiting for the warning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Orientation
&lt;/h3&gt;

&lt;p&gt;I'm constantly jumping between repositories and branches.&lt;/p&gt;

&lt;p&gt;At a glance I want to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What project am I in?&lt;/li&gt;
&lt;li&gt;What branch am I on?&lt;/li&gt;
&lt;li&gt;Which model am I using?&lt;/li&gt;
&lt;li&gt;How long has this session been running?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's the same reason every shell prompt shows the current git branch. Reducing context switching is free productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The status line API is almost disappointingly simple.&lt;/p&gt;

&lt;p&gt;You point Claude Code at a shell script. Every few seconds it pipes a JSON document to the script's stdin, and whatever the script prints becomes the status line.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;No SDK.&lt;/p&gt;

&lt;p&gt;No framework.&lt;/p&gt;

&lt;p&gt;No API.&lt;/p&gt;

&lt;p&gt;Just stdin and stdout.&lt;/p&gt;

&lt;p&gt;Even better, &lt;strong&gt;it costs nothing&lt;/strong&gt;. Everything runs locally. Claude Code sends your script session information over local IPC, your script formats it, and the result gets rendered in the terminal. Refresh it every second for an entire workday if you want—it won't consume a single token.&lt;/p&gt;

&lt;p&gt;Here's the JSON I'm interested in (trimmed down a bit):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/nsdrone/gitrepos/my-project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-opus-4-6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Opus"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_cost_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;342000&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"remaining_percentage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;81.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"current_usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;89400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cache_read_input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64200&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My script parses the JSON with a single &lt;code&gt;jq&lt;/code&gt; call, formats everything, and prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/gitrepos/my-project (main) »    Opus | 5m | in:89.4k out:12.3k cache:64.2k | ctx:81% left | session:$1.47
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's everything I care about and nothing I don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Script
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;~/.claude/statusline.sh&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;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Claude Code status line — renders session info at the bottom of the terminal.&lt;/span&gt;
&lt;span class="c"&gt;# Claude Code pipes session JSON to stdin every refreshInterval seconds;&lt;/span&gt;
&lt;span class="c"&gt;# whatever this script prints to stdout becomes the status bar.&lt;/span&gt;

&lt;span class="nv"&gt;INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Single jq call extracts all fields at once. @sh produces shell-safe quoted&lt;/span&gt;
&lt;span class="c"&gt;# strings so eval can assign them without word-splitting issues.&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'
  @sh "CWD=\(.cwd // empty)",
  @sh "GIT_BRANCH=\(.git.branch // empty)",
  @sh "MODEL=\(.model.display_name // .model // empty)",
  @sh "INPUT_TOKENS=\(.context_window.current_usage.input_tokens // 0)",
  @sh "OUTPUT_TOKENS=\(.context_window.current_usage.output_tokens // 0)",
  @sh "CACHE_READ=\(.context_window.current_usage.cache_read_input_tokens // 0)",
  @sh "COST_USD=\(.cost.total_cost_usd // 0)",
  @sh "CTX_REMAINING=\(.context_window.remaining_percentage // 100)",
  @sh "DURATION_MS=\(.cost.total_duration_ms // 0)"
'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Collapse /Users/whoever to ~ for a shorter path&lt;/span&gt;
&lt;span class="nv"&gt;CWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CWD&lt;/span&gt;&lt;span class="p"&gt;/#&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="p"&gt;/~&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 1234 → "1.2k", 1234567 → "1.2M", 500 → "500"&lt;/span&gt;
format_tokens&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ge&lt;/span&gt; 1000000 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s.%sM"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;n &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1000000&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;n &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="m"&gt;1000000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;100000&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ge&lt;/span&gt; 1000 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%s.%sk"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;n &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;n &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Under an hour: "12m". Over an hour: "1h23m".&lt;/span&gt;
format_duration&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;ms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;total_sec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;ms &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;total_sec &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;hrs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;min &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;remaining_min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;min &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$hrs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ge&lt;/span&gt; 1 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%dh%dm"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$hrs&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$remaining_min&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%dm"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$min&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;IN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;format_tokens &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT_TOKENS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;format_tokens &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT_TOKENS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CACHE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;format_tokens &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CACHE_READ&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;COST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;%.2f"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COST_USD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DUR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;format_duration &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DURATION_MS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Left side: working directory and branch&lt;/span&gt;
&lt;span class="nv"&gt;LEFT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CWD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nv"&gt;LEFT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LEFT&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="nv"&gt;$GIT_BRANCH&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nv"&gt;LEFT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LEFT&lt;/span&gt;&lt;span class="s2"&gt; »"&lt;/span&gt;

&lt;span class="c"&gt;# Right side: model, duration, tokens, context remaining, cost&lt;/span&gt;
&lt;span class="nv"&gt;RIGHT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MODEL&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="nv"&gt;$DUR&lt;/span&gt;&lt;span class="s2"&gt; | in:&lt;/span&gt;&lt;span class="nv"&gt;$IN&lt;/span&gt;&lt;span class="s2"&gt; out:&lt;/span&gt;&lt;span class="nv"&gt;$OUT&lt;/span&gt;&lt;span class="s2"&gt; cache:&lt;/span&gt;&lt;span class="nv"&gt;$CACHE&lt;/span&gt;&lt;span class="s2"&gt; | ctx:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CTX_REMAINING&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% left | session:&lt;/span&gt;&lt;span class="nv"&gt;$COST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s    %s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LEFT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RIGHT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Make It Executable
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/.claude/statusline.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Wire It Up
&lt;/h2&gt;

&lt;p&gt;Add this to &lt;code&gt;~/.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~/.claude/statusline.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"refreshInterval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The refresh interval is measured in seconds. I use &lt;code&gt;1&lt;/code&gt; because I like seeing everything update in real time. If that's overkill, bump it to &lt;code&gt;5&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Test It
&lt;/h2&gt;

&lt;p&gt;You don't have to launch Claude Code to verify the script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"cwd":"/Users/you/project","git":{"branch":"main"},"model":{"display_name":"Opus"},"context_window":{"current_usage":{"input_tokens":45000,"output_tokens":8000,"cache_read_input_tokens":32000},"remaining_percentage":78},"cost":{"total_cost_usd":0.83,"total_duration_ms":420000}}'&lt;/span&gt; | ~/.claude/statusline.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/project (main) »    Opus | 7m | in:45.0k out:8.0k cache:32.0k | ctx:78% left | session:$0.83
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Restart Claude Code
&lt;/h2&gt;

&lt;p&gt;Start a new session and your status line will be waiting at the bottom of the terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make It Yours
&lt;/h2&gt;

&lt;p&gt;The best part is that it's just a shell script.&lt;/p&gt;

&lt;p&gt;A few ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add ANSI colors when context gets low.&lt;/li&gt;
&lt;li&gt;Show &lt;code&gt;+256/-43&lt;/code&gt; lines changed.&lt;/li&gt;
&lt;li&gt;Build a multi-line status bar.&lt;/li&gt;
&lt;li&gt;Add git dirty state.&lt;/li&gt;
&lt;li&gt;Add a progress bar.&lt;/li&gt;
&lt;li&gt;Whatever else you can do in bash.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you don't want to write any of this yourself, Claude Code already has a shortcut:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/statusline show me model, branch, token counts, context percentage, and cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will generate and wire up a status line for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This was the first thing I customized in Claude Code, and it completely changed how I use it.&lt;/p&gt;

&lt;p&gt;Once you realize that status lines, hooks, and other extension points are just shell scripts receiving structured JSON, the whole system starts to click. You stop thinking about "features" and start thinking about automation.&lt;/p&gt;

&lt;p&gt;And that's where the fun begins.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>claude</category>
    </item>
    <item>
      <title>Automated Service Principal Password Rotation</title>
      <dc:creator>Nicholas Drone</dc:creator>
      <pubDate>Thu, 04 Nov 2021 19:53:26 +0000</pubDate>
      <link>https://dev.to/ndrone/automated-service-principal-password-rotation-571f</link>
      <guid>https://dev.to/ndrone/automated-service-principal-password-rotation-571f</guid>
      <description>&lt;h2&gt;
  
  
  Assumptions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Knowledge of Terraform&lt;/li&gt;
&lt;li&gt;Knowledge of using Terraform with Azure&lt;/li&gt;
&lt;li&gt;Knowledge of GitHub Actions&lt;/li&gt;
&lt;li&gt;Knowledge of Bash Scripting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;This code block holds all the Azure resources built out by Terraform. The resource group and key vault are not required. It's only to show the storage of the service principal password and to show that when a terraform apply -replace is done that whatever is consuming the output of that resource is also updated. After the resource group and key vault are two service principals examples for this example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  backend "azurerm" {
    subscription_id      = "xxx-xxx-xxx-xxx"
    resource_group_name  = "tfstate"
    storage_account_name = "dronetfstate"
    container_name       = "tfstate"
    key                  = "sp_rotate.tfstate"
  }
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "2.83.0"
    }
    azuread = {
      source  = "hashicorp/azuread"
      version = "2.8.0"
    }
  }
}

provider "azurerm" {
  features {}
}

provider "azuread" {
}

resource "azurerm_resource_group" "example" {
  name     = "example"
  location = "Eastus2"
}

data "azurerm_client_config" "current" {}

resource "azurerm_key_vault" "example" {
  name                        = "droneexamplekeyvault"
  location                    = azurerm_resource_group.example.location
  resource_group_name         = azurerm_resource_group.example.name
  enabled_for_disk_encryption = true
  tenant_id                   = data.azurerm_client_config.current.tenant_id
  soft_delete_retention_days  = 7
  purge_protection_enabled    = false

  sku_name = "standard"

  access_policy {
    tenant_id = data.azurerm_client_config.current.tenant_id
    object_id = data.azurerm_client_config.current.object_id

    secret_permissions = [
      "Get",
      "List",
      "Set"
    ]
  }
}

data "azuread_client_config" "current" {}

resource "azuread_application" "example1" {
  display_name = "example1"
  owners       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal" "example1" {
  application_id               = azuread_application.example1.application_id
  app_role_assignment_required = false
  owners                       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal_password" "example1" {
  service_principal_id = azuread_service_principal.example1.object_id
}

resource "azurerm_key_vault_secret" "example1" {
  name         = "example1"
  value        = azuread_service_principal_password.example1.value
  key_vault_id = azurerm_key_vault.example.id
}

resource "azuread_application" "example2" {
  display_name = "example2"
  owners       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal" "example2" {
  application_id               = azuread_application.example2.application_id
  app_role_assignment_required = false
  owners                       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal_password" "example2" {
  service_principal_id = azuread_service_principal.example2.object_id
}

resource "azurerm_key_vault_secret" "example2" {
  name         = "example2"
  value        = azuread_service_principal_password.example2.value
  key_vault_id = azurerm_key_vault.example.id
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When having service principals created its good security practice to rotate those passwords on a regular basis. If you are using GitHub Actions to already apply/execute your resource changes, then adding a workflow to then rotate the password by doing forcing a replacement of the password resource causes a new password to be generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotation Workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Rotate Secrets

on:
  schedule:
    - cron: '0 1 1 12/3 *' # run every 4 months.
  workflow_dispatch:

env:
  ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
  ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
  ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
  ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
  TF_IN_AUTOMATION: true

jobs:
  replace:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1
        with:
          terraform_version: 1.0.8
      - name: Terraform Init
        run: terraform init -input=false -no-color
        # Pull all the resources from the state file put them in a file for the next step
      - name: Terraform List State Resources
        run: terraform state list &amp;gt; stateList
        # We are going to loop through each line in the file of the resources
        # We only want to replace the service_principal_password resource, so we
        # need to check the start of each resource starts with the correct address.
      - name: Terraform Replace
        run: while read target; do if [[ "${target:0:34}" == "azuread_service_principal_password" ]]; then terraform apply -replace="$target" -input=false -no-color -auto-approve; fi; done &amp;lt; stateList
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last two steps are where the work is really done. First we want to create a temporary file to store all the resources within the state file. Echoing out the created file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data.azuread_client_config.current
data.azurerm_client_config.current
azuread_application.example1
azuread_application.example2
azuread_service_principal.example1
azuread_service_principal.example2
azuread_service_principal_password.example1
azuread_service_principal_password.example2
azurerm_key_vault.example
azurerm_key_vault_secret.example1
azurerm_key_vault_secret.example2
azurerm_resource_group.example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the following step we are iterating over all those resources and looking for the ones with the block label of &lt;code&gt;azuread_service_principal_password&lt;/code&gt;. When we find those block labels we then want to pass the entire address to Terraform to force  a replacement of those resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;while read target; do 
    if [[ "${target:0:34}" == "azuread_service_principal_password" ]]; then 
        terraform apply -replace="$target" -input=false -no-color -auto-approve
    fi
done &amp;lt; stateList
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>terraform</category>
      <category>azure</category>
      <category>github</category>
      <category>devops</category>
    </item>
    <item>
      <title>Repositories are not just for Databases</title>
      <dc:creator>Nicholas Drone</dc:creator>
      <pubDate>Thu, 14 Nov 2019 23:01:32 +0000</pubDate>
      <link>https://dev.to/ndrone/repositories-are-not-just-for-databases-4a85</link>
      <guid>https://dev.to/ndrone/repositories-are-not-just-for-databases-4a85</guid>
      <description>&lt;p&gt;This blog post came out of the necessity of my own mistake. I have been working strictly with databases longer than I care to admit to. So in a project, we were making changes so that a specific application didn't have direct database access to a different application. Simple separation of concerns right, and microservice architecture. All that jazz right? So I brought in an HttpClient into the service layer, removing the repository. It seemed right at the time. Not sure why, several weeks later this pattern was copied by may other developers on the project, and just turn that project to no longer a clean looking platform of microservices. Then one day it hit me. The HTTP crud operations were doing the same thing as the database operations. And both should be treated as a datastore. Whether the data lives in a file of any structure, HTTP request, and or database. The access to that data should all live in the repository layer. &lt;/p&gt;

&lt;p&gt;The code in this blog can be seen &lt;a href="https://github.com/ndrone/repositoryisnotjustdatabase" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The git repository as of writing this post contains a library module with several sub-modules. The idea is that the libraries are then used by the applications that have yet to be written to further explain why repositories are for &lt;strong&gt;datastores&lt;/strong&gt; not just databases. &lt;/p&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%2Fraw.githubusercontent.com%2Fndrone%2Fdev.to%2Fmaster%2Fblog-posts%2Frepositories-are-not-just-for-db%2Ftps%3A%2F%2Fraw.githubusercontent.com%2Fndrone%2Fdev.to%2Fmaster%2Fblog-posts%2Frepositories-are-not-just-for-db%2Fassets%2FlibraryModules.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%2Fraw.githubusercontent.com%2Fndrone%2Fdev.to%2Fmaster%2Fblog-posts%2Frepositories-are-not-just-for-db%2Ftps%3A%2F%2Fraw.githubusercontent.com%2Fndrone%2Fdev.to%2Fmaster%2Fblog-posts%2Frepositories-are-not-just-for-db%2Fassets%2FlibraryModules.png" alt="Image of library modules" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's ignore the contact-core-test module for now. It contains a base test class. Each ContactRepository implementation test class can extend the base test class, and then only needs to write tests based on their implementation, but should pass all tests in the base class. I may turn that into a blog post of its own.&lt;/p&gt;

&lt;p&gt;The contact-core module. This is a small simple module that contains the contact object. The repository interface for the contact object. The exception class for that object, and the validator class. The validator class just validates that the inputs specified in the methods of the interface adhere to the specifications of the javadocs of the interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * A simple ContactRepository. Any implementation of this interface should
 * leverage {@link ContactRepositoryValidator} to validate the overriding methods
 * arguments. The implementations Tests classes should extend ContactRepositoryTests
 *
 * @see contact-core-test module
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ContactRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Search for the contact with the existing &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;
   *
   * @param id of the contact to be searched for
   * @return &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; maybe &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; if &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt;
   * with the &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; doesn't exist
   * @throws IllegalArgumentException if &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;
   * @throws ContactException         if an error has occurred
   */&lt;/span&gt;
  &lt;span class="nx"&gt;Contact&lt;/span&gt; &lt;span class="nf"&gt;findContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Integer&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;throws&lt;/span&gt; &lt;span class="nx"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ContactException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Searching for &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; that exactly matches the parameters
   * passed into the method.
   *
   * @param firstName to be exactly searched with
   * @param lastName  to be exactly searched with
   * @return a collection of &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; that match the parameters
   * @throws IllegalArgumentException if both parameters are &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;
   * @throws ContactException         if an error has occurred
   */&lt;/span&gt;
  &lt;span class="nx"&gt;Iterable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Contact&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findContacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;throws&lt;/span&gt; &lt;span class="nx"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ContactException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Saves a new or updated contact information. If a &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; is passed with the
   * &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;. The contact will be treated as a new contact, and
   * an &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; will be generated for it. If an &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; has an existing
   * &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; the &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; with be updated.
   *
   * @param contact to be saved or updated
   * @return saved &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt;
   * @throws IllegalArgumentException if a &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; with an existing &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;
   *                                  can not be found.
   * @throws ContactException         if an error has occurred
   */&lt;/span&gt;
  &lt;span class="nx"&gt;Contact&lt;/span&gt; &lt;span class="nf"&gt;saveContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Contact&lt;/span&gt; &lt;span class="nx"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;throws&lt;/span&gt; &lt;span class="nx"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ContactException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contact-file, contact-http, and contact-jdbc are all modules that implement the &lt;code&gt;ContactRepository&lt;/code&gt;. So an application then can bring in the specific technology implementation and build an application based on the interface. &lt;/p&gt;

&lt;h2&gt;
  
  
  A hypothetical scenario
&lt;/h2&gt;

&lt;p&gt;As a product team, we are spinning up a contact service. We've requested a database, but that won't be ready for weeks, and the boss wants it done by next week. Well, we still can write that service. The rest application is written to the &lt;code&gt;ContactRepository&lt;/code&gt; interface. We bring in the contact-file dependency for the short term while we are waiting on our database to be complete. Once the database is complete we then can swap out the contact-file dependency for the jdbc one, only changing enough code to connect to the database. Then file contents are moved to the database after deployment. The boss shows off your work to another team that wants access to the contact info. Except they can't get past the firewall use the contact-jdbc implementation. Not that you wanted them to. So you create the HTTP implementation to your applications URL endpoints. Then they just need to write code to the interface you just did, but leverage HTTP to get the same data you got from the JDBC implementation. The good news is if your API change, you can control the HTTP implementation too, so you can save your customer the headache of handling the versioning, just give them the new HTTP jar with the versioned changes in it.&lt;/p&gt;

</description>
      <category>oop</category>
    </item>
  </channel>
</rss>
