@crouton-kit/crouter 0.3.17 → 0.3.19

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 (283) hide show
  1. package/dist/build-root.js +6 -0
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +164 -0
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +58 -0
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +60 -0
  5. package/dist/builtin-memory/crouter-development/personas.md +117 -0
  6. package/dist/builtin-memory/crouter-development/plugins.md +163 -0
  7. package/dist/builtin-memory/design.md +60 -0
  8. package/dist/builtin-memory/development.md +116 -0
  9. package/dist/builtin-memory/planning.md +66 -0
  10. package/dist/builtin-memory/spec.md +99 -0
  11. package/dist/builtin-personas/design/{base.md → PERSONA.md} +4 -0
  12. package/dist/builtin-personas/design/orchestrator.md +1 -1
  13. package/dist/builtin-personas/developer/{base.md → PERSONA.md} +4 -0
  14. package/dist/builtin-personas/developer/orchestrator.md +1 -1
  15. package/dist/builtin-personas/explore/{base.md → PERSONA.md} +4 -0
  16. package/dist/builtin-personas/general/{base.md → PERSONA.md} +4 -0
  17. package/dist/builtin-personas/lifecycle/resident.md +1 -1
  18. package/dist/builtin-personas/lifecycle/terminal.md +5 -2
  19. package/dist/builtin-personas/orchestration-kernel.md +15 -24
  20. package/dist/builtin-personas/plan/{base.md → PERSONA.md} +4 -0
  21. package/dist/builtin-personas/plan/orchestrator.md +1 -1
  22. package/dist/builtin-personas/plan/reviewers/architecture-fit/{base.md → PERSONA.md} +1 -1
  23. package/dist/builtin-personas/plan/reviewers/code-smells/{base.md → PERSONA.md} +1 -1
  24. package/dist/builtin-personas/plan/reviewers/pattern-consistency/{base.md → PERSONA.md} +1 -1
  25. package/dist/builtin-personas/plan/reviewers/requirements-coverage/{base.md → PERSONA.md} +1 -1
  26. package/dist/builtin-personas/plan/reviewers/security/{base.md → PERSONA.md} +1 -1
  27. package/dist/builtin-personas/review/{base.md → PERSONA.md} +4 -0
  28. package/dist/builtin-personas/runtime-base.md +6 -3
  29. package/dist/builtin-personas/spec/{base.md → PERSONA.md} +4 -0
  30. package/dist/builtin-personas/spec/orchestrator.md +1 -1
  31. package/dist/builtin-personas/waiting.md +8 -0
  32. package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +24 -14
  33. package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +4 -4
  34. package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
  35. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
  36. package/dist/builtin-views/_lib/states.mjs +161 -0
  37. package/dist/builtin-views/canvas/client.mjs +303 -0
  38. package/dist/builtin-views/canvas/view.mjs +576 -0
  39. package/dist/builtin-views/git-pr/client.mjs +440 -0
  40. package/dist/builtin-views/git-pr/view.mjs +675 -0
  41. package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
  42. package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
  43. package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
  44. package/dist/builtin-views/inbox/view.mjs +889 -0
  45. package/dist/builtin-views/linkedin/client.mjs +610 -0
  46. package/dist/builtin-views/linkedin/view.mjs +1171 -0
  47. package/dist/clients/attach/attach-cmd.d.ts +2 -0
  48. package/dist/clients/attach/attach-cmd.js +354 -0
  49. package/dist/clients/attach/chat-view.d.ts +77 -0
  50. package/dist/clients/attach/chat-view.js +450 -0
  51. package/dist/clients/attach/clipboard-image.d.ts +16 -0
  52. package/dist/clients/attach/clipboard-image.js +113 -0
  53. package/dist/clients/attach/config-load.d.ts +31 -0
  54. package/dist/clients/attach/config-load.js +113 -0
  55. package/dist/clients/attach/extension-dialogs.d.ts +29 -0
  56. package/dist/clients/attach/extension-dialogs.js +101 -0
  57. package/dist/clients/attach/input-controller.d.ts +54 -0
  58. package/dist/clients/attach/input-controller.js +204 -0
  59. package/dist/clients/attach/slash-commands.d.ts +36 -0
  60. package/dist/clients/attach/slash-commands.js +200 -0
  61. package/dist/clients/attach/view-socket.d.ts +48 -0
  62. package/dist/clients/attach/view-socket.js +126 -0
  63. package/dist/commands/attention.js +3 -3
  64. package/dist/commands/canvas-prune.js +1 -1
  65. package/dist/commands/daemon.js +4 -3
  66. package/dist/commands/human/prompts.js +4 -10
  67. package/dist/commands/human/queue.js +43 -8
  68. package/dist/commands/human/shared.d.ts +28 -1
  69. package/dist/commands/human/shared.js +48 -10
  70. package/dist/commands/memory/find.d.ts +1 -0
  71. package/dist/commands/memory/find.js +180 -0
  72. package/dist/commands/memory/lint.d.ts +1 -0
  73. package/dist/commands/memory/lint.js +140 -0
  74. package/dist/commands/memory/list.d.ts +1 -0
  75. package/dist/commands/memory/list.js +79 -0
  76. package/dist/commands/memory/read.js +103 -0
  77. package/dist/commands/memory/shared.d.ts +30 -0
  78. package/dist/commands/memory/shared.js +122 -0
  79. package/dist/commands/memory/write.d.ts +1 -0
  80. package/dist/commands/memory/write.js +85 -0
  81. package/dist/commands/memory.d.ts +2 -0
  82. package/dist/commands/memory.js +27 -0
  83. package/dist/commands/node.d.ts +3 -2
  84. package/dist/commands/node.js +660 -65
  85. package/dist/commands/pkg/market-manage.js +1 -1
  86. package/dist/commands/push.js +6 -6
  87. package/dist/commands/revive.js +1 -1
  88. package/dist/commands/skill/author.js +1 -1
  89. package/dist/commands/skill/shared.d.ts +1 -8
  90. package/dist/commands/skill/shared.js +2 -55
  91. package/dist/commands/skill.js +9 -14
  92. package/dist/commands/sys/doctor.js +1 -1
  93. package/dist/commands/sys/update.js +1 -1
  94. package/dist/commands/view-cycle.d.ts +2 -0
  95. package/dist/commands/view-cycle.js +125 -0
  96. package/dist/commands/view-list.d.ts +2 -0
  97. package/dist/commands/view-list.js +66 -0
  98. package/dist/commands/view-new.d.ts +2 -0
  99. package/dist/commands/view-new.js +70 -0
  100. package/dist/commands/view-pick.d.ts +2 -0
  101. package/dist/commands/view-pick.js +119 -0
  102. package/dist/commands/view-run.d.ts +2 -0
  103. package/dist/commands/view-run.js +191 -0
  104. package/dist/commands/view.d.ts +2 -0
  105. package/dist/commands/view.js +29 -0
  106. package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
  107. package/dist/core/__tests__/broker-lifecycle.test.js +677 -0
  108. package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
  109. package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
  110. package/dist/core/__tests__/cascade-close.test.js +12 -2
  111. package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
  112. package/dist/core/__tests__/child-death-wake.test.js +245 -0
  113. package/dist/core/__tests__/context-intro.test.js +76 -62
  114. package/dist/core/__tests__/daemon-boot.test.js +14 -5
  115. package/dist/core/__tests__/daemon-liveness.test.js +34 -9
  116. package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
  117. package/dist/core/__tests__/detach-focus.test.js +206 -0
  118. package/dist/core/__tests__/draw-style.test.d.ts +1 -0
  119. package/dist/core/__tests__/draw-style.test.js +258 -0
  120. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
  121. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
  122. package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
  123. package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
  124. package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
  125. package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
  126. package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
  127. package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
  128. package/dist/core/__tests__/helpers/harness.d.ts +9 -0
  129. package/dist/core/__tests__/helpers/harness.js +111 -1
  130. package/dist/core/__tests__/home-session.test.js +41 -8
  131. package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
  132. package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
  133. package/dist/core/__tests__/human-surface-target.test.d.ts +1 -0
  134. package/dist/core/__tests__/human-surface-target.test.js +98 -0
  135. package/dist/core/__tests__/kickoff.test.js +37 -3
  136. package/dist/core/__tests__/live-mutation.test.js +50 -33
  137. package/dist/core/__tests__/memory.test.js +23 -115
  138. package/dist/core/__tests__/persona-subkind.test.js +18 -15
  139. package/dist/core/__tests__/placement-focus.test.js +5 -0
  140. package/dist/core/__tests__/placement-teardown.test.js +54 -11
  141. package/dist/core/__tests__/relaunch.test.js +4 -3
  142. package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
  143. package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
  144. package/dist/core/__tests__/spawn-root.test.js +10 -0
  145. package/dist/core/__tests__/spike-harness.test.js +1 -0
  146. package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
  147. package/dist/core/__tests__/wake-bearings.test.js +156 -0
  148. package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
  149. package/dist/core/__tests__/wake-origin.test.js +110 -0
  150. package/dist/core/bootstrap.js +1 -1
  151. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  152. package/dist/core/canvas/browse/app.js +24 -2
  153. package/dist/core/canvas/browse/model.d.ts +38 -2
  154. package/dist/core/canvas/browse/model.js +134 -10
  155. package/dist/core/canvas/browse/render.d.ts +6 -12
  156. package/dist/core/canvas/browse/render.js +72 -104
  157. package/dist/core/canvas/canvas.js +15 -22
  158. package/dist/core/canvas/db.js +46 -0
  159. package/dist/core/canvas/index.d.ts +1 -0
  160. package/dist/core/canvas/index.js +1 -0
  161. package/dist/core/canvas/paths.d.ts +4 -1
  162. package/dist/core/canvas/paths.js +10 -4
  163. package/dist/core/canvas/pid.d.ts +4 -0
  164. package/dist/core/canvas/pid.js +23 -0
  165. package/dist/core/canvas/render.d.ts +11 -2
  166. package/dist/core/canvas/render.js +69 -0
  167. package/dist/core/canvas/types.d.ts +85 -3
  168. package/dist/core/canvas/types.js +2 -2
  169. package/dist/core/canvas/wakeups.d.ts +76 -0
  170. package/dist/core/canvas/wakeups.js +185 -0
  171. package/dist/core/config.js +4 -1
  172. package/dist/core/frontmatter.js +37 -124
  173. package/dist/core/help.d.ts +6 -0
  174. package/dist/core/help.js +7 -0
  175. package/dist/core/memory-resolver.d.ts +49 -0
  176. package/dist/core/memory-resolver.js +141 -0
  177. package/dist/core/personas/index.d.ts +4 -3
  178. package/dist/core/personas/index.js +3 -2
  179. package/dist/core/personas/loader.d.ts +41 -16
  180. package/dist/core/personas/loader.js +133 -29
  181. package/dist/core/personas/resolve.d.ts +4 -4
  182. package/dist/core/personas/resolve.js +28 -16
  183. package/dist/core/predicate.d.ts +63 -0
  184. package/dist/core/predicate.js +189 -0
  185. package/dist/core/resolver.js +26 -5
  186. package/dist/core/runtime/bearings.d.ts +53 -12
  187. package/dist/core/runtime/bearings.js +132 -59
  188. package/dist/core/runtime/broker-cli.d.ts +1 -0
  189. package/dist/core/runtime/broker-cli.js +46 -0
  190. package/dist/core/runtime/broker-protocol.d.ts +332 -0
  191. package/dist/core/runtime/broker-protocol.js +153 -0
  192. package/dist/core/runtime/broker-sdk.d.ts +48 -0
  193. package/dist/core/runtime/broker-sdk.js +72 -0
  194. package/dist/core/runtime/broker.d.ts +55 -0
  195. package/dist/core/runtime/broker.js +1128 -0
  196. package/dist/core/runtime/close.js +35 -6
  197. package/dist/core/runtime/host.d.ts +53 -0
  198. package/dist/core/runtime/host.js +186 -0
  199. package/dist/core/runtime/kickoff.d.ts +2 -1
  200. package/dist/core/runtime/kickoff.js +91 -5
  201. package/dist/core/runtime/launch.d.ts +45 -2
  202. package/dist/core/runtime/launch.js +65 -2
  203. package/dist/core/runtime/lifecycle.js +23 -6
  204. package/dist/core/runtime/memory.d.ts +2 -42
  205. package/dist/core/runtime/memory.js +11 -162
  206. package/dist/core/runtime/nodes.d.ts +33 -0
  207. package/dist/core/runtime/nodes.js +59 -1
  208. package/dist/core/runtime/persona.js +21 -11
  209. package/dist/core/runtime/pi-vendored.d.ts +18 -0
  210. package/dist/core/runtime/pi-vendored.js +49 -0
  211. package/dist/core/runtime/placement.d.ts +42 -14
  212. package/dist/core/runtime/placement.js +228 -38
  213. package/dist/core/runtime/promote.d.ts +0 -6
  214. package/dist/core/runtime/promote.js +1 -12
  215. package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
  216. package/dist/core/runtime/{demote.js → recycle.js} +27 -11
  217. package/dist/core/runtime/reset.js +8 -6
  218. package/dist/core/runtime/revive.d.ts +2 -0
  219. package/dist/core/runtime/revive.js +34 -34
  220. package/dist/core/runtime/spawn.d.ts +19 -0
  221. package/dist/core/runtime/spawn.js +75 -22
  222. package/dist/core/runtime/stop-guard.d.ts +1 -1
  223. package/dist/core/runtime/stop-guard.js +6 -1
  224. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  225. package/dist/core/runtime/tmux-chrome.js +1 -1
  226. package/dist/core/runtime/tmux.d.ts +28 -0
  227. package/dist/core/runtime/tmux.js +80 -6
  228. package/dist/core/scope.d.ts +11 -0
  229. package/dist/core/scope.js +39 -0
  230. package/dist/core/spawn.d.ts +25 -1
  231. package/dist/core/spawn.js +72 -7
  232. package/dist/core/substrate/gate.d.ts +13 -0
  233. package/dist/core/substrate/gate.js +21 -0
  234. package/dist/core/substrate/index.d.ts +7 -0
  235. package/dist/core/substrate/index.js +18 -0
  236. package/dist/core/substrate/on-read.d.ts +14 -0
  237. package/dist/core/substrate/on-read.js +292 -0
  238. package/dist/core/substrate/render.d.ts +25 -0
  239. package/dist/core/substrate/render.js +256 -0
  240. package/dist/core/substrate/schema.d.ts +76 -0
  241. package/dist/core/substrate/schema.js +124 -0
  242. package/dist/core/substrate/session-cache.d.ts +30 -0
  243. package/dist/core/substrate/session-cache.js +77 -0
  244. package/dist/core/substrate/subject.d.ts +41 -0
  245. package/dist/core/substrate/subject.js +54 -0
  246. package/dist/core/tui/contract.d.ts +83 -0
  247. package/dist/core/tui/contract.js +8 -0
  248. package/dist/core/tui/draw.d.ts +96 -0
  249. package/dist/core/tui/draw.js +339 -0
  250. package/dist/core/tui/host.d.ts +29 -0
  251. package/dist/core/tui/host.js +379 -0
  252. package/dist/core/tui/loader.d.ts +16 -0
  253. package/dist/core/tui/loader.js +94 -0
  254. package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
  255. package/dist/core/wake.d.ts +86 -0
  256. package/dist/core/wake.js +308 -0
  257. package/dist/daemon/crtrd.d.ts +29 -4
  258. package/dist/daemon/crtrd.js +662 -46
  259. package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
  260. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
  261. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
  262. package/dist/pi-extensions/canvas-commands.d.ts +3 -0
  263. package/dist/pi-extensions/canvas-commands.js +10 -0
  264. package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
  265. package/dist/pi-extensions/canvas-context-intro.js +55 -15
  266. package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
  267. package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
  268. package/dist/pi-extensions/canvas-nav.d.ts +3 -0
  269. package/dist/pi-extensions/canvas-nav.js +145 -34
  270. package/dist/pi-extensions/canvas-stophook.js +17 -8
  271. package/dist/pi-extensions/canvas-view.d.ts +21 -0
  272. package/dist/pi-extensions/canvas-view.js +75 -0
  273. package/dist/prompts/skill.js +19 -26
  274. package/dist/prompts/view.d.ts +7 -0
  275. package/dist/prompts/view.js +101 -0
  276. package/dist/types.d.ts +4 -0
  277. package/dist/types.js +1 -0
  278. package/package.json +9 -4
  279. package/dist/commands/skill/find.d.ts +0 -4
  280. package/dist/commands/skill/find.js +0 -257
  281. package/dist/commands/skill/read.js +0 -91
  282. /package/dist/commands/{skill → memory}/read.d.ts +0 -0
  283. /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
@@ -18,9 +18,22 @@
18
18
  // alt+c is a tmux display-menu (not a pi key), so prefix chords (m/e/1-9/custom)
19
19
  // are tmux menu items that route through `crtr canvas chord`.
20
20
  //
21
- // Two selection signals, both NO_COLOR-safe:
22
- // SELF row = reverse video (ESC[7m), full width — an attribute, not a color.
23
- // CURSOR = + bold on the row. Status stays on the colored dot.
21
+ // Selection / attachment signals:
22
+ // CURSOR (selected) = reverse-video bar (ESC[7m), full width — an attribute,
23
+ // not a colour, so it reads under NO_COLOR.
24
+ // ATTACHED (watched) = a coloured background bar — a human is currently
25
+ // viewing the node: a `focuses` viewport points at it
26
+ // (tmux host) or ≥1 helloed viewer is connected to its
27
+ // broker (job/attach.json). Running is a separate axis,
28
+ // signaled by the dot glyph alone (● = engine active on
29
+ // its host — which may be an unwatched backstage pane
30
+ // or a paneless broker).
31
+ // SELF = bold name — a quiet "you are here" marker.
32
+ //
33
+ // Folding is auto by default: a branch stays COLLAPSED unless its subtree holds
34
+ // a running ('active') agent or self. h/l override that per-node and persist.
35
+ // A ▸ caret marks an expandable (collapsed-with-kids) row — but NOT the cursor
36
+ // row, whose reverse-video bar already sets it apart.
24
37
  //
25
38
  // ⚑K pending-asks is PER-NODE, inline on each waiting node's own row (manager,
26
39
  // reports, tree rows; self shows a trailing ⚑ line in BASE). ⤳M direct-children
@@ -33,7 +46,7 @@
33
46
  import { execFile, execFileSync } from 'node:child_process';
34
47
  import { existsSync, readFileSync } from 'node:fs';
35
48
  import { join } from 'node:path';
36
- import { getNode, subscribersOf, subscriptionsOf, jobDir, fullName } from '../core/canvas/index.js';
49
+ import { getNode, subscribersOf, subscriptionsOf, jobDir, fullName, listFocuses } from '../core/canvas/index.js';
37
50
  import { readConfig } from '../core/config.js';
38
51
  // ---------------------------------------------------------------------------
39
52
  // Module-level state — persists across /reload so guards don't stack and fold
@@ -45,10 +58,13 @@ let liveTimer;
45
58
  * exactly one key tap exists (mirrors the liveTimer double-guard). */
46
59
  let liveUnsub;
47
60
  let view = 'base';
48
- /** Fold state node ids whose children are hidden in GRAPH. Survives renders
49
- * AND BASE↔GRAPH toggles. Keyed by id so a topology change never corrupts it;
50
- * stale ids are harmless (ignored when absent). */
51
- const collapsed = new Set();
61
+ /** Manual fold OVERRIDES in GRAPH, keyed by id (so a topology change can't
62
+ * corrupt them; stale ids are ignored). They override the default policy
63
+ * collapsed UNLESS the subtree holds a running ('active') agent or self (see
64
+ * computeDefaultExpanded). `h` collapses → userCollapsed; `l` expands →
65
+ * userExpanded. Both survive renders AND BASE↔GRAPH toggles. */
66
+ const userCollapsed = new Set();
67
+ const userExpanded = new Set();
52
68
  /** GRAPH cursor (a node id, not an index — indices shift as topology changes). */
53
69
  let cursorId;
54
70
  /** GRAPH viewport scroll offset (row index of the top visible row). */
@@ -69,16 +85,21 @@ const PI_MAX_WIDGET_LINES = 10;
69
85
  const VIEWPORT_FALLBACK_ROWS = 30;
70
86
  // ---------------------------------------------------------------------------
71
87
  // ANSI styling. pi renders embedded escapes in widget lines and measures width
72
- // ANSI-aware, so raw escapes are safe and need no pi-tui dependency. Selection
73
- // uses theme-agnostic ATTRIBUTES (reverse / bold), never colour alone, so it
74
- // reads under NO_COLOR and on any background; status uses the standard 8 colors
75
- // on the dot only.
88
+ // ANSI-aware, so raw escapes are safe and need no pi-tui dependency. The cursor
89
+ // (selected row) uses a theme-agnostic ATTRIBUTE (reverse), so it reads under
90
+ // NO_COLOR; the attached-row tint is a background COLOUR, while the dot glyph
91
+ // (●/○/✓/✗) carries the running signal independently, even where colour is
92
+ // stripped.
76
93
  // ---------------------------------------------------------------------------
77
94
  const ESC = '\x1b[';
78
95
  const RESET = `${ESC}0m`;
79
96
  const BOLD = `${ESC}1m`;
80
97
  const DIM = `${ESC}2m`;
81
98
  const REVERSE = `${ESC}7m`;
99
+ /** Dark-green background bar marking an ATTACHED node (a human is currently
100
+ * watching it) — distinct from the cursor's reverse-video bar; chosen so
101
+ * default-fg text stays readable. */
102
+ const BG_ATTACHED = `${ESC}48;5;22m`;
82
103
  const GREEN = `${ESC}32m`;
83
104
  const RED = `${ESC}31m`;
84
105
  const YELLOW = `${ESC}33m`;
@@ -137,15 +158,16 @@ function truncate(s, max = fillWidth()) {
137
158
  function fillWidth() {
138
159
  return Math.max(20, Math.min((process.stdout.columns ?? 80) - 2, 180));
139
160
  }
140
- /** Wrap `content` in a full-width reverse-video bar. REVERSE is re-asserted
141
- * after every embedded RESET so a colored cell (the status dot) doesn't punch
142
- * a hole in the bar; the visible width is padded out to `width`; the line
143
- * closes with a real RESET. */
144
- function reverseFill(content, width) {
161
+ /** Wrap `content` in a full-width background bar opened by `open` (REVERSE for
162
+ * the cursor, BG_ATTACHED for a human-watched node). `open` is re-asserted after every
163
+ * embedded RESET so a coloured cell (the status dot) can't punch a hole in the
164
+ * bar; the visible width is padded out to `width`; the line closes with a real
165
+ * RESET so the style never bleeds into the editor below. */
166
+ function fillBar(content, width, open) {
145
167
  const clipped = truncate(content, width);
146
- const reasserted = clipped.replace(/\x1b\[0m/g, `${RESET}${REVERSE}`);
168
+ const reasserted = clipped.replace(/\x1b\[0m/g, `${RESET}${open}`);
147
169
  const pad = Math.max(0, width - visibleWidth(clipped));
148
- return `${REVERSE}${reasserted}${' '.repeat(pad)}${RESET}`;
170
+ return `${open}${reasserted}${' '.repeat(pad)}${RESET}`;
149
171
  }
150
172
  function readTelemetry(nodeId) {
151
173
  try {
@@ -168,6 +190,42 @@ function shortId(id) {
168
190
  return id.slice(0, 8);
169
191
  }
170
192
  // ---------------------------------------------------------------------------
193
+ // Attachment — is a human currently WATCHING a node? A separate axis from
194
+ // running (status 'active' = the engine is live on its host, which may be an
195
+ // unwatched backstage pane or a paneless broker). Two hosts, two signals:
196
+ // tmux — a `focuses` row points at the node (one cheap sqlite read per
197
+ // render pass; pane-existence alone is NOT the signal).
198
+ // broker — the broker persists its helloed-viewer count to job/attach.json
199
+ // on every viewer change (src/core/runtime/broker.ts). Trusted only
200
+ // while the node is 'active': a broker crash can leave a stale file.
201
+ // ---------------------------------------------------------------------------
202
+ /** Node ids currently shown in a tmux focus viewport. Built once per render. */
203
+ function focusedNodeIds() {
204
+ try {
205
+ return new Set(listFocuses().map((f) => f.node_id));
206
+ }
207
+ catch {
208
+ return new Set();
209
+ }
210
+ }
211
+ /** True when a human is watching `id` right now (tmux focus or broker viewer). */
212
+ function isAttached(id, node, focused) {
213
+ if (focused.has(id))
214
+ return true;
215
+ if (node?.status !== 'active')
216
+ return false; // stale attach.json from a crash
217
+ try {
218
+ const p = join(jobDir(id), 'attach.json');
219
+ if (!existsSync(p))
220
+ return false;
221
+ const rec = JSON.parse(readFileSync(p, 'utf8'));
222
+ return typeof rec.viewers === 'number' && rec.viewers > 0;
223
+ }
224
+ catch {
225
+ return false;
226
+ }
227
+ }
228
+ // ---------------------------------------------------------------------------
171
229
  // Per-node ask counts — ONE shell-out per poll. `crtr canvas attention map`
172
230
  // buckets a whole sub-DAG's pending asks by node in a single process, so the
173
231
  // timer stays cheap (< 2 s) regardless of how many nodes are visible. --json
@@ -294,21 +352,48 @@ function statusRank(id) {
294
352
  function sortedChildIds(id) {
295
353
  return convoChildIds(id).sort((a, b) => statusRank(a) - statusRank(b));
296
354
  }
355
+ /** Default fold policy: which nodes auto-EXPAND. A node expands only when one
356
+ * of its child subtrees holds a running ('active') agent or self — so the path
357
+ * to any live agent (and to you) is revealed while quiescent branches stay
358
+ * folded. One bottom-up O(N) pass from the ancestry root; cycle-guarded. */
359
+ function computeDefaultExpanded(root, self) {
360
+ const expand = new Set();
361
+ const seen = new Set();
362
+ // Returns whether subtree(id), INCLUDING id, holds an active node or self.
363
+ const visit = (id) => {
364
+ if (seen.has(id))
365
+ return id === self || getNode(id)?.status === 'active';
366
+ seen.add(id);
367
+ let childRevealing = false;
368
+ for (const c of convoChildIds(id))
369
+ if (visit(c))
370
+ childRevealing = true;
371
+ if (childRevealing)
372
+ expand.add(id); // a descendant is worth revealing → unfold id
373
+ return childRevealing || id === self || getNode(id)?.status === 'active';
374
+ };
375
+ visit(root);
376
+ return expand;
377
+ }
297
378
  function buildGraphModel(self) {
298
379
  const rootId = climbRoot(self);
380
+ const defaultExpanded = computeDefaultExpanded(rootId, self);
381
+ // userExpanded / userCollapsed override the auto policy; absent → policy decides.
382
+ const isFolded = (id) => userExpanded.has(id) ? false : userCollapsed.has(id) ? true : !defaultExpanded.has(id);
299
383
  const rows = [];
300
384
  const visited = new Set();
301
385
  const walk = (id, prefix, isRoot, isLast) => {
302
386
  if (visited.has(id)) {
303
387
  const connector = isRoot ? '' : isLast ? '└─ ' : '├─ ';
304
- rows.push({ id, hasKids: false, isSelf: id === self, branch: prefix + connector, cycle: true });
388
+ rows.push({ id, hasKids: false, isSelf: id === self, branch: prefix + connector, cycle: true, collapsed: false });
305
389
  return;
306
390
  }
307
391
  visited.add(id);
308
392
  const kids = sortedChildIds(id);
393
+ const folded = isFolded(id);
309
394
  const connector = isRoot ? '' : isLast ? '└─ ' : '├─ ';
310
- rows.push({ id, hasKids: kids.length > 0, isSelf: id === self, branch: prefix + connector, cycle: false });
311
- if (collapsed.has(id))
395
+ rows.push({ id, hasKids: kids.length > 0, isSelf: id === self, branch: prefix + connector, cycle: false, collapsed: folded });
396
+ if (folded)
312
397
  return; // folded — don't descend
313
398
  const childPrefix = isRoot ? '' : prefix + (isLast ? ' ' : '│ ');
314
399
  for (let i = 0; i < kids.length; i++)
@@ -317,22 +402,32 @@ function buildGraphModel(self) {
317
402
  walk(rootId, '', true, true);
318
403
  return rows;
319
404
  }
320
- /** Render one GRAPH row. SELF → reverse fill; CURSOR → ▸ + bold caret/name. */
321
- function renderGraphRow(r, isCursor) {
405
+ /** Render one GRAPH row. CURSOR (selected) → reverse-video bar; an ATTACHED
406
+ * (human-watched) node → a coloured background bar; SELF → bold name. The
407
+ * cursor outranks the attached tint when both land on the same row. Running
408
+ * is signaled by the dot glyph alone (● green = active engine). */
409
+ function renderGraphRow(r, isCursor, focused) {
410
+ const wrap = (line, attached) => isCursor ? fillBar(line, fillWidth(), REVERSE)
411
+ : attached ? fillBar(line, fillWidth(), BG_ATTACHED)
412
+ : truncate(line);
322
413
  if (r.cycle) {
323
414
  const line = `${r.branch} ${DIM}↺ ${shortId(r.id)}${RESET}`;
324
- return r.isSelf ? reverseFill(line, fillWidth()) : truncate(line);
415
+ return wrap(line, false);
325
416
  }
326
417
  const node = getNode(r.id);
327
418
  const dot = coloredGlyph(node);
328
419
  const rawName = node !== null ? fullName(node) : shortId(r.id);
329
- const name = isCursor ? `${BOLD}${rawName}${RESET}` : rawName;
420
+ const name = r.isSelf ? `${BOLD}${rawName}${RESET}` : rawName;
330
421
  const kind = `${DIM}${node?.kind ?? ''}${RESET}`;
331
422
  const tokens = `${DIM}${tokensCell(r.id)}${RESET}`;
332
- const caret = isCursor ? `${BOLD}▸${RESET} ` : ' ';
333
- const fold = r.hasKids && collapsed.has(r.id) ? ` ${DIM}[+${childCount(r.id)}]${RESET}` : '';
423
+ // marks an expandable (collapsed-with-kids) row. The cursor row gets no
424
+ // caret its reverse-video bar already distinguishes it so the triangle
425
+ // reads purely as "this unfolds".
426
+ const expandable = r.hasKids && r.collapsed;
427
+ const caret = !isCursor && expandable ? `${DIM}▸${RESET} ` : ' ';
428
+ const fold = expandable ? ` ${DIM}[+${childCount(r.id)}]${RESET}` : '';
334
429
  const line = `${r.branch}${caret}${dot} ${name} ${kind} ${tokens}${childBadge(node)}${fold}${askBadge(r.id)}`;
335
- return r.isSelf ? reverseFill(line, fillWidth()) : truncate(line);
430
+ return wrap(line, isAttached(r.id, node, focused));
336
431
  }
337
432
  /** Total lines the GRAPH widget may emit. pi hard-caps extension widgets at
338
433
  * MAX_WIDGET_LINES — anything past that pi truncates itself, eating our own
@@ -436,6 +531,10 @@ export function registerCanvasNav(pi) {
436
531
  // Captured from session_start; used in every subsequent render.
437
532
  let ui;
438
533
  let renderScheduled = false;
534
+ // Run mode captured at session_start. The nav chrome + ask-poll timer are
535
+ // interactive-only; a headless ('print') broker leaves this non-'tui' so the
536
+ // timer no-ops (no per-tick `crtr` shell-out) and no chrome is rendered.
537
+ let liveMode;
439
538
  // Cache config once (binds rarely change within a session; readConfig is sync
440
539
  // and never throws). prefixKey drives the non-tmux GRAPH toggle shortcut.
441
540
  let navConfig;
@@ -531,10 +630,11 @@ export function registerCanvasNav(pi) {
531
630
  }
532
631
  const end = Math.min(rows.length, scrollTop + viewportH);
533
632
  const lines = [];
633
+ const focused = focusedNodeIds(); // one sqlite read per render pass
534
634
  if (scrollTop > 0)
535
635
  lines.push(`${DIM} ↑ ${scrollTop} more${RESET}`);
536
636
  for (let i = scrollTop; i < end; i++)
537
- lines.push(renderGraphRow(rows[i], i === cursorIdx));
637
+ lines.push(renderGraphRow(rows[i], i === cursorIdx, focused));
538
638
  if (end < rows.length)
539
639
  lines.push(`${DIM} ↓ ${rows.length - end} more${RESET}`);
540
640
  const hint = pendingConfirm !== undefined
@@ -677,8 +777,9 @@ export function registerCanvasNav(pi) {
677
777
  return { consume: true };
678
778
  }
679
779
  if (isPlain(data, 'h')) {
680
- if (cur !== undefined && cur.hasKids && !collapsed.has(cur.id)) {
681
- collapsed.add(cur.id);
780
+ if (cur !== undefined && cur.hasKids && !cur.collapsed) {
781
+ userCollapsed.add(cur.id);
782
+ userExpanded.delete(cur.id);
682
783
  }
683
784
  else {
684
785
  const p = managerOf(cursorId ?? nodeId);
@@ -689,8 +790,9 @@ export function registerCanvasNav(pi) {
689
790
  return { consume: true };
690
791
  }
691
792
  if (isPlain(data, 'l')) {
692
- if (cur !== undefined && collapsed.has(cur.id)) {
693
- collapsed.delete(cur.id);
793
+ if (cur !== undefined && cur.collapsed && cur.hasKids) {
794
+ userExpanded.add(cur.id);
795
+ userCollapsed.delete(cur.id);
694
796
  }
695
797
  else if (cur !== undefined && cur.hasKids) {
696
798
  const c = sortedChildIds(cur.id)[0];
@@ -797,6 +899,12 @@ export function registerCanvasNav(pi) {
797
899
  // Event handlers
798
900
  // -------------------------------------------------------------------------
799
901
  pi.on('session_start', (_event, ctx) => {
902
+ liveMode = ctx.mode;
903
+ // The nav chrome (widgets, key taps, the ask-poll timer) is interactive-only.
904
+ // A headless (print-mode) broker loads this extension but renders no chrome
905
+ // and must not poll. Under tmux ctx.mode is always 'tui' — byte-identical.
906
+ if (ctx.mode !== 'tui')
907
+ return;
800
908
  ui = ctx.ui;
801
909
  // Fresh session / hot-swap: start in BASE and clear any legacy or
802
910
  // inactive-view widgets so nothing stale bleeds through.
@@ -840,6 +948,9 @@ export function registerCanvasNav(pi) {
840
948
  if (liveTimer !== undefined)
841
949
  clearInterval(liveTimer);
842
950
  const timer = setInterval(() => {
951
+ // Inert in a headless ('print') broker — never shell out to `crtr` per tick.
952
+ if (liveMode !== 'tui')
953
+ return;
843
954
  try {
844
955
  const rootId = climbRoot(nodeId);
845
956
  const fresh = fetchAsksMap(rootId);
@@ -500,14 +500,23 @@ export function registerCanvasStophook(pi) {
500
500
  // down; the daemon watches its inbox and revives it (resume) the moment a
501
501
  // subscribed worker delivers. An 'attended' root never releases: the human
502
502
  // is its wake source, so we keep its window live and dormant.
503
- if (decision.reason === 'awaiting') {
504
- // AWAITING = F3. transition('release') marks it idle-released. If this
505
- // node is FOCUSED its pane FREEZES in place (remain-on-exit, armed at
506
- // focus time) so the daemon can respawn-pane -k it back into the SAME
507
- // focus pane when a worker pushes; if UNFOCUSED its backstage pane
508
- // closes (dormant) and the daemon revives it into the backstage on the
509
- // inbox. Both are the same release tmux's per-window remain-on-exit
510
- // decides freeze vs close. NO manager-takeover (awaiting ≠ done).
503
+ if (decision.reason === 'awaiting' || decision.reason === 'scheduled') {
504
+ // AWAITING done (no manager-takeover). What happens next splits on
505
+ // whether the user is WATCHING this node:
506
+ // FOCUSED it holds the user's viewport, so keep pi LIVE and
507
+ // dormant (exactly like a resident root): do NOT release or shut
508
+ // down. The in-process inbox-watcher (still alive) wakes it the
509
+ // instant a worker pushes no respawn, no frozen pane, the pane
510
+ // stays interactive. When the user later focuses AWAY, placement's
511
+ // retarget reclassifies it as a parked terminal viewer and releases
512
+ // it then (transition 'release' + pane reaped), so the kept-alive
513
+ // pi is reclaimed on focus-away, never held forever.
514
+ // • UNFOCUSED → no one is watching, so holding a live pi for a window
515
+ // is waste. Release it (idle + idle-release) and shut pi down; its
516
+ // backstage pane closes and the daemon revives it (resume) on the
517
+ // next unseen inbox entry.
518
+ if (focusOf(nodeId) !== null)
519
+ return; // focused → stay alive, dormant
511
520
  transition(nodeId, 'release');
512
521
  try {
513
522
  ctx?.shutdown?.();
@@ -0,0 +1,21 @@
1
+ interface CommandUI {
2
+ notify(message: string, type?: 'info' | 'warning' | 'error'): void;
3
+ }
4
+ interface CommandCtx {
5
+ mode: string;
6
+ ui: CommandUI;
7
+ }
8
+ interface PiLike {
9
+ registerCommand?(name: string, options: {
10
+ description?: string;
11
+ handler: (args: string, ctx: CommandCtx) => void | Promise<void>;
12
+ }): void;
13
+ }
14
+ /**
15
+ * Register the /view command on `pi`.
16
+ *
17
+ * Returns immediately when CRTR_NODE_ID is absent — the extension is fully
18
+ * inert in a non-canvas pi session.
19
+ */
20
+ export declare function registerCanvasView(pi: PiLike): void;
21
+ export default registerCanvasView;
@@ -0,0 +1,75 @@
1
+ // canvas-view.ts — pi extension registering the /view canvas command.
2
+ //
3
+ // /view — open the view picker (`crtr view pick`) as a tmux popup:
4
+ // a raw-ANSI list of every available view; Enter hands off (one-way exec)
5
+ // to `crtr view run <id>`, which paints the full-screen view inside the
6
+ // same popup. Quitting the view closes the popup back to THIS pi pane.
7
+ // /view <name> — skip the picker and popup `crtr view run <name>` directly.
8
+ //
9
+ // Views are stateless render surfaces with NO pi session. Unlike /resume-node
10
+ // this command MUST NOT touch the node-focus / swap-pane / reviveNode path
11
+ // (spec Decision 8) — there is no pane to return to and no session to revive.
12
+ // The popup is fire-and-forget: it owns its own screen and closes itself when
13
+ // the view exits, dropping back to the pi pane on its own.
14
+ //
15
+ // crtr ONLY runs inside tmux (see crouter/CLAUDE.md) — there is no non-tmux
16
+ // fallback picker. Outside tmux the command notifies and no-ops.
17
+ //
18
+ // INERT when CRTR_NODE_ID is absent (a plain pi session, not a canvas node).
19
+ //
20
+ // Plain TS-with-types — no imports from @earendil-works/* so this compiles
21
+ // inside crouter's own tsc build without a dep on the pi packages (mirrors
22
+ // canvas-resume.ts / canvas-nav.ts / canvas-commands.ts).
23
+ import { execFile } from 'node:child_process';
24
+ /** Single-quote a string for safe interpolation into a `sh -c` command line —
25
+ * tmux runs the display-popup trailing string through the shell, so a view
26
+ * name with spaces or quotes must be escaped. */
27
+ function shellQuote(s) {
28
+ return `'${s.replace(/'/g, `'\\''`)}'`;
29
+ }
30
+ /**
31
+ * Register the /view command on `pi`.
32
+ *
33
+ * Returns immediately when CRTR_NODE_ID is absent — the extension is fully
34
+ * inert in a non-canvas pi session.
35
+ */
36
+ export function registerCanvasView(pi) {
37
+ const nodeId = process.env['CRTR_NODE_ID'];
38
+ if (nodeId === undefined || nodeId.trim() === '')
39
+ return; // not a canvas node
40
+ if (typeof pi.registerCommand !== 'function')
41
+ return;
42
+ pi.registerCommand('view', {
43
+ description: 'Open a view in a popup — bare for the picker, or /view <name> to open that view directly',
44
+ handler: async (args, ctx) => {
45
+ // The popup is terminal-only — guard the run mode before opening it.
46
+ if (ctx.mode !== 'tui') {
47
+ try {
48
+ ctx.ui.notify('/view needs the interactive TUI', 'warning');
49
+ }
50
+ catch { /* best-effort */ }
51
+ return;
52
+ }
53
+ // crtr only runs in tmux: open the view (picker, or a named view) as a
54
+ // popup. It owns the screen and closes itself when the view exits,
55
+ // dropping back to THIS pi pane — no node-focus, no return-pane.
56
+ // Fire-and-forget: tmux runs the trailing string through sh -c.
57
+ if (process.env['TMUX'] !== undefined) {
58
+ const name = args.trim();
59
+ // bare /view → the picker; /view <name> → that view directly.
60
+ const cmd = name === '' ? 'crtr view pick' : `crtr view run ${shellQuote(name)}`;
61
+ try {
62
+ execFile('tmux', ['display-popup', '-E', '-w', '90%', '-h', '85%', cmd], () => { });
63
+ }
64
+ catch { /* best-effort */ }
65
+ return;
66
+ }
67
+ // Not in tmux → crtr is tmux-only, so there is nothing to fall back to.
68
+ try {
69
+ ctx.ui.notify('/view needs tmux', 'warning');
70
+ }
71
+ catch { /* best-effort */ }
72
+ },
73
+ });
74
+ }
75
+ export default registerCanvasView;
@@ -108,7 +108,7 @@ ${topicLine(topic)}
108
108
  - \`ls\` repo top level
109
109
  - check stack manifests
110
110
  - \`git log --oneline -15\` in this area
111
- - \`crtr skill find search "<topic>"\` / \`crtr skill find list\` — does a primer already exist?
111
+ - \`crtr memory find "<topic>"\` / \`crtr memory list\` — does a primer already exist?
112
112
 
113
113
  If subsystem is small/self-evident, **stop**. Suggest a CLAUDE.md note. Primers
114
114
  are for large, complicated, or unintuitive systems only.
@@ -116,7 +116,7 @@ are for large, complicated, or unintuitive systems only.
116
116
  ## 2. Scope + name
117
117
 
118
118
  - **Scope**: \`project\` by default. \`user\` only if cross-repo.
119
- - **Name**: kebab-case. Confirm no collision: \`crtr skill read <name> --no-body\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
119
+ - **Name**: kebab-case. Confirm no collision: \`crtr memory read <name>\` (returns \`.path\`, \`.scope\`).
120
120
 
121
121
  ## 3. Parallel exploration
122
122
 
@@ -182,9 +182,8 @@ Domain terms, invariants, non-obvious constraints.
182
182
  Non-obvious coupling. Looks-broken-but-isn't. Past footguns.
183
183
  \`\`\`
184
184
 
185
- **No \`## Related\` for within-plugin siblings**the CLI auto-appends a
186
- \`## Neighbors\` section on \`crtr skill read <name>\`. Add a manual \`## Related\`
187
- only for cross-plugin or distant refs.
185
+ Add a \`## Related\` section only for genuinely related skills cross-plugin
186
+ or distant refs included; skip it when there is nothing worth pointing at.
188
187
 
189
188
  **Density rules:**
190
189
  - \`file:line\` over prose
@@ -200,9 +199,8 @@ only for cross-plugin or distant refs.
200
199
  Output is JSON; \`.content\` has the body, \`.path\` has the location:
201
200
 
202
201
  \`\`\`
203
- crtr skill read <name> --no-body
204
- crtr skill read <name>
205
- crtr skill find search "<keyword>" # confirm description triggers discovery
202
+ crtr memory read <name>
203
+ crtr memory find "<keyword>" # confirm description triggers discovery
206
204
  \`\`\`
207
205
 
208
206
  Sharpen description if discovery misses. Cut body if bloated.
@@ -251,7 +249,7 @@ PR over many small ones for refactors here, because review churn dominates"*
251
249
 
252
250
  - **Scope**: \`user\` for cross-project methodology. \`project\` for repo-specific.
253
251
  - **Name**: kebab-case, verb-or-noun-phrase. Not "guide-to-X".
254
- - Check \`crtr skill read <name> --no-body\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
252
+ - Check \`crtr memory read <name>\` (returns \`.path\`, \`.scope\`).
255
253
 
256
254
  ## 3. Scaffold
257
255
 
@@ -301,9 +299,8 @@ to read the whole thing for value, you've buried the judgment.
301
299
  - **<name>**: what it looks like; how to avoid
302
300
  \`\`\`
303
301
 
304
- **No \`## Related\` for within-plugin siblings**the CLI auto-appends a
305
- \`## Neighbors\` section on \`crtr skill read <name>\`. Add a manual \`## Related\`
306
- only for cross-plugin or distant refs.
302
+ Add a \`## Related\` section only for genuinely related skills cross-plugin
303
+ or distant refs included; skip it when there is nothing worth pointing at.
307
304
 
308
305
  ## 6. Progressive disclosure
309
306
 
@@ -324,9 +321,8 @@ loads supporting files only when needed.
324
321
  Output is JSON; \`.content\` has the body, \`.path\` has the location:
325
322
 
326
323
  \`\`\`
327
- crtr skill read <name> --no-body
328
- crtr skill read <name>
329
- crtr skill find search "<keyword>"
324
+ crtr memory read <name>
325
+ crtr memory find "<keyword>"
330
326
  \`\`\`
331
327
 
332
328
  ## Constraints
@@ -415,9 +411,8 @@ Or invent your own. Stay tight — no padding.
415
411
  Output is JSON; \`.content\` has the body, \`.path\` has the location:
416
412
 
417
413
  \`\`\`
418
- crtr skill read <name> --no-body
419
- crtr skill read <name>
420
- crtr skill find search "<keyword>"
414
+ crtr memory read <name>
415
+ crtr memory find "<keyword>"
421
416
  \`\`\`
422
417
 
423
418
  ## Switch templates if needed
@@ -469,7 +464,7 @@ field/flag/code values** — pull verbatim from source.
469
464
 
470
465
  - **Scope**: \`user\` for cross-project facts. \`project\` for repo-specific.
471
466
  - **Name**: noun-phrase. \`http-status-codes\` not \`learn-http-status\`.
472
- - Check \`crtr skill read <name> --no-body\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
467
+ - Check \`crtr memory read <name>\` (returns \`.path\`, \`.scope\`).
473
468
 
474
469
  ## 3. Scaffold
475
470
 
@@ -530,9 +525,8 @@ SKILL.md links to siblings (\`see [full-table.md](full-table.md)\`).
530
525
  Output is JSON; \`.content\` has the body, \`.path\` has the location:
531
526
 
532
527
  \`\`\`
533
- crtr skill read <name> --no-body
534
- crtr skill read <name>
535
- crtr skill find search "<keyword>"
528
+ crtr memory read <name>
529
+ crtr memory find "<keyword>"
536
530
  \`\`\`
537
531
 
538
532
  Search must surface the skill on a typical lookup query. Sharpen the
@@ -582,7 +576,7 @@ push to \\\`main\\\`, wait for green CI, click promote"* is a runbook step.
582
576
 
583
577
  - **Scope**: \`project\` for repo-specific procedures. \`user\` for cross-project.
584
578
  - **Name**: verb-phrase. \`deploy-to-prod\` not \`production-deployment-guide\`.
585
- - Check \`crtr skill read <name> --no-body\` (returns \`.path\`, \`.scope\`, \`.plugin\`).
579
+ - Check \`crtr memory read <name>\` (returns \`.path\`, \`.scope\`).
586
580
 
587
581
  ## 3. Scaffold
588
582
 
@@ -637,9 +631,8 @@ crtr skill author scaffold <name> --type runbook --scope <user|project> --descri
637
631
  Output is JSON; \`.content\` has the body, \`.path\` has the location:
638
632
 
639
633
  \`\`\`
640
- crtr skill read <name> --no-body
641
- crtr skill read <name>
642
- crtr skill find search "<keyword>"
634
+ crtr memory read <name>
635
+ crtr memory find "<keyword>"
643
636
  \`\`\`
644
637
 
645
638
  Walk through the runbook mentally. Each step verifiable? Each decision
@@ -0,0 +1,7 @@
1
+ export interface ViewScaffoldVars {
2
+ id: string;
3
+ title: string;
4
+ description: string;
5
+ }
6
+ /** Build the `view.mjs` source for a freshly scaffolded view. */
7
+ export declare function viewScaffold(v: ViewScaffoldVars): string;