[{"data":1,"prerenderedAt":1024},["ShallowReactive",2],{"article:two-herdr-mods-github-tasks-and-plan-usage":3,"\u002Fblog\u002Ftwo-herdr-mods-github-tasks-and-plan-usage-surround":1013},{"id":4,"title":5,"archived":6,"author":7,"body":8,"date":1000,"description":1001,"extension":1002,"image":1003,"meta":1004,"minRead":260,"navigation":207,"path":1005,"published":207,"seo":1006,"sitemap":1007,"stem":1008,"tags":1009,"__hash__":1012},"blog\u002Fblog\u002Ftwo-herdr-mods-github-tasks-and-plan-usage.md","Two Herdr Mods: GitHub Tasks and Plan Usage",false,"Baljeet Singh",{"type":9,"value":10,"toc":993},"minimark",[11,37,40,62,69,78,83,114,125,128,593,597,604,609,636,643,654,658,665,668,671,695,783,786,790,796,816,824,839,937,956,960,989],[12,13,14,15,22,23,27,28,31,32,36],"p",{},"I run several coding agents at once in ",[16,17,21],"a",{"href":18,"rel":19},"https:\u002F\u002Fherdr.dev",[20],"nofollow","Herdr",", a desktop app for terminals and agents. Two questions kept pulling me out of it: ",[24,25,26],"em",{},"what should I work on next?"," (the answer was on my GitHub project board, in a browser tab) and ",[24,29,30],{},"how much of my Claude plan have I used?"," (the answer was a ",[33,34,35],"code",{},"\u002Fusage"," command away, in whichever agent I happened to be in).",[12,38,39],{},"Herdr lets you add your own panels to its window, so I moved both answers into it:",[41,42,43,56],"ul",{},[44,45,46,47,51,52,55],"li",{},"a ",[48,49,50],"strong",{},"Tasks"," panel beside Files and Changes, showing the board's In Progress, In Review and Todo cards for the repo I'm in, each with a ",[48,53,54],{},"Start"," button;",[44,57,46,58,61],{},[48,59,60],{},"usage strip"," at the right of the status bar, showing Claude and Codex plan usage with time to reset.",[12,63,64],{},[65,66],"img",{"alt":67,"src":68},"Herdr with the Tasks panel open on the right and the usage strip in the status bar, on sample data","\u002Fimg\u002Fherdr-full-window.png",[12,70,71,72,77],{},"Both are open source: ",[16,73,76],{"href":74,"rel":75},"https:\u002F\u002Fgithub.com\u002Fmbaljeetsingh\u002Fherdr-tasks",[20],"mbaljeetsingh\u002Fherdr-tasks",". You can take either one on its own.",[79,80,82],"h2",{"id":81},"what-a-herdr-mod-is","What a Herdr mod is",[12,84,85,86,89,90,93,94,97,98,101,102,105,106,109,110,113],{},"A mod is a folder in ",[33,87,88],{},"~\u002F.config\u002Fherdr-app\u002Fmods\u002F"," with a ",[33,91,92],{},"main.js"," and a small ",[33,95,96],{},"mod.json",". The JavaScript describes native UI elements (",[33,99,100],{},"div()",", flex, ",[33,103,104],{},".on_click","), and Herdr renders them natively. There is no HTML, CSS or web view. A ",[33,107,108],{},"herdr"," module gives the mod the app's live data (spaces, tabs, agents, the focused terminal, the theme's colors) and a few ways to act: run an app command, run a program with ",[33,111,112],{},"exec",", open a popup.",[12,115,116,117,120,121,124],{},"Saving a file reloads the mod in place, and ",[33,118,119],{},"herdr-app check mods\u002F\u003Cname>"," loads and renders it once and fails with the exact error. Herdr also writes an ",[33,122,123],{},"AGENTS.md"," into its config folder that explains all of this, so the usual way to build a mod is to ask your agent for one.",[12,126,127],{},"Here is the smaller of the two, the usage strip, cut down:",[129,130,136],"pre",{"className":131,"code":132,"filename":133,"language":134,"meta":135,"style":135},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { View } from \"gpui-kit\";\nimport { agents, setShared } from \"herdr\";\n\nexport default class Usage extends View {\n  init(_props, cx) {\n    cx.timer.every(5 * 60 * 1000, (cx) => this.refresh(cx));\n    \u002F\u002F An agent finishing a turn is when usage moves: refetch then.\n    this.working = new Set();\n    cx.timer.every(5000, (cx) => {\n      const now = new Set(agents().filter((a) => a.status === \"working\").map((a) => a.pane));\n      \u002F\u002F at most every 2 minutes\n      if ([...this.working].some((p) => !now.has(p))) this.refresh(cx, 2 * 60 * 1000);\n      this.working = now;\n    });\n  }\n  \u002F\u002F refresh() fetches and stores the result with setShared(\"usage.data\", …),\n  \u002F\u002F so the popup opened by a click reads the same numbers.\n}\n","mods\u002Fusage\u002Fmain.js","js","",[33,137,138,174,202,209,234,258,321,328,351,381,463,469,545,559,569,575,581,587],{"__ignoreMap":135},[139,140,143,147,151,155,158,161,164,168,171],"span",{"class":141,"line":142},"line",1,[139,144,146],{"class":145},"s7zQu","import",[139,148,150],{"class":149},"sMK4o"," {",[139,152,154],{"class":153},"sTEyZ"," View",[139,156,157],{"class":149}," }",[139,159,160],{"class":145}," from",[139,162,163],{"class":149}," \"",[139,165,167],{"class":166},"sfazB","gpui-kit",[139,169,170],{"class":149},"\"",[139,172,173],{"class":149},";\n",[139,175,177,179,181,184,187,190,192,194,196,198,200],{"class":141,"line":176},2,[139,178,146],{"class":145},[139,180,150],{"class":149},[139,182,183],{"class":153}," agents",[139,185,186],{"class":149},",",[139,188,189],{"class":153}," setShared",[139,191,157],{"class":149},[139,193,160],{"class":145},[139,195,163],{"class":149},[139,197,108],{"class":166},[139,199,170],{"class":149},[139,201,173],{"class":149},[139,203,205],{"class":141,"line":204},3,[139,206,208],{"emptyLinePlaceholder":207},true,"\n",[139,210,212,215,218,222,226,229,231],{"class":141,"line":211},4,[139,213,214],{"class":145},"export",[139,216,217],{"class":145}," default",[139,219,221],{"class":220},"spNyl"," class",[139,223,225],{"class":224},"sBMFI"," Usage",[139,227,228],{"class":220}," extends",[139,230,154],{"class":224},[139,232,233],{"class":149}," {\n",[139,235,237,241,244,248,250,253,256],{"class":141,"line":236},5,[139,238,240],{"class":239},"swJcz","  init",[139,242,243],{"class":149},"(",[139,245,247],{"class":246},"sHdIc","_props",[139,249,186],{"class":149},[139,251,252],{"class":246}," cx",[139,254,255],{"class":149},")",[139,257,233],{"class":149},[139,259,261,264,267,270,272,276,278,282,285,288,290,293,295,298,301,303,306,309,312,314,316,319],{"class":141,"line":260},6,[139,262,263],{"class":153},"    cx",[139,265,266],{"class":149},".",[139,268,269],{"class":153},"timer",[139,271,266],{"class":149},[139,273,275],{"class":274},"s2Zo4","every",[139,277,243],{"class":239},[139,279,281],{"class":280},"sbssI","5",[139,283,284],{"class":149}," *",[139,286,287],{"class":280}," 60",[139,289,284],{"class":149},[139,291,292],{"class":280}," 1000",[139,294,186],{"class":149},[139,296,297],{"class":149}," (",[139,299,300],{"class":246},"cx",[139,302,255],{"class":149},[139,304,305],{"class":220}," =>",[139,307,308],{"class":149}," this.",[139,310,311],{"class":274},"refresh",[139,313,243],{"class":239},[139,315,300],{"class":153},[139,317,318],{"class":239},"))",[139,320,173],{"class":149},[139,322,324],{"class":141,"line":323},7,[139,325,327],{"class":326},"sHwdD","    \u002F\u002F An agent finishing a turn is when usage moves: refetch then.\n",[139,329,331,334,337,340,343,346,349],{"class":141,"line":330},8,[139,332,333],{"class":149},"    this.",[139,335,336],{"class":153},"working",[139,338,339],{"class":149}," =",[139,341,342],{"class":149}," new",[139,344,345],{"class":274}," Set",[139,347,348],{"class":239},"()",[139,350,173],{"class":149},[139,352,354,356,358,360,362,364,366,369,371,373,375,377,379],{"class":141,"line":353},9,[139,355,263],{"class":153},[139,357,266],{"class":149},[139,359,269],{"class":153},[139,361,266],{"class":149},[139,363,275],{"class":274},[139,365,243],{"class":239},[139,367,368],{"class":280},"5000",[139,370,186],{"class":149},[139,372,297],{"class":149},[139,374,300],{"class":246},[139,376,255],{"class":149},[139,378,305],{"class":220},[139,380,233],{"class":149},[139,382,384,387,390,392,394,396,398,401,403,405,408,410,412,414,416,418,421,423,426,429,431,433,435,437,439,442,444,446,448,450,452,454,456,459,461],{"class":141,"line":383},10,[139,385,386],{"class":220},"      const",[139,388,389],{"class":153}," now",[139,391,339],{"class":149},[139,393,342],{"class":149},[139,395,345],{"class":274},[139,397,243],{"class":239},[139,399,400],{"class":274},"agents",[139,402,348],{"class":239},[139,404,266],{"class":149},[139,406,407],{"class":274},"filter",[139,409,243],{"class":239},[139,411,243],{"class":149},[139,413,16],{"class":246},[139,415,255],{"class":149},[139,417,305],{"class":220},[139,419,420],{"class":153}," a",[139,422,266],{"class":149},[139,424,425],{"class":153},"status",[139,427,428],{"class":149}," ===",[139,430,163],{"class":149},[139,432,336],{"class":166},[139,434,170],{"class":149},[139,436,255],{"class":239},[139,438,266],{"class":149},[139,440,441],{"class":274},"map",[139,443,243],{"class":239},[139,445,243],{"class":149},[139,447,16],{"class":246},[139,449,255],{"class":149},[139,451,305],{"class":220},[139,453,420],{"class":153},[139,455,266],{"class":149},[139,457,458],{"class":153},"pane",[139,460,318],{"class":239},[139,462,173],{"class":149},[139,464,466],{"class":141,"line":465},11,[139,467,468],{"class":326},"      \u002F\u002F at most every 2 minutes\n",[139,470,472,475,478,481,483,486,488,491,493,495,497,499,501,504,507,509,512,514,516,519,522,524,526,528,530,533,535,537,539,541,543],{"class":141,"line":471},12,[139,473,474],{"class":145},"      if",[139,476,477],{"class":239}," ([",[139,479,480],{"class":149},"...this.",[139,482,336],{"class":153},[139,484,485],{"class":239},"]",[139,487,266],{"class":149},[139,489,490],{"class":274},"some",[139,492,243],{"class":239},[139,494,243],{"class":149},[139,496,12],{"class":246},[139,498,255],{"class":149},[139,500,305],{"class":220},[139,502,503],{"class":149}," !",[139,505,506],{"class":153},"now",[139,508,266],{"class":149},[139,510,511],{"class":274},"has",[139,513,243],{"class":239},[139,515,12],{"class":153},[139,517,518],{"class":239},"))) ",[139,520,521],{"class":149},"this.",[139,523,311],{"class":274},[139,525,243],{"class":239},[139,527,300],{"class":153},[139,529,186],{"class":149},[139,531,532],{"class":280}," 2",[139,534,284],{"class":149},[139,536,287],{"class":280},[139,538,284],{"class":149},[139,540,292],{"class":280},[139,542,255],{"class":239},[139,544,173],{"class":149},[139,546,548,551,553,555,557],{"class":141,"line":547},13,[139,549,550],{"class":149},"      this.",[139,552,336],{"class":153},[139,554,339],{"class":149},[139,556,389],{"class":153},[139,558,173],{"class":149},[139,560,562,565,567],{"class":141,"line":561},14,[139,563,564],{"class":149},"    }",[139,566,255],{"class":239},[139,568,173],{"class":149},[139,570,572],{"class":141,"line":571},15,[139,573,574],{"class":149},"  }\n",[139,576,578],{"class":141,"line":577},16,[139,579,580],{"class":326},"  \u002F\u002F refresh() fetches and stores the result with setShared(\"usage.data\", …),\n",[139,582,584],{"class":141,"line":583},17,[139,585,586],{"class":326},"  \u002F\u002F so the popup opened by a click reads the same numbers.\n",[139,588,590],{"class":141,"line":589},18,[139,591,592],{"class":149},"}\n",[79,594,596],{"id":595},"the-tasks-panel","The Tasks panel",[12,598,599,600,603],{},"The panel watches the focused terminal's repo and picks the project board most of that repo's open issues are on (you can switch boards). It reads the board through ",[33,601,602],{},"gh api graphql"," and lists every open card by status, in board order. Parent issues show their sub-issue progress (\"3\u002F5 sub-issues\"), and clicking that lists the sub-issues, each with its own Start.",[12,605,606,608],{},[48,607,54],{}," does what I used to do by hand:",[610,611,612,630,633],"ol",{},[44,613,614,617,618,621,622,625,626,629],{},[33,615,616],{},"git fetch origin main"," and ",[33,619,620],{},"git worktree add"," a new branch named ",[33,623,624],{},"\u003Cissue number>-\u003Cslug>"," off ",[33,627,628],{},"origin\u002Fmain",";",[44,631,632],{},"opens a new space in that worktree with my chosen agent, model and effort, and a first message: \"Let's discuss this issue before writing any code:\" followed by the issue URL;",[44,634,635],{},"moves the card to In Progress on GitHub.",[12,637,638,639,642],{},"Because the branch starts with the issue number, the panel can match running agents back to their cards. A card being worked on shows the agent's status (\"claude · working\", \"needs you\") and a ",[48,640,641],{},"Go to tab"," button instead of Start.",[12,644,645,646,649,650,653],{},"Search covers the board and the repo's open issues that aren't on it yet. Anything outside the three columns gets ",[48,647,648],{},"Move to Todo",", which adds it to the board first if needed. At the bottom, ",[48,651,652],{},"What's next?"," opens an agent in the repo with one job: read the board, suggest three or four Backlog items to move to Todo with a reason for each, and change nothing until I agree.",[79,655,657],{"id":656},"the-usage-strip","The usage strip",[12,659,660],{},[65,661],{"alt":662,"src":663,"width":664},"The usage strip: Claude at 57% with 3h 29m to reset, Codex at 6%","\u002Fimg\u002Fherdr-usage-strip.png",486,[12,666,667],{},"The strip shows \"Used\", then per agent the session percentage and the time to reset (\"57% 3h 29m\"). A limit at 70% or more turns yellow, at 90% red. Clicking it opens a popup with a bar for every limit (session, weekly, weekly Opus) and the plan.",[12,669,670],{},"The numbers come from local files where possible:",[41,672,673,686],{},[44,674,675,678,679,682,683,266],{},[48,676,677],{},"Codex"," writes its rate limits into its session logs, so the strip reads the last ",[33,680,681],{},"rate_limits"," record from the newest file in ",[33,684,685],{},"~\u002F.codex\u002Fsessions",[44,687,688,691,692,694],{},[48,689,690],{},"Claude Code"," hands its status line a JSON with ",[33,693,681],{},". A few lines in my status line script save it to a file, and the strip reads that file while it's under 15 minutes old:",[129,696,701],{"className":697,"code":698,"filename":699,"language":700,"meta":135,"style":135},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","if echo \"$input\" | jq -e '.rate_limits' >\u002Fdev\u002Fnull 2>&1; then\n  echo \"$input\" | jq -c '{at: (now * 1000 | floor), rate_limits}' > ~\u002F.claude\u002Fstate\u002Frate-limits.json\nfi\n","statusline","sh",[33,702,703,748,778],{"__ignoreMap":135},[139,704,705,708,711,713,716,718,721,724,727,730,733,736,739,742,745],{"class":141,"line":142},[139,706,707],{"class":145},"if",[139,709,710],{"class":274}," echo",[139,712,163],{"class":149},[139,714,715],{"class":153},"$input",[139,717,170],{"class":149},[139,719,720],{"class":149}," |",[139,722,723],{"class":224}," jq",[139,725,726],{"class":166}," -e",[139,728,729],{"class":149}," '",[139,731,732],{"class":166},".rate_limits",[139,734,735],{"class":149},"'",[139,737,738],{"class":149}," >",[139,740,741],{"class":166},"\u002Fdev\u002Fnull",[139,743,744],{"class":149}," 2>&1;",[139,746,747],{"class":145}," then\n",[139,749,750,753,755,757,759,761,763,766,768,771,773,775],{"class":141,"line":176},[139,751,752],{"class":274},"  echo",[139,754,163],{"class":149},[139,756,715],{"class":153},[139,758,170],{"class":149},[139,760,720],{"class":149},[139,762,723],{"class":224},[139,764,765],{"class":166}," -c",[139,767,729],{"class":149},[139,769,770],{"class":166},"{at: (now * 1000 | floor), rate_limits}",[139,772,735],{"class":149},[139,774,738],{"class":149},[139,776,777],{"class":166}," ~\u002F.claude\u002Fstate\u002Frate-limits.json\n",[139,779,780],{"class":141,"line":204},[139,781,782],{"class":145},"fi\n",[12,784,785],{},"When that file is stale, it falls back to Anthropic's usage endpoint, signed in with Claude Code's credentials from the macOS Keychain. That endpoint is undocumented and rate-limited, which leads to the first lesson.",[79,787,789],{"id":788},"what-tripped-me-up","What tripped me up",[12,791,792,795],{},[48,793,794],{},"Polling every 30 seconds earned a 429 with a 55-minute Retry-After."," My first version asked the usage endpoint twice a minute. Now it fetches every 5 minutes, plus when an agent finishes a turn (which is when usage actually changes) or the popup opens, but never more than once every 2 minutes. A 429 keeps the last numbers and waits out the Retry-After. The status-line file means the endpoint is rarely called at all.",[12,797,798,803,804,807,808,811,812,815],{},[48,799,800,802],{},[33,801,112],{}," leaves stdin open."," ",[33,805,806],{},"codex exec"," and Antigravity's ",[33,809,810],{},"agy"," wait for input that never comes, forever. Running them through ",[33,813,814],{},"sh -c '… \u003C\u002Fdev\u002Fnull'"," fixed it.",[12,817,818,823],{},[48,819,820,822],{},[33,821,112],{}," stops after 30 seconds."," My first What's next? asked an agent for suggestions in one shot and showed them in the panel. Over a whole board, that took about 40 seconds and was cut off. Opening a tab with the agent turned out better anyway: I can argue with its picks.",[12,825,826,297,829,834,835,838],{},[48,827,828],{},"A mod can't add itself to a panel or a bar yet",[16,830,833],{"href":831,"rel":832},"https:\u002F\u002Fgithub.com\u002Fherdrdev\u002Fherdr-app\u002Fissues\u002F520",[20],"herdrdev\u002Fherdr-app#520","). A mod is placed by the layout, and I didn't want to ship a copy of Herdr's layout that goes stale on the next update. So each mod has a ",[33,836,837],{},"place.json"," saying where it wants to go:",[129,840,845],{"className":841,"code":842,"filename":843,"language":844,"meta":135,"style":135},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{ \"tabbar\": { \"size\": 84, \"props\": { \"view\": \"button\" } }, \"panel\": {}, \"order\": 10 }\n","mods\u002Ftasks\u002Fplace.json","json",[33,846,847],{"__ignoreMap":135},[139,848,849,852,854,857,859,862,864,866,869,871,873,876,878,880,883,885,887,889,891,894,896,898,900,903,905,907,910,912,915,917,919,922,924,927,929,931,934],{"class":141,"line":142},[139,850,851],{"class":149},"{",[139,853,163],{"class":149},[139,855,856],{"class":220},"tabbar",[139,858,170],{"class":149},[139,860,861],{"class":149},":",[139,863,150],{"class":149},[139,865,163],{"class":149},[139,867,868],{"class":224},"size",[139,870,170],{"class":149},[139,872,861],{"class":149},[139,874,875],{"class":280}," 84",[139,877,186],{"class":149},[139,879,163],{"class":149},[139,881,882],{"class":224},"props",[139,884,170],{"class":149},[139,886,861],{"class":149},[139,888,150],{"class":149},[139,890,163],{"class":149},[139,892,893],{"class":280},"view",[139,895,170],{"class":149},[139,897,861],{"class":149},[139,899,163],{"class":149},[139,901,902],{"class":166},"button",[139,904,170],{"class":149},[139,906,157],{"class":149},[139,908,909],{"class":149}," },",[139,911,163],{"class":149},[139,913,914],{"class":220},"panel",[139,916,170],{"class":149},[139,918,861],{"class":149},[139,920,921],{"class":149}," {},",[139,923,163],{"class":149},[139,925,926],{"class":220},"order",[139,928,170],{"class":149},[139,930,861],{"class":149},[139,932,933],{"class":280}," 10",[139,935,936],{"class":149}," }\n",[12,938,939,940,943,944,947,948,951,952,955],{},"and a ",[33,941,942],{},"setup.py"," that reads ",[24,945,946],{},"your"," installed ",[33,949,950],{},"main"," layout and writes a ",[33,953,954],{},"main-mods"," layout with every installed mod placed. If Herdr moves a node it hooks into, it stops with a message instead of guessing. Re-run it after a Herdr update, or after adding or removing a mod.",[79,957,959],{"id":958},"try-it","Try it",[12,961,962,963,966,967,972,973,976,977,980,981,984,985,988],{},"Ask your Herdr agent to add ",[33,964,965],{},"herdr.dev\u002Flayouts\u002Fmbaljeetsingh\u002Fherdr-tasks",", or follow the ",[16,968,971],{"href":969,"rel":970},"https:\u002F\u002Fgithub.com\u002Fmbaljeetsingh\u002Fherdr-tasks#add-it",[20],"README",". It needs ",[33,974,975],{},"gh"," with the ",[33,978,979],{},"project"," scope (",[33,982,983],{},"gh auth refresh -s project","). The repo's ",[33,986,987],{},"RECIPE.md"," describes both mods in enough detail for an agent to rebuild or change them.",[990,991,992],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":135,"searchDepth":176,"depth":176,"links":994},[995,996,997,998,999],{"id":81,"depth":176,"text":82},{"id":595,"depth":176,"text":596},{"id":656,"depth":176,"text":657},{"id":788,"depth":176,"text":789},{"id":958,"depth":176,"text":959},"2026-11-05","Herdr lets you add native panels to its window with a little JavaScript. I added two, a Tasks panel that starts work from my GitHub project board and a strip showing my Claude and Codex plan usage, and here is how they work and what tripped me up.","md","\u002Fimg\u002Fherdr-tasks-cover.png",{},"\u002Fblog\u002Ftwo-herdr-mods-github-tasks-and-plan-usage",{"title":5,"description":1001},{"loc":1005},"blog\u002Ftwo-herdr-mods-github-tasks-and-plan-usage",[1010,690,1011],"AI","Tooling","Y5h9dVuF7im_FXUVuwbs0ncZaq2XQ8uD_AwYIk4cBpI",[1014,1019],{"title":1015,"path":1016,"stem":1017,"description":1018,"children":-1},"Stable .localhost URLs for Every Dev Server (and Every Worktree) with Portless","\u002Fblog\u002Fstable-named-localhost-urls-with-portless","blog\u002Fstable-named-localhost-urls-with-portless","How I replaced port numbers with stable, named .localhost URLs across all my monorepos using Portless — including a pattern that gives every Claude Code worktree its own dev URL automatically.",{"title":1020,"path":1021,"stem":1022,"description":1023,"children":-1},"What Happened When I Measured My Vision Pipeline","\u002Fblog\u002Fwhat-happened-when-i-measured-my-vision-pipeline","blog\u002Fwhat-happened-when-i-measured-my-vision-pipeline","I had been saying \"roughly 90% accurate\" for months without measuring it. The eval showed that effort and model choice both move the result more than I expected, and that one config scored 59.4%, 59.4% and 90.6% on the same page.",1793861887518]