@crouton-kit/crouter 0.3.36 → 0.3.38

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 (177) hide show
  1. package/dist/build-root.js +3 -6
  2. package/dist/{builtin-personas/runtime-base.md → builtin-memory/00-runtime-base.md} +8 -1
  3. package/dist/{builtin-personas/spine/has-manager.md → builtin-memory/01-spine/00-has-manager.md} +8 -0
  4. package/dist/builtin-memory/01-spine/01-no-manager.md +10 -0
  5. package/dist/{builtin-personas/lifecycle/terminal.md → builtin-memory/02-lifecycle/00-terminal.md} +8 -0
  6. package/dist/{builtin-personas/lifecycle/resident.md → builtin-memory/02-lifecycle/01-resident.md} +8 -0
  7. package/dist/{builtin-personas/waiting.md → builtin-memory/03-waiting.md} +7 -0
  8. package/dist/{builtin-personas/orchestration-kernel.md → builtin-memory/04-orchestration-kernel.md} +8 -0
  9. package/dist/{builtin-personas/advisor/PERSONA.md → builtin-memory/05-kinds/advisor/00-base.md} +5 -2
  10. package/dist/{builtin-personas/design/PERSONA.md → builtin-memory/05-kinds/design/00-base.md} +5 -2
  11. package/dist/{builtin-personas/design/orchestrator.md → builtin-memory/05-kinds/design/01-orchestrator.md} +5 -4
  12. package/dist/{builtin-personas/developer/PERSONA.md → builtin-memory/05-kinds/developer/00-base.md} +5 -2
  13. package/dist/{builtin-personas/developer/orchestrator.md → builtin-memory/05-kinds/developer/01-orchestrator.md} +5 -4
  14. package/dist/{builtin-personas/explore/PERSONA.md → builtin-memory/05-kinds/explore/00-base.md} +5 -2
  15. package/dist/{builtin-personas/explore/orchestrator.md → builtin-memory/05-kinds/explore/01-orchestrator.md} +5 -3
  16. package/dist/{builtin-personas/general/PERSONA.md → builtin-memory/05-kinds/general/00-base.md} +5 -2
  17. package/dist/{builtin-personas/general/orchestrator.md → builtin-memory/05-kinds/general/01-orchestrator.md} +5 -3
  18. package/dist/{builtin-personas/plan/PERSONA.md → builtin-memory/05-kinds/plan/00-base.md} +5 -2
  19. package/dist/{builtin-personas/plan/orchestrator.md → builtin-memory/05-kinds/plan/01-orchestrator.md} +5 -4
  20. package/dist/{builtin-personas/plan/reviewers/architecture-fit/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/architecture-fit.md} +5 -2
  21. package/dist/{builtin-personas/plan/reviewers/code-smells/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/code-smells.md} +5 -2
  22. package/dist/{builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md} +5 -2
  23. package/dist/{builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md} +5 -2
  24. package/dist/{builtin-personas/plan/reviewers/security/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/security.md} +5 -2
  25. package/dist/{builtin-personas/product/PERSONA.md → builtin-memory/05-kinds/product/00-base.md} +5 -2
  26. package/dist/{builtin-personas/product/orchestrator.md → builtin-memory/05-kinds/product/01-orchestrator.md} +5 -4
  27. package/dist/{builtin-personas/product/teardown/PERSONA.md → builtin-memory/05-kinds/product/teardown.md} +5 -3
  28. package/dist/{builtin-personas/review/PERSONA.md → builtin-memory/05-kinds/review/00-base.md} +5 -2
  29. package/dist/{builtin-personas/review/orchestrator.md → builtin-memory/05-kinds/review/01-orchestrator.md} +5 -3
  30. package/dist/{builtin-personas/spec/PERSONA.md → builtin-memory/05-kinds/spec/00-base.md} +5 -2
  31. package/dist/{builtin-personas/spec/orchestrator.md → builtin-memory/05-kinds/spec/01-orchestrator.md} +5 -4
  32. package/dist/{builtin-personas/spec/requirements/PERSONA.md → builtin-memory/05-kinds/spec/requirements.md} +5 -2
  33. package/dist/builtin-memory/internal/INDEX.md +1 -1
  34. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  35. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  36. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +4 -4
  37. package/dist/builtin-views/canvas/core.mjs +3 -2
  38. package/dist/builtin-views/prompt-review/core.mjs +6 -134
  39. package/dist/builtin-views/settings/core.mjs +26 -94
  40. package/dist/builtin-views/settings/text.mjs +0 -5
  41. package/dist/builtin-views/settings/tui.mjs +1 -7
  42. package/dist/builtin-views/settings/web.jsx +3 -49
  43. package/dist/clients/attach/__tests__/crtr-output-render.test.js +21 -205
  44. package/dist/clients/attach/attach-cmd.js +429 -432
  45. package/dist/clients/web/web-cmd.js +7 -7
  46. package/dist/commands/__tests__/human.test.js +1 -15
  47. package/dist/commands/attention.js +9 -6
  48. package/dist/commands/canvas-history/search.js +2 -2
  49. package/dist/commands/canvas-history/show.js +3 -3
  50. package/dist/commands/canvas-history.js +1 -1
  51. package/dist/commands/canvas-issue.js +1 -0
  52. package/dist/commands/canvas-snapshot.js +1 -0
  53. package/dist/commands/canvas.js +10 -11
  54. package/dist/commands/chord.js +1 -0
  55. package/dist/commands/human/prompts.js +0 -1
  56. package/dist/commands/human/queue.js +1 -0
  57. package/dist/commands/memory/read.js +3 -3
  58. package/dist/commands/memory/shared.d.ts +13 -8
  59. package/dist/commands/memory/shared.js +22 -8
  60. package/dist/commands/memory/write.js +5 -3
  61. package/dist/commands/node-snapshot.js +1 -0
  62. package/dist/commands/node.js +52 -22
  63. package/dist/commands/profile/add-project.d.ts +1 -0
  64. package/dist/commands/profile/add-project.js +42 -0
  65. package/dist/commands/profile/delete.d.ts +1 -0
  66. package/dist/commands/profile/delete.js +39 -0
  67. package/dist/commands/profile/list.d.ts +1 -0
  68. package/dist/commands/profile/list.js +35 -0
  69. package/dist/commands/profile/new.d.ts +1 -0
  70. package/dist/commands/profile/new.js +50 -0
  71. package/dist/commands/profile/remove-project.d.ts +1 -0
  72. package/dist/commands/profile/remove-project.js +42 -0
  73. package/dist/commands/profile/rename.d.ts +1 -0
  74. package/dist/commands/profile/rename.js +42 -0
  75. package/dist/commands/profile/show.d.ts +1 -0
  76. package/dist/commands/profile/show.js +50 -0
  77. package/dist/commands/profile.d.ts +2 -0
  78. package/dist/commands/profile.js +33 -0
  79. package/dist/commands/push.d.ts +0 -1
  80. package/dist/commands/push.js +2 -228
  81. package/dist/commands/revive.js +2 -2
  82. package/dist/commands/{canvas-tmux-spread.js → surface-tmux-spread.js} +9 -6
  83. package/dist/commands/surface.js +3 -2
  84. package/dist/commands/sys/config.js +6 -21
  85. package/dist/commands/sys/daemon.d.ts +2 -0
  86. package/dist/commands/{daemon.js → sys/daemon.js} +12 -12
  87. package/dist/commands/sys/doctor.js +1 -0
  88. package/dist/commands/sys/prompt-review.js +90 -17
  89. package/dist/commands/sys.js +4 -3
  90. package/dist/core/__tests__/daemon-boot.test.js +1 -1
  91. package/dist/core/__tests__/daemon-wedge.test.d.ts +1 -0
  92. package/dist/core/__tests__/daemon-wedge.test.js +87 -0
  93. package/dist/core/__tests__/hearth-bootstrap.test.js +2 -2
  94. package/dist/core/__tests__/history-inbox.test.d.ts +1 -0
  95. package/dist/core/__tests__/history-inbox.test.js +105 -0
  96. package/dist/core/__tests__/kickoff.test.js +3 -3
  97. package/dist/core/__tests__/revive.test.js +39 -1
  98. package/dist/core/canvas/canvas.js +10 -9
  99. package/dist/core/canvas/db.js +11 -0
  100. package/dist/core/canvas/history.d.ts +1 -1
  101. package/dist/core/canvas/history.js +91 -3
  102. package/dist/core/canvas/paths.d.ts +6 -0
  103. package/dist/core/canvas/paths.js +9 -0
  104. package/dist/core/canvas/status-glyph.js +1 -0
  105. package/dist/core/canvas/types.d.ts +10 -0
  106. package/dist/core/config.d.ts +44 -1
  107. package/dist/core/config.js +155 -12
  108. package/dist/core/fault-classifier.d.ts +2 -2
  109. package/dist/core/feed/inbox.d.ts +7 -12
  110. package/dist/core/feed/inbox.js +45 -29
  111. package/dist/core/hearth/providers/blaxel-bootstrap.js +1 -1
  112. package/dist/core/memory-resolver.d.ts +26 -15
  113. package/dist/core/memory-resolver.js +158 -40
  114. package/dist/core/preview-registry.js +14 -112
  115. package/dist/core/profiles/manifest.d.ts +33 -0
  116. package/dist/core/profiles/manifest.js +332 -0
  117. package/dist/core/profiles/select.d.ts +11 -0
  118. package/dist/core/profiles/select.js +115 -0
  119. package/dist/core/render.js +1 -1
  120. package/dist/core/runtime/bearings.d.ts +9 -0
  121. package/dist/core/runtime/bearings.js +31 -0
  122. package/dist/core/runtime/busy.d.ts +15 -0
  123. package/dist/core/runtime/busy.js +32 -1
  124. package/dist/core/runtime/close.d.ts +10 -0
  125. package/dist/core/runtime/close.js +18 -13
  126. package/dist/core/runtime/fault-recovery.js +4 -0
  127. package/dist/core/runtime/front-door.js +9 -2
  128. package/dist/core/runtime/kickoff.js +5 -7
  129. package/dist/core/runtime/launch.d.ts +25 -7
  130. package/dist/core/runtime/launch.js +42 -55
  131. package/dist/core/runtime/nodes.d.ts +7 -0
  132. package/dist/core/runtime/nodes.js +6 -0
  133. package/dist/core/runtime/persona.js +18 -32
  134. package/dist/core/runtime/placement.js +1 -1
  135. package/dist/core/runtime/revive.js +9 -1
  136. package/dist/core/runtime/spawn.d.ts +13 -0
  137. package/dist/core/runtime/spawn.js +25 -0
  138. package/dist/core/runtime/tmux.d.ts +2 -2
  139. package/dist/core/runtime/tmux.js +2 -2
  140. package/dist/core/scope.d.ts +14 -7
  141. package/dist/core/scope.js +76 -20
  142. package/dist/core/substrate/index.d.ts +3 -3
  143. package/dist/core/substrate/index.js +5 -3
  144. package/dist/core/substrate/on-read.js +3 -2
  145. package/dist/core/substrate/render.d.ts +16 -11
  146. package/dist/core/substrate/render.js +249 -62
  147. package/dist/core/substrate/schema.d.ts +11 -3
  148. package/dist/core/substrate/schema.js +25 -0
  149. package/dist/core/substrate/subject.d.ts +15 -2
  150. package/dist/core/substrate/subject.js +24 -2
  151. package/dist/daemon/crtrd-cli.js +1 -1
  152. package/dist/daemon/crtrd.d.ts +24 -0
  153. package/dist/daemon/crtrd.js +213 -5
  154. package/dist/daemon/manage.js +2 -2
  155. package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
  156. package/dist/pi-extensions/canvas-nav.js +2 -2
  157. package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
  158. package/dist/pi-extensions/canvas-stophook.js +59 -2
  159. package/dist/types.d.ts +57 -1
  160. package/dist/types.js +83 -1
  161. package/dist/web-client/assets/{index-IAJVtuVe.js → index-CbO8L0mN.js} +4 -4
  162. package/dist/web-client/assets/index-DwO46Cs5.css +2 -0
  163. package/dist/web-client/index.html +2 -2
  164. package/package.json +2 -2
  165. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +0 -56
  166. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +0 -57
  167. package/dist/builtin-memory/crouter-development/personas.md +0 -112
  168. package/dist/builtin-personas/spine/no-manager.md +0 -2
  169. package/dist/commands/daemon.d.ts +0 -2
  170. package/dist/core/personas/index.d.ts +0 -13
  171. package/dist/core/personas/index.js +0 -11
  172. package/dist/core/personas/loader.d.ts +0 -159
  173. package/dist/core/personas/loader.js +0 -327
  174. package/dist/core/personas/resolve.d.ts +0 -59
  175. package/dist/core/personas/resolve.js +0 -376
  176. package/dist/web-client/assets/index-DUThOUzU.css +0 -2
  177. /package/dist/commands/{canvas-tmux-spread.d.ts → surface-tmux-spread.d.ts} +0 -0
@@ -36,11 +36,11 @@ function openBrowser(url) {
36
36
  }
37
37
  const webServeLeaf = defineLeaf({
38
38
  name: 'serve',
39
- description: 'start the crouter web UI server — the shell, the source/command bridge, and a browser broker relay',
40
- whenToUse: 'you want the crouter web UI: a browser shell to watch the canvas and drive headless nodes, instead of a terminal pane. It starts ONE long-running server (default 127.0.0.1) that serves the shell SPA, the source + command bridge (POST /__crtr/source — views\' reads AND `crtr` command writes), an SSE change-invalidation lane (GET /__crtr/events), and a VERBATIM browser ⇄ broker relay on /node/<id> (the browser becomes the same protocol peer as surface attach: one controller drives, extra viewers follow read-only). If you bind a non-loopback host, pass --token so the remote-capable bridge, SSE, and relay endpoints can authorize the page. The relay NEVER starts an engine each node must already have a running headless broker (focus or revive it first); all writes flow through `crtr` subprocesses the bridge runs. Pass --dev for Vite middleware (HMR) while iterating on the shell. The server runs until ctrl+c; the node is picked in the browser via the URL, not on the command line.',
39
+ description: 'start the crouter web UI server — the browser alternative to a tmux pane for watching the canvas and driving headless nodes',
40
+ whenToUse: 'you want the crouter web UI in a browser instead of a tmux pane — watching the canvas and driving headless nodes from a page rather than a terminal surface. Binds 127.0.0.1 by default; pass --host to bind elsewhere, in which case --token is required so the page can authorize. Pass --dev only if you are iterating on the shell itself (Vite HMR) rather than just running it. The node is picked in the browser via the URL, not on the command line; the server runs until ctrl+c.',
41
41
  help: {
42
42
  name: 'web serve',
43
- summary: 'start the long-running crouter web UI server: serves the shell SPA, the POST /__crtr/source bridge, the GET /__crtr/events SSE lane, and a VERBATIM browser ⇄ node-broker relay (browser picks the node via /node/<id>)',
43
+ summary: 'start the crouter web UI server the browser alternative to a tmux pane for watching the canvas and driving headless nodes',
44
44
  params: [
45
45
  {
46
46
  kind: 'flag',
@@ -125,14 +125,14 @@ export function registerWeb() {
125
125
  return defineBranch({
126
126
  name: 'web',
127
127
  rootEntry: {
128
- concept: 'the crouter web UI server — one host serving the browser shell, the source/command bridge, an SSE change lane, and a VERBATIM relay to nodes\' running brokers',
128
+ concept: 'the crouter web UI server — the browser alternative to a tmux pane for watching the canvas and driving headless nodes',
129
129
  desc: 'serve the crouter web UI for the canvas and headless nodes',
130
- useWhen: 'you want the crouter web UI in a browser instead of a tmux pane: watch the canvas and drive headless nodes. It runs one long-lived server that serves the shell SPA, runs view sources and crtr command writes over POST /__crtr/source, streams canvas-change invalidations over GET /__crtr/events, and relays a browser connection VERBATIM to a node\'s already-running broker on /node/<id>. The node is selected in the browser via the URL; if the server is bound off-loopback, the page must carry a token so the relay and bridge can authorize.',
130
+ useWhen: 'you want the crouter web UI in a browser instead of a tmux pane: watch the canvas and drive headless nodes from a page. Binds 127.0.0.1 by default; a non-loopback bind requires a token so the page can authorize. The node is selected in the browser via the URL, not on the command line.',
131
131
  },
132
132
  help: {
133
133
  name: 'web',
134
- summary: 'serve the crouter web UI: the shell SPA, the source/command bridge, the SSE change lane, and a VERBATIM relay to headless nodes\' brokers',
135
- model: 'The branch is a long-running browser surface. `serve` starts the unified server on the bound host and port, serves the shell SPA, exposes the source/command bridge and SSE change lane, and relays browser frames to a node\'s running broker. The node is chosen in the browser via the URL; the server itself only hosts the surface and never spawns an engine.',
134
+ summary: 'serve the crouter web UI the browser alternative to a tmux pane for the canvas and headless nodes',
135
+ model: 'The branch is a long-running browser surface, an alternative to a tmux pane. `serve` binds a host and port (loopback by default; --token required off-loopback) and runs until interrupted. The node is chosen in the browser via the URL, not on the command line; the server itself only hosts the surface and never spawns an engine.',
136
136
  },
137
137
  children: [webServeLeaf],
138
138
  });
@@ -100,7 +100,7 @@ describe('human show: params', () => {
100
100
  });
101
101
  });
102
102
  // ---------------------------------------------------------------------------
103
- // human ask — --context-file (context-file kind, key=deckFile) + --wait bool
103
+ // human ask — --context-file (context-file kind, key=deckFile)
104
104
  //
105
105
  // NOTE: The argv parser hardcodes the CLI token as `--context-file` regardless
106
106
  // of the param's `name` field. The `name` field ("deckFile") becomes the key
@@ -111,7 +111,6 @@ describe('human show: params', () => {
111
111
  describe('human ask: params', () => {
112
112
  const params = [
113
113
  { kind: 'context-file', name: 'deckFile', required: true, constraint: 'Humanloop deck JSON.' },
114
- { kind: 'flag', name: 'wait', type: 'bool', required: false, constraint: 'Presence-only.' },
115
114
  ];
116
115
  test('--context-file parses JSON file and yields input.deckFile', async () => {
117
116
  const deck = { interactions: [{ id: 'q', title: 'Go?', options: [{ id: 'yes', label: 'Yes' }] }] };
@@ -132,18 +131,6 @@ describe('human ask: params', () => {
132
131
  writeFileSync(p, 'not json at all');
133
132
  await assert.rejects(() => parseArgv(params, ['--context-file', p]), (err) => { assert.match(err.message, /not valid JSON/); return true; });
134
133
  });
135
- test('--wait absent → false', async () => {
136
- const deck = { interactions: [] };
137
- const path = tmpJson(deck);
138
- const result = await parseArgv(params, ['--context-file', path]);
139
- assert.equal(result['wait'], false);
140
- });
141
- test('--wait present → true', async () => {
142
- const deck = { interactions: [] };
143
- const path = tmpJson(deck);
144
- const result = await parseArgv(params, ['--context-file', path, '--wait']);
145
- assert.equal(result['wait'], true);
146
- });
147
134
  });
148
135
  // ---------------------------------------------------------------------------
149
136
  // human ask — shorthand question + strict --context-file deck
@@ -159,7 +146,6 @@ describe('human ask: shorthand', () => {
159
146
  test('parses a positional question into input.question', async () => {
160
147
  const result = await parseArgv(humanAsk.help.params ?? [], ['Should we ship today?']);
161
148
  assert.equal(result['question'], 'Should we ship today?');
162
- assert.equal(result['wait'], false);
163
149
  });
164
150
  test('parses --context-file into input.deck while keeping the strict deck path', async () => {
165
151
  const deck = {
@@ -1,9 +1,11 @@
1
1
  // `crtr canvas attention` — aggregate pending human asks across the canvas.
2
2
  //
3
3
  // Pending asks are stored per-cwd by humanloop. This subtree surfaces two
4
- // views:
5
- // count — a single integer (stdout.count is parsed by nav chrome)
4
+ // public views:
5
+ // count — a single integer (stdout.count is machine-parseable)
6
6
  // list — itemised entries with cwd, node id, and per-cwd ask count
7
+ // `map` (hidden) is the batched per-node form the attach viewer's nav chrome
8
+ // polls; it is not an agent-facing decision.
7
9
  //
8
10
  // All three scope modes share the same underlying helpers in attention.ts:
9
11
  // --node <id> → countAsks(id) (one cwd)
@@ -21,7 +23,7 @@ import { countAsks, pendingAsksForView, asksAcrossCanvas, asksForNodes, } from '
21
23
  const attentionCount = defineLeaf({
22
24
  name: 'count',
23
25
  description: 'total pending ask count (machine-parseable stdout.count)',
24
- whenToUse: 'getting a single pending-ask count for a script or nav chrome to read; scope it to a node or a sub-DAG, or default to canvas-wide',
26
+ whenToUse: 'a lightweight summary when you just need the number, not which cwds are blocked — scope it to a node or a sub-DAG, or default to canvas-wide. Use `list` when you need to act on where to go answer.',
25
27
  help: {
26
28
  name: 'canvas attention count',
27
29
  // stdout.count is parsed directly by the nav chrome — keep the contract.
@@ -93,7 +95,7 @@ const attentionCount = defineLeaf({
93
95
  const attentionList = defineLeaf({
94
96
  name: 'list',
95
97
  description: 'itemised list of cwds with pending asks',
96
- whenToUse: 'finding which agents are blocked waiting on a human — an itemised list of the cwds with pending asks, oldest first, so you know where to go answer. Scope it to a sub-DAG or list canvas-wide. Use `canvas attention count` instead when a script just needs the number, or `canvas attention map` for per-node counts to label a UI',
98
+ whenToUse: 'finding which agents are blocked waiting on a human — an itemised list of the cwds with pending asks, oldest first, so you know where to go answer. Scope it to a sub-DAG or list canvas-wide. Use `canvas attention count` instead when you just need the number.',
97
99
  help: {
98
100
  name: 'canvas attention list',
99
101
  summary: 'list nodes with pending human asks, grouped by cwd, oldest first',
@@ -145,6 +147,7 @@ const attentionMap = defineLeaf({
145
147
  name: 'map',
146
148
  description: 'per-node ask counts for a visible set, batched in one pass',
147
149
  whenToUse: 'labelling every node in a UI with its pending-ask count in one batched pass — the form nav chrome polls (one process, one JSON blob) instead of N count shell-outs',
150
+ tier: 'hidden',
148
151
  help: {
149
152
  name: 'canvas attention map',
150
153
  summary: 'per-node pending-ask counts for a visible set of nodes in ONE pass — the batched form the nav chrome polls (one process, one JSON blob) instead of N count shell-outs',
@@ -211,11 +214,11 @@ const attentionMap = defineLeaf({
211
214
  export const attentionBranch = defineBranch({
212
215
  name: 'attention',
213
216
  description: 'count/list pending human asks across the graph',
214
- whenToUse: 'checking whether any agent on the canvas is blocked waiting on a human, and where: count the pending asks, list the cwds that have them, or map per-node counts. Scope it to a node or a sub-DAG, or go canvas-wide. Use `canvas dashboard` instead for the graph SHAPE, or `node inspect list` for a plain node roster',
217
+ whenToUse: 'checking whether any agent on the canvas is blocked waiting on a human, and where: count the pending asks or list the cwds that have them. Scope it to a node or a sub-DAG, or go canvas-wide. Use `canvas dashboard` instead for the graph SHAPE, or `node inspect list` for a plain node roster',
215
218
  help: {
216
219
  name: 'canvas attention',
217
220
  summary: 'aggregate pending human asks across the canvas',
218
- model: 'Human asks are stored per-cwd by humanloop. `count` returns a single integer (stdout.count is parsed by nav chrome); `list` returns itemised entries. Scope with --node (one node) or --view (sub-DAG) — default is canvas-wide.',
221
+ model: '`count` returns a single integer (stdout.count is machine-parseable); `list` returns itemised entries — the cwds with pending asks, oldest first. Scope with --node (one node) or --view (sub-DAG) — default is canvas-wide.',
219
222
  },
220
223
  children: [attentionCount, attentionList, attentionMap],
221
224
  });
@@ -1,7 +1,7 @@
1
1
  import { defineLeaf } from '../../core/command.js';
2
2
  import { usage } from '../../core/errors.js';
3
3
  import { buildCorpus, } from '../../core/canvas/index.js';
4
- const TYPES = ['report', 'doc', 'roadmap', 'meta'];
4
+ const TYPES = ['report', 'doc', 'roadmap', 'meta', 'inbox'];
5
5
  const NODE_KINDS = ['advisor', 'developer', 'spec', 'design', 'review', 'explore', 'plan', 'general'];
6
6
  const STATUSES = ['active', 'idle', 'done', 'dead', 'canceled'];
7
7
  const SORTS = ['relevance', 'recency', 'oldest'];
@@ -86,7 +86,7 @@ export const searchLeaf = defineLeaf({
86
86
  { kind: 'flag', name: 'all-cwds', type: 'bool', required: false, constraint: 'Search every cwd on the canvas. Mutually exclusive with --cwd.' },
87
87
  { kind: 'flag', name: 'under', type: 'string', required: false, constraint: 'Restrict to a node and its subscription descendants (one initiative / sub-DAG).' },
88
88
  { kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Restrict to specific node ids. Comma-separated for several.' },
89
- { kind: 'flag', name: 'type', type: 'string', required: false, constraint: `Corpus to search; comma-separated for several. One of: ${TYPES.join(', ')}. report = push history; doc = context artifacts; roadmap = roadmap.md specifically; meta = node name/description/kind. Default: all.` },
89
+ { kind: 'flag', name: 'type', type: 'string', required: false, constraint: `Corpus to search; comma-separated for several. One of: ${TYPES.join(', ')}. report = push history; doc = context artifacts; roadmap = roadmap.md specifically; meta = node name/description/kind; inbox = raw inbox.jsonl history, cursor-independent and including full un-clipped direct-message bodies (diagnostic, not curated work). Default: report, doc, roadmap, meta — inbox is opt-in only, pass --type inbox explicitly.` },
90
90
  { kind: 'flag', name: 'report-kind', type: 'enum', choices: ['final', 'update'], required: false, constraint: 'Narrow reports to outcome summaries (final) or progress (update). Implies --type report.' },
91
91
  { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: `Node kind; comma-separated for several. One of: ${NODE_KINDS.join(', ')}.` },
92
92
  { kind: 'flag', name: 'status', type: 'string', required: false, constraint: `Node lifecycle status; comma-separated for several. One of: ${STATUSES.join(', ')} (e.g. done = completed work only).` },
@@ -2,7 +2,7 @@ import { defineLeaf } from '../../core/command.js';
2
2
  import { notFound } from '../../core/errors.js';
3
3
  import { getNode, nodeArtifacts } from '../../core/canvas/index.js';
4
4
  import { statSync } from 'node:fs';
5
- const TYPES = ['report', 'doc', 'roadmap'];
5
+ const TYPES = ['report', 'doc', 'roadmap', 'inbox'];
6
6
  export const showLeaf = defineLeaf({
7
7
  name: 'show',
8
8
  description: 'enumerate one node\'s artifacts as refs',
@@ -12,12 +12,12 @@ export const showLeaf = defineLeaf({
12
12
  summary: 'list one node\'s artifacts (reports + context docs) as refs ready for `canvas history read`',
13
13
  params: [
14
14
  { kind: 'positional', name: 'node-id', required: true, constraint: 'The node whose artifacts to list.' },
15
- { kind: 'flag', name: 'type', type: 'enum', choices: [...TYPES], required: false, constraint: 'Narrow to one corpus: report | doc | roadmap. Default: all.' },
15
+ { kind: 'flag', name: 'type', type: 'enum', choices: [...TYPES], required: false, constraint: 'Narrow to one corpus: report | doc | roadmap | inbox. Default: report, doc, roadmap (inbox is opt-in only — the node\'s raw inbox.jsonl history, cursor-independent and including full un-clipped direct-message bodies; a diagnostic surface, not curated past work).' },
16
16
  { kind: 'flag', name: 'sort', type: 'enum', choices: ['recency', 'oldest'], required: false, constraint: 'Order by artifact timestamp. Default recency.' },
17
17
  ],
18
18
  output: [
19
19
  { name: 'node', type: 'string', required: true, constraint: 'Node name + id.' },
20
- { name: 'artifacts', type: 'object[]', required: true, constraint: 'Each: {ref, source, ts, detail}. source = report:<kind> | doc | roadmap. detail = report title / doc heading + size. ref passes verbatim to `canvas history read`.' },
20
+ { name: 'artifacts', type: 'object[]', required: true, constraint: 'Each: {ref, source, ts, detail}. source = report:<kind> | doc | roadmap | inbox. detail = report title / doc heading + size (inbox: `[kind] label`, no size). ref passes verbatim to `canvas history read`.' },
21
21
  { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
22
22
  ],
23
23
  outputKind: 'object',
@@ -23,7 +23,7 @@ export const historyBranch = defineBranch({
23
23
  help: {
24
24
  name: 'canvas history',
25
25
  summary: 'search and recall the canvas\'s record of past work in this cwd',
26
- model: 'The accumulated record of every node that ran in a cwd and the artifacts it left — its reports (final/update outcome summaries) and context docs (specs, designs, roadmaps, findings). `search` finds by content (ranked, filtered, sorted; omit the query to browse by recency); `read` pulls one hit\'s full body by its <node-id>:<relpath> ref; `show` lists everything one node left. This is the episodic record of what was DONE — distinct from `crtr memory`, which is curated semantic knowledge. To reopen a node you found, use `canvas revive`; for its place in the graph, `node inspect show`.',
26
+ model: 'The accumulated record of every node that ran in a cwd and the artifacts it left — its reports (final/update outcome summaries) and context docs (specs, designs, roadmaps, findings). `search` finds by content (ranked, filtered, sorted; omit the query to browse by recency); `read` pulls one hit\'s full body by its <node-id>:<relpath> ref; `show` lists everything one node left. This is the episodic record of what was DONE — distinct from `crtr memory`, which is curated semantic knowledge. A node\'s raw inbox history (`--type inbox`) is a separate, opt-in diagnostic corpus — the full cursor-independent inbox.jsonl for one node, including un-clipped direct-message bodies; reach for it only when the curated reports/docs above don\'t already answer the question. To reopen a node you found, use `canvas revive`; for its place in the graph, `node inspect show`.',
27
27
  dynamicState: corpusBlock,
28
28
  },
29
29
  children: [searchLeaf, readLeaf, showLeaf],
@@ -66,6 +66,7 @@ export const issueNewLeaf = defineLeaf({
66
66
  name: 'new',
67
67
  description: 'capture a todo/idea/bug note into a GitHub issue (or a .todos/ markdown note)',
68
68
  whenToUse: 'the alt+c \u2192 i chord runs this inside a tmux popup; rarely run by hand',
69
+ tier: 'hidden',
69
70
  help: {
70
71
  name: 'canvas issue new',
71
72
  summary: 'Capture a free-text note as a GitHub issue labeled with its type (idea|bug). Reads the note from the popup TTY, auto-generates the title via a headless pi call, and files it with `gh issue create` when the cwd is a GitHub repo, else writes `<git-root>/.todos/<slug>.md`. Bound to the alt+c \u2192 i prefix chord.',
@@ -5,6 +5,7 @@ export const canvasSnapshotLeaf = defineLeaf({
5
5
  name: 'snapshot',
6
6
  description: 'emit the browser canvas roster as structured data',
7
7
  whenToUse: 'you need a machine-readable roster of every canvas node for a browser or local tool; use dashboard for the human-readable tree',
8
+ tier: 'hidden',
8
9
  help: {
9
10
  name: 'canvas snapshot',
10
11
  summary: 'machine-readable snapshot of the whole canvas roster',
@@ -1,20 +1,18 @@
1
1
  // `crtr canvas` — observe and supervise the whole graph.
2
2
  //
3
- // Where `node` operates on one node and `push`/`feed` are a node's own spine
4
- // I/O, `canvas` is the bird's-eye / supervisor surface over the entire canvas:
3
+ // Where `node` operates on one node and `push` is a node's own spine write
4
+ // verb, `canvas` is the bird's-eye / supervisor surface over the entire canvas:
5
5
  // render the subscription forest (`dashboard`), see who is blocked on a human
6
- // (`attention`), bring a node back (`revive`), and manage the supervisor
7
- // process (`daemon`). It assembles leaves/branches the sibling command files
8
- // own, so each piece declares its own help one level down.
6
+ // (`attention`), and bring a node back (`revive`). It assembles leaves/branches
7
+ // the sibling command files own, so each piece declares its own help one level
8
+ // down.
9
9
  import { defineBranch } from '../core/command.js';
10
10
  import { dashboardLeaf } from './dashboard.js';
11
11
  import { canvasSnapshotLeaf } from './canvas-snapshot.js';
12
12
  import { browseLeaf } from './canvas-browse.js';
13
13
  import { reviveLeaf } from './revive.js';
14
14
  import { attentionBranch } from './attention.js';
15
- import { daemonBranch } from './daemon.js';
16
15
  import { chordLeaf } from './chord.js';
17
- import { tmuxSpreadLeaf } from './canvas-tmux-spread.js';
18
16
  import { canvasPruneLeaf } from './canvas-prune.js';
19
17
  import { canvasRebuildIndexLeaf } from './canvas-rebuild-index.js';
20
18
  import { historyBranch } from './canvas-history.js';
@@ -23,6 +21,7 @@ export const issueBranch = defineBranch({
23
21
  name: 'issue',
24
22
  description: 'capture a quick todo/idea/bug into a GitHub issue (or a .todos/ note)',
25
23
  whenToUse: 'the alt+c \u2192 i chord uses this; capture a passing thought without leaving your pane',
24
+ tier: 'hidden',
26
25
  help: {
27
26
  name: 'canvas issue',
28
27
  summary: 'capture a quick todo/idea/bug into a GitHub issue',
@@ -34,15 +33,15 @@ export function registerCanvas() {
34
33
  return defineBranch({
35
34
  name: 'canvas',
36
35
  rootEntry: {
37
- concept: 'the whole agent graph at a glance — render it, see who is blocked, revive a node, supervise the daemon',
36
+ concept: 'the whole agent graph at a glance — render it, see who is blocked, revive a node',
38
37
  desc: 'bird\'s-eye view and supervision of the entire canvas',
39
- useWhen: 'surveying the full graph, finding blocked agents, or managing revival/the daemon',
38
+ useWhen: 'surveying the full graph, finding blocked agents, or reviving a node',
40
39
  },
41
40
  help: {
42
41
  name: 'canvas',
43
42
  summary: 'observe and supervise the whole agent graph',
44
- model: 'Canvas-wide operations, distinct from per-node work (`node`) and a node\'s own spine I/O (`push`/`feed`). `dashboard` renders the subscription forest as a tree; `browse` opens an interactive full-screen navigator (tabs/tree/search) over the whole canvas and resumes the chosen node; `attention` aggregates pending human asks across the graph; `revive` reopens a window for a done/idle/dead/canceled node; `history` searches and recalls the content record (reports + context docs) of past work in a cwd; `daemon` manages the thin crtrd supervisor that auto-revives nodes on window exit; `prune` bounds growth by deleting terminal nodes past a TTL.',
43
+ model: 'Canvas-wide operations, distinct from per-node work (`node`) and a node\'s own spine write verb (`push`). `dashboard` renders the subscription forest as a tree; `browse` opens an interactive full-screen navigator (tabs/tree/search) over the whole canvas and resumes the chosen node; `attention` aggregates pending human asks across the graph; `revive` reopens a window for a done/idle/dead/canceled node; `history` searches and recalls the content record (reports + context docs) of past work in a cwd; `prune` bounds growth by deleting terminal nodes past a TTL.',
45
44
  },
46
- children: [dashboardLeaf, canvasSnapshotLeaf, browseLeaf, attentionBranch, reviveLeaf, historyBranch, daemonBranch, chordLeaf, tmuxSpreadLeaf, issueBranch, canvasPruneLeaf, canvasRebuildIndexLeaf],
45
+ children: [dashboardLeaf, canvasSnapshotLeaf, browseLeaf, attentionBranch, reviveLeaf, historyBranch, chordLeaf, issueBranch, canvasPruneLeaf, canvasRebuildIndexLeaf],
47
46
  });
48
47
  }
@@ -154,6 +154,7 @@ export const chordLeaf = defineLeaf({
154
154
  name: 'chord',
155
155
  description: 'tmux prefix-menu dispatcher (bound by alt+c; not run by hand)',
156
156
  whenToUse: 'never directly — the alt+c menu routes node-relative actions through it',
157
+ tier: 'hidden',
157
158
  help: {
158
159
  name: 'canvas chord',
159
160
  summary: 'tmux prefix-menu dispatcher — resolve the node in --pane, interpolate --run against it, and exec `crtr <argv>`. Bound by the alt+c menu; not meant to be run by hand.',
@@ -40,7 +40,6 @@ export const humanAsk = defineLeaf({
40
40
  params: [
41
41
  { kind: 'positional', name: 'question', type: 'string', required: false, constraint: 'Bare-question shorthand for a simple open-text ask. Mutually exclusive with `--context-file`.' },
42
42
  { kind: 'context-file', name: 'deck', required: false, constraint: 'Strict complex-ask deck JSON. Use this when you need multiple interactions, options, or other deck structure. Mutually exclusive with the question shorthand.', shape: DECK_SCHEMA_HINT },
43
- { kind: 'flag', name: 'wait', type: 'bool', required: false, constraint: 'Accepted for symmetry with the job contract; the kickoff never blocks.' },
44
43
  ],
45
44
  output: [
46
45
  { name: 'job_id', type: 'string', required: true, constraint: 'Node id of this human interaction. Its answer is pushed to your inbox when the human responds.' },
@@ -520,6 +520,7 @@ export const humanResolve = defineLeaf({
520
520
  name: 'resolve',
521
521
  description: 'resolve one humanloop deck from stdin answers',
522
522
  whenToUse: 'a browser or local tool is submitting answers for a pending human interaction; this writes through the crouter/humanloop finalize path',
523
+ tier: 'hidden',
523
524
  help: {
524
525
  name: 'human resolve',
525
526
  summary: 'write deck answers atomically and deliver them back to the asking node',
@@ -7,19 +7,19 @@ import { MEMORY_KINDS } from './shared.js';
7
7
  export const readLeaf = defineLeaf({
8
8
  name: 'read',
9
9
  description: 'load a memory document body by name',
10
- whenToUse: 'a task in front of you matches a stored document and you already know its name — read it before improvising. Resolves the path-derived name across scopes by precedence (project > user > builtin), with leaf-name fallback. You name the document by its crtr identifier, never a file path — do not cat or find the markdown off disk. Reach for `crtr memory find` first when you do not yet know which document applies.',
10
+ whenToUse: 'a task in front of you matches a stored document and you already know its name — read it before improvising. Resolves the path-derived name across scopes by precedence (project stack > profile > user > builtin), with leaf-name fallback. You name the document by its crtr identifier, never a file path — do not cat or find the markdown off disk. Reach for `crtr memory find` first when you do not yet know which document applies.',
11
11
  help: {
12
12
  name: 'memory read',
13
13
  summary: 'resolve a path-derived name to its document body, frontmatter stripped unless --frontmatter',
14
14
  params: [
15
- { kind: 'positional', name: 'name', required: true, constraint: 'Path-derived memory identifier (e.g. `topic` or `area/topic`). Resolved across scopes by precedence: project > user > builtin, with leaf-name fallback.' },
15
+ { kind: 'positional', name: 'name', required: true, constraint: 'Path-derived memory identifier (e.g. `topic` or `area/topic`). Resolved across scopes by precedence: project stack > profile > user > builtin, with leaf-name fallback.' },
16
16
  { kind: 'flag', name: 'kind', type: 'enum', choices: [...MEMORY_KINDS], required: false, constraint: 'Narrows resolution when the name is ambiguous across kinds.' },
17
17
  { kind: 'flag', name: 'frontmatter', type: 'bool', required: false, constraint: 'When present, includes the YAML frontmatter in the returned body. Off by default — only the body is returned.' },
18
18
  ],
19
19
  output: [
20
20
  { name: 'name', type: 'string', required: true, constraint: 'Resolved document name.' },
21
21
  { name: 'kind', type: 'string', required: true, constraint: 'Resolved kind: knowledge or preference.' },
22
- { name: 'scope', type: 'string', required: true, constraint: 'Scope the document was resolved from: project, user, or builtin.' },
22
+ { name: 'scope', type: 'string', required: true, constraint: 'Scope the document was resolved from: project, profile, user, or builtin.' },
23
23
  { name: 'path', type: 'string', required: true, constraint: 'Absolute path to the document on disk — edit this file directly to tweak the doc in place.' },
24
24
  { name: 'content', type: 'string', required: true, constraint: 'Document body. Frontmatter stripped unless --frontmatter is set.' },
25
25
  { name: 'follow_up', type: 'string', required: true, constraint: 'Hints at variant flags or next commands.' },
@@ -1,16 +1,21 @@
1
- import type { Scope } from '../../types.js';
1
+ import type { MemoryScope } from '../../core/memory-resolver.js';
2
2
  export declare const MEMORY_KINDS: readonly ["knowledge", "preference"];
3
3
  export declare const VISIBILITY_RUNGS: readonly ["none", "name", "preview", "content"];
4
- export declare const MEMORY_SCOPES: readonly ["user", "project"];
5
- /** Scope sort weight matching resolution precedence (project > user > builtin).
6
- * Used by `list` for its "scope then kind then name" ordering. */
7
- export declare function scopeRank(scope: Scope): number;
4
+ export declare const MEMORY_SCOPES: readonly ["user", "project", "profile"];
5
+ /** Scope sort weight matching resolution precedence (project stack > profile >
6
+ * user > builtin). Used by `list` for its "scope then kind then name"
7
+ * ordering. */
8
+ export declare function scopeRank(scope: MemoryScope): number;
8
9
  /** Resolve the write target scope + its memory dir. Default: project when a
9
10
  * project scope exists for the cwd, else user. An explicit `--scope project`
10
11
  * with no project scope yet scaffolds one (ensureProjectScopeRoot). User scope
11
- * always resolves. Returns the absolute `<root>/memory` dir to write under. */
12
- export declare function resolveWriteTarget(scopeArg: string | undefined): {
13
- scope: Scope;
12
+ * always resolves. `--scope profile` is NEVER a default it requires a
13
+ * selected profile, from `profileArg` (an explicit `--profile <id-or-name>`)
14
+ * or else the process's `CRTR_PROFILE_ID`, resolved through the centralized
15
+ * `loadProfileManifest` (never a raw path join). Returns the absolute
16
+ * `<root>/memory` dir to write under. */
17
+ export declare function resolveWriteTarget(scopeArg: string | undefined, profileArg?: string): {
18
+ scope: MemoryScope;
14
19
  memoryDir: string;
15
20
  };
16
21
  /** Map a path-derived name (`topic` or `area/topic`) to its file path under a
@@ -7,6 +7,7 @@ import { join } from 'node:path';
7
7
  import { stringify as yamlStringify, parse as yamlParse } from 'yaml';
8
8
  import { usage } from '../../core/errors.js';
9
9
  import { scopeMemoryDir, projectScopeRoot, ensureProjectScopeRoot, } from '../../core/scope.js';
10
+ import { loadProfileManifest, profileMemoryDir } from '../../core/profiles/manifest.js';
10
11
  // The two memory kinds — knowledge (consult: procedural playbooks + factual
11
12
  // references merged) vs preference (behave: standing directives). Used as the
12
13
  // `--kind` enum choices everywhere.
@@ -15,27 +16,40 @@ export const MEMORY_KINDS = ['knowledge', 'preference'];
15
16
  // content). Shared by --system-prompt-visibility and --file-read-visibility.
16
17
  export const VISIBILITY_RUNGS = ['none', 'name', 'preview', 'content'];
17
18
  // Scope choices for filtering / targeting (builtin is read-only, not writable).
18
- export const MEMORY_SCOPES = ['user', 'project'];
19
- /** Scope sort weight matching resolution precedence (project > user > builtin).
20
- * Used by `list` for its "scope then kind then name" ordering. */
19
+ export const MEMORY_SCOPES = ['user', 'project', 'profile'];
20
+ /** Scope sort weight matching resolution precedence (project stack > profile >
21
+ * user > builtin). Used by `list` for its "scope then kind then name"
22
+ * ordering. */
21
23
  export function scopeRank(scope) {
22
- return scope === 'project' ? 0 : scope === 'user' ? 1 : 2;
24
+ return scope === 'project' ? 0 : scope === 'profile' ? 1 : scope === 'user' ? 2 : 3;
23
25
  }
24
26
  /** Resolve the write target scope + its memory dir. Default: project when a
25
27
  * project scope exists for the cwd, else user. An explicit `--scope project`
26
28
  * with no project scope yet scaffolds one (ensureProjectScopeRoot). User scope
27
- * always resolves. Returns the absolute `<root>/memory` dir to write under. */
28
- export function resolveWriteTarget(scopeArg) {
29
+ * always resolves. `--scope profile` is NEVER a default it requires a
30
+ * selected profile, from `profileArg` (an explicit `--profile <id-or-name>`)
31
+ * or else the process's `CRTR_PROFILE_ID`, resolved through the centralized
32
+ * `loadProfileManifest` (never a raw path join). Returns the absolute
33
+ * `<root>/memory` dir to write under. */
34
+ export function resolveWriteTarget(scopeArg, profileArg) {
29
35
  let scope;
30
- if (scopeArg === 'user' || scopeArg === 'project') {
36
+ if (scopeArg === 'user' || scopeArg === 'project' || scopeArg === 'profile') {
31
37
  scope = scopeArg;
32
38
  }
33
39
  else if (scopeArg !== undefined) {
34
- throw usage(`invalid --scope: ${scopeArg} (expected user|project)`);
40
+ throw usage(`invalid --scope: ${scopeArg} (expected user|project|profile)`);
35
41
  }
36
42
  else {
37
43
  scope = projectScopeRoot() !== null ? 'project' : 'user';
38
44
  }
45
+ if (scope === 'profile') {
46
+ const profileIdOrName = profileArg && profileArg !== '' ? profileArg : process.env['CRTR_PROFILE_ID'] || '';
47
+ if (profileIdOrName === '') {
48
+ throw usage('profile scope requires a selected profile; rerun inside a profiled node or pass --profile');
49
+ }
50
+ const { profileId } = loadProfileManifest(profileIdOrName);
51
+ return { scope: 'profile', memoryDir: profileMemoryDir(profileId) };
52
+ }
39
53
  let memoryDir = scopeMemoryDir(scope);
40
54
  if (!memoryDir && scope === 'project') {
41
55
  // Explicit --scope project with no project root yet → scaffold it.
@@ -27,13 +27,14 @@ export const writeLeaf = defineLeaf({
27
27
  { kind: 'flag', name: 'gate', type: 'string', required: false, constraint: 'Frontmatter gate — YAML/JSON object predicate over node config using the same field/matcher vocabulary described in the guide.' },
28
28
  { kind: 'flag', name: 'applies-to', type: 'string', required: false, constraint: 'Frontmatter applies-to — glob/path scope the document applies to.' },
29
29
  { kind: 'flag', name: 'read-when', type: 'string', required: false, constraint: 'Frontmatter read-when — YAML/JSON object predicate over a read file’s own frontmatter using the same field/matcher vocabulary described in the guide.' },
30
- { kind: 'flag', name: 'scope', type: 'enum', choices: [...MEMORY_SCOPES], required: false, constraint: 'Target scope. Default: project when inside a project, else user.' },
30
+ { kind: 'flag', name: 'scope', type: 'enum', choices: [...MEMORY_SCOPES], required: false, constraint: 'Target scope. Default: project when inside a project, else user. `profile` requires a selected profile (CRTR_PROFILE_ID) or an explicit --profile.' },
31
+ { kind: 'flag', name: 'profile', type: 'string', required: false, constraint: 'Profile id or name to write under, for --scope profile. Default: the process CRTR_PROFILE_ID (the node\u2019s selected profile). Resolved through the same profile lookup as `crtr profile show`; ignored for any other --scope.' },
31
32
  { kind: 'stdin', name: 'body', required: true, constraint: 'Document body (markdown, no frontmatter). Piped on stdin, or passed as the bare positional after <name>.' },
32
33
  ],
33
34
  output: [
34
35
  { name: 'name', type: 'string', required: true, constraint: 'The path-derived document name written.' },
35
36
  { name: 'kind', type: 'string', required: true, constraint: 'Kind recorded in frontmatter.' },
36
- { name: 'scope', type: 'string', required: true, constraint: 'Scope the document was written to: user or project.' },
37
+ { name: 'scope', type: 'string', required: true, constraint: 'Scope the document was written to: user, project, or profile.' },
37
38
  { name: 'path', type: 'string', required: true, constraint: 'Absolute path to the written document — edit this file directly for later body tweaks instead of re-running write.' },
38
39
  { name: 'created', type: 'boolean', required: true, constraint: 'true when a new document was created, false when an existing one was updated in place.' },
39
40
  { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands — read it back or list the inventory.' },
@@ -47,8 +48,9 @@ export const writeLeaf = defineLeaf({
47
48
  const name = input['name'];
48
49
  const kind = input['kind'];
49
50
  const scopeArg = input['scope'];
51
+ const profileArg = input['profile'];
50
52
  const body = input['body'] ?? '';
51
- const { scope, memoryDir } = resolveWriteTarget(scopeArg);
53
+ const { scope, memoryDir } = resolveWriteTarget(scopeArg, profileArg);
52
54
  const path = memoryFilePath(memoryDir, name);
53
55
  const created = !pathExists(path);
54
56
  // CREATE requires the read-routing line that becomes the preview — without
@@ -63,6 +63,7 @@ export const nodeSnapshotLeaf = defineLeaf({
63
63
  name: 'snapshot',
64
64
  description: 'read a dormant node session snapshot without launching its broker',
65
65
  whenToUse: 'you need a read-only broker-compatible snapshot of a dormant node; it copies the session JSONL before parsing and never revives the node',
66
+ tier: 'hidden',
66
67
  help: {
67
68
  name: 'node inspect snapshot',
68
69
  summary: 'read-only dormant node snapshot in the broker welcome shape',