@north-light/crouter 0.3.231 → 0.3.232

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 (173) hide show
  1. package/dist/api/client.d.ts +3 -1
  2. package/dist/api/client.js +4 -0
  3. package/dist/api/dto/canvas.d.ts +10 -0
  4. package/dist/api/dto/common.d.ts +1 -1
  5. package/dist/api/dto/health.d.ts +2 -1
  6. package/dist/api/dto/lifecycle.d.ts +3 -4
  7. package/dist/api/dto/messages.d.ts +5 -4
  8. package/dist/api/dto/nodes.d.ts +2 -0
  9. package/dist/api/dto/profiles.d.ts +5 -0
  10. package/dist/api/routes.d.ts +1 -0
  11. package/dist/api/routes.js +1 -0
  12. package/dist/builtin-memory/00-runtime-base/00-authoring.md +8 -0
  13. package/dist/builtin-memory/00-runtime-base/01-escalation.md +1 -1
  14. package/dist/builtin-memory/01-spine/00-has-manager.md +1 -1
  15. package/dist/builtin-memory/02-turn-lifecycle/00-ending-a-turn.md +5 -0
  16. package/dist/builtin-memory/02-turn-lifecycle/02-resident.md +5 -3
  17. package/dist/builtin-memory/04-orchestration-kernel.md +2 -2
  18. package/dist/builtin-memory/insights/capture.md +3 -2
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +6 -1
  20. package/dist/clients/attach/render/diagram.js +13 -5
  21. package/dist/clients/attach/render/page-block.d.ts +0 -1
  22. package/dist/clients/attach/render/page-block.js +4 -57
  23. package/dist/clients/attach/viewer.js +570 -563
  24. package/dist/clients/inbox/__tests__/integration/inbox-controller.test.js +9 -0
  25. package/dist/clients/inbox/__tests__/integration/mount-panel.test.js +62 -1
  26. package/dist/clients/inbox/controller.d.ts +10 -0
  27. package/dist/clients/inbox/controller.js +56 -12
  28. package/dist/clients/inbox/tui/input.js +38 -10
  29. package/dist/clients/inbox/tui/page-body.d.ts +10 -0
  30. package/dist/clients/inbox/tui/page-body.js +66 -0
  31. package/dist/clients/inbox/tui/panel.js +6 -4
  32. package/dist/clients/inbox/tui/render.js +89 -17
  33. package/dist/clients/inbox/tui/types.d.ts +4 -4
  34. package/dist/commands/__tests__/human.test.js +18 -3
  35. package/dist/commands/__tests__/node-message.test.js +3 -3
  36. package/dist/commands/api-client.js +1 -7
  37. package/dist/commands/canvas-config.js +6 -14
  38. package/dist/commands/canvas-use.js +4 -6
  39. package/dist/commands/cron.js +16 -22
  40. package/dist/commands/human/prompts.d.ts +1 -1
  41. package/dist/commands/human/prompts.js +118 -112
  42. package/dist/commands/human/request.js +13 -14
  43. package/dist/commands/human/review.js +3 -4
  44. package/dist/commands/human/shared.d.ts +6 -0
  45. package/dist/commands/human/shared.js +43 -5
  46. package/dist/commands/human.js +1 -1
  47. package/dist/commands/memory/delete.js +4 -6
  48. package/dist/commands/memory/edit.js +0 -4
  49. package/dist/commands/memory/move.js +3 -5
  50. package/dist/commands/memory/shared.d.ts +1 -1
  51. package/dist/commands/memory/shared.js +9 -5
  52. package/dist/commands/memory/write.js +60 -29
  53. package/dist/commands/memory.js +1 -1
  54. package/dist/commands/node/bash.js +6 -9
  55. package/dist/commands/node/create.js +89 -22
  56. package/dist/commands/node/inspect.js +3 -3
  57. package/dist/commands/node/lifecycle.js +30 -27
  58. package/dist/commands/node/message.js +24 -45
  59. package/dist/commands/node/subscription.js +6 -15
  60. package/dist/commands/node/wait.js +2 -3
  61. package/dist/commands/node-lifecycle-revive.js +1 -12
  62. package/dist/commands/pkg/browse/actions.js +2 -3
  63. package/dist/commands/pkg/market-manage.js +2 -5
  64. package/dist/commands/pkg/plugin-manage.js +7 -8
  65. package/dist/commands/profile/default.js +5 -5
  66. package/dist/commands/profile/delete.js +1 -1
  67. package/dist/commands/profile/env.js +9 -15
  68. package/dist/commands/profile/kind.js +3 -7
  69. package/dist/commands/profile/meta.js +3 -5
  70. package/dist/commands/profile/new.js +0 -6
  71. package/dist/commands/profile/pause.js +4 -8
  72. package/dist/commands/profile/project.js +5 -9
  73. package/dist/commands/profile/rename.js +3 -7
  74. package/dist/commands/profile/show.js +3 -3
  75. package/dist/commands/profile.js +4 -3
  76. package/dist/commands/surface-tmux-spread.js +1 -3
  77. package/dist/commands/sys/config.js +3 -4
  78. package/dist/commands/sys/support/prepare.js +8 -4
  79. package/dist/commands/sys/support/submit.js +2 -3
  80. package/dist/commands/sys/sync-deps.js +1 -9
  81. package/dist/commands/sys/sync-project-guidance.js +1 -7
  82. package/dist/commands/sys/sync-skills.js +1 -11
  83. package/dist/core/__tests__/cron-node-sink-parked-root.test.d.ts +1 -0
  84. package/dist/core/__tests__/cron-node-sink-parked-root.test.js +147 -0
  85. package/dist/core/__tests__/history-inbox.test.js +11 -1
  86. package/dist/core/__tests__/human-deliver.test.js +2 -1
  87. package/dist/core/__tests__/integration/command-plugins.test.js +0 -1
  88. package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -1
  89. package/dist/core/__tests__/lifecycle.test.js +30 -2
  90. package/dist/core/__tests__/revive-parked-fresh.test.d.ts +1 -0
  91. package/dist/core/__tests__/revive-parked-fresh.test.js +109 -0
  92. package/dist/core/__tests__/seam/dormancy-release.test.js +32 -5
  93. package/dist/core/canvas/attention.d.ts +2 -0
  94. package/dist/core/canvas/attention.js +25 -18
  95. package/dist/core/canvas/extensions.d.ts +1 -1
  96. package/dist/core/canvas/extensions.js +7 -1
  97. package/dist/core/canvas/history.js +20 -2
  98. package/dist/core/canvas/types.d.ts +1 -1
  99. package/dist/core/command.js +33 -8
  100. package/dist/core/help.d.ts +28 -2
  101. package/dist/core/help.js +46 -10
  102. package/dist/core/human/__tests__/page-html-markdown.test.d.ts +1 -0
  103. package/dist/core/human/__tests__/page-html-markdown.test.js +48 -0
  104. package/dist/core/human/component-docs.js +4 -4
  105. package/dist/core/human/page-html-markdown.d.ts +8 -0
  106. package/dist/core/human/page-html-markdown.js +260 -0
  107. package/dist/core/memory/lint.d.ts +15 -0
  108. package/dist/core/memory/lint.js +150 -90
  109. package/dist/core/profiles/__tests__/fuzzy-match.test.d.ts +1 -0
  110. package/dist/core/profiles/__tests__/fuzzy-match.test.js +51 -0
  111. package/dist/core/profiles/fuzzy-match.d.ts +19 -0
  112. package/dist/core/profiles/fuzzy-match.js +92 -0
  113. package/dist/core/profiles/manifest.d.ts +14 -7
  114. package/dist/core/profiles/manifest.js +62 -12
  115. package/dist/core/profiles/select.d.ts +3 -1
  116. package/dist/core/profiles/select.js +5 -3
  117. package/dist/core/profiles/state-block.js +4 -3
  118. package/dist/core/runtime/boot-root.d.ts +3 -2
  119. package/dist/core/runtime/canvas-extensions.d.ts +7 -1
  120. package/dist/core/runtime/canvas-extensions.js +8 -1
  121. package/dist/core/runtime/lifecycle.d.ts +11 -2
  122. package/dist/core/runtime/lifecycle.js +15 -2
  123. package/dist/core/runtime/model-selection.d.ts +4 -0
  124. package/dist/core/runtime/model-selection.js +5 -0
  125. package/dist/core/runtime/nodes.js +5 -0
  126. package/dist/core/runtime/reopen.d.ts +6 -0
  127. package/dist/core/runtime/reopen.js +12 -1
  128. package/dist/core/runtime/revive.d.ts +6 -0
  129. package/dist/core/runtime/revive.js +22 -2
  130. package/dist/core/runtime/spawn.d.ts +5 -2
  131. package/dist/core/runtime/spawn.js +18 -32
  132. package/dist/core/runtime/structured-output.d.ts +6 -0
  133. package/dist/core/runtime/structured-output.js +6 -0
  134. package/dist/core/substrate/__tests__/surface-match-command.test.d.ts +1 -0
  135. package/dist/core/substrate/__tests__/surface-match-command.test.js +89 -0
  136. package/dist/core/substrate/on-read.js +2 -1
  137. package/dist/core/substrate/surface-match.js +164 -12
  138. package/dist/core/termrender/version.d.ts +1 -1
  139. package/dist/core/termrender/version.js +1 -1
  140. package/dist/core/user-settings.js +1 -1
  141. package/dist/daemon/api/__tests__/broker-settle-park.test.d.ts +1 -0
  142. package/dist/daemon/api/__tests__/broker-settle-park.test.js +102 -0
  143. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.d.ts +1 -0
  144. package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +188 -0
  145. package/dist/daemon/api/__tests__/node-create-description.test.d.ts +1 -0
  146. package/dist/daemon/api/__tests__/node-create-description.test.js +83 -0
  147. package/dist/daemon/api/__tests__/profile-metadata-route.test.d.ts +1 -0
  148. package/dist/daemon/api/__tests__/profile-metadata-route.test.js +92 -0
  149. package/dist/daemon/api/__tests__/reopen-delivery.test.d.ts +1 -0
  150. package/dist/daemon/api/__tests__/reopen-delivery.test.js +173 -0
  151. package/dist/daemon/api/handlers/broker-ops.js +21 -0
  152. package/dist/daemon/api/handlers/canvas.js +10 -0
  153. package/dist/daemon/api/handlers/messages.js +25 -16
  154. package/dist/daemon/api/handlers/nodes.js +5 -0
  155. package/dist/daemon/api/handlers/profiles.js +22 -1
  156. package/dist/daemon/cron-run.js +19 -1
  157. package/dist/daemon/manage.d.ts +16 -1
  158. package/dist/daemon/manage.js +20 -1
  159. package/dist/daemon/park-pending.d.ts +13 -0
  160. package/dist/daemon/park-pending.js +42 -0
  161. package/dist/daemon/reconcilers/broker-supervision.d.ts +13 -0
  162. package/dist/daemon/reconcilers/broker-supervision.js +139 -21
  163. package/dist/daemon/reconcilers/live-obligation.d.ts +10 -4
  164. package/dist/daemon/reconcilers/live-obligation.js +7 -3
  165. package/dist/daemon/reconcilers/storage-maintenance.d.ts +0 -4
  166. package/dist/daemon/reconcilers/storage-maintenance.js +1 -33
  167. package/dist/pi-extensions/canvas-prompt-scrub.d.ts +13 -0
  168. package/dist/pi-extensions/canvas-prompt-scrub.js +53 -0
  169. package/dist/shared/generated-context.d.ts +7 -0
  170. package/dist/shared/generated-context.js +11 -0
  171. package/package.json +4 -4
  172. package/runtime.lock.json +2 -2
  173. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/strip-skills-docs.ts +0 -47
@@ -124,7 +124,6 @@ const nodeWait = defineLeaf({
124
124
  ],
125
125
  output: [
126
126
  { name: 'node_id', type: 'string', required: true, constraint: 'The terminal node that declared the wait.' },
127
- { name: 'controller', type: 'string', required: true, constraint: 'The named live sender whose next inbox delivery clears this wait.' },
128
127
  ],
129
128
  outputKind: 'object',
130
129
  effects: ['Durably records the controller on this node. The stop guard permits dormancy only while that controller remains active or idle; its next inbox delivery clears the record before the delivery wakes this node.'],
@@ -146,9 +145,9 @@ const nodeWait = defineLeaf({
146
145
  await cliClient()
147
146
  .waitNode(nodeId, { controller })
148
147
  .catch((err) => rethrowAsCliError(err));
149
- return { node_id: nodeId, controller };
148
+ return { node_id: nodeId };
150
149
  },
151
- render: (r) => `Waiting for controller ${r['controller']}. End your turn to dorm; its next message wakes you and clears this wait.`,
150
+ render: () => 'Waiting for the controller. End your turn to dorm; its next message wakes you and clears this wait.',
152
151
  });
153
152
  export const nodeWaitBranch = defineBranch({
154
153
  name: 'wait',
@@ -101,11 +101,10 @@ export const nodeReviveLeaf = defineLeaf({
101
101
  },
102
102
  ],
103
103
  output: [
104
- { name: 'window', type: 'string', required: false, constraint: 'Always null — the revived broker is headless and opens no tmux window. Kept for caller back-compat.' },
105
- { name: 'session', type: 'string', required: false, constraint: 'The node\'s last live location session, or null — the headless broker has no tmux session of its own.' },
106
104
  { name: 'resumed', type: 'boolean', required: false, constraint: 'True ONLY for a true resume (the existing `.jsonl` replayed as-is). False for a --fresh revive, whether that landed as an in-place CYCLE (an existing `.jsonl` reused with a fresh `crtr-cycle` branch — pi is still told --session, but this is not a resume) or a genuinely fresh launch (no `.jsonl` existed). Absent for a --now kick.' },
107
105
  { name: 'ready', type: 'boolean', required: false, constraint: 'True when the revived broker\'s view.sock accepted a connection before return — the node is immediately attachable/drivable. Absent for a --now kick.' },
108
106
  { name: 'kicked', type: 'boolean', required: false, constraint: 'True when --now SIGTERM\'d the hanging node\'s live broker (the daemon resumes it on the saved session within ~20s). Absent for an ordinary revive.' },
107
+ { name: 'message', type: 'string', required: false, constraint: 'Kick confirmation naming the signalled node, pid, and the daemon\'s ~20s resume handoff. Present only for --now.' },
109
108
  ],
110
109
  outputKind: 'object',
111
110
  effects: [
@@ -192,10 +191,6 @@ export const nodeReviveLeaf = defineLeaf({
192
191
  /* best-effort: the broker may have just died; the daemon owns the resume */
193
192
  }
194
193
  return {
195
- window: null,
196
- // The DTO carries no `tmux_session` (the headless broker has none); the
197
- // pre-API leaf returned `meta.tmux_session ?? null`, which is null here.
198
- session: null,
199
194
  kicked: true,
200
195
  message: `Sent SIGTERM to ${fullName(detail)} (${nodeId}, pid ${pid}). The daemon will resume it on the saved session within ~20s (crash→grace→resume).`,
201
196
  };
@@ -227,12 +222,6 @@ export const nodeReviveLeaf = defineLeaf({
227
222
  // return need this leaf to wait until the socket accepts.
228
223
  const ready = await waitForViewSocketReady(nodeId);
229
224
  return {
230
- // Always null (broker headless, no tmux window); `undefined` omits it from
231
- // the JSON payload, matching the pre-API leaf's `result.window ?? undefined`.
232
- window: undefined,
233
- // The revive DTO carries no location session (the headless broker has none);
234
- // the pre-API leaf returned `result.session`, which was null here.
235
- session: null,
236
225
  resumed: result.resumed,
237
226
  ready,
238
227
  };
@@ -186,9 +186,8 @@ export async function runAction(request) {
186
186
  return missing("Choose a marketplace to remove.");
187
187
  if (request.scope === undefined)
188
188
  return missing("Choose a marketplace scope to remove.");
189
- const result = asRecord(await marketRemove.run({ name: request.marketplace, scope: request.scope }));
190
- const name = typeof result["name"] === "string" ? result["name"] : request.marketplace;
191
- return { ok: true, message: `Removed marketplace ${name}.` };
189
+ await marketRemove.run({ name: request.marketplace, scope: request.scope });
190
+ return { ok: true, message: `Removed marketplace ${request.marketplace}.` };
192
191
  }
193
192
  }
194
193
  return missing("Unsupported action.");
@@ -86,10 +86,7 @@ export const marketRemove = defineLeaf({
86
86
  { kind: 'positional', name: 'name', type: 'string', required: true, constraint: 'Marketplace name to remove.' },
87
87
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Narrows resolution.' },
88
88
  ],
89
- output: [
90
- { name: 'name', type: 'string', required: true, constraint: 'Removed marketplace name.' },
91
- { name: 'removed', type: 'boolean', required: true, constraint: 'Always true on success.' },
92
- ],
89
+ output: [],
93
90
  outputKind: 'object',
94
91
  effects: ['Deletes the marketplace directory. Removes the entry from config.json. Removes plugin files and config entries sourced from that marketplace in the same scope.'],
95
92
  },
@@ -144,7 +141,7 @@ export const marketRemove = defineLeaf({
144
141
  delete c.plugins[pluginName];
145
142
  }
146
143
  });
147
- return { name, removed: true };
144
+ return {};
148
145
  },
149
146
  });
150
147
  /** Stage a fresh checkout beside the active root. A clone that fails throws
@@ -793,7 +793,6 @@ export const pluginRemove = defineLeaf({
793
793
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Default: searches all scopes.' },
794
794
  ],
795
795
  output: [
796
- { name: 'removed', type: 'boolean', required: true, constraint: 'True if removed from at least one scope.' },
797
796
  { name: 'scopes', type: 'string[]', required: true, constraint: 'Scopes the plugin was removed from.' },
798
797
  ],
799
798
  outputKind: 'object',
@@ -832,7 +831,7 @@ export const pluginRemove = defineLeaf({
832
831
  if (removedFrom.length === 0) {
833
832
  throw notFound(`plugin not found: ${name}`);
834
833
  }
835
- return { removed: true, scopes: removedFrom };
834
+ return { scopes: removedFrom };
836
835
  },
837
836
  });
838
837
  export const pluginEnable = defineLeaf({
@@ -847,9 +846,7 @@ export const pluginEnable = defineLeaf({
847
846
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Default: scope where the plugin is installed.' },
848
847
  ],
849
848
  output: [
850
- { name: 'name', type: 'string', required: true, constraint: 'Plugin name.' },
851
849
  { name: 'scope', type: 'string', required: true, constraint: 'Scope the change was applied in.' },
852
- { name: 'enabled', type: 'boolean', required: true, constraint: 'Always true.' },
853
850
  { name: 'memory', type: 'object', required: false, constraint: 'Present only when the plugin ships a memory store. {docs: integer (documents checked), migrated: string[] (store-relative documents rewritten to reach exact canonical identity), linkedSource?: string (the caller-owned directory converged in place, present only for a symlinked install)}.' },
854
851
  ],
855
852
  outputKind: 'object',
@@ -868,7 +865,8 @@ export const pluginEnable = defineLeaf({
868
865
  throw notFound(`plugin manifest not found at ${target.root}/.crouter-plugin/plugin.json`);
869
866
  const scopeRootPath = target.scope === 'project' ? requireScopeRoot('project') : userScopeRoot();
870
867
  const memory = await assertPluginCandidateValid(manifest, target.root, target.scope, scopeRootPath, `${target.root}/.crouter-plugin/plugin.json`, undefined, isSymlink(target.root) ? 'linked' : 'package');
871
- return { ...(await setPluginEnabled(input, true)), ...(memory !== undefined ? { memory } : {}) };
868
+ const result = await setPluginEnabled(input, true);
869
+ return { scope: result['scope'], ...(memory !== undefined ? { memory } : {}) };
872
870
  },
873
871
  });
874
872
  export const pluginDisable = defineLeaf({
@@ -883,14 +881,15 @@ export const pluginDisable = defineLeaf({
883
881
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Default: scope where the plugin is installed.' },
884
882
  ],
885
883
  output: [
886
- { name: 'name', type: 'string', required: true, constraint: 'Plugin name.' },
887
884
  { name: 'scope', type: 'string', required: true, constraint: 'Scope the change was applied in.' },
888
- { name: 'enabled', type: 'boolean', required: true, constraint: 'Always false.' },
889
885
  ],
890
886
  outputKind: 'object',
891
887
  effects: ['Sets plugin enabled=false in config.json.'],
892
888
  },
893
- run: async (input) => setPluginEnabled(input, false),
889
+ run: async (input) => {
890
+ const result = await setPluginEnabled(input, false);
891
+ return { scope: result['scope'] };
892
+ },
894
893
  });
895
894
  export const pluginUpdate = defineLeaf({
896
895
  name: 'update',
@@ -1,7 +1,7 @@
1
1
  // `crtr profile default` manages the per-cwd profile-selection pin.
2
2
  import { defineLeaf, defineBranch } from '../../core/command.js';
3
3
  import { InputError } from '../../core/io.js';
4
- import { loadProfileManifest } from '../../core/profiles/manifest.js';
4
+ import { loadProfileManifest, resolveProfileOperand } from '../../core/profiles/manifest.js';
5
5
  import { profileCoversCwd } from '../../core/profiles/select.js';
6
6
  import { getDefaultProfileId, setDefaultProfileId, clearDefaultProfile, resolveBindingCwd, } from '../../core/profiles/default-binding.js';
7
7
  const setLeaf = defineLeaf({
@@ -16,14 +16,14 @@ const setLeaf = defineLeaf({
16
16
  kind: 'positional',
17
17
  name: 'profile',
18
18
  required: true,
19
- constraint: 'Exact profile id, or a unique manifest name. Must already cover the current directory (be at/above/below it in its project purview); this pin does not change automatic memory disclosure.',
19
+ constraint: 'Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr. Must already cover the current directory (be at/above/below it in its project purview); this pin does not change automatic memory disclosure.',
20
20
  },
21
21
  ],
22
22
  output: [
23
23
  { name: 'dir', type: 'string', required: true, constraint: "The resolved directory the pin was set for." },
24
24
  { name: 'profile_id', type: 'string', required: true, constraint: 'The pinned profile id.' },
25
25
  { name: 'name', type: 'string', required: true, constraint: 'The pinned profile name.' },
26
- { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
26
+ { name: 'follow_up', type: 'string', required: true, constraint: 'The resulting ambient effect.' },
27
27
  ],
28
28
  outputKind: 'object',
29
29
  effects: ['Writes this directory\'s pin into `~/.crouter/profile-defaults.json`. The next launch here auto-picks this profile with no prompt (override once with `crtr --pick-profile`).'],
@@ -31,7 +31,7 @@ const setLeaf = defineLeaf({
31
31
  run: async (_input) => {
32
32
  const cwd = process.cwd();
33
33
  const resolved = resolveBindingCwd(cwd);
34
- const entry = loadProfileManifest(_input['profile']);
34
+ const entry = resolveProfileOperand(_input['profile']);
35
35
  if (!profileCoversCwd(entry, resolved)) {
36
36
  throw new InputError({
37
37
  error: 'not_covered',
@@ -44,7 +44,7 @@ const setLeaf = defineLeaf({
44
44
  dir: resolved,
45
45
  profile_id: entry.profileId,
46
46
  name: entry.manifest.name,
47
- follow_up: `Launches here now auto-pick "${entry.manifest.name}". Clear with \`crtr profile default clear\`, or reopen the chooser once with \`crtr --pick-profile\`.`,
47
+ follow_up: `Launches here now auto-pick "${entry.manifest.name}".`,
48
48
  };
49
49
  },
50
50
  });
@@ -12,7 +12,7 @@ export const deleteLeaf = defineLeaf({
12
12
  kind: 'positional',
13
13
  name: 'profile',
14
14
  required: true,
15
- constraint: 'Exact profile id, or a unique manifest name.',
15
+ constraint: 'Exact profile id, or a unique manifest name. Deletion never guesses at a near miss — unlike every other profile verb, an unrecognized value fails.',
16
16
  },
17
17
  {
18
18
  kind: 'flag',
@@ -8,7 +8,7 @@
8
8
  // `core/profiles/env-store.ts` for the on-disk (0600) store this wraps.
9
9
  import { defineLeaf, defineBranch } from '../../core/command.js';
10
10
  import { InputError } from '../../core/io.js';
11
- import { loadProfileManifest } from '../../core/profiles/manifest.js';
11
+ import { resolveProfileOperand } from '../../core/profiles/manifest.js';
12
12
  import { validateEnvVarName } from '../../core/env-name.js';
13
13
  import { listProfileEnvNames, setProfileEnvVar, removeProfileEnvVar } from '../../core/profiles/env-store.js';
14
14
  /** Strips exactly one trailing line ending (`\r\n` or `\n`) — the one
@@ -47,7 +47,7 @@ const setLeaf = defineLeaf({
47
47
  kind: 'positional',
48
48
  name: 'profile',
49
49
  required: true,
50
- constraint: 'Exact profile id, or a unique manifest name.',
50
+ constraint: 'Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr.',
51
51
  },
52
52
  {
53
53
  kind: 'flag',
@@ -65,9 +65,8 @@ const setLeaf = defineLeaf({
65
65
  ],
66
66
  output: [
67
67
  { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
68
- { name: 'name', type: 'string', required: true, constraint: 'The env var name written.' },
69
68
  { name: 'created', type: 'boolean', required: true, constraint: 'true when this name was newly added, false when it replaced an existing value.' },
70
- { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
69
+ { name: 'follow_up', type: 'string', required: true, constraint: 'The effect on already-running nodes.' },
71
70
  ],
72
71
  outputKind: 'object',
73
72
  effects: [
@@ -78,7 +77,7 @@ const setLeaf = defineLeaf({
78
77
  run: async (input) => {
79
78
  const name = input['name'];
80
79
  requireValidName(name);
81
- const { profileId } = loadProfileManifest(input['profile']);
80
+ const { profileId } = resolveProfileOperand(input['profile']);
82
81
  const value = stripSingleTrailingLineEnding(input['value'] ?? '');
83
82
  if (value === '') {
84
83
  throw new InputError({
@@ -91,9 +90,8 @@ const setLeaf = defineLeaf({
91
90
  const created = setProfileEnvVar(profileId, name, value);
92
91
  return {
93
92
  profile_id: profileId,
94
- name,
95
93
  created,
96
- follow_up: `List stored names with \`crtr profile env list ${profileId}\`. A running node under this profile picks up the change on its next revive.`,
94
+ follow_up: 'A running node under this profile picks up the change on its next revive.',
97
95
  };
98
96
  },
99
97
  });
@@ -109,7 +107,7 @@ const listLeaf = defineLeaf({
109
107
  kind: 'positional',
110
108
  name: 'profile',
111
109
  required: true,
112
- constraint: 'Exact profile id, or a unique manifest name.',
110
+ constraint: 'Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr.',
113
111
  },
114
112
  ],
115
113
  output: [
@@ -122,7 +120,7 @@ const listLeaf = defineLeaf({
122
120
  effects: ['None. Read-only \u2014 and value-blind by construction: this leaf never reads the stored values.'],
123
121
  },
124
122
  run: async (input) => {
125
- const { profileId } = loadProfileManifest(input['profile']);
123
+ const { profileId } = resolveProfileOperand(input['profile']);
126
124
  const names = listProfileEnvNames(profileId);
127
125
  return {
128
126
  profile_id: profileId,
@@ -146,7 +144,7 @@ const removeLeaf = defineLeaf({
146
144
  kind: 'positional',
147
145
  name: 'profile',
148
146
  required: true,
149
- constraint: 'Exact profile id, or a unique manifest name.',
147
+ constraint: 'Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr.',
150
148
  },
151
149
  {
152
150
  kind: 'flag',
@@ -158,9 +156,7 @@ const removeLeaf = defineLeaf({
158
156
  ],
159
157
  output: [
160
158
  { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
161
- { name: 'name', type: 'string', required: true, constraint: 'The env var name targeted.' },
162
159
  { name: 'removed', type: 'boolean', required: true, constraint: 'true when a value existed and was removed; false when nothing was stored under this name.' },
163
- { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
164
160
  ],
165
161
  outputKind: 'object',
166
162
  effects: [
@@ -169,15 +165,13 @@ const removeLeaf = defineLeaf({
169
165
  ],
170
166
  },
171
167
  run: async (input) => {
172
- const { profileId } = loadProfileManifest(input['profile']);
168
+ const { profileId } = resolveProfileOperand(input['profile']);
173
169
  const name = input['name'];
174
170
  requireValidName(name);
175
171
  const removed = removeProfileEnvVar(profileId, name);
176
172
  return {
177
173
  profile_id: profileId,
178
- name,
179
174
  removed,
180
- follow_up: `List remaining names with \`crtr profile env list ${profileId}\`.`,
181
175
  };
182
176
  },
183
177
  });
@@ -1,6 +1,6 @@
1
1
  import { defineLeaf } from '../../core/command.js';
2
2
  import { assertInstalledKind, readMergedLaunchConfig } from '../../core/config.js';
3
- import { loadProfileManifest, setProfileDefaultKind } from '../../core/profiles/manifest.js';
3
+ import { resolveProfileOperand, setProfileDefaultKind } from '../../core/profiles/manifest.js';
4
4
  import { stateBlock } from '../../core/help.js';
5
5
  function kindsStateBlock() {
6
6
  return stateBlock('kinds', { count: Object.keys(readMergedLaunchConfig().kinds).length }, Object.keys(readMergedLaunchConfig().kinds).sort().join('\n'));
@@ -13,13 +13,11 @@ export const kindLeaf = defineLeaf({
13
13
  name: 'profile kind',
14
14
  summary: "set a profile's manifest `default_kind` for creates that omit --kind",
15
15
  params: [
16
- { kind: 'positional', name: 'profile', required: true, constraint: 'Exact profile id, or a unique manifest name.' },
16
+ { kind: 'positional', name: 'profile', required: true, constraint: 'Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr.' },
17
17
  { kind: 'positional', name: 'kind', required: true, constraint: 'Installed persona kind to use by default. The live <kinds> list below is the accepted value set.' },
18
18
  ],
19
19
  output: [
20
20
  { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
21
- { name: 'default_kind', type: 'string', required: true, constraint: 'The installed kind stored for creates that omit --kind.' },
22
- { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next command.' },
23
21
  ],
24
22
  dynamicState: kindsStateBlock,
25
23
  outputKind: 'object',
@@ -28,12 +26,10 @@ export const kindLeaf = defineLeaf({
28
26
  run: async (input) => {
29
27
  const kind = input['kind'];
30
28
  assertInstalledKind(kind);
31
- const { profileId } = loadProfileManifest(input['profile']);
29
+ const { profileId } = resolveProfileOperand(input['profile']);
32
30
  const { manifest } = setProfileDefaultKind(profileId, kind);
33
31
  return {
34
32
  profile_id: profileId,
35
- default_kind: manifest.default_kind,
36
- follow_up: `Create a node under it without --kind, or inspect it with \`crtr profile show ${profileId}\`.`,
37
33
  };
38
34
  },
39
35
  });
@@ -1,6 +1,6 @@
1
1
  import { defineLeaf } from '../../core/command.js';
2
2
  import { InputError } from '../../core/io.js';
3
- import { loadProfileManifest, updateProfileMetadata } from '../../core/profiles/manifest.js';
3
+ import { resolveProfileOperand, updateProfileMetadata } from '../../core/profiles/manifest.js';
4
4
  /** Fold `KEY=VALUE` flag repetitions into one map. Key-shape validation stays
5
5
  * with the manifest store (`assertProfileMetadata`); this only splits. */
6
6
  export function resolveMetaPairs(raw, field) {
@@ -33,14 +33,13 @@ export const metaLeaf = defineLeaf({
33
33
  name: 'profile meta',
34
34
  summary: "merge entries into (or drop entries from) a profile manifest's `metadata` map",
35
35
  params: [
36
- { kind: 'positional', name: 'profile', required: true, constraint: 'Exact profile id, or a unique manifest name.' },
36
+ { kind: 'positional', name: 'profile', required: true, constraint: 'Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr.' },
37
37
  { kind: 'flag', name: 'set', type: 'string', required: false, repeatable: true, constraint: 'KEY=VALUE to merge over the stored map (repeat per entry). KEY starts with a letter or digit, followed by letters, digits, `_`, or `-`; an existing key is overwritten.' },
38
38
  { kind: 'flag', name: 'unset', type: 'string', required: false, repeatable: true, constraint: 'KEY to remove (repeat per entry). Removing an absent key is a no-op.' },
39
39
  ],
40
40
  output: [
41
41
  { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
42
42
  { name: 'metadata', type: 'object', required: true, constraint: 'The full resulting metadata map.' },
43
- { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next command.' },
44
43
  ],
45
44
  outputKind: 'object',
46
45
  effects: [
@@ -58,12 +57,11 @@ export const metaLeaf = defineLeaf({
58
57
  next: 'Repeat --set/--unset per entry.',
59
58
  });
60
59
  }
61
- const { profileId } = loadProfileManifest(input['profile']);
60
+ const { profileId } = resolveProfileOperand(input['profile']);
62
61
  const { manifest } = updateProfileMetadata(profileId, set, unset);
63
62
  return {
64
63
  profile_id: profileId,
65
64
  metadata: manifest.metadata ?? {},
66
- follow_up: `Inspect it with \`crtr profile show ${profileId}\`.`,
67
65
  };
68
66
  },
69
67
  });
@@ -59,14 +59,12 @@ export const newLeaf = defineLeaf({
59
59
  ],
60
60
  output: [
61
61
  { 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.' },
62
- { name: 'name', type: 'string', required: true, constraint: 'The manifest name as given.' },
63
62
  { name: 'path', type: 'string', required: true, constraint: 'Absolute path to the profile directory.' },
64
63
  { name: 'projects', type: 'ProfileProject[]', required: true, constraint: 'Explicit `{path, memory}` project relationships in manifest order. `path` defines purview; `memory` caps automatic boot and workspace-open disclosure. Empty when --project was omitted.' },
65
64
  { name: 'home', type: 'string | null', required: true, constraint: 'Directory the profile\u2019s nodes are pinned to — the initial project, or null when none was given.' },
66
65
  { name: 'default_kind', type: 'string | null', required: true, constraint: 'Stored default persona kind, or null when node creates default to general.' },
67
66
  { name: 'metadata', type: 'object', required: true, constraint: 'Stored metadata map; empty when --meta was omitted.' },
68
67
  { name: 'created_at', type: 'string', required: true, constraint: 'ISO 8601 creation timestamp.' },
69
- { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
70
68
  ],
71
69
  outputKind: 'object',
72
70
  effects: ['Creates `<profile-dir>/profile.json` and `<profile-dir>/memory/` (the fixed root dir when `--root`).'],
@@ -85,14 +83,12 @@ export const newLeaf = defineLeaf({
85
83
  const { profileId, manifest } = ensureRootProfile();
86
84
  return {
87
85
  profile_id: profileId,
88
- name: manifest.name,
89
86
  path: profileRoot(profileId),
90
87
  projects: manifest.projects,
91
88
  home: manifest.home,
92
89
  default_kind: manifest.default_kind ?? null,
93
90
  metadata: manifest.metadata ?? {},
94
91
  created_at: manifest.created_at,
95
- follow_up: `Root profile ensured. Spawn nodes under it with \`--profile ${profileId}\`, or widen its purview and choose its automatic memory disclosure cap with \`crtr profile project add ${profileId} --dir <path> --memory <none|name|preview|content>\`.`,
96
92
  };
97
93
  }
98
94
  if (name === undefined)
@@ -108,14 +104,12 @@ export const newLeaf = defineLeaf({
108
104
  });
109
105
  return {
110
106
  profile_id: profileId,
111
- name: manifest.name,
112
107
  path: profileRoot(profileId),
113
108
  projects: manifest.projects,
114
109
  home: manifest.home,
115
110
  default_kind: manifest.default_kind ?? null,
116
111
  metadata: manifest.metadata ?? {},
117
112
  created_at: manifest.created_at,
118
- follow_up: `Show it with \`crtr profile show ${profileId}\`, add purview and choose its automatic memory disclosure cap with \`crtr profile project add ${profileId} --dir <path> --memory <none|name|preview|content>\`, or list every profile with \`crtr profile list\`.`,
119
113
  };
120
114
  },
121
115
  });
@@ -1,10 +1,10 @@
1
1
  import { defineLeaf } from '../../core/command.js';
2
- import { loadProfileManifest, pauseProfile, resumeProfile } from '../../core/profiles/manifest.js';
2
+ import { resolveProfileOperand, pauseProfile, resumeProfile } from '../../core/profiles/manifest.js';
3
3
  const profileParam = {
4
4
  kind: 'positional',
5
5
  name: 'profile',
6
6
  required: true,
7
- constraint: 'Exact profile id, or a unique manifest name.',
7
+ constraint: 'Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr.',
8
8
  };
9
9
  export const pauseLeaf = defineLeaf({
10
10
  name: 'pause',
@@ -17,18 +17,16 @@ export const pauseLeaf = defineLeaf({
17
17
  output: [
18
18
  { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
19
19
  { name: 'paused_at', type: 'string', required: true, constraint: 'ISO 8601 timestamp when the profile became inert.' },
20
- { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete resume command.' },
21
20
  ],
22
21
  outputKind: 'object',
23
22
  effects: ['Writes `paused_at` to the profile manifest. Existing brokers continue their current turn; queued inbox messages do not revive nodes until resume.'],
24
23
  },
25
24
  run: async (input) => {
26
- const { profileId } = loadProfileManifest(input['profile']);
25
+ const { profileId } = resolveProfileOperand(input['profile']);
27
26
  const { manifest } = pauseProfile(profileId);
28
27
  return {
29
28
  profile_id: profileId,
30
29
  paused_at: manifest.paused_at,
31
- follow_up: `Resume it with \`crtr profile resume ${profileId}\`.`,
32
30
  };
33
31
  },
34
32
  });
@@ -43,18 +41,16 @@ export const resumeLeaf = defineLeaf({
43
41
  output: [
44
42
  { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
45
43
  { name: 'paused_at', type: 'null', required: true, constraint: 'Always null after resume; null means active.' },
46
- { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next command.' },
47
44
  ],
48
45
  outputKind: 'object',
49
46
  effects: ['Clears `paused_at` in the profile manifest. Future creates and revives are allowed.'],
50
47
  },
51
48
  run: async (input) => {
52
- const { profileId } = loadProfileManifest(input['profile']);
49
+ const { profileId } = resolveProfileOperand(input['profile']);
53
50
  const { manifest } = resumeProfile(profileId);
54
51
  return {
55
52
  profile_id: profileId,
56
53
  paused_at: manifest.paused_at,
57
- follow_up: `Inspect it with \`crtr profile show ${profileId}\`.`,
58
54
  };
59
55
  },
60
56
  });
@@ -1,6 +1,6 @@
1
1
  // `crtr profile project` manages purview relationships and their automatic-memory caps.
2
2
  import { defineLeaf, defineBranch } from '../../core/command.js';
3
- import { addProfileProject, removeProfileProject, loadProfileManifest } from '../../core/profiles/manifest.js';
3
+ import { addProfileProject, removeProfileProject, resolveProfileOperand } from '../../core/profiles/manifest.js';
4
4
  import { PROFILE_PROJECT_MEMORY_VALUES } from '../../api/dto/profiles.js';
5
5
  const addLeaf = defineLeaf({
6
6
  name: 'add',
@@ -14,7 +14,7 @@ const addLeaf = defineLeaf({
14
14
  kind: 'positional',
15
15
  name: 'profile',
16
16
  required: true,
17
- constraint: 'Exact profile id, or a unique manifest name.',
17
+ constraint: 'Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr.',
18
18
  },
19
19
  {
20
20
  kind: 'flag',
@@ -43,13 +43,12 @@ const addLeaf = defineLeaf({
43
43
  { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
44
44
  { name: 'projects', type: 'ProfileProject[]', required: true, constraint: 'The updated `{path, memory}` relationships in manifest order. `path` defines purview; `memory` caps automatic boot and workspace-open disclosure.' },
45
45
  { name: 'home', type: 'string | null', required: true, constraint: "The profile's home directory after the change." },
46
- { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
47
46
  ],
48
47
  outputKind: 'object',
49
48
  effects: ["Appends a new project relationship or updates an existing relationship’s `memory` in the profile manifest, and rewrites `home` when --home is given. Purview follows `path`; `memory` caps automatic boot and workspace-open disclosure. Invalidates the process scope cache."],
50
49
  },
51
50
  run: async (input) => {
52
- const { profileId } = loadProfileManifest(input['profile']);
51
+ const { profileId } = resolveProfileOperand(input['profile']);
53
52
  const { manifest } = addProfileProject(profileId, input['dir'], {
54
53
  memory: input['memory'],
55
54
  home: input['home'] === true,
@@ -58,7 +57,6 @@ const addLeaf = defineLeaf({
58
57
  profile_id: profileId,
59
58
  projects: manifest.projects,
60
59
  home: manifest.home,
61
- follow_up: `Show the full manifest with \`crtr profile show ${profileId}\`.`,
62
60
  };
63
61
  },
64
62
  });
@@ -74,7 +72,7 @@ const removeLeaf = defineLeaf({
74
72
  kind: 'positional',
75
73
  name: 'profile',
76
74
  required: true,
77
- constraint: 'Exact profile id, or a unique manifest name.',
75
+ constraint: 'Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr.',
78
76
  },
79
77
  {
80
78
  kind: 'flag',
@@ -88,19 +86,17 @@ const removeLeaf = defineLeaf({
88
86
  { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
89
87
  { name: 'projects', type: 'ProfileProject[]', required: true, constraint: 'The updated `{path, memory}` relationships in manifest order.' },
90
88
  { name: 'home', type: 'string | null', required: true, constraint: "The profile's home directory after the change." },
91
- { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
92
89
  ],
93
90
  outputKind: 'object',
94
91
  effects: ["Removes a project relationship from the profile manifest, re-pointing `home` when that entry was it. This narrows purview and removes its automatic memory disclosure cap. Invalidates the process scope cache."],
95
92
  },
96
93
  run: async (input) => {
97
- const { profileId } = loadProfileManifest(input['profile']);
94
+ const { profileId } = resolveProfileOperand(input['profile']);
98
95
  const { manifest } = removeProfileProject(profileId, input['dir']);
99
96
  return {
100
97
  profile_id: profileId,
101
98
  projects: manifest.projects,
102
99
  home: manifest.home,
103
- follow_up: `Show the full manifest with \`crtr profile show ${profileId}\`.`,
104
100
  };
105
101
  },
106
102
  });
@@ -1,5 +1,5 @@
1
1
  import { defineLeaf } from '../../core/command.js';
2
- import { loadProfileManifest, renameProfile } from '../../core/profiles/manifest.js';
2
+ import { resolveProfileOperand, renameProfile } from '../../core/profiles/manifest.js';
3
3
  export const renameLeaf = defineLeaf({
4
4
  name: 'rename',
5
5
  description: "change a profile's display name",
@@ -12,7 +12,7 @@ export const renameLeaf = defineLeaf({
12
12
  kind: 'positional',
13
13
  name: 'profile',
14
14
  required: true,
15
- constraint: 'Exact profile id, or a unique manifest name (the CURRENT name, before this rename).',
15
+ constraint: 'Profile id or manifest name (the CURRENT name, before this rename); a close-enough name resolves to the nearest profile, reported on stderr.',
16
16
  },
17
17
  {
18
18
  kind: 'flag',
@@ -24,19 +24,15 @@ export const renameLeaf = defineLeaf({
24
24
  ],
25
25
  output: [
26
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
27
  ],
30
28
  outputKind: 'object',
31
29
  effects: ["Changes the profile manifest's `name` field. Invalidates the process scope cache."],
32
30
  },
33
31
  run: async (input) => {
34
- const { profileId } = loadProfileManifest(input['profile']);
32
+ const { profileId } = resolveProfileOperand(input['profile']);
35
33
  const { manifest } = renameProfile(profileId, input['name']);
36
34
  return {
37
35
  profile_id: profileId,
38
- name: manifest.name,
39
- follow_up: `Show the full manifest with \`crtr profile show ${profileId}\`.`,
40
36
  };
41
37
  },
42
38
  });
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { defineLeaf } from '../../core/command.js';
4
- import { loadProfileManifest, profileRoot, sanitizeProfileMetadata } from '../../core/profiles/manifest.js';
4
+ import { resolveProfileOperand, profileRoot, sanitizeProfileMetadata } from '../../core/profiles/manifest.js';
5
5
  import { CONFIG_FILE } from '../../types.js';
6
6
  export const showLeaf = defineLeaf({
7
7
  name: 'show',
@@ -15,7 +15,7 @@ export const showLeaf = defineLeaf({
15
15
  kind: 'positional',
16
16
  name: 'profile',
17
17
  required: true,
18
- constraint: 'Exact profile id, or a unique manifest name. An ambiguous name fails listing every matching id.',
18
+ constraint: 'Profile id or manifest name; a close-enough name resolves to the nearest profile, reported on stderr. A name shared by several profiles fails listing every matching id.',
19
19
  },
20
20
  ],
21
21
  output: [
@@ -37,7 +37,7 @@ export const showLeaf = defineLeaf({
37
37
  effects: ['None. Read-only.'],
38
38
  },
39
39
  run: async (input) => {
40
- const { profileId, manifest } = loadProfileManifest(input['profile']);
40
+ const { profileId, manifest } = resolveProfileOperand(input['profile']);
41
41
  const configPath = join(profileRoot(profileId), CONFIG_FILE);
42
42
  return {
43
43
  profile_id: profileId,