@crouton-kit/crouter 0.3.70 → 0.3.79

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 (193) hide show
  1. package/README.md +1 -1
  2. package/dist/build-root.d.ts +12 -4
  3. package/dist/build-root.js +25 -6
  4. package/dist/builtin-memory/00-runtime-base.md +3 -10
  5. package/dist/builtin-memory/04-base-worker.md +18 -0
  6. package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
  7. package/dist/builtin-memory/crouter-development/plugins.md +82 -5
  8. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
  9. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +1115 -1
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +234 -71
  11. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
  12. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +2 -516
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
  14. package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
  15. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
  16. package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
  17. package/dist/builtin-views/canvas/tui.mjs +8 -9
  18. package/dist/builtin-views/chat/tui.mjs +12 -12
  19. package/dist/builtin-views/git-pr/tui.mjs +7 -8
  20. package/dist/builtin-views/inbox/tui.mjs +27 -41
  21. package/dist/builtin-views/linkedin/tui.mjs +23 -37
  22. package/dist/builtin-views/prompt-review/tui.mjs +11 -11
  23. package/dist/builtin-views/settings/tui.mjs +11 -11
  24. package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
  25. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -6
  26. package/dist/clients/attach/__tests__/attach-keybindings.test.d.ts +1 -0
  27. package/dist/clients/attach/__tests__/attach-keybindings.test.js +113 -0
  28. package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
  29. package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
  30. package/dist/clients/attach/__tests__/mermaid-render.test.js +28 -0
  31. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
  32. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
  33. package/dist/clients/attach/attach-cmd.d.ts +9 -1
  34. package/dist/clients/attach/attach-cmd.js +841 -792
  35. package/dist/clients/attach/auth-pickers.d.ts +0 -12
  36. package/dist/clients/attach/auth-pickers.js +64 -15
  37. package/dist/clients/attach/canvas-panels.js +2 -3
  38. package/dist/clients/attach/chat-view.d.ts +7 -8
  39. package/dist/clients/attach/chat-view.js +119 -79
  40. package/dist/clients/attach/edit-diff-render.d.ts +6 -0
  41. package/dist/clients/attach/edit-diff-render.js +263 -0
  42. package/dist/clients/attach/graph-overlay.d.ts +12 -2
  43. package/dist/clients/attach/graph-overlay.js +83 -33
  44. package/dist/clients/attach/input-controller.d.ts +28 -2
  45. package/dist/clients/attach/input-controller.js +38 -5
  46. package/dist/clients/attach/mermaid-render.js +5 -2
  47. package/dist/clients/attach/pickers.d.ts +8 -7
  48. package/dist/clients/attach/pickers.js +11 -17
  49. package/dist/clients/attach/slash-commands.d.ts +9 -0
  50. package/dist/clients/attach/slash-commands.js +127 -7
  51. package/dist/clients/attach/titled-editor.d.ts +12 -1
  52. package/dist/clients/attach/titled-editor.js +103 -8
  53. package/dist/commands/canvas-browse.js +2 -2
  54. package/dist/commands/human/queue.js +3 -4
  55. package/dist/commands/memory/lint.js +39 -5
  56. package/dist/commands/memory/write.js +1 -0
  57. package/dist/commands/node.js +9 -2
  58. package/dist/commands/pkg/plugin-inspect.js +22 -1
  59. package/dist/commands/pkg/plugin-manage.js +31 -9
  60. package/dist/commands/surface-tmux-spread.js +1 -3
  61. package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
  62. package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
  63. package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
  64. package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
  65. package/dist/commands/sys/__tests__/setup-core.test.js +158 -1
  66. package/dist/commands/sys/config.js +18 -21
  67. package/dist/commands/sys/doctor.js +42 -4
  68. package/dist/commands/sys/setup-core.d.ts +49 -1
  69. package/dist/commands/sys/setup-core.js +161 -4
  70. package/dist/commands/sys/setup.d.ts +88 -0
  71. package/dist/commands/sys/setup.js +943 -169
  72. package/dist/commands/view-pick.d.ts +4 -0
  73. package/dist/commands/view-pick.js +17 -7
  74. package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
  75. package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
  76. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
  77. package/dist/core/__tests__/canvas-inbox-watcher.test.js +34 -9
  78. package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +1 -0
  79. package/dist/core/__tests__/command-plugins-surfaces.test.js +298 -0
  80. package/dist/core/__tests__/command-plugins.test.d.ts +1 -0
  81. package/dist/core/__tests__/command-plugins.test.js +444 -0
  82. package/dist/core/__tests__/fault-classifier.test.js +15 -0
  83. package/dist/core/__tests__/fixtures/fake-engine.d.ts +6 -0
  84. package/dist/core/__tests__/fixtures/fake-engine.js +9 -1
  85. package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
  86. package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
  87. package/dist/core/__tests__/preview-registry-sync.test.js +30 -1
  88. package/dist/core/__tests__/scope-crouter-home-fence.test.d.ts +1 -0
  89. package/dist/core/__tests__/scope-crouter-home-fence.test.js +55 -0
  90. package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
  91. package/dist/core/__tests__/stream-watchdog.test.js +70 -0
  92. package/dist/core/__tests__/tmux-surface.test.js +72 -0
  93. package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
  94. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  95. package/dist/core/canvas/browse/app.d.ts +6 -0
  96. package/dist/core/canvas/browse/app.js +230 -41
  97. package/dist/core/canvas/browse/model.d.ts +10 -6
  98. package/dist/core/canvas/browse/model.js +20 -12
  99. package/dist/core/canvas/browse/pins.d.ts +4 -0
  100. package/dist/core/canvas/browse/pins.js +29 -0
  101. package/dist/core/canvas/browse/render.d.ts +13 -1
  102. package/dist/core/canvas/browse/render.js +72 -15
  103. package/dist/core/canvas/nav-model.js +5 -11
  104. package/dist/core/canvas/paths.d.ts +5 -0
  105. package/dist/core/canvas/paths.js +7 -0
  106. package/dist/core/command-plugins/adapter.d.ts +15 -0
  107. package/dist/core/command-plugins/adapter.js +145 -0
  108. package/dist/core/command-plugins/compose.d.ts +5 -0
  109. package/dist/core/command-plugins/compose.js +56 -0
  110. package/dist/core/command-plugins/discovery.d.ts +104 -0
  111. package/dist/core/command-plugins/discovery.js +565 -0
  112. package/dist/core/config.d.ts +48 -0
  113. package/dist/core/config.js +228 -110
  114. package/dist/core/fault-classifier.js +1 -1
  115. package/dist/core/fs-utils.d.ts +1 -0
  116. package/dist/core/fs-utils.js +15 -1
  117. package/dist/core/keybindings/__tests__/bespoke-consumers.test.d.ts +1 -0
  118. package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +40 -0
  119. package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
  120. package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
  121. package/dist/core/keybindings/catalog.d.ts +14 -0
  122. package/dist/core/keybindings/catalog.js +257 -0
  123. package/dist/core/keybindings/index.d.ts +5 -0
  124. package/dist/core/keybindings/index.js +4 -0
  125. package/dist/core/keybindings/match.d.ts +29 -0
  126. package/dist/core/keybindings/match.js +99 -0
  127. package/dist/core/keybindings/persistence.d.ts +14 -0
  128. package/dist/core/keybindings/persistence.js +31 -0
  129. package/dist/core/keybindings/resolve.d.ts +14 -0
  130. package/dist/core/keybindings/resolve.js +305 -0
  131. package/dist/core/keybindings/types.d.ts +37 -0
  132. package/dist/core/keybindings/types.js +1 -0
  133. package/dist/core/memory-resolver.d.ts +1 -1
  134. package/dist/core/memory-resolver.js +10 -3
  135. package/dist/core/predicate.d.ts +5 -3
  136. package/dist/core/predicate.js +5 -3
  137. package/dist/core/profiles/select.d.ts +6 -0
  138. package/dist/core/profiles/select.js +86 -52
  139. package/dist/core/provider-management.d.ts +12 -0
  140. package/dist/core/provider-management.js +24 -0
  141. package/dist/core/runtime/broker-protocol.d.ts +33 -5
  142. package/dist/core/runtime/broker.js +164 -14
  143. package/dist/core/runtime/launch.d.ts +39 -6
  144. package/dist/core/runtime/launch.js +78 -20
  145. package/dist/core/runtime/naming.js +3 -3
  146. package/dist/core/runtime/pi-cli.d.ts +6 -0
  147. package/dist/core/runtime/pi-cli.js +16 -2
  148. package/dist/core/runtime/pi-vendored.d.ts +8 -0
  149. package/dist/core/runtime/pi-vendored.js +14 -0
  150. package/dist/core/runtime/placement.d.ts +2 -2
  151. package/dist/core/runtime/placement.js +4 -1
  152. package/dist/core/runtime/promote.js +4 -0
  153. package/dist/core/runtime/recap.d.ts +3 -3
  154. package/dist/core/runtime/recap.js +75 -47
  155. package/dist/core/runtime/recycle.js +6 -1
  156. package/dist/core/runtime/reset.js +5 -0
  157. package/dist/core/runtime/session-list-cache.d.ts +33 -0
  158. package/dist/core/runtime/session-list-cache.js +338 -0
  159. package/dist/core/runtime/spawn.js +52 -45
  160. package/dist/core/runtime/stream-watchdog.d.ts +26 -0
  161. package/dist/core/runtime/stream-watchdog.js +75 -0
  162. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  163. package/dist/core/runtime/tmux-chrome.js +2 -2
  164. package/dist/core/runtime/tmux.d.ts +18 -15
  165. package/dist/core/runtime/tmux.js +217 -110
  166. package/dist/core/scope.js +27 -4
  167. package/dist/core/subscription-state.d.ts +90 -0
  168. package/dist/core/subscription-state.js +762 -0
  169. package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
  170. package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
  171. package/dist/core/tui/host.d.ts +30 -1
  172. package/dist/core/tui/host.js +148 -34
  173. package/dist/core/view/contract.d.ts +17 -3
  174. package/dist/daemon/crtrd.js +253 -12
  175. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
  176. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
  177. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
  178. package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
  179. package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
  180. package/dist/pi-extensions/canvas-recap.d.ts +4 -0
  181. package/dist/pi-extensions/canvas-recap.js +73 -46
  182. package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
  183. package/dist/pi-extensions/canvas-stophook.js +33 -4
  184. package/dist/types.d.ts +9 -26
  185. package/dist/types.js +1 -28
  186. package/dist/web-client/assets/{index-DiFuLcp6.js → index--md2ylfi.js} +1 -1
  187. package/dist/web-client/index.html +1 -1
  188. package/dist/web-client/sw.js +1 -1
  189. package/package.json +8 -4
  190. package/dist/pi-extensions/canvas-nav.d.ts +0 -43
  191. package/dist/pi-extensions/canvas-nav.js +0 -640
  192. package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
  193. package/dist/pi-extensions/widget-order-bus.js +0 -34
package/README.md CHANGED
@@ -30,5 +30,5 @@ To opt out of the bootstrap (e.g. in CI), set `CRTR_NO_BOOTSTRAP=1`.
30
30
  The node runtime hosts the [pi coding agent](https://pi.dev) in each broker. To set your pi install up the way crouter's author runs it, add these to the `packages` array in `~/.pi/agent/settings.json` (neither ships with pi — you add them yourself):
31
31
 
32
32
  - **[pi-claude-oauth-adapter](https://github.com/minzique/dotfiles-agents/tree/main/packages/pi-claude-oauth-adapter)** — third-party npm package; Anthropic OAuth / Claude Code compatibility adapter for pi. Install with `"npm:pi-claude-oauth-adapter"`.
33
- - **[pi-personal-extensions](https://github.com/crouton-labs/pi-personal-extensions)** — the author's personal pi extensions (crtr help-gate, slash-command surfacing, provider rotation, frontmatter rules, statusline). Its README documents the full machine setup.
33
+ - **pi-crtr-extensions** — crtr's engine-side pi extensions (crtr help-gate, slash-command surfacing, provider rotation, frontmatter rules, statusline). Bundled in this repo at `src/builtin-pi-packages/pi-crtr-extensions` and wired into `~/.pi/agent/settings.json` automatically by `crtr sys setup` — nothing to install separately.
34
34
 
@@ -9,8 +9,16 @@ export declare const SUBTREE_NAMES: string[];
9
9
  * every valid child). This keeps every help/error surface byte-identical while
10
10
  * the common leaf-dispatch path loads exactly one subtree. */
11
11
  export declare function resolveRoot(first: string | undefined): Promise<RootDef>;
12
- /** Assemble the full crtr command tree (all subtrees). Used for root -h, the
13
- * unknown-path error, bare-root boot, and the listing-completeness test. Root
14
- * owns only the tagline; every subtree declares its own root representation via
15
- * its rootEntry. */
12
+ /** Assemble the full crtr command tree: every core subtree plus any top-level
13
+ * branches contributed by enabled command plugins. Used for root -h, the
14
+ * unknown-path error, bare-root boot, front-door recognition, and the
15
+ * listing-completeness test — i.e. only the FALLTHROUGH path, never the core
16
+ * fast path (`resolveRoot` short-circuits a recognized core first token with
17
+ * zero plugin I/O). Root owns only the tagline; every subtree (core or
18
+ * external) declares its own root representation via its rootEntry.
19
+ *
20
+ * Order is deterministic: core subtrees in declared order first, then accepted
21
+ * external branches sorted by name (from discovery). Discovery is
22
+ * invocation-local — no cache, no persistence — so plugin enable/update/remove
23
+ * takes effect on the next invocation automatically. */
16
24
  export declare function buildRoot(): Promise<RootDef>;
@@ -36,11 +36,30 @@ export async function resolveRoot(first) {
36
36
  }
37
37
  return buildRoot();
38
38
  }
39
- /** Assemble the full crtr command tree (all subtrees). Used for root -h, the
40
- * unknown-path error, bare-root boot, and the listing-completeness test. Root
41
- * owns only the tagline; every subtree declares its own root representation via
42
- * its rootEntry. */
39
+ /** Assemble the full crtr command tree: every core subtree plus any top-level
40
+ * branches contributed by enabled command plugins. Used for root -h, the
41
+ * unknown-path error, bare-root boot, front-door recognition, and the
42
+ * listing-completeness test — i.e. only the FALLTHROUGH path, never the core
43
+ * fast path (`resolveRoot` short-circuits a recognized core first token with
44
+ * zero plugin I/O). Root owns only the tagline; every subtree (core or
45
+ * external) declares its own root representation via its rootEntry.
46
+ *
47
+ * Order is deterministic: core subtrees in declared order first, then accepted
48
+ * external branches sorted by name (from discovery). Discovery is
49
+ * invocation-local — no cache, no persistence — so plugin enable/update/remove
50
+ * takes effect on the next invocation automatically. */
43
51
  export async function buildRoot() {
44
- const subtrees = await Promise.all(SUBTREE_NAMES.map((n) => SUBTREE_LOADERS[n]()));
45
- return defineRoot({ tagline: TAGLINE, globals: [], subtrees });
52
+ const core = await Promise.all(SUBTREE_NAMES.map((n) => SUBTREE_LOADERS[n]()));
53
+ // Dynamic import keeps command-plugin discovery/compose (and their
54
+ // resolver/manifest deps) OFF the hot leaf path's module graph — they load
55
+ // only here, on the fallthrough, matching this file's lazy-subtree rationale.
56
+ const [{ discoverCommandContributions }, { composeExternalSubtrees }] = await Promise.all([
57
+ import('./core/command-plugins/discovery.js'),
58
+ import('./core/command-plugins/compose.js'),
59
+ ]);
60
+ // Core always wins: a plugin claiming a core top-level name is skipped +
61
+ // recorded as an issue (surfaced via pkg plugin show / sys doctor).
62
+ const snapshot = discoverCommandContributions(new Set(SUBTREE_NAMES));
63
+ const external = composeExternalSubtrees(snapshot);
64
+ return defineRoot({ tagline: TAGLINE, globals: [], subtrees: [...core, ...external] });
46
65
  }
@@ -11,15 +11,6 @@ You are a **node** in a live agent graph (the crtr canvas). This section is your
11
11
  ## Identity
12
12
  You have a node id (`$CRTR_NODE_ID`), a context dir on disk, and a pi session as your vehicle. You are pinned to one working dir.
13
13
 
14
- ## Delegating
15
- Hand any self-contained unit of work to a child instead of doing it inline — that keeps your own context window (your scarce resource) free for steering, and lets independent units run in parallel:
16
-
17
- crtr node new "<task>" --kind <kind> # `crtr node -h` lists the kinds + the delegate→feed loop
18
-
19
- You auto-subscribe to every child you spawn, so you're woken when it finishes; the wake message already carries the digest — dereference the reports that matter. Prefer delegating over grinding it out yourself.
20
-
21
- When the work depends on context you already hold — an explore report, a design, a spec, earlier findings — name those files by path in the task itself: a child starts blind and inherits only what you point it at, so unreferenced context you have is context it won't find.
22
-
23
14
  ## Reports vs artifacts
24
15
  Two different things, two different homes. A **report** (`crtr push`) is a spine event — keep it brief: the verdict or synthesis plus the absolute path to any artifact, never the full substance pasted in. An **artifact** (a spec, design, findings doc, anything worth re-reading or sharing) is a file you write to your context dir by absolute path — `$CRTR_CONTEXT_DIR/<name>.md`. Your working dir is the project, so a bare `context/...` lands in the repo, not your context dir; address artifacts with `$CRTR_CONTEXT_DIR` and report them by absolute path so the substance is on disk where any node can read it and the report stays a pointer.
25
16
 
@@ -30,6 +21,8 @@ Don't stall and don't guess at a decision a person should make:
30
21
 
31
22
  crtr human ask "<question>"
32
23
 
24
+ ANY question aimed at the user goes through it, not just hard blocks — a question posed as prose in a reply or report pings nobody, while an ask lands on their screen and pushes the answer back to your inbox.
25
+
33
26
  ## When crtr itself misbehaves
34
27
  A `crtr` command that errors unexpectedly, hangs, churns, double-spawns, or contradicts its own `-h` is a harness bug — don't silently work around it. Run `crtr sys feedback` to report it (`-h` for how), then continue.
35
28
 
@@ -39,7 +32,7 @@ Two different moves; don't conflate them. **Promote** when the *shape* of the jo
39
32
  crtr node promote --kind <kind> # `crtr node promote -h` — become a long-lived orchestrator now
40
33
  crtr node yield # `crtr node yield -h` — refresh into a clean window, carrying a note forward
41
34
 
42
- Don't promote or yield for work that fits one window — finish it with `crtr push final`.
35
+ Don't promote or yield for work that fits one window — finish it with `crtr push final`. And never yield carrying an unasked question: put anything you're still wondering for the human through `crtr human ask` BEFORE you yield — an in-flight ask survives the refresh, and its answer wakes your fresh window like any child's report.
43
36
 
44
37
  ## Diagrams
45
38
  When structure would land faster as a picture than as prose — a flow, a state machine, a small architecture — write it as a ```mermaid fenced code block. The human's viewer renders it inline as a terminal diagram. Reach for it when the shape is the point, not for everything.
@@ -0,0 +1,18 @@
1
+ ---
2
+ kind: preference
3
+ when-and-why-to-read: When a node runs in base mode, this preference should be read because base nodes own one bounded task and finish it hands-on rather than recursively passing the same assignment down the graph.
4
+ system-prompt-visibility: content
5
+ file-read-visibility: none
6
+ gate: {mode: base}
7
+ rationale: >-
8
+ A base security reviewer handed its entire assignment to another base security reviewer,
9
+ which repeated the move through a 35-node chain in under five minutes. The universal prompt
10
+ had said to delegate any self-contained work while no base-mode layer told sub-kinds to work
11
+ hands-on; exact sub-kind gating also meant plan/reviewers/security did not inherit plan/00-base.
12
+ ---
13
+
14
+ ## You are a hands-on worker
15
+
16
+ Own the task you were given and complete it yourself in this window. Your scarce resource is task completion, not preserving your context for steering.
17
+
18
+ Delegate only a genuinely independent subtask that can run in parallel while you continue owning the parent task. Each child gets a bounded outcome distinct from your whole assignment; passing the same task to another base node creates recursion instead of progress. When the goal itself needs decomposition or will not fit one window, become an orchestrator rather than building a chain of base workers.
@@ -94,7 +94,7 @@ You own the goal; the human is a stakeholder, not your manager. They answer ques
94
94
 
95
95
  Engage (`crtr human ask`) when the goal is genuinely ambiguous and the codebase doesn't settle it, when you're choosing between approaches with real tradeoffs, when you've found something that changes scope or direction, when an action is irreversible or high-risk, or when finished work needs sign-off. Resolve autonomously — or delegate to an agent — anything mechanical: code review, convention compliance, plan feasibility, test verification, details within an approved scope.
96
96
 
97
- **Never yield while waiting on an ask.** Yielding tears down your window and the in-flight question with it, so you would wake to the same prompt with no answer and loop forever. While a decision is outstanding, stay resident and let it block; yield only once you have the answer or have other work to do.
97
+ **Never yield holding an unasked question.** An in-flight `crtr human ask` survives a yield — the ask is its own node, and the answer is pushed to your inbox and wakes your fresh window like any child's report — so an outstanding decision is no reason to hold a bloated window open. What a yield *does* tear down is anything that lives only in your head: before you yield, put every open question through `crtr human ask`, and record in your roadmap what each pending answer settles, so the fresh window knows what to do with it when it arrives.
98
98
 
99
99
  ## Before you finish
100
100
 
@@ -1,10 +1,12 @@
1
1
  ---
2
2
  kind: knowledge
3
3
  when-and-why-to-read: When creating a crtr plugin, packaging memory docs for
4
- distribution, or debugging install/resolution, this knowledge should be read.
4
+ distribution, adding top-level CLI commands via a command manifest, or
5
+ debugging install/resolution, this knowledge should be read.
5
6
  short-form: How to author a crtr plugin — plugin.json manifest, directory
6
- layout, scopes, install mechanics, versioning. Use when creating a new plugin,
7
- packaging memory docs for distribution, or debugging install/resolution.
7
+ layout, scopes, install mechanics, versioning, and command plugins (top-level
8
+ CLI commands via commands.json + one executable). Use when creating a plugin,
9
+ packaging memory docs, contributing commands, or debugging install/resolution.
8
10
  system-prompt-visibility: name
9
11
  file-read-visibility: none
10
12
  ---
@@ -39,7 +41,7 @@ If it's a one-off note for yourself, scope-owned memory docs are simpler. Promot
39
41
  └── <name>.md
40
42
  ```
41
43
 
42
- The `<plugin-name>` directory IS the plugin. The manifest's `name` field must match the directory name (install renames if needed). Sibling dirs (`rules/`, `agents/`, and future `commands/`, `hooks/`) hold the other artifact types.
44
+ The `<plugin-name>` directory IS the plugin. The manifest's `name` field must match the directory name (install renames if needed). Sibling dirs (`rules/`, `agents/`, and future `hooks/`) hold the other artifact types. A plugin that contributes CLI commands adds a `commands.json` manifest plus its executable — see [Command plugins](#command-plugins).
43
45
 
44
46
  ## The manifest
45
47
 
@@ -65,6 +67,7 @@ The `<plugin-name>` directory IS the plugin. The manifest's `name` field must ma
65
67
  | `description` | yes | One sentence. |
66
68
  | `source` | recommended | Git URL where the plugin lives. Used by `crtr pkg plugin update --name <name>`. |
67
69
  | `owner` | optional | Author info. |
70
+ | `commands` | optional | Plugin-root-relative path to a `commands.json` command manifest. Present ⇒ the plugin contributes top-level `crtr` commands — see [Command plugins](#command-plugins). |
68
71
 
69
72
  ## Scopes
70
73
 
@@ -150,13 +153,87 @@ Bad plugin scope:
150
153
 
151
154
  If your memory doc conceptually depends on another plugin's doc, link via `## Related` with `` `<plugin>/<doc>` ``. Don't fork content; link it.
152
155
 
156
+ ## Command plugins
157
+
158
+ Beyond docs, a plugin may contribute **top-level `crtr` commands** — new noun branches with their own leaves. You declare one pointer in the manifest and ship one executable; crtr owns parsing, help, rendering, and errors, and direct-spawns your executable once per leaf invocation.
159
+
160
+ ### The manifest pointer
161
+
162
+ Add `commands` to `plugin.json` — a plugin-root-relative path to one static manifest:
163
+
164
+ ```json
165
+ { "name": "crouter-infra", "version": "0.1.0", "description": "...", "commands": "commands.json" }
166
+ ```
167
+
168
+ Only an installed, **enabled** plugin's `commands` manifest contributes. Discovery is per-invocation: enable/disable/update/remove takes effect on the very next `crtr` call — no daemon restart, no cache to clear.
169
+
170
+ ### commands.json shape
171
+
172
+ ```json
173
+ {
174
+ "schemaVersion": 1,
175
+ "executable": "bin/cmd.js",
176
+ "mounts": [
177
+ { "parent": [], "node": { "kind": "branch", "name": "app", "...": "..." } }
178
+ ]
179
+ }
180
+ ```
181
+
182
+ - `schemaVersion` — exactly the integer `1`. Anything else rejects the whole manifest.
183
+ - `executable` — plugin-root-relative path to the one command binary. Must resolve inside the plugin root, be a regular file, and carry the POSIX exec bit.
184
+ - `mounts[]` — each `{ parent: [], node }`. v1 supports **top-level mounts only**: `parent` must be `[]`.
185
+
186
+ Every top-level `node` is a **branch** (`kind: "branch"`) carrying a `rootEntry { concept, description, whenToUse }` — the representation crtr renders at root help. Branch children are nested branches (no rootEntry) or leaves. A leaf declares `params`, `output` (array of `{ name, type, required, constraint }`), `outputKind: "object"`, and a non-empty `effects` array. Params use crtr's public vocabulary — one positional max, long-form `flag`s (types `string|int|bool|path|enum`, `choices` for enum), `stdin`, `context-file`; kebab-case names, no aliases. The declaration mirrors crtr's stable help descriptors, not its internal TypeScript defs — no closures, no dynamic state, no renderers.
187
+
188
+ ### Execution: the trust boundary
189
+
190
+ On explicit leaf invocation — **never on install, help, or discovery** — crtr direct-spawns your executable (no shell) with `--crtr-command-protocol 1`, the caller's cwd, and the full environment. Installed command plugins are **trusted local code running with the caller's authority**: crtr does not sandbox them, filter the environment, mint a credential, or interpret your backend's auth. Your executable owns authentication to its own backend. This is an execution trust boundary, not a sandbox — installed code can already read the caller's files, so env filtering would only imply a security guarantee that does not exist.
191
+
192
+ ### The protocol
193
+
194
+ crtr writes exactly one JSON request to your executable's stdin:
195
+
196
+ ```json
197
+ {
198
+ "protocolVersion": 1,
199
+ "command": ["app", "show"],
200
+ "input": { "appId": "app_123" },
201
+ "context": { "cwd": "/caller/cwd", "plugin": { "name": "crouter-infra", "version": "0.1.0", "scope": "project", "root": "/plugin/root" } }
202
+ }
203
+ ```
204
+
205
+ `input` keys are the parser's camelCase form; a declared `stdin` param arrives as an `input` string, not a second stream. Your executable writes exactly one JSON envelope to stdout and nothing else — diagnostics go to stderr:
206
+
207
+ ```json
208
+ { "protocolVersion": 1, "ok": true, "result": { "app_id": "app_123" } }
209
+ { "protocolVersion": 1, "ok": false, "error": { "code": "authentication_required", "message": "...", "field": "session", "next": "..." } }
210
+ ```
211
+
212
+ `ok` is the source of truth (a valid envelope is honored regardless of exit code). crtr validates `result` against your declared `output` (top-level field presence + type) and renders it; `--json` mirrors the same object. An error envelope becomes a normal crtr error with your `code` — lowercase snake_case, never crtr-reserved `internal`, `unknown_path`, `command_collision`, or `plugin_protocol_error`. No envelope at all (invalid JSON, empty, extra stdout, output over 10 MiB, signal kill) becomes `plugin_protocol_error`.
213
+
214
+ ### Two rules that prevent silent breakage
215
+
216
+ - **Generate `commands.json` from your command definitions — never hand-write it.** The manifest must stay in lockstep with the executable's actual command surface; a hand-maintained copy drifts, and a drifted param or output field surfaces as a validation issue or a `plugin_protocol_error` at invocation. Emit it from the same source your executable dispatches on.
217
+ - **A required output field must be non-null.** The adapter treats an explicit `null` for a declared-required field as *absent* → `plugin_protocol_error`. If a value is genuinely optional, declare it `required: false`; if it's required, always return a real value.
218
+
219
+ ### Validating your command manifest
220
+
221
+ crtr validates command manifests **statically — it never executes your binary** to check them:
222
+
223
+ - `crtr pkg plugin show <name>` — inventories the manifest path, executable, accepted top-level command names, and every current validation issue (each with received/expected/next).
224
+ - `crtr sys doctor` — validates the manifest + executable path for every effective command plugin and reports structured remediation (disable/update/remove). `--fix` never chmods or rewrites plugin content.
225
+ - `crtr pkg plugin install` / `update` — report the accepted top-level commands and any issues in their result.
226
+
227
+ A fixed manifest goes live on the next invocation; there is nothing to restart.
228
+
153
229
  ## Validation
154
230
 
155
231
  `crtr sys doctor` checks each plugin's manifest:
156
232
  - Manifest exists and is valid JSON.
157
233
  - Manifest `name` matches the directory name.
234
+ - When the plugin declares `commands`, its command manifest + executable path are validated statically (never executed) — see [Command plugins](#command-plugins).
158
235
 
159
- `crtr memory lint` checks the docs under `memory/`: frontmatter parses, valid `kind`, both visibility rungs set. Run `crtr memory write -h` for the authoring + routing guide. Sibling artifact dirs (`rules/`, `agents/`, `commands/`, `hooks/`) are validated by their respective specs as those land.
236
+ `crtr memory lint` checks the docs under `memory/`: frontmatter parses, valid `kind`, both visibility rungs set. Run `crtr memory write -h` for the authoring + routing guide. Other sibling artifact dirs (`rules/`, `agents/`, `hooks/`) are validated by their respective specs as those land.
160
237
 
161
238
  ## Cross-publishing with Claude Code
162
239
 
@@ -1,32 +1,16 @@
1
- # pi-personal-extensions
1
+ # pi-crtr-extensions
2
2
 
3
- Personal [pi](https://pi.dev) package bundling my extensions and prompt templates,
4
- so they live in version control instead of loose in `~/.pi/agent/`.
3
+ crtr's engine-side [pi](https://pi.dev) package the extensions and prompt
4
+ templates every pi engine (including crouter's brokers) loads. This directory,
5
+ inside the crouter tree, is the canonical and only copy; it ships in crouter's
6
+ `dist/builtin-pi-packages/pi-crtr-extensions` and `crtr sys setup` wires that
7
+ path into the `packages` array of `~/.pi/agent/settings.json` (alongside the
8
+ bundled `pi-mode-switch`) — there is nothing to clone or install separately.
5
9
 
6
- Installed as a local-path package in `~/.pi/agent/settings.json` (see below).
7
-
8
- ## Setup on a fresh machine the whole pi rig
9
-
10
- My pi install is four packages wired together through the `packages` array in
11
- `~/.pi/agent/settings.json`. None of these ship with pi; each is added explicitly.
12
-
13
- ```jsonc
14
- {
15
- "packages": [
16
- "npm:pi-claude-oauth-adapter", // third-party npm: Anthropic OAuth / Claude-Code compat adapter
17
- "../../Code/cli/sisyphus/pi-plugins/sisyphus", // local: sisyphus orchestration prompts/skills
18
- "../../Code/cli/pi-personal-extensions", // local: THIS package
19
- "../../Code/cli/pi-mode-switch" // local: Alt+M spec/plan/normal mode cycling
20
- ],
21
- "defaultProvider": "anthropic",
22
- "defaultModel": "claude-opus-4-8",
23
- "defaultThinkingLevel": "high",
24
- "enableSkillCommands": true,
25
- "steeringMode": "all",
26
- "quietStartup": true,
27
- "theme": "dark"
28
- }
29
- ```
10
+ The package is excluded from crouter's tsc compile unit; pi loads the `.ts`
11
+ sources directly with its own loader. Imports may reach from here into
12
+ crouter's compiled core (e.g. `lib/subscription-state.ts` imports the model
13
+ ladder from `../../../types.js`), never the other direction.
30
14
 
31
15
  **Does the OAuth plugin ship with pi? No.** `pi-claude-oauth-adapter` is a
32
16
  third-party npm package ([minzique/dotfiles-agents](https://github.com/minzique/dotfiles-agents/tree/main/packages/pi-claude-oauth-adapter)) —
@@ -37,14 +21,9 @@ If you want Claude-subscription auth, you add this line yourself — it is not b
37
21
  OAuth login/refresh via `@earendil-works/pi-ai/oauth` to rotate across
38
22
  subscription credentials — that's a different mechanism, built in here.)
39
23
 
40
- For this package specifically, after cloning into `~/Code/cli/pi-personal-extensions`:
41
-
42
- ```bash
43
- npm install # installs runtime deps (yaml)
44
- ```
45
-
46
24
  `@earendil-works/pi-coding-agent` is a peer dependency provided by pi at runtime —
47
- do not install it locally.
25
+ do not install it locally. The one runtime dep (`yaml`) is vendored under this
26
+ directory's `node_modules/`.
48
27
 
49
28
  ## Contents
50
29