@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
@@ -26,7 +26,8 @@ import { buildLaunchSpec, normalizeModel } from '../core/runtime/launch.js';
26
26
  import { closeNode } from '../core/runtime/close.js';
27
27
  import { isBrokerLive, setModelLive, persistDormantModel } from '../core/runtime/model-swap.js';
28
28
  import { appendInbox } from '../core/feed/inbox.js';
29
- import { availableKinds, kindWhenToUse } from '../core/personas/index.js';
29
+ import { readMergedLaunchConfig } from '../core/config.js';
30
+ import { listProfiles } from '../core/profiles/manifest.js';
30
31
  import { stateBlock } from '../core/help.js';
31
32
  import { getNode, updateNode, listNodes, subscribe, unsubscribe, subscriptionsOf, subscribersOf, readContextTokens, view, armTrigger, listTriggers, cancelTrigger, TriggerArmError, fullName, } from '../core/canvas/index.js';
32
33
  // Past this much context, an ORCHESTRATOR that spawns a managed child is better
@@ -54,7 +55,7 @@ export function childFollowUp(spawnerId) {
54
55
  }
55
56
  /** Validate a `--kind` against the installed personas; throws a listing InputError. */
56
57
  function assertKind(kind) {
57
- const kinds = availableKinds();
58
+ const kinds = Object.keys(readMergedLaunchConfig().kinds).sort();
58
59
  if (!kinds.includes(kind)) {
59
60
  throw new InputError({ error: 'unknown_kind', message: `unknown kind: ${kind}`, field: 'kind', next: `Valid kinds: ${kinds.join(', ')}.` });
60
61
  }
@@ -65,12 +66,37 @@ function assertKind(kind) {
65
66
  * `node promote -h` so custom kinds appear in help. Soft-fails via the renderer.
66
67
  *
67
68
  * Keep this block cheap and caller-agnostic: help should not depend on the
68
- * running node's current persona env, which can lag after polymorph. */
69
+ * running node's current persona env, which can lag after polymorph. Sub-kinds
70
+ * (e.g. `plan/reviewers/security`) are OMITTED here — `KindConfig.availableTo`
71
+ * defaults to the sub-kind's own top-level ancestor, and this generic block has
72
+ * no single ancestor to check against, so only an explicit `availableTo: ['*']`
73
+ * sub-kind (universally available) earns a place in this list. A direct launch
74
+ * by full sub-kind string is always valid regardless (visibility-only
75
+ * filtering) — discover the roster for a specific kind via
76
+ * `crtr sys prompt-review --list --kind <kind>`. */
77
+ /** A live `<profiles count=N>` state element — one `<profile_id> — <name>` line
78
+ * per profile on this host, so `--profile` has an enumerable value set on this
79
+ * same leaf (command-surface rule) without a separate `crtr profile list` hop.
80
+ * Soft-fails via the renderer like `kindsStateBlock`. */
81
+ function profilesStateBlock() {
82
+ const all = listProfiles();
83
+ const lines = all.map((p) => `${p.profileId} — ${p.manifest.name}`).join('\n');
84
+ return stateBlock('profiles', { count: all.length }, lines);
85
+ }
69
86
  function kindsStateBlock() {
70
- const kinds = availableKinds();
87
+ const registry = readMergedLaunchConfig().kinds;
88
+ const kinds = Object.keys(registry)
89
+ .filter((k) => {
90
+ const slash = k.indexOf('/');
91
+ if (slash === -1)
92
+ return true;
93
+ const availableTo = registry[k]?.availableTo ?? [k.slice(0, slash)];
94
+ return availableTo.includes('*');
95
+ })
96
+ .sort();
71
97
  const lines = kinds
72
98
  .map((k) => {
73
- const w = kindWhenToUse(k);
99
+ const w = registry[k]?.whenToUse ?? '';
74
100
  return w ? `${k} — ${w}` : k;
75
101
  })
76
102
  .join('\n');
@@ -89,7 +115,7 @@ const nodeNew = defineLeaf({
89
115
  summary: 'spawn a terminal worker onto the canvas as a detached broker — returns its node id. With --at/--every/--when, arms a node_birth trigger instead: nothing is spawned now, and the full recipe is stored/re-derived at fire time',
90
116
  params: [
91
117
  { kind: 'stdin', name: 'prompt', required: true, constraint: 'First user message for the spawned node. Deliver it on stdin from a quoted heredoc (`<<\'EOF\'`) or a file (`< prompt.md`).' },
92
- { kind: 'flag', name: 'kind', type: 'string', required: false, default: 'general', constraint: 'Persona kind — match the work to the kind. The <kinds> list below names every installable kind and when to use each. For an exact composed persona list, use `crtr sys prompt-review --list --kind <kind>`.' },
118
+ { kind: 'flag', name: 'kind', type: 'string', required: false, default: 'general', constraint: 'Persona kind — match the work to the kind. The <kinds> list below names every top-level installable kind and when to use each. Sub-kinds (e.g. plan/reviewers/security) are valid too by exact path but not listed here — for the full roster available to a given kind, use `crtr sys prompt-review --list --kind <kind>`.' },
93
119
  { kind: 'flag', name: 'mode', type: 'enum', choices: ['base', 'orchestrator'], required: false, default: 'base', constraint: 'Persona mode. base for a worker that finishes in one window; orchestrator to create the child directly as a sub-orchestrator (it boots with the orchestrator persona + a seeded roadmap and fans its scope out) — use it when the unit is too large for one window, e.g. a big review, instead of spawning a base worker and counting on it to promote itself.' },
94
120
  { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Dir the node is pinned to. Defaults to the caller cwd, resolved now — for a triggered spawn it must still exist at fire time or the spawn fails loud.' },
95
121
  { kind: 'flag', name: 'name', type: 'string', required: false, constraint: 'Display name (tmux window + resume picker). Defaults to the kind.' },
@@ -97,6 +123,7 @@ const nodeNew = defineLeaf({
97
123
  { kind: 'flag', name: 'root', type: 'bool', required: false, constraint: 'Spawn an INDEPENDENT root instead of a managed child: no parent (top-level on the canvas), NO subscription back to you (you are NOT woken by it), resident lifecycle. It records spawned_by=you for provenance and is brought forefront so it can be driven directly. Use for a node you hand off and do not manage (e.g. a sub-orchestrator a human will discuss with).' },
98
124
  { kind: 'flag', name: 'fork-from', type: 'string', required: false, constraint: 'FORK the new node from an existing pi conversation instead of starting it fresh: pass a node id (forks from that node\'s session), an absolute session `.jsonl` path, or a partial pi session uuid. pi copies that whole history into a NEW session for the child (the source is untouched), then the prompt is delivered as the next message — i.e. the child wakes up as a continuation of that conversation. Use to branch exploratory work off a node that already built up the context you need, instead of re-deriving it. One-shot at birth: the fork resumes its own session thereafter.' },
99
125
  { kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong', 'medium', 'light'], required: false, constraint: 'Override the model the node runs on, by capability TIER: ultra (frontier), strong (opus), medium (sonnet), light (haiku). Omit to use the kind\'s persona default (advisor=ultra, explore=light, most other builtins=strong). The override is durable — it survives revives and polymorphs (promote/demote).' },
126
+ { kind: 'flag', name: 'profile', type: 'string', required: false, constraint: 'Select the profile this node runs under — an exact profile id or a unique manifest name (see the <profiles> list below, or `crtr profile list`). Omit to INHERIT the calling node\'s current profile (root has none, so an uninherited child has none either). --root does NOT reset this — it only means top-level, still inherits/uses the selected profile unless this overrides it.' },
100
127
  { kind: 'flag', name: 'at', type: 'string', required: false, constraint: 'DEFER the birth to a one-shot clock trigger at this future time instead of spawning now — a duration ("5m","1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00", host-local or in --tz). Required unless --every is given (the cadence then sets the first fire). Mutually exclusive with --when.' },
101
128
  { kind: 'flag', name: 'every', type: 'string', required: false, constraint: 'SPAWN-CRON: re-birth a fresh node on this cadence — a duration ("6h","30m") or a 5-field cron / @alias ("0 9 * * *","@daily"). Combine with --at to anchor the first fire, then recur normally. Min cadence 60s. With --when, this instead sets the PREDICATE EVALUATION cadence (required), not a repeating birth.' },
102
129
  { kind: 'flag', name: 'when', type: 'string', required: false, constraint: 'Arm a daemon-evaluated bash predicate instead of a clock trigger: exit 0 births the node once, non-zero leaves it armed for the next evaluation. Requires --every (eval cadence) and --timeout (wall-clock bound). Mutually exclusive with --at.' },
@@ -118,7 +145,7 @@ const nodeNew = defineLeaf({
118
145
  { name: 'recur', type: 'string', required: false, constraint: 'Cadence display ("every 6h", cron, or eval cadence for --when), present only when --every was given.' },
119
146
  { name: 'follow_up', type: 'string', required: true, constraint: 'Decision road sign for the caller. Spawned now: the child runs independently and its finish wakes you on its own, so never wait or poll on it — either pick up other work now or end your turn (an orchestrator already deep in context is instead steered to `crtr node yield`). Armed: no node exists yet — inspect/cancel via `crtr node triggers list`/`cancel <id>`, then pick up other work or end your turn.' },
120
147
  ],
121
- dynamicState: () => kindsStateBlock(),
148
+ dynamicState: () => [kindsStateBlock(), profilesStateBlock()].join('\n'),
122
149
  outputKind: 'object',
123
150
  effects: [
124
151
  'Spawning now: creates a node under ~/.crouter/canvas/nodes/<id>/ and indexes it in canvas.db. Default (managed child): parent auto-subscribes (active) and is woken on the child\'s pushes. With --root: no subscription — records a spawned_by edge for provenance only. Launches the node\'s engine as a detached broker (the only host); a managed child opens NO viewer, a --root opens one in your current tmux session.',
@@ -138,6 +165,7 @@ const nodeNew = defineLeaf({
138
165
  const root = input['root'] === true;
139
166
  const forkFrom = input['forkFrom'];
140
167
  const model = input['model'];
168
+ const profile = input['profile'];
141
169
  const at = input['at']?.trim();
142
170
  const every = input['every']?.trim();
143
171
  const whenRaw = input['when'];
@@ -156,7 +184,7 @@ const nodeNew = defineLeaf({
156
184
  throw new InputError({ error: 'tz_needs_clock', message: '--tz has no effect without --at or --every to parse it against', field: 'tz', next: 'Drop --tz, or pass --at/--every.' });
157
185
  }
158
186
  if (!triggered) {
159
- const res = await spawnChild({ kind, mode, cwd, name, prompt, parent, root, forkFrom, model });
187
+ const res = await spawnChild({ kind, mode, cwd, name, prompt, parent, root, forkFrom, model, profile });
160
188
  return {
161
189
  node_id: res.node.node_id,
162
190
  name: res.node.name,
@@ -193,6 +221,7 @@ const nodeNew = defineLeaf({
193
221
  ...(root ? { root: true } : {}),
194
222
  ...(forkFrom !== undefined ? { forkFrom } : {}),
195
223
  ...(model !== undefined ? { model } : {}),
224
+ ...(profile !== undefined ? { profile } : {}),
196
225
  };
197
226
  const id = `tr-${newNodeId()}`;
198
227
  if (hasWhen) {
@@ -306,7 +335,7 @@ const nodeList = defineLeaf({
306
335
  summary: 'list nodes on the canvas, filtered by any combination of attribute slices and a graph-scope (--under) that AND together',
307
336
  params: [
308
337
  { kind: 'flag', name: 'status', type: 'string', required: false, constraint: 'Filter status (comma-separated for several; accepted values: active, idle, done, dead, canceled). NOTE: `active` means the engine process is live (never closed), NOT that the node is generating right now — an active node is usually dormant between turns. To tell working-vs-idle, check the pi session-file mtime or CPU, not status.' },
309
- { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Filter by persona kind (comma-separated for several). The <kinds> list below names every installable kind.' },
338
+ { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Filter by persona kind (comma-separated for several). The <kinds> list below names every top-level installable kind; a sub-kind (e.g. plan/reviewers/security) filters too even though it is not listed.' },
310
339
  { kind: 'flag', name: 'mode', type: 'string', required: false, constraint: 'Filter by mode (comma-separated for several; accepted values: base, orchestrator).' },
311
340
  { kind: 'flag', name: 'lifecycle', type: 'string', required: false, constraint: 'Filter by lifecycle (comma-separated for several; accepted values: terminal, resident).' },
312
341
  { kind: 'flag', name: 'cwd', type: 'string', required: false, constraint: 'Filter to nodes pinned to this exact cwd (absolute path). Roster is canvas-wide (all cwds) by default.' },
@@ -400,15 +429,15 @@ const nodeShow = defineLeaf({
400
429
  },
401
430
  });
402
431
  // ---------------------------------------------------------------------------
403
- // node inspect — read the graph (list, show, snapshot, context)
432
+ // node inspect — read the graph (list, show, context)
404
433
  // ---------------------------------------------------------------------------
405
434
  const nodeInspect = defineBranch({
406
435
  name: 'inspect',
407
- description: 'read the graph (list, show, snapshot, context)',
408
- whenToUse: 'reading the graph: enumerate nodes on the canvas (`list`), inspect one and its spine neighbors (`show`), or pull a dormant session snapshot with `node inspect snapshot` / browse on-disk context dirs with `node inspect context` while reading a node. Use `canvas dashboard` instead to render the tree SHAPE, or `canvas attention` to find which nodes are blocked on a human',
436
+ description: 'read the graph (list, show, context)',
437
+ whenToUse: 'reading the graph: enumerate nodes on the canvas (`list`), inspect one and its spine neighbors (`show`), or browse on-disk context dirs with `node inspect context` while reading a node. Use `canvas dashboard` instead to render the tree SHAPE, or `canvas attention` to find which nodes are blocked on a human',
409
438
  help: {
410
439
  name: 'node inspect',
411
- summary: 'read the canvas graph — enumerate nodes, inspect one, or pull snapshot/context views for a node (including dormant session snapshots and on-disk context dirs)',
440
+ summary: 'read the canvas graph — enumerate nodes, inspect one, or browse on-disk context dirs for a node',
412
441
  },
413
442
  children: [nodeList, nodeShow, nodeSnapshotLeaf, nodeContextLeaf],
414
443
  });
@@ -627,7 +656,7 @@ const nodeConfig = defineLeaf({
627
656
  { kind: 'flag', name: 'pane', type: 'string', required: false, constraint: 'tmux pane id to resolve the node from. Defaults to $TMUX_PANE.' },
628
657
  { kind: 'flag', name: 'model', type: 'string', required: false, constraint: 'Model spec: exact provider/id, capability tier (ultra|strong|medium|light), family alias (opus|sonnet|haiku), or a free-text substring when the broker is live. Dormant nodes require a provider/id, tier, or alias.' },
629
658
  { kind: 'flag', name: 'lifecycle', type: 'enum', choices: ['terminal', 'resident'], required: false, constraint: 'Set lifecycle headlessly without closing the viewer. terminal owes a final and reaps when done; resident stays interactable and wakes on inbox/human.' },
630
- { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Persona kind. The <kinds> list below names every installable kind and when to use each.' },
659
+ { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Persona kind. The <kinds> list below names every top-level installable kind and when to use each; a sub-kind (e.g. plan/reviewers/security) is valid too by exact path but not listed here.' },
631
660
  { kind: 'flag', name: 'mode', type: 'enum', choices: ['base', 'orchestrator'], required: false, constraint: 'Set persona mode headlessly. base is hands-on; orchestrator holds a roadmap and delegates. orchestrator seeds a roadmap scaffold if absent.' },
632
661
  { kind: 'flag', name: 'name', type: 'string', required: false, constraint: 'Rename the node; if the node has a live window, also rename that viewer window.' },
633
662
  ],
@@ -962,10 +991,10 @@ function resolveMsgTarget(input) {
962
991
  const nodeMsg = defineLeaf({
963
992
  name: 'msg',
964
993
  description: 'message any node now, or arm a trigger that messages/revives it later',
965
- whenToUse: 'you want to address a specific node directly — steer it mid-flight, hand it a correction, ping it, pass it new information, or schedule any of that for later — regardless of subscriptions. Default delivers immediately and revives a dormant target; `--at`/`--every` arm a clock trigger instead of acting now; `--fresh` revives with no inbox entry; `--self --until` puts a deadline backstop on your current inbox-wait; `--when` arms a daemon-evaluated predicate for an external condition (CI, a deploy) you cannot otherwise be pushed for. Use `node subscribe` instead to wire ONGOING push delivery rather than a one-off, `push` to report UP your own spine, and `node new` to schedule the BIRTH of a node rather than message an existing one',
994
+ whenToUse: 'you want to address a specific node directly — steer it mid-flight, hand it a correction, ping it, pass it new information, or schedule any of that for later — regardless of subscriptions. Default delivers immediately and revives a dormant target; `--at`/`--every` arm a clock trigger instead of acting now; `--fresh` revives with no inbox entry; `--self --until` binds a deadline that changes your task policy if no push arrives by then; `--when` arms a daemon-evaluated predicate for an external condition (CI, a deploy) you cannot otherwise be pushed for. Use `node subscribe` instead to wire ONGOING push delivery rather than a one-off, `push` to report UP your own spine, and `node new` to schedule the BIRTH of a node rather than message an existing one',
966
995
  help: {
967
996
  name: 'node msg',
968
- summary: 'message a node now, or arm a trigger (clock or predicate) that messages/revives it later. No trigger flag → immediate inbox delivery (or immediate fresh revive with --fresh). --at/--every → clock trigger. --self --until → self-only deadline backstop on the current inbox-wait. --when → daemon-evaluated predicate, fires once on exit 0',
997
+ summary: 'message a node now, or arm a trigger (clock or predicate) that messages/revives it later. No trigger flag → immediate inbox delivery (or immediate fresh revive with --fresh). --at/--every → clock trigger. --self --until → self-only deadline: fires whichever comes first, an inbox message or this time, and that outcome is what should change your next action. --when → daemon-evaluated predicate, fires once on exit 0',
969
998
  params: [
970
999
  { kind: 'stdin', name: 'body', required: false, constraint: 'Message body. Positional or stdin. Required for a plain or clock-triggered message and for --until. Forbidden with --fresh. Optional with --when — if omitted, the predicate\'s captured stdout becomes the body when it fires.' },
971
1000
  { kind: 'flag', name: 'to', type: 'string', required: false, constraint: 'Target an existing node by id. Exactly one of --to/--self is required.' },
@@ -975,7 +1004,7 @@ const nodeMsg = defineLeaf({
975
1004
  { kind: 'flag', name: 'at', type: 'string', required: false, constraint: 'Arm a one-shot clock trigger at this future time instead of acting now — a duration ("5m","1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00", host-local or in --tz). Required unless --every is given (the cadence then sets the first fire). Mutually exclusive with --when.' },
976
1005
  { kind: 'flag', name: 'every', type: 'string', required: false, constraint: 'Arm a recurring clock trigger on this cadence — a duration ("6h","30m") or a 5-field cron / @alias ("0 9 * * *","@daily"). Combine with --at to anchor the first fire, then recur normally. Min cadence 60s. With --when, this instead sets the PREDICATE EVALUATION cadence (required), not a repeating action.' },
977
1006
  { kind: 'flag', name: 'when', type: 'string', required: false, constraint: 'Arm a daemon-evaluated bash predicate instead of a clock trigger: exit 0 fires the message once, non-zero leaves it armed for the next evaluation. Requires --every (eval cadence) and --timeout (wall-clock bound). Mutually exclusive with --at, --fresh, and --until.' },
978
- { kind: 'flag', name: 'until', type: 'string', required: false, constraint: 'Self-only: bind a deadline to your current dormancy — you wake on the first inbox message, or at this time if none arrives (whichever fires first cancels the other). One-shot, urgent tier, ≤ 1 per node (a new --until replaces any prior). Mutually exclusive with --at, --every, --when, --fresh, --tier, and --timeout. Requires --self and a body.' },
1007
+ { kind: 'flag', name: 'until', type: 'string', required: false, constraint: 'Self-only: bind a deadline that decides your next action — you wake on the first inbox message, or at this time if none arrives (whichever fires first cancels the other), and the body is the policy to follow in the no-message case. One-shot, urgent tier, ≤ 1 per node (a new --until replaces any prior). Mutually exclusive with --at, --every, --when, --fresh, --tier, and --timeout. Requires --self and a body.' },
979
1008
  { kind: 'flag', name: 'timeout', type: 'string', required: false, constraint: 'Wall-clock bound for a --when predicate — a duration or absolute time after which the trigger is consumed and the owner gets one urgent notice. Required with --when; rejected otherwise.' },
980
1009
  { kind: 'flag', name: 'tz', type: 'string', required: false, constraint: 'IANA zone for a calendar --every, a bare-ISO --at, or a bare-ISO --until (default: host-local). Valid only alongside --at/--every/--until; rejected with --when, and rejected when acting immediately (no trigger flags) since none of those have anything to parse it against.' },
981
1010
  ],
@@ -1225,8 +1254,9 @@ const nodeMsg = defineLeaf({
1225
1254
  }
1226
1255
  const tier = (hasTier ? tierRaw : 'normal');
1227
1256
  const from = process.env['CRTR_NODE_ID'] ?? 'human';
1228
- // A long body clips in the receiver's digest; appendInbox mints a short `id`
1229
- // for it so the receiver can read the full text back via `feed message <id>`.
1257
+ // A body over the preview bound spills to a ref file; appendInbox clips the
1258
+ // inline preview and the receiver dereferences the ref for the full text,
1259
+ // same as any other report.
1230
1260
  appendInbox(targetId, { from, tier, kind: 'message', label: body.split('\n')[0].slice(0, 120), data: { body: body } });
1231
1261
  // A direct message wakes any node: if the target has no live window
1232
1262
  // (done/dead/idle-released), revive it so its inbox-watcher delivers this.
@@ -1338,7 +1368,7 @@ const nodePromote = defineLeaf({
1338
1368
  name: 'node promote',
1339
1369
  summary: 'promote yourself to an orchestrator — do this when your task outgrows one context window (many phases to delegate and persist across refreshes); not for work that fits one window, and not merely because you spawned a child. Mode only — lifecycle stays as-is unless you pass --resident',
1340
1370
  params: [
1341
- { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Specialize as this kind of orchestrator. The <kinds> list below names every installable kind and when to use each. For an exact composed persona list, use `crtr sys prompt-review --list --kind <kind>` with the target kind. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one — it sets the orchestrator persona you revive into.' },
1371
+ { kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Specialize as this kind of orchestrator. The <kinds> list below names every top-level installable kind and when to use each; a sub-kind (e.g. plan/reviewers/security) is valid too by exact path but not listed here. For an exact composed persona list, use `crtr sys prompt-review --list --kind <kind>` with the target kind. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one — it sets the orchestrator persona you revive into.' },
1342
1372
  { kind: 'flag', name: 'resident', type: 'bool', required: false, constraint: 'ALSO flip lifecycle→resident: make the node interactable — it stays dormant, woken by inbox/human, and is never forced to submit a final. Omit to stay terminal/orchestrator (delegates + holds a roadmap, but still owes a final up the spine and reaps when done).' },
1343
1373
  { kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong'], required: false, constraint: 'Change the model you run on, by capability tier: `ultra` (frontier — reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus — regular dev work). Omit to keep your current model. An orchestrator steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives.' },
1344
1374
  { kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to promote. Defaults to the caller (CRTR_NODE_ID).' },
@@ -1510,7 +1540,7 @@ const nodeYield = defineLeaf({
1510
1540
  const nodeTriggersList = defineLeaf({
1511
1541
  name: 'list',
1512
1542
  description: 'list pending triggers for a scope (default self)',
1513
- whenToUse: 'before re-arming or finishing, to see what you already have armed and reap stale ones. Defaults to your own triggers, with a --node or --canvas scope for anything wider',
1543
+ whenToUse: 'seeing what triggers (clock or predicate) are currently armed your own, another node\'s, or the whole canvas. Defaults to your own triggers, with a --node or --canvas scope for anything wider',
1514
1544
  help: {
1515
1545
  name: 'node triggers list',
1516
1546
  summary: 'list pending triggers (clock and predicate, incl. deferred spawns) for a scope — id, owner, target, action/mode, type, next fire/eval, cadence, timeout, label, predicate diagnostics. Fired one-shots are gone; a recurrence shows its NEXT fire/eval, not past slots',
@@ -1602,7 +1632,7 @@ const nodeTriggersList = defineLeaf({
1602
1632
  const nodeTriggersCancel = defineLeaf({
1603
1633
  name: 'cancel',
1604
1634
  description: 'cancel a pending trigger by id (idempotent)',
1605
- whenToUse: 'a wait you no longer need — a poll whose goal is met, a deferred spawn you reconsidered, a deadline you are replacing. Idempotent: canceling an already-fired or already-canceled id is a no-op. Closing/reaping a node already reaps its own triggers and the detached ones it armed; reach for this to drop a single trigger, or to reap a detached spawn/cron a finished or crashed node left running',
1635
+ whenToUse: 'a wait you no longer need — a poll whose goal is met, a deferred spawn you reconsidered, a deadline you are replacing. Idempotent: canceling an already-fired or already-canceled id is a no-op.',
1606
1636
  help: {
1607
1637
  name: 'node triggers cancel',
1608
1638
  summary: 'cancel a pending trigger by id — it never fires/evaluates again and leaves the list. Idempotent (canceling an already-gone id is a no-op, not an error)',
@@ -0,0 +1 @@
1
+ export declare const addProjectLeaf: import("../../core/command.js").LeafDef;
@@ -0,0 +1,42 @@
1
+ import { defineLeaf } from '../../core/command.js';
2
+ import { addProfileProject, loadProfileManifest } from '../../core/profiles/manifest.js';
3
+ export const addProjectLeaf = defineLeaf({
4
+ name: 'add-project',
5
+ description: "add a project directory to a profile's purview",
6
+ whenToUse: "a profile needs to see (memory + config from) another project directory — extend its purview after creation, including mid-session from a node already running under that profile.",
7
+ help: {
8
+ name: 'profile add-project',
9
+ summary: "append a project directory to a profile's manifest, deduped by real path",
10
+ params: [
11
+ {
12
+ kind: 'positional',
13
+ name: 'profile',
14
+ required: true,
15
+ constraint: 'Exact profile id, or a unique manifest name.',
16
+ },
17
+ {
18
+ kind: 'flag',
19
+ name: 'dir',
20
+ type: 'path',
21
+ required: true,
22
+ constraint: 'Directory to add. Must exist and be a directory; resolved to its absolute real path before storing. A path already on the manifest is a no-op, not an error.',
23
+ },
24
+ ],
25
+ output: [
26
+ { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
27
+ { name: 'projects', type: 'string[]', required: true, constraint: 'The updated, deduped project list in manifest order.' },
28
+ { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
29
+ ],
30
+ outputKind: 'object',
31
+ effects: ["Appends to the profile manifest's `projects` array. Invalidates the process scope cache."],
32
+ },
33
+ run: async (input) => {
34
+ const { profileId } = loadProfileManifest(input['profile']);
35
+ const { manifest } = addProfileProject(profileId, input['dir']);
36
+ return {
37
+ profile_id: profileId,
38
+ projects: manifest.projects,
39
+ follow_up: `Show the full manifest with \`crtr profile show ${profileId}\`.`,
40
+ };
41
+ },
42
+ });
@@ -0,0 +1 @@
1
+ export declare const deleteLeaf: import("../../core/command.js").LeafDef;
@@ -0,0 +1,39 @@
1
+ import { defineLeaf } from '../../core/command.js';
2
+ import { deleteProfile, loadProfileManifest } from '../../core/profiles/manifest.js';
3
+ export const deleteLeaf = defineLeaf({
4
+ name: 'delete',
5
+ description: 'remove a profile entirely',
6
+ whenToUse: 'a profile is no longer needed — deletes its manifest AND its own memory store irreversibly. Does not touch the project directories it pointed at.',
7
+ help: {
8
+ name: 'profile delete',
9
+ summary: 'remove a profile directory (manifest + memory store) after validating it exists',
10
+ params: [
11
+ {
12
+ kind: 'positional',
13
+ name: 'profile',
14
+ required: true,
15
+ constraint: 'Exact profile id, or a unique manifest name.',
16
+ },
17
+ ],
18
+ output: [
19
+ { name: 'profile_id', type: 'string', required: true, constraint: 'The deleted profile\u2019s stable directory id.' },
20
+ { name: 'name', type: 'string', required: true, constraint: 'The deleted profile\u2019s manifest name.' },
21
+ { name: 'deleted', type: 'boolean', required: true, constraint: 'Always true on success.' },
22
+ { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
23
+ ],
24
+ outputKind: 'object',
25
+ effects: [
26
+ 'Recursively removes `<profiles-root>/<profile_id>/` — the manifest and its own memory store. Irreversible. Invalidates the process scope cache.',
27
+ ],
28
+ },
29
+ run: async (input) => {
30
+ const { profileId, manifest } = loadProfileManifest(input['profile']);
31
+ deleteProfile(profileId);
32
+ return {
33
+ profile_id: profileId,
34
+ name: manifest.name,
35
+ deleted: true,
36
+ follow_up: 'Run `crtr profile list` to see remaining profiles.',
37
+ };
38
+ },
39
+ });
@@ -0,0 +1 @@
1
+ export declare const listLeaf: import("../../core/command.js").LeafDef;
@@ -0,0 +1,35 @@
1
+ import { defineLeaf } from '../../core/command.js';
2
+ import { emitLine } from '../../core/io.js';
3
+ import { listProfiles } from '../../core/profiles/manifest.js';
4
+ export const listLeaf = defineLeaf({
5
+ name: 'list',
6
+ description: 'inventory every profile',
7
+ whenToUse: 'browse every profile that exists — one line each with its id, name, project count, and last-used time. Reach for `profile show` once you have picked one, for its full project list.',
8
+ help: {
9
+ name: 'profile list',
10
+ summary: 'every profile as JSONL, one line each, sorted by name then profile_id',
11
+ output: [
12
+ { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
13
+ { name: 'name', type: 'string', required: true, constraint: 'Manifest name.' },
14
+ { name: 'projects_count', type: 'int', required: true, constraint: 'Number of project directories on the manifest.' },
15
+ { name: 'last_used_at', type: 'string | null', required: true, constraint: 'ISO 8601 timestamp of the most recent selection, or null if never selected.' },
16
+ ],
17
+ outputKind: 'jsonl',
18
+ effects: ['None. Read-only.'],
19
+ },
20
+ run: async () => {
21
+ const all = listProfiles().sort((a, b) => {
22
+ const nc = a.manifest.name.localeCompare(b.manifest.name);
23
+ return nc !== 0 ? nc : a.profileId.localeCompare(b.profileId);
24
+ });
25
+ for (const { profileId, manifest } of all) {
26
+ emitLine({
27
+ profile_id: profileId,
28
+ name: manifest.name,
29
+ projects_count: manifest.projects.length,
30
+ last_used_at: manifest.last_used_at,
31
+ });
32
+ }
33
+ emitLine({ follow_up: 'Show one in full with `crtr profile show <profile>`.' });
34
+ },
35
+ });
@@ -0,0 +1 @@
1
+ export declare const newLeaf: import("../../core/command.js").LeafDef;
@@ -0,0 +1,50 @@
1
+ import { defineLeaf } from '../../core/command.js';
2
+ import { createProfile, profileRoot } from '../../core/profiles/manifest.js';
3
+ export const newLeaf = defineLeaf({
4
+ name: 'new',
5
+ description: 'create a new profile',
6
+ whenToUse: 'you are establishing a new agent identity — a stable id, a display name, and its own memory store. Pass at most one initial project directory here (the parser has no repeatable-flag support); use `profile add-project` afterward for every additional one.',
7
+ help: {
8
+ name: 'profile new',
9
+ summary: 'create a profile: `<profiles-root>/<slug>-<id>/` with a manifest + its own `memory/` store',
10
+ params: [
11
+ {
12
+ kind: 'flag',
13
+ name: 'name',
14
+ type: 'string',
15
+ required: true,
16
+ constraint: 'Display name for the profile. Also slugified into the stable profile id — the id never changes on later rename.',
17
+ },
18
+ {
19
+ kind: 'flag',
20
+ name: 'project',
21
+ type: 'path',
22
+ required: false,
23
+ constraint: 'One initial project directory. Must already exist and be a directory; resolved to its absolute real path before storing. Singular — add more with `crtr profile add-project` after creation.',
24
+ },
25
+ ],
26
+ output: [
27
+ { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id (`<slug>-<shortid>`). Pass this, or the name, to every other `profile` verb and to `--profile` elsewhere.' },
28
+ { name: 'name', type: 'string', required: true, constraint: 'The manifest name as given.' },
29
+ { name: 'path', type: 'string', required: true, constraint: 'Absolute path to the profile directory.' },
30
+ { name: 'projects', type: 'string[]', required: true, constraint: 'Resolved absolute project directories, in manifest order. Empty when --project was omitted.' },
31
+ { name: 'created_at', type: 'string', required: true, constraint: 'ISO 8601 creation timestamp.' },
32
+ { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
33
+ ],
34
+ outputKind: 'object',
35
+ effects: ['Creates `<profile-dir>/profile.json` and `<profile-dir>/memory/`.'],
36
+ },
37
+ run: async (input) => {
38
+ const name = input['name'];
39
+ const project = input['project'];
40
+ const { profileId, manifest } = createProfile(name, project !== undefined ? [project] : []);
41
+ return {
42
+ profile_id: profileId,
43
+ name: manifest.name,
44
+ path: profileRoot(profileId),
45
+ projects: manifest.projects,
46
+ created_at: manifest.created_at,
47
+ follow_up: `Show it with \`crtr profile show ${profileId}\`, add another project with \`crtr profile add-project ${profileId} --dir <path>\`, or list every profile with \`crtr profile list\`.`,
48
+ };
49
+ },
50
+ });
@@ -0,0 +1 @@
1
+ export declare const removeProjectLeaf: import("../../core/command.js").LeafDef;
@@ -0,0 +1,42 @@
1
+ import { defineLeaf } from '../../core/command.js';
2
+ import { loadProfileManifest, removeProfileProject } from '../../core/profiles/manifest.js';
3
+ export const removeProjectLeaf = defineLeaf({
4
+ name: 'remove-project',
5
+ description: "remove a project directory from a profile's purview",
6
+ whenToUse: "a profile no longer needs purview over one of its listed project directories — narrow it back. Works even if the directory itself was since deleted.",
7
+ help: {
8
+ name: 'profile remove-project',
9
+ summary: "remove a project directory from a profile's manifest",
10
+ params: [
11
+ {
12
+ kind: 'positional',
13
+ name: 'profile',
14
+ required: true,
15
+ constraint: 'Exact profile id, or a unique manifest name.',
16
+ },
17
+ {
18
+ kind: 'flag',
19
+ name: 'dir',
20
+ type: 'path',
21
+ required: true,
22
+ constraint: 'Directory to remove, matched by its resolved real path against the manifest. Does not require the directory to still exist on disk.',
23
+ },
24
+ ],
25
+ output: [
26
+ { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
27
+ { name: 'projects', type: 'string[]', required: true, constraint: 'The updated project list in manifest order.' },
28
+ { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
29
+ ],
30
+ outputKind: 'object',
31
+ effects: ["Removes an entry from the profile manifest's `projects` array. Invalidates the process scope cache."],
32
+ },
33
+ run: async (input) => {
34
+ const { profileId } = loadProfileManifest(input['profile']);
35
+ const { manifest } = removeProfileProject(profileId, input['dir']);
36
+ return {
37
+ profile_id: profileId,
38
+ projects: manifest.projects,
39
+ follow_up: `Show the full manifest with \`crtr profile show ${profileId}\`.`,
40
+ };
41
+ },
42
+ });
@@ -0,0 +1 @@
1
+ export declare const renameLeaf: import("../../core/command.js").LeafDef;
@@ -0,0 +1,42 @@
1
+ import { defineLeaf } from '../../core/command.js';
2
+ import { loadProfileManifest, renameProfile } from '../../core/profiles/manifest.js';
3
+ export const renameLeaf = defineLeaf({
4
+ name: 'rename',
5
+ description: "change a profile's display name",
6
+ whenToUse: "you want to change a profile's display name — the gateable `profile` subject value and the name shown in bearings/listings. The profile id (its directory) never changes.",
7
+ help: {
8
+ name: 'profile rename',
9
+ summary: "change a profile's manifest `name` only — the profile_id is stable and never changes",
10
+ params: [
11
+ {
12
+ kind: 'positional',
13
+ name: 'profile',
14
+ required: true,
15
+ constraint: 'Exact profile id, or a unique manifest name (the CURRENT name, before this rename).',
16
+ },
17
+ {
18
+ kind: 'flag',
19
+ name: 'name',
20
+ type: 'string',
21
+ required: true,
22
+ constraint: 'New display name.',
23
+ },
24
+ ],
25
+ output: [
26
+ { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id — unchanged by this leaf.' },
27
+ { name: 'name', type: 'string', required: true, constraint: 'The new manifest name.' },
28
+ { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
29
+ ],
30
+ outputKind: 'object',
31
+ effects: ["Changes the profile manifest's `name` field. Invalidates the process scope cache."],
32
+ },
33
+ run: async (input) => {
34
+ const { profileId } = loadProfileManifest(input['profile']);
35
+ const { manifest } = renameProfile(profileId, input['name']);
36
+ return {
37
+ profile_id: profileId,
38
+ name: manifest.name,
39
+ follow_up: `Show the full manifest with \`crtr profile show ${profileId}\`.`,
40
+ };
41
+ },
42
+ });
@@ -0,0 +1 @@
1
+ export declare const showLeaf: import("../../core/command.js").LeafDef;
@@ -0,0 +1,50 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { defineLeaf } from '../../core/command.js';
4
+ import { loadProfileManifest, profileRoot } from '../../core/profiles/manifest.js';
5
+ import { CONFIG_FILE } from '../../types.js';
6
+ export const showLeaf = defineLeaf({
7
+ name: 'show',
8
+ description: "one profile's manifest in full",
9
+ whenToUse: "you need a profile's full project list, id, and timestamps — after `profile list` narrows to a name, or to inspect what a node's bearings named as its profile.",
10
+ help: {
11
+ name: 'profile show',
12
+ summary: "print one profile's manifest plus its on-disk path and config.json location",
13
+ params: [
14
+ {
15
+ kind: 'positional',
16
+ name: 'profile',
17
+ required: true,
18
+ constraint: 'Exact profile id, or a unique manifest name. An ambiguous name fails listing every matching id.',
19
+ },
20
+ ],
21
+ output: [
22
+ { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
23
+ { name: 'name', type: 'string', required: true, constraint: 'Manifest name.' },
24
+ { name: 'projects', type: 'string[]', required: true, constraint: 'Absolute project directories, in manifest order.' },
25
+ { name: 'created_at', type: 'string', required: true, constraint: 'ISO 8601 creation timestamp.' },
26
+ { name: 'last_used_at', type: 'string | null', required: true, constraint: 'ISO 8601 timestamp of the most recent selection, or null if never selected.' },
27
+ { name: 'path', type: 'string', required: true, constraint: 'Absolute path to the profile directory.' },
28
+ { name: 'config_path', type: 'string', required: true, constraint: 'Absolute path to the profile\'s config.json (hand-edited the same way as user/project config.json — no profile-specific editor verb exists).' },
29
+ { name: 'config_exists', type: 'boolean', required: true, constraint: 'Whether config.json exists at that path yet.' },
30
+ { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
31
+ ],
32
+ outputKind: 'object',
33
+ effects: ['None. Read-only.'],
34
+ },
35
+ run: async (input) => {
36
+ const { profileId, manifest } = loadProfileManifest(input['profile']);
37
+ const configPath = join(profileRoot(profileId), CONFIG_FILE);
38
+ return {
39
+ profile_id: profileId,
40
+ name: manifest.name,
41
+ projects: manifest.projects,
42
+ created_at: manifest.created_at,
43
+ last_used_at: manifest.last_used_at,
44
+ path: profileRoot(profileId),
45
+ config_path: configPath,
46
+ config_exists: existsSync(configPath),
47
+ follow_up: `Add a project with \`crtr profile add-project ${profileId} --dir <path>\`, or rename with \`crtr profile rename ${profileId} --name <name>\`.`,
48
+ };
49
+ },
50
+ });
@@ -0,0 +1,2 @@
1
+ import type { BranchDef } from '../core/command.js';
2
+ export declare function registerProfile(): BranchDef;
@@ -0,0 +1,33 @@
1
+ // `crtr profile` subtree — the profile store (spec §2.1/§2.5): agent
2
+ // identities defined at the user root, each owning a manifest naming project
3
+ // directories plus its own memory store. Flat leaves; no sub-branches — 7
4
+ // verbs fits the 5-7 hard cap.
5
+ //
6
+ // Every verb but `new`/`list` takes <profile> as an exact id OR a unique
7
+ // manifest name (never two positionals: `add-project`/`remove-project` pair
8
+ // <profile> with `--dir`, `rename` pairs it with `--name`, matching the
9
+ // one-positional-per-leaf argv contract used across the whole tree).
10
+ import { defineBranch } from '../core/command.js';
11
+ import { newLeaf } from './profile/new.js';
12
+ import { listLeaf } from './profile/list.js';
13
+ import { showLeaf } from './profile/show.js';
14
+ import { addProjectLeaf } from './profile/add-project.js';
15
+ import { removeProjectLeaf } from './profile/remove-project.js';
16
+ import { renameLeaf } from './profile/rename.js';
17
+ import { deleteLeaf } from './profile/delete.js';
18
+ export function registerProfile() {
19
+ return defineBranch({
20
+ name: 'profile',
21
+ rootEntry: {
22
+ concept: 'an agent identity defined at the user root — a manifest naming project directories plus its own memory store',
23
+ desc: 'create, inspect, and manage profiles',
24
+ useWhen: 'creating a new agent identity, inspecting or listing existing ones, or changing a profile\u2019s project purview or display name. Selecting a profile for a running node (`--profile`, startup selection) lives elsewhere; this family only manages the manifest.',
25
+ },
26
+ help: {
27
+ name: 'profile',
28
+ summary: 'create, inspect, and manage profiles \u2014 agent identities with their own project purview and memory store',
29
+ model: '`new` creates one from a name (+ at most one initial `--project`). `list` inventories every profile as JSONL. `show` prints one manifest in full. `add-project` / `remove-project` mutate the ordered project list after creation, including mid-session on an already-running profile. `rename` changes only the manifest name \u2014 the profile_id is stable across a rename. `delete` removes a profile\u2019s directory (manifest + its own memory store) entirely and does not touch the project directories it pointed at. Every verb but `new`/`list` takes <profile> as an exact id or a unique manifest name; an ambiguous name fails listing every matching id.',
30
+ },
31
+ children: [newLeaf, listLeaf, showLeaf, addProjectLeaf, removeProjectLeaf, renameLeaf, deleteLeaf],
32
+ });
33
+ }
@@ -2,4 +2,3 @@ import type { BranchDef } from '../core/command.js';
2
2
  import { InputError } from '../core/io.js';
3
3
  export declare function mapFinalizeTransitionRaceError(nodeStatus: string | null | undefined, err: unknown): InputError | null;
4
4
  export declare function registerPush(): BranchDef;
5
- export declare function registerFeed(): BranchDef;