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