[{"data":1,"prerenderedAt":836},["ShallowReactive",2],{"docs-\u002Fdocs\u002Fv2\u002Fextending\u002Fplugins":3},{"id":4,"title":5,"body":6,"description":829,"extension":830,"meta":831,"navigation":88,"path":832,"seo":833,"stem":834,"__hash__":835},"docs\u002Fdocs\u002Fv2\u002Fextending\u002Fplugins.md","Plugins & Hooks",{"type":7,"value":8,"toc":819},"minimark",[9,13,22,27,43,308,311,327,331,338,351,361,364,413,420,425,428,434,438,569,589,593,596,758,763,767,794,802,806,815],[10,11,5],"h1",{"id":12},"plugins-hooks",[14,15,16,17,21],"p",{},"Commands and keys run when someone asks. Hooks run when something ",[18,19,20],"strong",{},"happens"," — an agent\nfinishes booting, a message goes out, the terminal is shutting down.",[23,24,26],"h2",{"id":25},"plugins","Plugins",[14,28,29,30,34,35,38,39,42],{},"Every ",[31,32,33],"code",{},".ts"," file in ",[31,36,37],{},"plugins\u002F"," is loaded on launch. No export, no wrapper — call\n",[31,40,41],{},"tui.hook()"," at the top level and it registers as the file runs.",[44,45,50],"pre",{"className":46,"code":47,"language":48,"meta":49,"style":49},"language-ts shiki shiki-themes dark-plus","\u002F\u002F plugins\u002Fworklog.ts\nconst sent: string[] = []\n\ntui.hook(\"message:sent\", ({ content, instance }) => {\n    sent.push(`${instance.name}: ${content}`)\n})\n\ntui.hook(\"tui:shutdown\", async () => {\n    if (sent.length === 0) return\n    await Bun.write(`${process.env.HOME}\u002Fworklog.md`, sent.join(\"\\n\"))\n})\n","ts","",[31,51,52,61,83,90,132,174,180,185,211,242,303],{"__ignoreMap":49},[53,54,57],"span",{"class":55,"line":56},"line",1,[53,58,60],{"class":59},"sOLPB","\u002F\u002F plugins\u002Fworklog.ts\n",[53,62,64,68,72,76,80],{"class":55,"line":63},2,[53,65,67],{"class":66},"scz_3","const",[53,69,71],{"class":70},"s9McN"," sent",[53,73,75],{"class":74},"sTNBD",": ",[53,77,79],{"class":78},"sNl3T","string",[53,81,82],{"class":74},"[] = []\n",[53,84,86],{"class":55,"line":85},3,[53,87,89],{"emptyLinePlaceholder":88},true,"\n",[53,91,93,97,100,104,107,111,114,117,120,123,126,129],{"class":55,"line":92},4,[53,94,96],{"class":95},"s3F5K","tui",[53,98,99],{"class":74},".",[53,101,103],{"class":102},"sCudf","hook",[53,105,106],{"class":74},"(",[53,108,110],{"class":109},"sKc5r","\"message:sent\"",[53,112,113],{"class":74},", ({ ",[53,115,116],{"class":95},"content",[53,118,119],{"class":74},", ",[53,121,122],{"class":95},"instance",[53,124,125],{"class":74}," }) ",[53,127,128],{"class":66},"=>",[53,130,131],{"class":74}," {\n",[53,133,135,138,140,143,145,148,151,153,155,158,161,163,165,167,169,171],{"class":55,"line":134},5,[53,136,137],{"class":95},"    sent",[53,139,99],{"class":74},[53,141,142],{"class":102},"push",[53,144,106],{"class":74},[53,146,147],{"class":109},"`",[53,149,150],{"class":66},"${",[53,152,122],{"class":95},[53,154,99],{"class":74},[53,156,157],{"class":95},"name",[53,159,160],{"class":66},"}",[53,162,75],{"class":109},[53,164,150],{"class":66},[53,166,116],{"class":95},[53,168,160],{"class":66},[53,170,147],{"class":109},[53,172,173],{"class":74},")\n",[53,175,177],{"class":55,"line":176},6,[53,178,179],{"class":74},"})\n",[53,181,183],{"class":55,"line":182},7,[53,184,89],{"emptyLinePlaceholder":88},[53,186,188,190,192,194,196,199,201,204,207,209],{"class":55,"line":187},8,[53,189,96],{"class":95},[53,191,99],{"class":74},[53,193,103],{"class":102},[53,195,106],{"class":74},[53,197,198],{"class":109},"\"tui:shutdown\"",[53,200,119],{"class":74},[53,202,203],{"class":66},"async",[53,205,206],{"class":74}," () ",[53,208,128],{"class":66},[53,210,131],{"class":74},[53,212,214,218,221,224,226,229,232,236,239],{"class":55,"line":213},9,[53,215,217],{"class":216},"sYbnZ","    if",[53,219,220],{"class":74}," (",[53,222,223],{"class":95},"sent",[53,225,99],{"class":74},[53,227,228],{"class":95},"length",[53,230,231],{"class":74}," === ",[53,233,235],{"class":234},"sDGX8","0",[53,237,238],{"class":74},") ",[53,240,241],{"class":216},"return\n",[53,243,245,248,251,253,256,258,260,262,265,267,270,272,275,277,280,282,284,286,289,291,294,298,300],{"class":55,"line":244},10,[53,246,247],{"class":216},"    await",[53,249,250],{"class":95}," Bun",[53,252,99],{"class":74},[53,254,255],{"class":102},"write",[53,257,106],{"class":74},[53,259,147],{"class":109},[53,261,150],{"class":66},[53,263,264],{"class":95},"process",[53,266,99],{"class":74},[53,268,269],{"class":95},"env",[53,271,99],{"class":74},[53,273,274],{"class":70},"HOME",[53,276,160],{"class":66},[53,278,279],{"class":109},"\u002Fworklog.md`",[53,281,119],{"class":74},[53,283,223],{"class":95},[53,285,99],{"class":74},[53,287,288],{"class":102},"join",[53,290,106],{"class":74},[53,292,293],{"class":109},"\"",[53,295,297],{"class":296},"svVle","\\n",[53,299,293],{"class":109},[53,301,302],{"class":74},"))\n",[53,304,306],{"class":55,"line":305},11,[53,307,179],{"class":74},[14,309,310],{},"Files load alphabetically. Each is independent: one that throws disables itself and\nnothing else.",[14,312,313,315,316,318,319,322,323,326],{},[31,314,37],{}," is a convention, not a capability — ",[31,317,41],{}," works from ",[31,320,321],{},"main.ts"," too. The\nfolder exists so lifecycle code has somewhere obvious to live, and so it loads without\nyou importing it. Just don't do both: a file that is auto-loaded ",[18,324,325],{},"and"," imported\nregisters twice.",[23,328,330],{"id":329},"the-two-kinds-of-hook","The two kinds of hook",[14,332,333,334,337],{},"Whether Axon waits for you is a property of the ",[18,335,336],{},"event",", not of how you registered.",[14,339,340,350],{},[18,341,342,345,346,349],{},[31,343,344],{},"tui:boot"," and ",[31,347,348],{},"tui:shutdown"," gate."," There is a meaningful \"before\" to run in, and the\nTUI waits. Flushing to disk on shutdown only works because something waits for it.",[14,352,353,356,357,360],{},[18,354,355],{},"Everything else notifies."," It announces something that already happened. Waiting would\ndelay the interface for nothing, so these return ",[31,358,359],{},"void"," and nothing is awaited.",[14,362,363],{},"If a notification handler needs to do async work, own it yourself:",[44,365,367],{"className":46,"code":366,"language":48,"meta":49,"style":49},"tui.hook(\"agent:ready\", instance => {\n    void warmCacheFor(instance)   \u002F\u002F fire-and-forget, and visibly so\n})\n",[31,368,369,391,409],{"__ignoreMap":49},[53,370,371,373,375,377,379,382,384,386,389],{"class":55,"line":56},[53,372,96],{"class":95},[53,374,99],{"class":74},[53,376,103],{"class":102},[53,378,106],{"class":74},[53,380,381],{"class":109},"\"agent:ready\"",[53,383,119],{"class":74},[53,385,122],{"class":95},[53,387,388],{"class":66}," =>",[53,390,131],{"class":74},[53,392,393,396,399,401,403,406],{"class":55,"line":63},[53,394,395],{"class":66},"    void",[53,397,398],{"class":102}," warmCacheFor",[53,400,106],{"class":74},[53,402,122],{"class":95},[53,404,405],{"class":74},")   ",[53,407,408],{"class":59},"\u002F\u002F fire-and-forget, and visibly so\n",[53,410,411],{"class":55,"line":85},[53,412,179],{"class":74},[14,414,415,416,419],{},"That is why there is one ",[31,417,418],{},"hook()"," verb rather than a blocking one and a non-blocking one.\nTwo nearly identical names differing only in a consequence you cannot see is how you get\neither a race or a hang, depending on which you picked.",[421,422,424],"h3",{"id":423},"gates-are-bounded","Gates are bounded",[14,426,427],{},"A gating hook that takes too long does not hang the terminal — Axon reports which handler\nblocked and continues. A config must never be able to freeze the thing you would use to\nfix it.",[14,429,430,431,433],{},"A gate gives your handler its moment; it does not hand your config a veto. ",[31,432,348],{},"\nthrowing is reported, and the shutdown proceeds.",[23,435,437],{"id":436},"the-events","The events",[439,440,441,457],"table",{},[442,443,444],"thead",{},[445,446,447,451,454],"tr",{},[448,449,450],"th",{},"Hook",[448,452,453],{},"Fires",[448,455,456],{},"Waits",[458,459,460,473,484,497,509,521,533,545,557],"tbody",{},[445,461,462,467,470],{},[463,464,465],"td",{},[31,466,344],{},[463,468,469],{},"Config is loaded, before the TUI is interactive",[463,471,472],{},"yes",[445,474,475,479,482],{},[463,476,477],{},[31,478,348],{},[463,480,481],{},"Before shutdown proceeds — the last chance to flush",[463,483,472],{},[445,485,486,491,494],{},[463,487,488],{},[31,489,490],{},"tui:ready",[463,492,493],{},"The TUI is live",[463,495,496],{},"no",[445,498,499,504,507],{},[463,500,501],{},[31,502,503],{},"agent:ready",[463,505,506],{},"An instance finished booting",[463,508,496],{},[445,510,511,516,519],{},[463,512,513],{},[31,514,515],{},"agent:stopped",[463,517,518],{},"An instance shut down",[463,520,496],{},[445,522,523,528,531],{},[463,524,525],{},[31,526,527],{},"agent:focused",[463,529,530],{},"A different instance came on screen",[463,532,496],{},[445,534,535,540,543],{},[463,536,537],{},[31,538,539],{},"message:sent",[463,541,542],{},"You sent a message",[463,544,496],{},[445,546,547,552,555],{},[463,548,549],{},[31,550,551],{},"message:received",[463,553,554],{},"The agent finished a wake",[463,556,496],{},[445,558,559,564,567],{},[463,560,561],{},[31,562,563],{},"mode:changed",[463,565,566],{},"The active mode changed",[463,568,496],{},[14,570,571,573,574,577,578,583,584,588],{},[31,572,551],{}," carries no content, deliberately. Reacting to ",[18,575,576],{},"what"," a model said is\nan agent's job — see ",[579,580,582],"a",{"href":581},"\u002Fdocs\u002Fv2\u002Fcognets","Cognets",". This tells you only ",[585,586,587],"em",{},"that"," a\nwake completed, which is enough for a spinner, a timer, or a notification.",[23,590,592],{"id":591},"a-worked-example","A worked example",[14,594,595],{},"Warn before quitting while an agent is mid-thought:",[44,597,599],{"className":46,"code":598,"language":48,"meta":49,"style":49},"\u002F\u002F plugins\u002Ffocus-guard.ts\ntui.hook(\"tui:shutdown\", async () => {\n    const working = agents.list().filter(a => a.activity === \"working\")\n    if (working.length === 0) return\n\n    tui.warn(`${working.map(a => a.name).join(\", \")} still working`)\n})\n",[31,600,601,606,628,674,695,699,754],{"__ignoreMap":49},[53,602,603],{"class":55,"line":56},[53,604,605],{"class":59},"\u002F\u002F plugins\u002Ffocus-guard.ts\n",[53,607,608,610,612,614,616,618,620,622,624,626],{"class":55,"line":63},[53,609,96],{"class":95},[53,611,99],{"class":74},[53,613,103],{"class":102},[53,615,106],{"class":74},[53,617,198],{"class":109},[53,619,119],{"class":74},[53,621,203],{"class":66},[53,623,206],{"class":74},[53,625,128],{"class":66},[53,627,131],{"class":74},[53,629,630,633,636,639,642,644,647,650,653,655,657,659,662,664,667,669,672],{"class":55,"line":85},[53,631,632],{"class":66},"    const",[53,634,635],{"class":70}," working",[53,637,638],{"class":74}," = ",[53,640,641],{"class":95},"agents",[53,643,99],{"class":74},[53,645,646],{"class":102},"list",[53,648,649],{"class":74},"().",[53,651,652],{"class":102},"filter",[53,654,106],{"class":74},[53,656,579],{"class":95},[53,658,388],{"class":66},[53,660,661],{"class":95}," a",[53,663,99],{"class":74},[53,665,666],{"class":95},"activity",[53,668,231],{"class":74},[53,670,671],{"class":109},"\"working\"",[53,673,173],{"class":74},[53,675,676,678,680,683,685,687,689,691,693],{"class":55,"line":92},[53,677,217],{"class":216},[53,679,220],{"class":74},[53,681,682],{"class":95},"working",[53,684,99],{"class":74},[53,686,228],{"class":95},[53,688,231],{"class":74},[53,690,235],{"class":234},[53,692,238],{"class":74},[53,694,241],{"class":216},[53,696,697],{"class":55,"line":134},[53,698,89],{"emptyLinePlaceholder":88},[53,700,701,704,706,709,711,713,715,717,719,722,724,726,728,730,732,734,737,739,741,744,747,749,752],{"class":55,"line":176},[53,702,703],{"class":95},"    tui",[53,705,99],{"class":74},[53,707,708],{"class":102},"warn",[53,710,106],{"class":74},[53,712,147],{"class":109},[53,714,150],{"class":66},[53,716,682],{"class":95},[53,718,99],{"class":74},[53,720,721],{"class":102},"map",[53,723,106],{"class":74},[53,725,579],{"class":95},[53,727,388],{"class":66},[53,729,661],{"class":95},[53,731,99],{"class":74},[53,733,157],{"class":95},[53,735,736],{"class":74},").",[53,738,288],{"class":102},[53,740,106],{"class":74},[53,742,743],{"class":109},"\", \"",[53,745,746],{"class":74},")",[53,748,160],{"class":66},[53,750,751],{"class":109}," still working`",[53,753,173],{"class":74},[53,755,756],{"class":55,"line":182},[53,757,179],{"class":74},[14,759,760,762],{},[31,761,348],{}," gates, so the warning is shown before the terminal goes away. The palette\nprimitives work here too — a gating hook is an ordinary async function, so it can ask a\nquestion and wait for the answer.",[23,764,766],{"id":765},"reference","Reference",[44,768,770],{"className":46,"code":769,"language":48,"meta":49,"style":49},"tui.hook(name, handler)   \u002F\u002F → Disposer\n",[31,771,772],{"__ignoreMap":49},[53,773,774,776,778,780,782,784,786,789,791],{"class":55,"line":56},[53,775,96],{"class":95},[53,777,99],{"class":74},[53,779,103],{"class":102},[53,781,106],{"class":74},[53,783,157],{"class":95},[53,785,119],{"class":74},[53,787,788],{"class":95},"handler",[53,790,405],{"class":74},[53,792,793],{"class":59},"\u002F\u002F → Disposer\n",[14,795,796,797,119,799,801],{},"Available from ",[31,798,37],{},[31,800,321],{},", and an extension alike. There is no capability\ndifference between those places — only a difference in when the file runs.",[23,803,805],{"id":804},"whats-next","What's next",[14,807,808,814],{},[18,809,810],{},[579,811,813],{"href":812},"\u002Fdocs\u002Fv2\u002Fextending\u002Fextensions","Extensions"," — packaging all of this so someone else\ncan install it.",[816,817,818],"style",{},"html pre.shiki code .sOLPB, html code.shiki .sOLPB{--shiki-default:#6A9955}html pre.shiki code .scz_3, html code.shiki .scz_3{--shiki-default:#569CD6}html pre.shiki code .s9McN, html code.shiki .s9McN{--shiki-default:#4FC1FF}html pre.shiki code .sTNBD, html code.shiki .sTNBD{--shiki-default:#D4D4D4}html pre.shiki code .sNl3T, html code.shiki .sNl3T{--shiki-default:#4EC9B0}html pre.shiki code .s3F5K, html code.shiki .s3F5K{--shiki-default:#9CDCFE}html pre.shiki code .sCudf, html code.shiki .sCudf{--shiki-default:#DCDCAA}html pre.shiki code .sKc5r, html code.shiki .sKc5r{--shiki-default:#CE9178}html pre.shiki code .sYbnZ, html code.shiki .sYbnZ{--shiki-default:#C586C0}html pre.shiki code .sDGX8, html code.shiki .sDGX8{--shiki-default:#B5CEA8}html pre.shiki code .svVle, html code.shiki .svVle{--shiki-default:#D7BA7D}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);}",{"title":49,"searchDepth":63,"depth":63,"links":820},[821,822,825,826,827,828],{"id":25,"depth":63,"text":26},{"id":329,"depth":63,"text":330,"children":823},[824],{"id":423,"depth":85,"text":424},{"id":436,"depth":63,"text":437},{"id":591,"depth":63,"text":592},{"id":765,"depth":63,"text":766},{"id":804,"depth":63,"text":805},"Commands and keys run when someone asks. Hooks run when something happens — an agent\nfinishes booting, a message goes out, the terminal is shutting down.","md",{},"\u002Fdocs\u002Fv2\u002Fextending\u002Fplugins",{"title":5,"description":829},"docs\u002Fv2\u002Fextending\u002Fplugins","z3LQlo6gcSfzCWNypLmgb82KxxePCKsiNuDiLJiTCRM",1786520846935]