trybeacon 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +5 -5
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/server/app/_global-error.html +1 -1
  7. package/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/server/app/_not-found.html +1 -1
  16. package/.next/server/app/_not-found.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/.next/server/app/api/map/describe/route.js +1 -1
  24. package/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  25. package/.next/server/app/docs.html +1 -1
  26. package/.next/server/app/docs.rsc +1 -1
  27. package/.next/server/app/docs.segments/_full.segment.rsc +1 -1
  28. package/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  29. package/.next/server/app/docs.segments/_index.segment.rsc +1 -1
  30. package/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
  31. package/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  32. package/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  33. package/.next/server/app/feedback/page_client-reference-manifest.js +1 -1
  34. package/.next/server/app/feedback.html +1 -1
  35. package/.next/server/app/feedback.rsc +1 -1
  36. package/.next/server/app/feedback.segments/_full.segment.rsc +1 -1
  37. package/.next/server/app/feedback.segments/_head.segment.rsc +1 -1
  38. package/.next/server/app/feedback.segments/_index.segment.rsc +1 -1
  39. package/.next/server/app/feedback.segments/_tree.segment.rsc +1 -1
  40. package/.next/server/app/feedback.segments/feedback/__PAGE__.segment.rsc +1 -1
  41. package/.next/server/app/feedback.segments/feedback.segment.rsc +1 -1
  42. package/.next/server/app/help/page_client-reference-manifest.js +1 -1
  43. package/.next/server/app/help.html +1 -1
  44. package/.next/server/app/help.rsc +1 -1
  45. package/.next/server/app/help.segments/_full.segment.rsc +1 -1
  46. package/.next/server/app/help.segments/_head.segment.rsc +1 -1
  47. package/.next/server/app/help.segments/_index.segment.rsc +1 -1
  48. package/.next/server/app/help.segments/_tree.segment.rsc +1 -1
  49. package/.next/server/app/help.segments/help/__PAGE__.segment.rsc +1 -1
  50. package/.next/server/app/help.segments/help.segment.rsc +1 -1
  51. package/.next/server/app/index.html +1 -1
  52. package/.next/server/app/index.rsc +1 -1
  53. package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  54. package/.next/server/app/index.segments/_full.segment.rsc +1 -1
  55. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  56. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  57. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  58. package/.next/server/app/map/page.js +2 -2
  59. package/.next/server/app/map/page.js.nft.json +1 -1
  60. package/.next/server/app/map/page_client-reference-manifest.js +1 -1
  61. package/.next/server/app/page_client-reference-manifest.js +1 -1
  62. package/.next/server/app/plan/page.js +2 -2
  63. package/.next/server/app/plan/page.js.nft.json +1 -1
  64. package/.next/server/app/plan/page_client-reference-manifest.js +1 -1
  65. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app-paths-manifest.json +5 -5
  67. package/.next/server/chunks/1130.js +1 -1
  68. package/.next/server/chunks/9159.js +1 -0
  69. package/.next/server/middleware-build-manifest.js +1 -1
  70. package/.next/server/pages/404.html +1 -1
  71. package/.next/server/pages/500.html +1 -1
  72. package/.next/server/server-reference-manifest.js +1 -1
  73. package/.next/server/server-reference-manifest.json +1 -1
  74. package/.next/static/chunks/{3973-c52f930a38c6c787.js → 5354-31b73f8e3dfd92b7.js} +1 -1
  75. package/.next/static/chunks/app/map/{page-834f326619f2477b.js → page-3fe31101c63d9668.js} +1 -1
  76. package/.next/static/chunks/app/plan/{page-86a130ff444297a1.js → page-51bc3f04374541b9.js} +1 -1
  77. package/.next/trace +37 -37
  78. package/.next/trace-build +1 -1
  79. package/dist/bin/doctor.js +5 -3
  80. package/dist/bin/hook.js +5 -3
  81. package/dist/bin/mcp.js +30 -28
  82. package/dist/bin/plan.js +28 -22
  83. package/dist/bin/uninstall.js +5 -3
  84. package/dist/lib/assets.js +5 -3
  85. package/dist/lib/global-install.js +5 -3
  86. package/package.json +1 -1
  87. package/.next/server/chunks/8465.js +0 -1
  88. /package/.next/static/{dmwr94-U2a-24WsqmRue9 → ct96P0zTV3e7YqJ2NLI_X}/_buildManifest.js +0 -0
  89. /package/.next/static/{dmwr94-U2a-24WsqmRue9 → ct96P0zTV3e7YqJ2NLI_X}/_ssgManifest.js +0 -0
package/dist/bin/plan.js CHANGED
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- var AQ=Object.defineProperty;var kQ=(Q)=>Q;function wQ(Q,X){this[Q]=kQ.bind(null,X)}var o=(Q,X)=>{for(var Z in X)AQ(Q,Z,{get:X[Z],enumerable:!0,configurable:!0,set:wQ.bind(X,Z)})};var r=(Q,X)=>()=>(Q&&(X=Q(Q=0)),X);var ZQ={};o(ZQ,{removeRepoAssets:()=>hQ,installRefreshSkill:()=>_Q,installPlanSkill:()=>LQ,installInitSkill:()=>gQ,ensureWorkflowDoc:()=>bQ,ensureMcp:()=>yQ,auditRepo:()=>SQ,REFRESH_SKILL:()=>QQ,PLAN_SKILL:()=>XQ,INIT_SKILL:()=>e});import{existsSync as w,mkdirSync as g,readFileSync as B,rmSync as k,writeFileSync as x}from"fs";import{join as q}from"path";function gQ(Q){let X=q(Q,".claude","skills","beacon-init");g(X,{recursive:!0});let Z=q(X,"SKILL.md");return x(Z,e),Z}function _Q(Q){let X=q(Q,".claude","skills","beacon-refresh");g(X,{recursive:!0});let Z=q(X,"SKILL.md");return x(Z,QQ),Z}function LQ(Q){let X=q(Q,".claude","skills","beacon-plan");g(X,{recursive:!0});let Z=q(X,"SKILL.md");return x(Z,XQ),Z}function bQ(Q){let X=q(Q,"AGENTS.md"),Z="";try{Z=B(X,"utf8")}catch{}let $=new RegExp(`${f}[\\s\\S]*?${_}`);Z=$.test(Z)?Z.replace($,i):`${Z.trim()}
3
+ var wQ=Object.defineProperty;var kQ=(Q)=>Q;function _Q(Q,X){this[Q]=kQ.bind(null,X)}var r=(Q,X)=>{for(var Z in X)wQ(Q,Z,{get:X[Z],enumerable:!0,configurable:!0,set:_Q.bind(X,Z)})};var o=(Q,X)=>()=>(Q&&(X=Q(Q=0)),X);var ZQ={};r(ZQ,{removeRepoAssets:()=>mQ,installRefreshSkill:()=>gQ,installPlanSkill:()=>bQ,installInitSkill:()=>LQ,ensureWorkflowDoc:()=>yQ,ensureMcp:()=>SQ,auditRepo:()=>hQ,REFRESH_SKILL:()=>QQ,PLAN_SKILL:()=>XQ,INIT_SKILL:()=>e});import{existsSync as k,mkdirSync as _,readFileSync as B,rmSync as w,writeFileSync as W}from"fs";import{join as q}from"path";function LQ(Q){let X=q(Q,".claude","skills","beacon-init");_(X,{recursive:!0});let Z=q(X,"SKILL.md");return W(Z,e),Z}function gQ(Q){let X=q(Q,".claude","skills","beacon-refresh");_(X,{recursive:!0});let Z=q(X,"SKILL.md");return W(Z,QQ),Z}function bQ(Q){let X=q(Q,".claude","skills","beacon-plan");_(X,{recursive:!0});let Z=q(X,"SKILL.md");return W(Z,XQ),Z}function yQ(Q){let X=q(Q,"AGENTS.md"),Z="";try{Z=B(X,"utf8")}catch{}let $=new RegExp(`${O}[\\s\\S]*?${L}`);Z=$.test(Z)?Z.replace($,i):`${Z.trim()}
4
4
 
5
5
  ${i}
6
- `.trimStart(),x(X,Z.endsWith(`
6
+ `.trimStart(),W(X,Z.endsWith(`
7
7
  `)?Z:`${Z}
8
- `);let Y=q(Q,"CLAUDE.md"),J="";try{J=B(Y,"utf8")}catch{}if(!/@AGENTS\.md/.test(J))x(Y,`${J?`${J.trim()}
8
+ `);let Y=q(Q,"CLAUDE.md"),J="";try{J=B(Y,"utf8")}catch{}if(!/@AGENTS\.md/.test(J))W(Y,`${J?`${J.trim()}
9
9
 
10
10
  `:""}@AGENTS.md
11
- `)}function yQ(Q){let X=q(Q,".mcp.json"),Z={};try{Z=JSON.parse(B(X,"utf8"))}catch{}if(Z.mcpServers=Z.mcpServers??{},Z.mcpServers.beacon)return{path:X,added:!1};return Z.mcpServers.beacon={command:"beacon",args:["mcp"]},x(X,JSON.stringify(Z,null,2)+`
12
- `),{path:X,added:!0}}function SQ(Q){let X=q(Q,".mcp.json"),Z=!1;try{Z=!!JSON.parse(B(X,"utf8")).mcpServers?.beacon}catch{}let $=!1;try{$=B(q(Q,"AGENTS.md"),"utf8").includes(f)}catch{}let Y=!1;try{Y=/@AGENTS\.md/.test(B(q(Q,"CLAUDE.md"),"utf8"))}catch{}return{repo:Q,mcpRegistered:Z,agentsMdBlock:$,claudeMdImport:Y,skills:{"beacon-init":w(q(Q,".claude","skills","beacon-init","SKILL.md")),"beacon-refresh":w(q(Q,".claude","skills","beacon-refresh","SKILL.md"))}}}function hQ(Q){let X=[];for(let z of["beacon-init","beacon-refresh","beacon-db-design"]){let N=q(Q,".claude","skills",z);if(w(N))k(N,{recursive:!0,force:!0}),X.push(z)}let Z=!1,$=q(Q,".mcp.json");try{let z=JSON.parse(B($,"utf8"));if(z.mcpServers?.beacon){if(delete z.mcpServers.beacon,Z=!0,z.mcpServers&&Object.keys(z.mcpServers).length===0)delete z.mcpServers;if(Object.keys(z).length===0)k($,{force:!0});else x($,JSON.stringify(z,null,2)+`
13
- `)}}catch{}let Y=!1,J=q(Q,"AGENTS.md");try{let z=B(J,"utf8"),N=new RegExp(`\\n?${f}[\\s\\S]*?${_}\\n?`);if(N.test(z)){let M=z.replace(N,`
11
+ `)}function SQ(Q){let X=q(Q,".mcp.json"),Z={};try{Z=JSON.parse(B(X,"utf8"))}catch{}if(Z.mcpServers=Z.mcpServers??{},Z.mcpServers.beacon)return{path:X,added:!1};return Z.mcpServers.beacon={command:"beacon",args:["mcp"]},W(X,JSON.stringify(Z,null,2)+`
12
+ `),{path:X,added:!0}}function hQ(Q){let X=q(Q,".mcp.json"),Z=!1;try{Z=!!JSON.parse(B(X,"utf8")).mcpServers?.beacon}catch{}let $=!1;try{$=B(q(Q,"AGENTS.md"),"utf8").includes(O)}catch{}let Y=!1;try{Y=/@AGENTS\.md/.test(B(q(Q,"CLAUDE.md"),"utf8"))}catch{}return{repo:Q,mcpRegistered:Z,agentsMdBlock:$,claudeMdImport:Y,skills:{"beacon-init":k(q(Q,".claude","skills","beacon-init","SKILL.md")),"beacon-refresh":k(q(Q,".claude","skills","beacon-refresh","SKILL.md"))}}}function mQ(Q){let X=[];for(let z of["beacon-init","beacon-refresh","beacon-db-design"]){let N=q(Q,".claude","skills",z);if(k(N))w(N,{recursive:!0,force:!0}),X.push(z)}let Z=!1,$=q(Q,".mcp.json");try{let z=JSON.parse(B($,"utf8"));if(z.mcpServers?.beacon){if(delete z.mcpServers.beacon,Z=!0,z.mcpServers&&Object.keys(z.mcpServers).length===0)delete z.mcpServers;if(Object.keys(z).length===0)w($,{force:!0});else W($,JSON.stringify(z,null,2)+`
13
+ `)}}catch{}let Y=!1,J=q(Q,"AGENTS.md");try{let z=B(J,"utf8"),N=new RegExp(`\\n?${O}[\\s\\S]*?${L}\\n?`);if(N.test(z)){let P=z.replace(N,`
14
14
  `).replace(/\n{3,}/g,`
15
15
 
16
- `).trimStart();x(J,M.endsWith(`
17
- `)?M:`${M}
18
- `),Y=!0}}catch{}let H=!1,V=q(Q,"CLAUDE.md");try{let z=B(V,"utf8"),N=z.replace(/^[ \t]*@AGENTS\.md[ \t]*\n?/m,"");if(N!==z){if(!N.trim())k(V,{force:!0});else x(V,N);H=!0}}catch{}return{skillsRemoved:X,mcpUnregistered:Z,agentsBlockRemoved:Y,claudeImportRemoved:H}}var e=`---
16
+ `).trimStart();W(J,P.endsWith(`
17
+ `)?P:`${P}
18
+ `),Y=!0}}catch{}let H=!1,V=q(Q,"CLAUDE.md");try{let z=B(V,"utf8"),N=z.replace(/^[ \t]*@AGENTS\.md[ \t]*\n?/m,"");if(N!==z){if(!N.trim())w(V,{force:!0});else W(V,N);H=!0}}catch{}return{skillsRemoved:X,mcpUnregistered:Z,agentsBlockRemoved:Y,claudeImportRemoved:H}}var e=`---
19
19
  name: beacon-init
20
20
  description: Read this repository and map its architecture, schema, and roadmap into Beacon (the local visual planning panel). Use when the user runs /beacon-init, asks to "set up Beacon for this repo", or asks to initialize/bootstrap Beacon's map of this codebase.
21
21
  ---
@@ -140,7 +140,7 @@ If it returns feedback, revise and call it again.
140
140
 
141
141
  If \`beacon_present_plan\` isn't available, the panel isn't wired here \u2014 fall back to presenting via
142
142
  ExitPlanMode with the same \`\`\`beacon block, or tell the user to run \`beacon\` in this repo once.
143
- `,f="<!-- beacon:workflow:start -->",_="<!-- beacon:workflow:end -->",i;var $Q=r(()=>{i=`${f}
143
+ `,O="<!-- beacon:workflow:start -->",L="<!-- beacon:workflow:end -->",i;var $Q=o(()=>{i=`${O}
144
144
  ## Beacon \u2014 feature workflow
145
145
 
146
146
  This project uses Beacon (a local planning/visualization panel; run \`beacon\` to open it). When you start work on a FEATURE \u2014 whether referenced via an \`@beacon:feature://\u2026\` mention, an \`@beacon:note://\u2026\` note the user wrote in the Notes panel (treat its checkbox \`- [ ]\` todos as subtasks and order features by their dependencies), or just described in chat \u2014 follow these steps in order:
@@ -181,16 +181,18 @@ Beacon extracts it deterministically and **strips the block from the prose** (it
181
181
 
182
182
  **The board is built ONLY from the block \u2014 prose is NOT parsed.** If your plan describes ANY database models/tables/columns in the prose (e.g. "Model \`legal_precedent.py\` \u2014 natural key (court, \u2026)"), you MUST also put them in the block's \`tables\` array (with \`columns\`), or the /db tab will be empty for that plan. Same for endpoints (\`endpoints\` with \`uses:[{table,access}]\`) and features (\`features\`). A plan that lists five tables in prose but ships a block with only \`features\` renders an empty database board \u2014 exactly the "I described models but the DB tab is empty" failure. Mirror every DB entity you mention into the block.
183
183
 
184
- ### 3. At the end, register the work
184
+ ### 3. At the end, register the work \u2014 in ONE call
185
185
 
186
- Call \`beacon_describe_feature\` for **EVERY** feature the plan created \u2014 once per feature, **by its exact title** (the same titles you listed in \`features\`/the \`\`\`beacon block) \u2014 with the files you touched and a short markdown description. This flips each one to **Done** and keeps \`beacon_context_for_feature\` accurate for the next session.
186
+ Call \`beacon_describe_feature\` **ONCE** with a \`features\` array \u2014 one entry per feature the plan created \u2014 each with the files you touched and a short markdown description. This flips each one to **Done** and keeps \`beacon_context_for_feature\` accurate for the next session.
187
187
 
188
- Do this per feature. If a plan added five features, that's five \`beacon_describe_feature\` calls \u2014 registering only an umbrella ("Harden auth") leaves the individual features stuck on **Pending** on the map.
188
+ Key each entry by its node \`id\`: the ids are handed back to you when the plan is approved (in the approval message / additionalContext), so you don't fuzzy-match titles or pay a disambiguation round-trip. If you don't have an id, \`title\` still works.
189
+
190
+ Register them all in that single batched call. If a plan added five features, that's ONE \`beacon_describe_feature\` call with five entries \u2014 NOT five calls, and NOT just an umbrella ("Harden auth"), which leaves the individual features stuck on **Pending**.
189
191
 
190
192
  If the feature added or materially changed a REAL architectural component (a subsystem \u2014 NOT a file), also pass \`architecture: [{ title, domain, role, \u2026 }]\` so the Architecture map stays accurate. It upserts curated components by title; never list files as components.
191
193
 
192
194
  Pull raw planning data anytime with \`beacon_entities\` (features / architecture / tables / endpoints).
193
- ${_}`});var HQ={};o(HQ,{provisionDb:()=>HX});import{createClient as eQ}from"@libsql/client";import{drizzle as QX}from"drizzle-orm/libsql";import{migrate as XX}from"drizzle-orm/libsql/migrator";import{readMigrationFiles as ZX}from"drizzle-orm/migrator";import{dirname as c,join as $X}from"path";import{fileURLToPath as YX}from"url";async function I(Q,X){return(await Q.execute({sql:"SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?",args:[X]})).rows.length>0}async function qX(Q,X){let Z=await Q.execute(`PRAGMA table_info("${X}")`);return new Set(Z.rows.map(($)=>String($.name)))}async function GQ(Q){if(!await I(Q,"__drizzle_migrations"))return 0;let X=await Q.execute('SELECT count(*) AS n FROM "__drizzle_migrations"');return Number(X.rows[0]?.n??0)}async function GX(Q){for(let[X,Z]of Object.entries(zX)){if(!await I(Q,X))continue;let $=await qX(Q,X);for(let Y of Z){if(!$.has(Y))continue;await Q.execute(`UPDATE "${X}" SET "${Y}" = CAST((julianday("${Y}") - 2440587.5) * 86400000 AS INTEGER) WHERE typeof("${Y}") = 'text'`)}}}async function HX(Q,X=VX){let Z=eQ({url:Q});try{await Z.execute("PRAGMA journal_mode = WAL;"),await Z.execute("PRAGMA foreign_keys = ON;");let $=await I(Z,"__drizzle_migrations"),Y=await I(Z,"Node"),J=!1;if(Y&&!$){let V=ZX({migrationsFolder:X})[0];if(V)await Z.execute('CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (id SERIAL PRIMARY KEY, hash text NOT NULL, created_at numeric)'),await Z.execute({sql:'INSERT INTO "__drizzle_migrations" ("hash", "created_at") VALUES (?, ?)',args:[V.hash,V.folderMillis]}),J=!0}let H=await GQ(Z);if(await XX(QX(Z),{migrationsFolder:X}),await GQ(Z)>H)J=!0;return await GX(Z),{migrated:J}}finally{Z.close()}}var JX,VX,zX;var NQ=r(async()=>{JX=c(c(c(YX(import.meta.url)))),VX=process.env.BEACON_MIGRATIONS_DIR||$X(JX,"drizzle"),zX={Node:["createdAt","updatedAt"],Note:["createdAt","updatedAt"],DbTable:["createdAt","updatedAt"],Endpoint:["createdAt","updatedAt"],DraftTable:["createdAt"],SyncState:["codeGraphSyncedAt","updatedAt"],AppSetting:["updatedAt"],ProjectMeta:["updatedAt"],CodeFile:["updatedAt"]}});import{existsSync as b,mkdirSync as y,readFileSync as F,rmSync as mQ,writeFileSync as u}from"fs";import{homedir as pQ}from"os";import{join as W}from"path";function cQ(){return process.env.HOME||process.env.USERPROFILE||pQ()}var j=()=>W(cQ(),".claude"),S=()=>W(j(),"skills"),YQ=()=>W(j(),"settings.json"),P=()=>W(j(),"CLAUDE.md"),O="<!-- beacon:global:start -->",L="<!-- beacon:global:end -->",h=["beacon-init","beacon-refresh","beacon-plan"],m=[{event:"PostToolUse",matcher:"Edit|Write|MultiEdit",command:"beacon hook",description:"Report file edits to Beacon's active feature so the map stays fresh."},{event:"PermissionRequest",matcher:"ExitPlanMode",command:"beacon plan",description:"Pipe the agent's plan markdown into Beacon's /plan canvas for review."},{event:"UserPromptSubmit",matcher:"*",command:"beacon prompt",description:"On feature-y prompts in a Beacon-wired repo, remind the agent to run the context\u2192propose\u2192describe loop (no-op otherwise)."},{event:"Stop",matcher:"*",command:"beacon stop-hook",description:"When the agent ends a turn asking for plan approval in prose (instead of presenting it), nudge it to present the plan on Beacon's /plan canvas. Bounded by stop_hook_active \u2014 at most one nudge."}],lQ=`## Beacon (visual planning panel)
195
+ ${L}`});var HQ={};r(HQ,{provisionDb:()=>NX});import{createClient as QX}from"@libsql/client";import{drizzle as XX}from"drizzle-orm/libsql";import{migrate as ZX}from"drizzle-orm/libsql/migrator";import{readMigrationFiles as $X}from"drizzle-orm/migrator";import{dirname as c,join as YX}from"path";import{fileURLToPath as JX}from"url";async function E(Q,X){return(await Q.execute({sql:"SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?",args:[X]})).rows.length>0}async function GX(Q,X){let Z=await Q.execute(`PRAGMA table_info("${X}")`);return new Set(Z.rows.map(($)=>String($.name)))}async function GQ(Q){if(!await E(Q,"__drizzle_migrations"))return 0;let X=await Q.execute('SELECT count(*) AS n FROM "__drizzle_migrations"');return Number(X.rows[0]?.n??0)}async function HX(Q){for(let[X,Z]of Object.entries(qX)){if(!await E(Q,X))continue;let $=await GX(Q,X);for(let Y of Z){if(!$.has(Y))continue;await Q.execute(`UPDATE "${X}" SET "${Y}" = CAST((julianday("${Y}") - 2440587.5) * 86400000 AS INTEGER) WHERE typeof("${Y}") = 'text'`)}}}async function NX(Q,X=zX){let Z=QX({url:Q});try{await Z.execute("PRAGMA journal_mode = WAL;"),await Z.execute("PRAGMA foreign_keys = ON;");let $=await E(Z,"__drizzle_migrations"),Y=await E(Z,"Node"),J=!1;if(Y&&!$){let V=$X({migrationsFolder:X})[0];if(V)await Z.execute('CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (id SERIAL PRIMARY KEY, hash text NOT NULL, created_at numeric)'),await Z.execute({sql:'INSERT INTO "__drizzle_migrations" ("hash", "created_at") VALUES (?, ?)',args:[V.hash,V.folderMillis]}),J=!0}let H=await GQ(Z);if(await ZX(XX(Z),{migrationsFolder:X}),await GQ(Z)>H)J=!0;return await HX(Z),{migrated:J}}finally{Z.close()}}var VX,zX,qX;var NQ=o(async()=>{VX=c(c(c(JX(import.meta.url)))),zX=process.env.BEACON_MIGRATIONS_DIR||YX(VX,"drizzle"),qX={Node:["createdAt","updatedAt"],Note:["createdAt","updatedAt"],DbTable:["createdAt","updatedAt"],Endpoint:["createdAt","updatedAt"],DraftTable:["createdAt"],SyncState:["codeGraphSyncedAt","updatedAt"],AppSetting:["updatedAt"],ProjectMeta:["updatedAt"],CodeFile:["updatedAt"]}});import{existsSync as b,mkdirSync as y,readFileSync as I,rmSync as pQ,writeFileSync as D}from"fs";import{homedir as cQ}from"os";import{join as x}from"path";function lQ(){return process.env.HOME||process.env.USERPROFILE||cQ()}var T=()=>x(lQ(),".claude"),S=()=>x(T(),"skills"),YQ=()=>x(T(),"settings.json"),C=()=>x(T(),"CLAUDE.md"),F="<!-- beacon:global:start -->",g="<!-- beacon:global:end -->",h=["beacon-init","beacon-refresh","beacon-plan"],m=[{event:"PostToolUse",matcher:"Edit|Write|MultiEdit",command:"beacon hook",description:"Report file edits to Beacon's active feature so the map stays fresh."},{event:"PermissionRequest",matcher:"ExitPlanMode",command:"beacon plan",description:"Pipe the agent's plan markdown into Beacon's /plan canvas for review."},{event:"UserPromptSubmit",matcher:"*",command:"beacon prompt",description:"On feature-y prompts in a Beacon-wired repo, remind the agent to run the context\u2192propose\u2192describe loop (no-op otherwise)."},{event:"Stop",matcher:"*",command:"beacon stop-hook",description:"When the agent ends a turn asking for plan approval in prose (instead of presenting it), nudge it to present the plan on Beacon's /plan canvas. Bounded by stop_hook_active \u2014 at most one nudge."}],dQ=`## Beacon (visual planning panel)
194
196
 
195
197
  This machine has Beacon installed \u2014 a local visual planning surface for the terminal-side
196
198
  agent. Beacon proposes feature plans (roadmap features + DB schema + endpoints) via MCP,
@@ -211,18 +213,22 @@ the user reviews on a canvas at /plan, and feedback flows back as the next round
211
213
  Feedback bundles inline annotations on the markdown PLUS any edits the user made directly
212
214
  on the /map and /db boards (added features, attached subtasks, edited columns, new
213
215
  endpoints). Treat board edits as the user's revision intent \u2014 re-propose matching them
214
- verbatim. Do NOT implement until the tool returns approval.`;function dQ(Q,X){let Z=W(S(),Q);y(Z,{recursive:!0});let $=W(Z,"SKILL.md");return u($,X),$}function JQ(Q){return b(W(S(),Q,"SKILL.md"))}function tQ(Q){let X=W(S(),Q);if(!b(X))return!1;return mQ(X,{recursive:!0,force:!0}),!0}function p(){try{return JSON.parse(F(YQ(),"utf8"))}catch{return{}}}function VQ(Q){y(j(),{recursive:!0}),u(YQ(),JSON.stringify(Q,null,2)+`
215
- `)}function aQ(Q){let X=p();X.hooks=X.hooks??{},X.hooks[Q.event]=X.hooks[Q.event]??[];let Z=X.hooks[Q.event];if(Z.some((Y)=>Y.matcher===Q.matcher&&Y.hooks?.some((J)=>J.command===Q.command)))return!1;return Z.push({matcher:Q.matcher,hooks:[{type:"command",command:Q.command}]}),VQ(X),!0}function nQ(Q){return(p().hooks?.[Q.event]??[]).some(($)=>$.hooks?.some((Y)=>Y.command===Q.command))}function sQ(Q){let X=p(),Z=X.hooks?.[Q.event];if(!Z)return!1;let $=!1,Y=Z.map((J)=>{let H=J.hooks?.length??0,V=(J.hooks??[]).filter((z)=>z.command!==Q.command);if(V.length!==H)$=!0;return{...J,hooks:V}}).filter((J)=>(J.hooks??[]).length>0);if(!$)return!1;if(Y.length)X.hooks[Q.event]=Y;else delete X.hooks[Q.event];if(X.hooks&&Object.keys(X.hooks).length===0)delete X.hooks;return VQ(X),!0}function oQ(Q){let X=`${O}
216
+ verbatim. Do NOT implement until the tool returns approval.`;function nQ(Q,X){let Z=x(S(),Q);y(Z,{recursive:!0});let $=x(Z,"SKILL.md");return D($,X),$}function JQ(Q){return b(x(S(),Q,"SKILL.md"))}function aQ(Q){let X=x(S(),Q);if(!b(X))return!1;return pQ(X,{recursive:!0,force:!0}),!0}function p(){try{return JSON.parse(I(YQ(),"utf8"))}catch{return{}}}function VQ(Q){y(T(),{recursive:!0}),D(YQ(),JSON.stringify(Q,null,2)+`
217
+ `)}function tQ(Q){let X=p();X.hooks=X.hooks??{},X.hooks[Q.event]=X.hooks[Q.event]??[];let Z=X.hooks[Q.event];if(Z.some((Y)=>Y.matcher===Q.matcher&&Y.hooks?.some((J)=>J.command===Q.command)))return!1;return Z.push({matcher:Q.matcher,hooks:[{type:"command",command:Q.command}]}),VQ(X),!0}function sQ(Q){return(p().hooks?.[Q.event]??[]).some(($)=>$.hooks?.some((Y)=>Y.command===Q.command))}function rQ(Q){let X=p(),Z=X.hooks?.[Q.event];if(!Z)return!1;let $=!1,Y=Z.map((J)=>{let H=J.hooks?.length??0,V=(J.hooks??[]).filter((z)=>z.command!==Q.command);if(V.length!==H)$=!0;return{...J,hooks:V}}).filter((J)=>(J.hooks??[]).length>0);if(!$)return!1;if(Y.length)X.hooks[Q.event]=Y;else delete X.hooks[Q.event];if(X.hooks&&Object.keys(X.hooks).length===0)delete X.hooks;return VQ(X),!0}function oQ(Q){let X=`${F}
216
218
  ${Q.trim()}
217
- ${L}`,Z="";try{Z=F(P(),"utf8")}catch{}let $=new RegExp(`${O}[\\s\\S]*?${L}`);Z=$.test(Z)?Z.replace($,X):Z.trim()?`${Z.trim()}
219
+ ${g}`,Z="";try{Z=I(C(),"utf8")}catch{}let $=new RegExp(`${F}[\\s\\S]*?${g}`);Z=$.test(Z)?Z.replace($,X):Z.trim()?`${Z.trim()}
218
220
 
219
221
  ${X}
220
222
  `:`${X}
221
- `,y(j(),{recursive:!0}),u(P(),Z.endsWith(`
223
+ `,y(T(),{recursive:!0}),D(C(),Z.endsWith(`
222
224
  `)?Z:`${Z}
223
- `)}function zQ(){try{return F(P(),"utf8").includes(O)}catch{return!1}}function rQ(){let Q="";try{Q=F(P(),"utf8")}catch{return!1}let X=new RegExp(`${O}[\\s\\S]*?${L}\\n?`);if(!X.test(Q))return!1;let Z=Q.replace(X,"").replace(/\n{3,}/g,`
225
+ `)}function zQ(){try{return I(C(),"utf8").includes(F)}catch{return!1}}function iQ(){let Q="";try{Q=I(C(),"utf8")}catch{return!1}let X=new RegExp(`${F}[\\s\\S]*?${g}\\n?`);if(!X.test(Q))return!1;let Z=Q.replace(X,"").replace(/\n{3,}/g,`
226
+
227
+ `).trimStart();return D(C(),Z),!0}function eX(){let Q={};for(let Z of h)Q[Z]=JQ(Z);let X={};for(let Z of m)X[Z.event]=sQ({event:Z.event,command:Z.command});return{homeExists:b(T()),skills:Q,hooks:X,claudeMdBlock:zQ()}}async function eQ(){let{INIT_SKILL:Q,REFRESH_SKILL:X,PLAN_SKILL:Z}=await Promise.resolve().then(() => ($Q(),ZQ)),$={"beacon-init":Q,"beacon-refresh":X,"beacon-plan":Z},Y=[];for(let V of h){if(!JQ(V))Y.push(V);nQ(V,$[V])}let J=0;for(let V of m)if(tQ({event:V.event,matcher:V.matcher,command:V.command}))J++;let H=zQ();return oQ(dQ),{skillsAdded:Y,hooksAdded:J,claudeMdBlockTouched:!H}}async function qQ(){try{return{ok:!0,...await eQ()}}catch(Q){return{ok:!1,error:Q instanceof Error?Q.message:String(Q),skillsAdded:[],hooksAdded:0,claudeMdBlockTouched:!1}}}function QZ(){let Q=[];for(let $ of h)if(aQ($))Q.push($);let X=0;for(let $ of m)if(rQ($))X++;let Z=iQ();return{skillsRemoved:Q,hooksRemoved:X,claudeMdBlockRemoved:Z}}import{AsyncLocalStorage as BX}from"async_hooks";import{spawnSync as WX}from"child_process";import{createHash as xX}from"crypto";import{existsSync as WQ,mkdirSync as f,readFileSync as xQ,writeFileSync as UQ}from"fs";import{homedir as UX}from"os";import{basename as KX,join as M}from"path";function K(){return process.env.BEACON_HOME||M(UX(),".beacon")}function l(){return M(K(),"workspaces.json")}function KQ(Q){return xX("sha256").update(Q).digest("hex").slice(0,12)}function xZ(Q=process.cwd()){let X=WX("git",["rev-parse","--show-toplevel"],{cwd:Q,stdio:["ignore","pipe","ignore"]});if(X.status===0){let Z=X.stdout?.toString().trim();if(Z)return Z}return Q}function d(Q){return M(K(),Q)}function jX(Q){return`file:${M(d(Q),"db.sqlite")}`}function v(){try{let Q=JSON.parse(xQ(l(),"utf8"));return Array.isArray(Q)?Q:[]}catch{return[]}}function n(Q){f(K(),{recursive:!0}),UQ(l(),JSON.stringify(Q,null,2))}function TX(){return v().sort((Q,X)=>X.lastOpenedAt.localeCompare(Q.lastOpenedAt))}function U(Q){return v().find((X)=>X.id===Q)??null}function MX(Q,X,Z=new Date().toISOString()){let $=KQ(Q),Y=v(),J=Y.find((z)=>z.id===$),H={id:$,path:Q,name:X||J?.name||KX(Q),lastOpenedAt:Z},V=[H,...Y.filter((z)=>z.id!==$)];return f(d($),{recursive:!0}),n(V),H}function UZ(Q,X=new Date().toISOString()){let Z=v(),$=Z.find((Y)=>Y.id===Q);if(!$)return;$.lastOpenedAt=X,n(Z)}function KZ(Q){if(n(v().filter((X)=>X.id!==Q)),a()===Q)PX(TX()[0]?.id??null)}function jQ(){return M(K(),"active")}function a(){try{return xQ(jQ(),"utf8").trim()||null}catch{return null}}function PX(Q){f(K(),{recursive:!0}),UQ(jQ(),Q??"")}function jZ(){let Q=a();return Q?U(Q):null}function TZ(){let Q=vX()??a();return Q?U(Q):null}function MZ(Q){return U(Q)?.path??null}function PZ(){return WQ(l())}var TQ="beacon_ws";function MQ(Q,X){let Z=Q.headers.get("cookie");if(!Z)return null;for(let $ of Z.split(";")){let Y=$.indexOf("=");if(Y===-1)continue;if($.slice(0,Y).trim()===X)return decodeURIComponent($.slice(Y+1).trim())}return null}function CZ(Q){let X=Q.headers.get("x-beacon-workspace");if(X&&U(X))return X;let Z=MQ(Q,TQ);if(Z&&U(Z))return Z;return null}var CX="x-beacon-workspace-path";async function vZ(Q){let X=Q.headers.get("x-beacon-workspace");if(X&&U(X))return X;let Z=Q.headers.get(CX);if(Z&&(!X||KQ(Z)===X)){let Y=MX(Z);return await OX(Y.id),Y.id}let $=MQ(Q,TQ);if($&&U($))return $;return null}var PQ=new BX;function OZ(Q,X){return Q?PQ.run(Q,X):X()}function vX(){return PQ.getStore()??null}var BQ=new Set;async function OX(Q){let X=d(Q),Z=M(X,"db.sqlite"),$=WQ(Z);if($&&BQ.has(Z))return{ok:!0,created:!1,migrated:!1};try{f(X,{recursive:!0});let{provisionDb:Y}=await NQ().then(() => HQ),J=await Y(jX(Q));return BQ.add(Z),{ok:!0,created:!$,migrated:$&&J.migrated}}catch(Y){return{ok:!1,created:!1,migrated:!1,error:Y instanceof Error?Y.message:String(Y)}}}import{execSync as RQ,spawn as fX}from"child_process";import{mkdirSync as RX,openSync as uX,readFileSync as AX,writeFileSync as wX}from"fs";import{homedir as kX,platform as EQ}from"os";import{dirname as _X,join as u}from"path";import{fileURLToPath as LX}from"url";import{createHash as gX}from"crypto";var CQ=1500,vQ=345600000,IZ=1800000,DZ=3000;import{mkdirSync as uZ,readFileSync as IX,writeFileSync as AZ}from"fs";import{join as DX}from"path";var FX=["default","acceptEdits","bypassPermissions","auto"];function OQ(Q){return typeof Q==="string"&&FX.includes(Q)}function FQ(Q,X){let Z={behavior:"allow"};if(Q)Z.updatedPermissions=[{type:"setMode",mode:Q,destination:"session"}];let $={hookEventName:"PermissionRequest",decision:Z};if(X?.trim())$.additionalContext=X;return{hookSpecificOutput:$}}function EX(){return DX(K(),"preferences.json")}function IQ(){try{let Q=JSON.parse(IX(EX(),"utf8"));return{planApprovalMode:OQ(Q.planApprovalMode)?Q.planApprovalMode:void 0,planApprovalModeConfigured:Q.planApprovalModeConfigured===!0}}catch{return{}}}function DQ(Q){if(!Q?.length)return"";let X=Q.map(($)=>$.id?` \u2022 ${$.title} \u2014 id: ${$.id}`:` \u2022 ${$.title}`),Z=Q.some(($)=>$.id);return`
224
228
 
225
- `).trimStart();return u(P(),Z),!0}function iX(){let Q={};for(let Z of h)Q[Z]=JQ(Z);let X={};for(let Z of m)X[Z.event]=nQ({event:Z.event,command:Z.command});return{homeExists:b(j()),skills:Q,hooks:X,claudeMdBlock:zQ()}}async function iQ(){let{INIT_SKILL:Q,REFRESH_SKILL:X,PLAN_SKILL:Z}=await Promise.resolve().then(() => ($Q(),ZQ)),$={"beacon-init":Q,"beacon-refresh":X,"beacon-plan":Z},Y=[];for(let V of h){if(!JQ(V))Y.push(V);dQ(V,$[V])}let J=0;for(let V of m)if(aQ({event:V.event,matcher:V.matcher,command:V.command}))J++;let H=zQ();return oQ(lQ),{skillsAdded:Y,hooksAdded:J,claudeMdBlockTouched:!H}}async function qQ(){try{return{ok:!0,...await iQ()}}catch(Q){return{ok:!1,error:Q instanceof Error?Q.message:String(Q),skillsAdded:[],hooksAdded:0,claudeMdBlockTouched:!1}}}function eX(){let Q=[];for(let $ of h)if(tQ($))Q.push($);let X=0;for(let $ of m)if(sQ($))X++;let Z=rQ();return{skillsRemoved:Q,hooksRemoved:X,claudeMdBlockRemoved:Z}}import{AsyncLocalStorage as NX}from"async_hooks";import{spawnSync as BX}from"child_process";import{createHash as xX}from"crypto";import{existsSync as xQ,mkdirSync as D,readFileSync as WQ,writeFileSync as UQ}from"fs";import{homedir as WX}from"os";import{basename as UX,join as T}from"path";function K(){return process.env.BEACON_HOME||T(WX(),".beacon")}function l(){return T(K(),"workspaces.json")}function KQ(Q){return xX("sha256").update(Q).digest("hex").slice(0,12)}function xZ(Q=process.cwd()){let X=BX("git",["rev-parse","--show-toplevel"],{cwd:Q,stdio:["ignore","pipe","ignore"]});if(X.status===0){let Z=X.stdout?.toString().trim();if(Z)return Z}return Q}function d(Q){return T(K(),Q)}function KX(Q){return`file:${T(d(Q),"db.sqlite")}`}function v(){try{let Q=JSON.parse(WQ(l(),"utf8"));return Array.isArray(Q)?Q:[]}catch{return[]}}function t(Q){D(K(),{recursive:!0}),UQ(l(),JSON.stringify(Q,null,2))}function CX(){return v().sort((Q,X)=>X.lastOpenedAt.localeCompare(Q.lastOpenedAt))}function U(Q){return v().find((X)=>X.id===Q)??null}function jX(Q,X,Z=new Date().toISOString()){let $=KQ(Q),Y=v(),J=Y.find((z)=>z.id===$),H={id:$,path:Q,name:X||J?.name||UX(Q),lastOpenedAt:Z},V=[H,...Y.filter((z)=>z.id!==$)];return D(d($),{recursive:!0}),t(V),H}function WZ(Q,X=new Date().toISOString()){let Z=v(),$=Z.find((Y)=>Y.id===Q);if(!$)return;$.lastOpenedAt=X,t(Z)}function UZ(Q){if(t(v().filter((X)=>X.id!==Q)),a()===Q)TX(CX()[0]?.id??null)}function CQ(){return T(K(),"active")}function a(){try{return WQ(CQ(),"utf8").trim()||null}catch{return null}}function TX(Q){D(K(),{recursive:!0}),UQ(CQ(),Q??"")}function KZ(){let Q=a();return Q?U(Q):null}function CZ(){let Q=PX()??a();return Q?U(Q):null}function jZ(Q){return U(Q)?.path??null}function TZ(){return xQ(l())}var jQ="beacon_ws";function TQ(Q,X){let Z=Q.headers.get("cookie");if(!Z)return null;for(let $ of Z.split(";")){let Y=$.indexOf("=");if(Y===-1)continue;if($.slice(0,Y).trim()===X)return decodeURIComponent($.slice(Y+1).trim())}return null}function MZ(Q){let X=Q.headers.get("x-beacon-workspace");if(X&&U(X))return X;let Z=TQ(Q,jQ);if(Z&&U(Z))return Z;return null}var MX="x-beacon-workspace-path";async function PZ(Q){let X=Q.headers.get("x-beacon-workspace");if(X&&U(X))return X;let Z=Q.headers.get(MX);if(Z&&(!X||KQ(Z)===X)){let Y=jX(Z);return await vX(Y.id),Y.id}let $=TQ(Q,jQ);if($&&U($))return $;return null}var MQ=new NX;function vZ(Q,X){return Q?MQ.run(Q,X):X()}function PX(){return MQ.getStore()??null}var BQ=new Set;async function vX(Q){let X=d(Q),Z=T(X,"db.sqlite"),$=xQ(Z);if($&&BQ.has(Z))return{ok:!0,created:!1,migrated:!1};try{D(X,{recursive:!0});let{provisionDb:Y}=await NQ().then(() => HQ),J=await Y(KX(Q));return BQ.add(Z),{ok:!0,created:!$,migrated:$&&J.migrated}}catch(Y){return{ok:!1,created:!1,migrated:!1,error:Y instanceof Error?Y.message:String(Y)}}}import{execSync as DQ,spawn as IX}from"child_process";import{mkdirSync as DX,openSync as EX,readFileSync as RX,writeFileSync as AX}from"fs";import{homedir as kX,platform as uQ}from"os";import{dirname as wX,join as R}from"path";import{fileURLToPath as gX}from"url";import{createHash as _X}from"crypto";var PQ=1500,vQ=345600000,OZ=1800000,FZ=3000;import{mkdirSync as EZ,readFileSync as OX,writeFileSync as RZ}from"fs";import{join as FX}from"path";var fX=["default","acceptEdits","bypassPermissions","auto"];function fQ(Q){return typeof Q==="string"&&fX.includes(Q)}function OQ(Q){let X={behavior:"allow"};if(Q)X.updatedPermissions=[{type:"setMode",mode:Q,destination:"session"}];return{hookSpecificOutput:{hookEventName:"PermissionRequest",decision:X}}}function uX(){return FX(K(),"preferences.json")}function FQ(){try{let Q=JSON.parse(OX(uX(),"utf8"));return{planApprovalMode:fQ(Q.planApprovalMode)?Q.planApprovalMode:void 0,planApprovalModeConfigured:Q.planApprovalModeConfigured===!0}}catch{return{}}}var LX=R(wX(gX(import.meta.url)),".."),s=process.env.BEACON_HOME||R(kX(),".beacon"),EQ=R(s,"server.json"),IQ=process.env.PORT||"4319";function bX(Q){try{return process.kill(Q,0),!0}catch{return!1}}function yX(Q){try{return JSON.parse(RX(Q,"utf8"))}catch{return null}}async function RQ(Q){try{return(await fetch(Q)).ok}catch{return!1}}async function SX(Q,X=60){for(let Z=0;Z<X;Z++){if(await RQ(Q))return!0;await new Promise(($)=>setTimeout($,500))}return!1}function hX(){DX(s,{recursive:!0});let Q=EX(R(s,"server.log"),"a"),X={...process.env,PORT:IQ,BEACON_NO_OPEN:"1"};delete X.BEACON_REPO,delete X.BEACON_DATA_DIR,delete X.DATABASE_URL;let Z=IX("bun",["run","dev"],{cwd:LX,env:X,detached:!0,stdio:["ignore",Q,Q]});Z.unref();let $={pid:Z.pid??0,port:IQ};return AX(EQ,JSON.stringify($)),$}async function mX(){let Q=yX(EQ);if(Q&&bX(Q.pid)&&await RQ(`http://localhost:${Q.port}/api/workspace`))return Q.port;let{port:X}=hX();return await SX(`http://localhost:${X}/api/workspace`),X}function pX(Q){if(process.env.BEACON_NO_OPEN)return;let X=uQ()==="darwin"?"open":uQ()==="win32"?"start":"xdg-open";try{DQ(`${X} "${Q}"`,{stdio:"ignore"})}catch{}}function cX(){try{return DQ("git rev-parse --show-toplevel",{cwd:process.cwd(),stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return""}}function lX(Q){return _X("sha256").update(Q).digest("hex").slice(0,12)}async function dX(){if(process.stdin.isTTY)return null;let Q=[];for await(let Z of process.stdin)Q.push(Z);let X=Buffer.concat(Q).toString("utf8").trim();if(!X)return null;try{return JSON.parse(X)}catch{return null}}function n(Q){return OQ(Q)}function E(Q){return{hookSpecificOutput:{hookEventName:"PermissionRequest",decision:{behavior:"deny",message:Q}}}}function C(Q){process.stdout.write(JSON.stringify(Q)),process.exit(0)}(async()=>{await qQ();let X=(await dX())?.tool_input?.plan;if(!X||!X.trim())C(n());let $=`http://localhost:${await mX()}`,Y=cX()||process.cwd(),J=lX(Y);await fetch(`${$}/api/workspace/activate?id=${J}`).catch(()=>{});let H=X.split(`
226
- `,1)[0]?.replace(/^#+\s*/,"").slice(0,160)||"Plan review",V=await fetch(`${$}/api/plan`,{method:"POST",headers:{"content-type":"application/json","x-beacon-workspace":J},body:JSON.stringify({description:H,markdown:X})});if(V.status===422){let G=await V.json().catch(()=>({}));C(E(G.error??"Beacon rejected the plan: every roadmap feature needs a category and a priority. Add them and re-present."))}if(!V.ok)C(n());if(!await fetch(`${$}/api/plan/presence`,{headers:{"x-beacon-workspace":J}}).then((G)=>G.json()).then((G)=>!!G?.live).catch(()=>!1)){let G=encodeURIComponent(`/plan?ws=${J}`);pX(`${$}/api/workspace/activate?id=${J}&redirect=${G}`)}let N=Date.now()+vQ,M=(G)=>new Promise((A)=>setTimeout(A,G));while(Date.now()<N){await M(PQ);let G=await fetch(`${$}/api/plan/verdict`,{headers:{"x-beacon-workspace":J}}).then((A)=>A.json()).catch(()=>null);if(!G)continue;if(G.kind==="approved")C(n(FQ().planApprovalMode));if(G.kind==="discarded")C(E("The user discarded the plan in Beacon. Ask what they want to adjust before re-presenting."));if(G.kind==="feedback")C(E(`The user left feedback on the plan in Beacon (inline comments and/or edits on the /map and /db boards). Revise the plan based on the feedback below, then re-present (call ExitPlanMode again):
229
+ This plan created these roadmap feature(s):
230
+ `+X.join(`
231
+ `)+"\n\nWhen the work ships, register them in ONE call: `beacon_describe_feature` with a "+"`features` array \u2014 one entry per feature"+(Z?", each keyed by the `id` above (no title-matching needed)":"")+", each with the files you touched + a short markdown summary. Do them all in that single "+"batched call so every feature flips to Done \u2014 do NOT register only the umbrella, and do "+"NOT make one call per feature."}var bX=u(_X(LX(import.meta.url)),".."),s=process.env.BEACON_HOME||u(kX(),".beacon"),uQ=u(s,"server.json"),fQ=process.env.PORT||"4319";function yX(Q){try{return process.kill(Q,0),!0}catch{return!1}}function SX(Q){try{return JSON.parse(AX(Q,"utf8"))}catch{return null}}async function AQ(Q){try{return(await fetch(Q)).ok}catch{return!1}}async function hX(Q,X=60){for(let Z=0;Z<X;Z++){if(await AQ(Q))return!0;await new Promise(($)=>setTimeout($,500))}return!1}function mX(){RX(s,{recursive:!0});let Q=uX(u(s,"server.log"),"a"),X={...process.env,PORT:fQ,BEACON_NO_OPEN:"1"};delete X.BEACON_REPO,delete X.BEACON_DATA_DIR,delete X.DATABASE_URL;let Z=fX("bun",["run","dev"],{cwd:bX,env:X,detached:!0,stdio:["ignore",Q,Q]});Z.unref();let $={pid:Z.pid??0,port:fQ};return wX(uQ,JSON.stringify($)),$}async function pX(){let Q=SX(uQ);if(Q&&yX(Q.pid)&&await AQ(`http://localhost:${Q.port}/api/workspace`))return Q.port;let{port:X}=mX();return await hX(`http://localhost:${X}/api/workspace`),X}function cX(Q){if(process.env.BEACON_NO_OPEN)return;let X=EQ()==="darwin"?"open":EQ()==="win32"?"start":"xdg-open";try{RQ(`${X} "${Q}"`,{stdio:"ignore"})}catch{}}function lX(){try{return RQ("git rev-parse --show-toplevel",{cwd:process.cwd(),stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return""}}function dX(Q){return gX("sha256").update(Q).digest("hex").slice(0,12)}async function nX(){if(process.stdin.isTTY)return null;let Q=[];for await(let Z of process.stdin)Q.push(Z);let X=Buffer.concat(Q).toString("utf8").trim();if(!X)return null;try{return JSON.parse(X)}catch{return null}}function t(Q,X){return FQ(Q,X)}function R(Q){return{hookSpecificOutput:{hookEventName:"PermissionRequest",decision:{behavior:"deny",message:Q}}}}function j(Q){process.stdout.write(JSON.stringify(Q)),process.exit(0)}(async()=>{await qQ();let X=(await nX())?.tool_input?.plan;if(!X||!X.trim())j(t());let $=`http://localhost:${await pX()}`,Y=lX()||process.cwd(),J=dX(Y);await fetch(`${$}/api/workspace/activate?id=${J}`).catch(()=>{});let H=X.split(`
232
+ `,1)[0]?.replace(/^#+\s*/,"").slice(0,160)||"Plan review",V=await fetch(`${$}/api/plan`,{method:"POST",headers:{"content-type":"application/json","x-beacon-workspace":J},body:JSON.stringify({description:H,markdown:X})});if(V.status===422){let G=await V.json().catch(()=>({}));j(R(G.error??"Beacon rejected the plan: every roadmap feature needs a category and a priority. Add them and re-present."))}if(!V.ok)j(t());if(!await fetch(`${$}/api/plan/presence`,{headers:{"x-beacon-workspace":J}}).then((G)=>G.json()).then((G)=>!!G?.live).catch(()=>!1)){let G=encodeURIComponent(`/plan?ws=${J}`);cX(`${$}/api/workspace/activate?id=${J}&redirect=${G}`)}let N=Date.now()+vQ,P=(G)=>new Promise((A)=>setTimeout(A,G));while(Date.now()<N){await P(CQ);let G=await fetch(`${$}/api/plan/verdict`,{headers:{"x-beacon-workspace":J}}).then((A)=>A.json()).catch(()=>null);if(!G)continue;if(G.kind==="approved")j(t(IQ().planApprovalMode,DQ(G.features)));if(G.kind==="discarded")j(R("The user discarded the plan in Beacon. Ask what they want to adjust before re-presenting."));if(G.kind==="feedback")j(R(`The user left feedback on the plan in Beacon (inline comments and/or edits on the /map and /db boards). Revise the plan based on the feedback below, then re-present (call ExitPlanMode again):
227
233
 
228
- `+G.feedback))}C(E("Beacon plan review timed out without a verdict. Re-present the plan and ask the user to review it."))})();
234
+ `+G.feedback))}j(R("Beacon plan review timed out without a verdict. Re-present the plan and ask the user to review it."))})();
@@ -181,11 +181,13 @@ Beacon extracts it deterministically and **strips the block from the prose** (it
181
181
 
182
182
  **The board is built ONLY from the block \u2014 prose is NOT parsed.** If your plan describes ANY database models/tables/columns in the prose (e.g. "Model \`legal_precedent.py\` \u2014 natural key (court, \u2026)"), you MUST also put them in the block's \`tables\` array (with \`columns\`), or the /db tab will be empty for that plan. Same for endpoints (\`endpoints\` with \`uses:[{table,access}]\`) and features (\`features\`). A plan that lists five tables in prose but ships a block with only \`features\` renders an empty database board \u2014 exactly the "I described models but the DB tab is empty" failure. Mirror every DB entity you mention into the block.
183
183
 
184
- ### 3. At the end, register the work
184
+ ### 3. At the end, register the work \u2014 in ONE call
185
185
 
186
- Call \`beacon_describe_feature\` for **EVERY** feature the plan created \u2014 once per feature, **by its exact title** (the same titles you listed in \`features\`/the \`\`\`beacon block) \u2014 with the files you touched and a short markdown description. This flips each one to **Done** and keeps \`beacon_context_for_feature\` accurate for the next session.
186
+ Call \`beacon_describe_feature\` **ONCE** with a \`features\` array \u2014 one entry per feature the plan created \u2014 each with the files you touched and a short markdown description. This flips each one to **Done** and keeps \`beacon_context_for_feature\` accurate for the next session.
187
187
 
188
- Do this per feature. If a plan added five features, that's five \`beacon_describe_feature\` calls \u2014 registering only an umbrella ("Harden auth") leaves the individual features stuck on **Pending** on the map.
188
+ Key each entry by its node \`id\`: the ids are handed back to you when the plan is approved (in the approval message / additionalContext), so you don't fuzzy-match titles or pay a disambiguation round-trip. If you don't have an id, \`title\` still works.
189
+
190
+ Register them all in that single batched call. If a plan added five features, that's ONE \`beacon_describe_feature\` call with five entries \u2014 NOT five calls, and NOT just an umbrella ("Harden auth"), which leaves the individual features stuck on **Pending**.
189
191
 
190
192
  If the feature added or materially changed a REAL architectural component (a subsystem \u2014 NOT a file), also pass \`architecture: [{ title, domain, role, \u2026 }]\` so the Architecture map stays accurate. It upserts curated components by title; never list files as components.
191
193
 
@@ -180,11 +180,13 @@ Beacon extracts it deterministically and **strips the block from the prose** (it
180
180
 
181
181
  **The board is built ONLY from the block \u2014 prose is NOT parsed.** If your plan describes ANY database models/tables/columns in the prose (e.g. "Model \`legal_precedent.py\` \u2014 natural key (court, \u2026)"), you MUST also put them in the block's \`tables\` array (with \`columns\`), or the /db tab will be empty for that plan. Same for endpoints (\`endpoints\` with \`uses:[{table,access}]\`) and features (\`features\`). A plan that lists five tables in prose but ships a block with only \`features\` renders an empty database board \u2014 exactly the "I described models but the DB tab is empty" failure. Mirror every DB entity you mention into the block.
182
182
 
183
- ### 3. At the end, register the work
183
+ ### 3. At the end, register the work \u2014 in ONE call
184
184
 
185
- Call \`beacon_describe_feature\` for **EVERY** feature the plan created \u2014 once per feature, **by its exact title** (the same titles you listed in \`features\`/the \`\`\`beacon block) \u2014 with the files you touched and a short markdown description. This flips each one to **Done** and keeps \`beacon_context_for_feature\` accurate for the next session.
185
+ Call \`beacon_describe_feature\` **ONCE** with a \`features\` array \u2014 one entry per feature the plan created \u2014 each with the files you touched and a short markdown description. This flips each one to **Done** and keeps \`beacon_context_for_feature\` accurate for the next session.
186
186
 
187
- Do this per feature. If a plan added five features, that's five \`beacon_describe_feature\` calls \u2014 registering only an umbrella ("Harden auth") leaves the individual features stuck on **Pending** on the map.
187
+ Key each entry by its node \`id\`: the ids are handed back to you when the plan is approved (in the approval message / additionalContext), so you don't fuzzy-match titles or pay a disambiguation round-trip. If you don't have an id, \`title\` still works.
188
+
189
+ Register them all in that single batched call. If a plan added five features, that's ONE \`beacon_describe_feature\` call with five entries \u2014 NOT five calls, and NOT just an umbrella ("Harden auth"), which leaves the individual features stuck on **Pending**.
188
190
 
189
191
  If the feature added or materially changed a REAL architectural component (a subsystem \u2014 NOT a file), also pass \`architecture: [{ title, domain, role, \u2026 }]\` so the Architecture map stays accurate. It upserts curated components by title; never list files as components.
190
192
 
@@ -180,11 +180,13 @@ Beacon extracts it deterministically and **strips the block from the prose** (it
180
180
 
181
181
  **The board is built ONLY from the block \u2014 prose is NOT parsed.** If your plan describes ANY database models/tables/columns in the prose (e.g. "Model \`legal_precedent.py\` \u2014 natural key (court, \u2026)"), you MUST also put them in the block's \`tables\` array (with \`columns\`), or the /db tab will be empty for that plan. Same for endpoints (\`endpoints\` with \`uses:[{table,access}]\`) and features (\`features\`). A plan that lists five tables in prose but ships a block with only \`features\` renders an empty database board \u2014 exactly the "I described models but the DB tab is empty" failure. Mirror every DB entity you mention into the block.
182
182
 
183
- ### 3. At the end, register the work
183
+ ### 3. At the end, register the work \u2014 in ONE call
184
184
 
185
- Call \`beacon_describe_feature\` for **EVERY** feature the plan created \u2014 once per feature, **by its exact title** (the same titles you listed in \`features\`/the \`\`\`beacon block) \u2014 with the files you touched and a short markdown description. This flips each one to **Done** and keeps \`beacon_context_for_feature\` accurate for the next session.
185
+ Call \`beacon_describe_feature\` **ONCE** with a \`features\` array \u2014 one entry per feature the plan created \u2014 each with the files you touched and a short markdown description. This flips each one to **Done** and keeps \`beacon_context_for_feature\` accurate for the next session.
186
186
 
187
- Do this per feature. If a plan added five features, that's five \`beacon_describe_feature\` calls \u2014 registering only an umbrella ("Harden auth") leaves the individual features stuck on **Pending** on the map.
187
+ Key each entry by its node \`id\`: the ids are handed back to you when the plan is approved (in the approval message / additionalContext), so you don't fuzzy-match titles or pay a disambiguation round-trip. If you don't have an id, \`title\` still works.
188
+
189
+ Register them all in that single batched call. If a plan added five features, that's ONE \`beacon_describe_feature\` call with five entries \u2014 NOT five calls, and NOT just an umbrella ("Harden auth"), which leaves the individual features stuck on **Pending**.
188
190
 
189
191
  If the feature added or materially changed a REAL architectural component (a subsystem \u2014 NOT a file), also pass \`architecture: [{ title, domain, role, \u2026 }]\` so the Architecture map stays accurate. It upserts curated components by title; never list files as components.
190
192
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trybeacon",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "The visual planning surface for the coding agent in your terminal.",
5
5
  "homepage": "https://trybeacon.sh",
6
6
  "bin": {