@crouton-kit/crouter 0.3.37 → 0.3.39

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 (143) hide show
  1. package/dist/build-root.js +1 -0
  2. package/dist/{builtin-personas/runtime-base.md → builtin-memory/00-runtime-base.md} +7 -0
  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-views/prompt-review/core.mjs +6 -134
  36. package/dist/builtin-views/settings/core.mjs +26 -94
  37. package/dist/builtin-views/settings/text.mjs +0 -5
  38. package/dist/builtin-views/settings/tui.mjs +1 -7
  39. package/dist/builtin-views/settings/web.jsx +3 -49
  40. package/dist/clients/attach/attach-cmd.js +416 -416
  41. package/dist/commands/memory/read.js +3 -3
  42. package/dist/commands/memory/shared.d.ts +13 -8
  43. package/dist/commands/memory/shared.js +22 -8
  44. package/dist/commands/memory/write.js +5 -3
  45. package/dist/commands/node.js +40 -11
  46. package/dist/commands/profile/add-project.d.ts +1 -0
  47. package/dist/commands/profile/add-project.js +42 -0
  48. package/dist/commands/profile/delete.d.ts +1 -0
  49. package/dist/commands/profile/delete.js +39 -0
  50. package/dist/commands/profile/list.d.ts +1 -0
  51. package/dist/commands/profile/list.js +35 -0
  52. package/dist/commands/profile/new.d.ts +1 -0
  53. package/dist/commands/profile/new.js +50 -0
  54. package/dist/commands/profile/remove-project.d.ts +1 -0
  55. package/dist/commands/profile/remove-project.js +42 -0
  56. package/dist/commands/profile/rename.d.ts +1 -0
  57. package/dist/commands/profile/rename.js +42 -0
  58. package/dist/commands/profile/show.d.ts +1 -0
  59. package/dist/commands/profile/show.js +50 -0
  60. package/dist/commands/profile.d.ts +2 -0
  61. package/dist/commands/profile.js +33 -0
  62. package/dist/commands/sys/config.js +6 -21
  63. package/dist/commands/sys/prompt-review.js +90 -17
  64. package/dist/core/canvas/canvas.js +10 -9
  65. package/dist/core/canvas/db.js +11 -0
  66. package/dist/core/canvas/history.js +1 -0
  67. package/dist/core/canvas/types.d.ts +10 -0
  68. package/dist/core/config.d.ts +44 -1
  69. package/dist/core/config.js +155 -12
  70. package/dist/core/hearth/provider.d.ts +8 -0
  71. package/dist/core/hearth/providers/__tests__/sweep-and-release.test.d.ts +1 -0
  72. package/dist/core/hearth/providers/__tests__/sweep-and-release.test.js +254 -0
  73. package/dist/core/hearth/providers/blaxel-home.d.ts +39 -0
  74. package/dist/core/hearth/providers/blaxel-home.js +271 -21
  75. package/dist/core/hearth/providers/blaxel.d.ts +5 -0
  76. package/dist/core/hearth/providers/blaxel.js +86 -4
  77. package/dist/core/hearth/providers/types.d.ts +16 -0
  78. package/dist/core/memory-resolver.d.ts +26 -15
  79. package/dist/core/memory-resolver.js +158 -40
  80. package/dist/core/profiles/manifest.d.ts +33 -0
  81. package/dist/core/profiles/manifest.js +332 -0
  82. package/dist/core/profiles/select.d.ts +11 -0
  83. package/dist/core/profiles/select.js +115 -0
  84. package/dist/core/runtime/bearings.d.ts +9 -0
  85. package/dist/core/runtime/bearings.js +31 -0
  86. package/dist/core/runtime/front-door.js +9 -2
  87. package/dist/core/runtime/launch.d.ts +25 -7
  88. package/dist/core/runtime/launch.js +42 -55
  89. package/dist/core/runtime/nodes.d.ts +7 -0
  90. package/dist/core/runtime/nodes.js +6 -0
  91. package/dist/core/runtime/persona.js +18 -32
  92. package/dist/core/runtime/spawn.d.ts +13 -0
  93. package/dist/core/runtime/spawn.js +25 -0
  94. package/dist/core/scope.d.ts +14 -7
  95. package/dist/core/scope.js +76 -20
  96. package/dist/core/substrate/index.d.ts +3 -3
  97. package/dist/core/substrate/index.js +5 -3
  98. package/dist/core/substrate/on-read.js +3 -2
  99. package/dist/core/substrate/render.d.ts +16 -11
  100. package/dist/core/substrate/render.js +249 -62
  101. package/dist/core/substrate/schema.d.ts +11 -3
  102. package/dist/core/substrate/schema.js +25 -0
  103. package/dist/core/substrate/subject.d.ts +15 -2
  104. package/dist/core/substrate/subject.js +24 -2
  105. package/dist/hearth/control-plane/__tests__/error-serialization.test.d.ts +1 -0
  106. package/dist/hearth/control-plane/__tests__/error-serialization.test.js +29 -0
  107. package/dist/hearth/control-plane/__tests__/oauth-serving-marker.test.d.ts +1 -0
  108. package/dist/hearth/control-plane/__tests__/oauth-serving-marker.test.js +44 -0
  109. package/dist/hearth/control-plane/__tests__/wake-roll.test.d.ts +1 -0
  110. package/dist/hearth/control-plane/__tests__/wake-roll.test.js +230 -0
  111. package/dist/hearth/control-plane/db.js +27 -0
  112. package/dist/hearth/control-plane/hearth-target.d.ts +23 -0
  113. package/dist/hearth/control-plane/hearth-target.js +68 -0
  114. package/dist/hearth/control-plane/registry.d.ts +6 -1
  115. package/dist/hearth/control-plane/registry.js +7 -0
  116. package/dist/hearth/control-plane/relay.d.ts +4 -0
  117. package/dist/hearth/control-plane/relay.js +72 -3
  118. package/dist/hearth/control-plane/secrets.d.ts +14 -0
  119. package/dist/hearth/control-plane/secrets.js +21 -0
  120. package/dist/hearth/control-plane/server.js +140 -4
  121. package/dist/hearth/control-plane/serving.d.ts +15 -0
  122. package/dist/hearth/control-plane/serving.js +106 -0
  123. package/dist/hearth/control-plane/types.d.ts +20 -0
  124. package/dist/hearth/control-plane/wake.d.ts +8 -2
  125. package/dist/hearth/control-plane/wake.js +241 -3
  126. package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
  127. package/dist/types.d.ts +57 -1
  128. package/dist/types.js +83 -1
  129. package/dist/web-client/assets/{index-MSKSWwV0.js → index-CbO8L0mN.js} +4 -4
  130. package/dist/web-client/assets/index-DwO46Cs5.css +2 -0
  131. package/dist/web-client/index.html +2 -2
  132. package/package.json +2 -2
  133. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +0 -56
  134. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +0 -57
  135. package/dist/builtin-memory/crouter-development/personas.md +0 -112
  136. package/dist/builtin-personas/spine/no-manager.md +0 -2
  137. package/dist/core/personas/index.d.ts +0 -13
  138. package/dist/core/personas/index.js +0 -11
  139. package/dist/core/personas/loader.d.ts +0 -159
  140. package/dist/core/personas/loader.js +0 -327
  141. package/dist/core/personas/resolve.d.ts +0 -59
  142. package/dist/core/personas/resolve.js +0 -376
  143. package/dist/web-client/assets/index-DUThOUzU.css +0 -2
@@ -7,19 +7,19 @@ import { MEMORY_KINDS } from './shared.js';
7
7
  export const readLeaf = defineLeaf({
8
8
  name: 'read',
9
9
  description: 'load a memory document body by name',
10
- whenToUse: 'a task in front of you matches a stored document and you already know its name — read it before improvising. Resolves the path-derived name across scopes by precedence (project > user > builtin), with leaf-name fallback. You name the document by its crtr identifier, never a file path — do not cat or find the markdown off disk. Reach for `crtr memory find` first when you do not yet know which document applies.',
10
+ whenToUse: 'a task in front of you matches a stored document and you already know its name — read it before improvising. Resolves the path-derived name across scopes by precedence (project stack > profile > user > builtin), with leaf-name fallback. You name the document by its crtr identifier, never a file path — do not cat or find the markdown off disk. Reach for `crtr memory find` first when you do not yet know which document applies.',
11
11
  help: {
12
12
  name: 'memory read',
13
13
  summary: 'resolve a path-derived name to its document body, frontmatter stripped unless --frontmatter',
14
14
  params: [
15
- { kind: 'positional', name: 'name', required: true, constraint: 'Path-derived memory identifier (e.g. `topic` or `area/topic`). Resolved across scopes by precedence: project > user > builtin, with leaf-name fallback.' },
15
+ { kind: 'positional', name: 'name', required: true, constraint: 'Path-derived memory identifier (e.g. `topic` or `area/topic`). Resolved across scopes by precedence: project stack > profile > user > builtin, with leaf-name fallback.' },
16
16
  { kind: 'flag', name: 'kind', type: 'enum', choices: [...MEMORY_KINDS], required: false, constraint: 'Narrows resolution when the name is ambiguous across kinds.' },
17
17
  { kind: 'flag', name: 'frontmatter', type: 'bool', required: false, constraint: 'When present, includes the YAML frontmatter in the returned body. Off by default — only the body is returned.' },
18
18
  ],
19
19
  output: [
20
20
  { name: 'name', type: 'string', required: true, constraint: 'Resolved document name.' },
21
21
  { name: 'kind', type: 'string', required: true, constraint: 'Resolved kind: knowledge or preference.' },
22
- { name: 'scope', type: 'string', required: true, constraint: 'Scope the document was resolved from: project, user, or builtin.' },
22
+ { name: 'scope', type: 'string', required: true, constraint: 'Scope the document was resolved from: project, profile, user, or builtin.' },
23
23
  { name: 'path', type: 'string', required: true, constraint: 'Absolute path to the document on disk — edit this file directly to tweak the doc in place.' },
24
24
  { name: 'content', type: 'string', required: true, constraint: 'Document body. Frontmatter stripped unless --frontmatter is set.' },
25
25
  { name: 'follow_up', type: 'string', required: true, constraint: 'Hints at variant flags or next commands.' },
@@ -1,16 +1,21 @@
1
- import type { Scope } from '../../types.js';
1
+ import type { MemoryScope } from '../../core/memory-resolver.js';
2
2
  export declare const MEMORY_KINDS: readonly ["knowledge", "preference"];
3
3
  export declare const VISIBILITY_RUNGS: readonly ["none", "name", "preview", "content"];
4
- export declare const MEMORY_SCOPES: readonly ["user", "project"];
5
- /** Scope sort weight matching resolution precedence (project > user > builtin).
6
- * Used by `list` for its "scope then kind then name" ordering. */
7
- export declare function scopeRank(scope: Scope): number;
4
+ export declare const MEMORY_SCOPES: readonly ["user", "project", "profile"];
5
+ /** Scope sort weight matching resolution precedence (project stack > profile >
6
+ * user > builtin). Used by `list` for its "scope then kind then name"
7
+ * ordering. */
8
+ export declare function scopeRank(scope: MemoryScope): number;
8
9
  /** Resolve the write target scope + its memory dir. Default: project when a
9
10
  * project scope exists for the cwd, else user. An explicit `--scope project`
10
11
  * with no project scope yet scaffolds one (ensureProjectScopeRoot). User scope
11
- * always resolves. Returns the absolute `<root>/memory` dir to write under. */
12
- export declare function resolveWriteTarget(scopeArg: string | undefined): {
13
- scope: Scope;
12
+ * always resolves. `--scope profile` is NEVER a default it requires a
13
+ * selected profile, from `profileArg` (an explicit `--profile <id-or-name>`)
14
+ * or else the process's `CRTR_PROFILE_ID`, resolved through the centralized
15
+ * `loadProfileManifest` (never a raw path join). Returns the absolute
16
+ * `<root>/memory` dir to write under. */
17
+ export declare function resolveWriteTarget(scopeArg: string | undefined, profileArg?: string): {
18
+ scope: MemoryScope;
14
19
  memoryDir: string;
15
20
  };
16
21
  /** Map a path-derived name (`topic` or `area/topic`) to its file path under a
@@ -7,6 +7,7 @@ import { join } from 'node:path';
7
7
  import { stringify as yamlStringify, parse as yamlParse } from 'yaml';
8
8
  import { usage } from '../../core/errors.js';
9
9
  import { scopeMemoryDir, projectScopeRoot, ensureProjectScopeRoot, } from '../../core/scope.js';
10
+ import { loadProfileManifest, profileMemoryDir } from '../../core/profiles/manifest.js';
10
11
  // The two memory kinds — knowledge (consult: procedural playbooks + factual
11
12
  // references merged) vs preference (behave: standing directives). Used as the
12
13
  // `--kind` enum choices everywhere.
@@ -15,27 +16,40 @@ export const MEMORY_KINDS = ['knowledge', 'preference'];
15
16
  // content). Shared by --system-prompt-visibility and --file-read-visibility.
16
17
  export const VISIBILITY_RUNGS = ['none', 'name', 'preview', 'content'];
17
18
  // Scope choices for filtering / targeting (builtin is read-only, not writable).
18
- export const MEMORY_SCOPES = ['user', 'project'];
19
- /** Scope sort weight matching resolution precedence (project > user > builtin).
20
- * Used by `list` for its "scope then kind then name" ordering. */
19
+ export const MEMORY_SCOPES = ['user', 'project', 'profile'];
20
+ /** Scope sort weight matching resolution precedence (project stack > profile >
21
+ * user > builtin). Used by `list` for its "scope then kind then name"
22
+ * ordering. */
21
23
  export function scopeRank(scope) {
22
- return scope === 'project' ? 0 : scope === 'user' ? 1 : 2;
24
+ return scope === 'project' ? 0 : scope === 'profile' ? 1 : scope === 'user' ? 2 : 3;
23
25
  }
24
26
  /** Resolve the write target scope + its memory dir. Default: project when a
25
27
  * project scope exists for the cwd, else user. An explicit `--scope project`
26
28
  * with no project scope yet scaffolds one (ensureProjectScopeRoot). User scope
27
- * always resolves. Returns the absolute `<root>/memory` dir to write under. */
28
- export function resolveWriteTarget(scopeArg) {
29
+ * always resolves. `--scope profile` is NEVER a default it requires a
30
+ * selected profile, from `profileArg` (an explicit `--profile <id-or-name>`)
31
+ * or else the process's `CRTR_PROFILE_ID`, resolved through the centralized
32
+ * `loadProfileManifest` (never a raw path join). Returns the absolute
33
+ * `<root>/memory` dir to write under. */
34
+ export function resolveWriteTarget(scopeArg, profileArg) {
29
35
  let scope;
30
- if (scopeArg === 'user' || scopeArg === 'project') {
36
+ if (scopeArg === 'user' || scopeArg === 'project' || scopeArg === 'profile') {
31
37
  scope = scopeArg;
32
38
  }
33
39
  else if (scopeArg !== undefined) {
34
- throw usage(`invalid --scope: ${scopeArg} (expected user|project)`);
40
+ throw usage(`invalid --scope: ${scopeArg} (expected user|project|profile)`);
35
41
  }
36
42
  else {
37
43
  scope = projectScopeRoot() !== null ? 'project' : 'user';
38
44
  }
45
+ if (scope === 'profile') {
46
+ const profileIdOrName = profileArg && profileArg !== '' ? profileArg : process.env['CRTR_PROFILE_ID'] || '';
47
+ if (profileIdOrName === '') {
48
+ throw usage('profile scope requires a selected profile; rerun inside a profiled node or pass --profile');
49
+ }
50
+ const { profileId } = loadProfileManifest(profileIdOrName);
51
+ return { scope: 'profile', memoryDir: profileMemoryDir(profileId) };
52
+ }
39
53
  let memoryDir = scopeMemoryDir(scope);
40
54
  if (!memoryDir && scope === 'project') {
41
55
  // Explicit --scope project with no project root yet → scaffold it.
@@ -27,13 +27,14 @@ export const writeLeaf = defineLeaf({
27
27
  { kind: 'flag', name: 'gate', type: 'string', required: false, constraint: 'Frontmatter gate — YAML/JSON object predicate over node config using the same field/matcher vocabulary described in the guide.' },
28
28
  { kind: 'flag', name: 'applies-to', type: 'string', required: false, constraint: 'Frontmatter applies-to — glob/path scope the document applies to.' },
29
29
  { kind: 'flag', name: 'read-when', type: 'string', required: false, constraint: 'Frontmatter read-when — YAML/JSON object predicate over a read file’s own frontmatter using the same field/matcher vocabulary described in the guide.' },
30
- { kind: 'flag', name: 'scope', type: 'enum', choices: [...MEMORY_SCOPES], required: false, constraint: 'Target scope. Default: project when inside a project, else user.' },
30
+ { kind: 'flag', name: 'scope', type: 'enum', choices: [...MEMORY_SCOPES], required: false, constraint: 'Target scope. Default: project when inside a project, else user. `profile` requires a selected profile (CRTR_PROFILE_ID) or an explicit --profile.' },
31
+ { kind: 'flag', name: 'profile', type: 'string', required: false, constraint: 'Profile id or name to write under, for --scope profile. Default: the process CRTR_PROFILE_ID (the node\u2019s selected profile). Resolved through the same profile lookup as `crtr profile show`; ignored for any other --scope.' },
31
32
  { kind: 'stdin', name: 'body', required: true, constraint: 'Document body (markdown, no frontmatter). Piped on stdin, or passed as the bare positional after <name>.' },
32
33
  ],
33
34
  output: [
34
35
  { name: 'name', type: 'string', required: true, constraint: 'The path-derived document name written.' },
35
36
  { name: 'kind', type: 'string', required: true, constraint: 'Kind recorded in frontmatter.' },
36
- { name: 'scope', type: 'string', required: true, constraint: 'Scope the document was written to: user or project.' },
37
+ { name: 'scope', type: 'string', required: true, constraint: 'Scope the document was written to: user, project, or profile.' },
37
38
  { name: 'path', type: 'string', required: true, constraint: 'Absolute path to the written document — edit this file directly for later body tweaks instead of re-running write.' },
38
39
  { name: 'created', type: 'boolean', required: true, constraint: 'true when a new document was created, false when an existing one was updated in place.' },
39
40
  { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands — read it back or list the inventory.' },
@@ -47,8 +48,9 @@ export const writeLeaf = defineLeaf({
47
48
  const name = input['name'];
48
49
  const kind = input['kind'];
49
50
  const scopeArg = input['scope'];
51
+ const profileArg = input['profile'];
50
52
  const body = input['body'] ?? '';
51
- const { scope, memoryDir } = resolveWriteTarget(scopeArg);
53
+ const { scope, memoryDir } = resolveWriteTarget(scopeArg, profileArg);
52
54
  const path = memoryFilePath(memoryDir, name);
53
55
  const created = !pathExists(path);
54
56
  // CREATE requires the read-routing line that becomes the preview — without
@@ -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.' },
@@ -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
  ],
@@ -1339,7 +1368,7 @@ const nodePromote = defineLeaf({
1339
1368
  name: 'node promote',
1340
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',
1341
1370
  params: [
1342
- { 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.' },
1343
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).' },
1344
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.' },
1345
1374
  { kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to promote. Defaults to the caller (CRTR_NODE_ID).' },
@@ -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;