@crouton-kit/crouter 0.3.8 → 0.3.12
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/bin/crtrd +2 -0
- package/dist/builtin-personas/design/base.md +9 -0
- package/dist/builtin-personas/design/orchestrator.md +10 -0
- package/dist/builtin-personas/developer/base.md +9 -0
- package/dist/builtin-personas/developer/orchestrator.md +12 -0
- package/dist/builtin-personas/explore/base.md +9 -0
- package/dist/builtin-personas/explore/orchestrator.md +9 -0
- package/dist/builtin-personas/general/base.md +5 -0
- package/dist/builtin-personas/general/orchestrator.md +7 -0
- package/dist/builtin-personas/orchestration-kernel.md +71 -0
- package/dist/builtin-personas/plan/base.md +7 -0
- package/dist/builtin-personas/plan/orchestrator.md +12 -0
- package/dist/builtin-personas/review/base.md +7 -0
- package/dist/builtin-personas/review/orchestrator.md +9 -0
- package/dist/builtin-personas/runtime-base.md +39 -0
- package/dist/builtin-personas/spec/base.md +7 -0
- package/dist/builtin-personas/spec/orchestrator.md +10 -0
- package/dist/builtin-skills/skills/design/SKILL.md +51 -0
- package/dist/builtin-skills/skills/development/SKILL.md +109 -0
- package/dist/builtin-skills/skills/planning/SKILL.md +59 -0
- package/dist/builtin-skills/skills/spec/SKILL.md +83 -0
- package/dist/cli.js +25 -27
- package/dist/commands/{job.d.ts → attention.d.ts} +1 -1
- package/dist/commands/attention.js +152 -0
- package/dist/commands/canvas.d.ts +2 -0
- package/dist/commands/canvas.js +35 -0
- package/dist/commands/{agent.d.ts → daemon.d.ts} +1 -1
- package/dist/commands/daemon.js +111 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +65 -0
- package/dist/commands/human/prompts.d.ts +5 -0
- package/dist/commands/human/prompts.js +269 -0
- package/dist/commands/human/queue.d.ts +3 -0
- package/dist/commands/human/queue.js +133 -0
- package/dist/commands/human/shared.d.ts +43 -0
- package/dist/commands/human/shared.js +107 -0
- package/dist/commands/human.js +15 -427
- package/dist/commands/node.d.ts +2 -0
- package/dist/commands/node.js +354 -0
- package/dist/commands/pkg/market-inspect.d.ts +1 -0
- package/dist/commands/pkg/market-inspect.js +157 -0
- package/dist/commands/pkg/market-manage.d.ts +1 -0
- package/dist/commands/pkg/market-manage.js +316 -0
- package/dist/commands/pkg/market.d.ts +1 -0
- package/dist/commands/pkg/market.js +16 -0
- package/dist/commands/pkg/plugin-inspect.d.ts +1 -0
- package/dist/commands/pkg/plugin-inspect.js +142 -0
- package/dist/commands/pkg/plugin-manage.d.ts +1 -0
- package/dist/commands/pkg/plugin-manage.js +294 -0
- package/dist/commands/pkg/plugin.d.ts +1 -0
- package/dist/commands/pkg/plugin.js +16 -0
- package/dist/commands/pkg/shared.d.ts +5 -0
- package/dist/commands/pkg/shared.js +61 -0
- package/dist/commands/pkg.js +8 -1004
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.js +159 -0
- package/dist/commands/revive.d.ts +2 -0
- package/dist/commands/revive.js +64 -0
- package/dist/commands/skill/author.d.ts +3 -0
- package/dist/commands/skill/author.js +147 -0
- package/dist/commands/skill/find.d.ts +4 -0
- package/dist/commands/skill/find.js +254 -0
- package/dist/commands/skill/read.d.ts +1 -0
- package/dist/commands/skill/read.js +89 -0
- package/dist/commands/skill/shared.d.ts +19 -0
- package/dist/commands/skill/shared.js +207 -0
- package/dist/commands/skill/state.d.ts +3 -0
- package/dist/commands/skill/state.js +69 -0
- package/dist/commands/skill.js +12 -681
- package/dist/commands/sys/config.d.ts +1 -0
- package/dist/commands/sys/config.js +186 -0
- package/dist/commands/sys/doctor.d.ts +1 -0
- package/dist/commands/sys/doctor.js +369 -0
- package/dist/commands/sys/shared.d.ts +3 -0
- package/dist/commands/sys/shared.js +24 -0
- package/dist/commands/sys/update.d.ts +2 -0
- package/dist/commands/sys/update.js +114 -0
- package/dist/commands/sys.js +9 -694
- package/dist/core/__tests__/argv-parser.test.js +19 -1
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +100 -0
- package/dist/core/__tests__/canvas.test.js +154 -0
- package/dist/core/__tests__/reset.test.js +105 -0
- package/dist/core/__tests__/resolver.test.js +69 -1
- package/dist/core/__tests__/unknown-path.test.d.ts +1 -0
- package/dist/core/__tests__/unknown-path.test.js +52 -0
- package/dist/core/bootstrap.d.ts +2 -0
- package/dist/core/bootstrap.js +66 -0
- package/dist/core/canvas/attention.d.ts +24 -0
- package/dist/core/canvas/attention.js +94 -0
- package/dist/core/canvas/canvas.d.ts +40 -0
- package/dist/core/canvas/canvas.js +210 -0
- package/dist/core/canvas/db.d.ts +7 -0
- package/dist/core/canvas/db.js +61 -0
- package/dist/core/canvas/index.d.ts +4 -0
- package/dist/core/canvas/index.js +6 -0
- package/dist/core/canvas/paths.d.ts +16 -0
- package/dist/core/canvas/paths.js +62 -0
- package/dist/core/canvas/render.d.ts +30 -0
- package/dist/core/canvas/render.js +186 -0
- package/dist/core/canvas/types.d.ts +87 -0
- package/dist/core/canvas/types.js +8 -0
- package/dist/core/command.d.ts +63 -2
- package/dist/core/command.js +97 -24
- package/dist/core/feed/feed.d.ts +43 -0
- package/dist/core/feed/feed.js +116 -0
- package/dist/core/feed/inbox.d.ts +50 -0
- package/dist/core/feed/inbox.js +124 -0
- package/dist/core/frontmatter.d.ts +10 -0
- package/dist/core/frontmatter.js +24 -9
- package/dist/core/help.d.ts +39 -8
- package/dist/core/help.js +69 -35
- package/dist/core/io.d.ts +15 -1
- package/dist/core/io.js +56 -6
- package/dist/core/personas/index.d.ts +12 -0
- package/dist/core/personas/index.js +10 -0
- package/dist/core/personas/loader.d.ts +44 -0
- package/dist/core/personas/loader.js +157 -0
- package/dist/core/personas/resolve.d.ts +36 -0
- package/dist/core/personas/resolve.js +110 -0
- package/dist/core/render.d.ts +11 -0
- package/dist/core/render.js +126 -0
- package/dist/core/resolver.d.ts +10 -0
- package/dist/core/resolver.js +160 -2
- package/dist/core/runtime/front-door.d.ts +10 -0
- package/dist/core/runtime/front-door.js +97 -0
- package/dist/core/runtime/kickoff.d.ts +23 -0
- package/dist/core/runtime/kickoff.js +134 -0
- package/dist/core/runtime/launch.d.ts +34 -0
- package/dist/core/runtime/launch.js +85 -0
- package/dist/core/runtime/nodes.d.ts +38 -0
- package/dist/core/runtime/nodes.js +95 -0
- package/dist/core/runtime/presence.d.ts +38 -0
- package/dist/core/runtime/presence.js +152 -0
- package/dist/core/runtime/promote.d.ts +30 -0
- package/dist/core/runtime/promote.js +105 -0
- package/dist/core/runtime/reset.d.ts +13 -0
- package/dist/core/runtime/reset.js +97 -0
- package/dist/core/runtime/revive.d.ts +26 -0
- package/dist/core/runtime/revive.js +89 -0
- package/dist/core/runtime/roadmap.d.ts +12 -0
- package/dist/core/runtime/roadmap.js +52 -0
- package/dist/core/runtime/spawn.d.ts +33 -0
- package/dist/core/runtime/spawn.js +118 -0
- package/dist/core/runtime/stop-guard.d.ts +18 -0
- package/dist/core/runtime/stop-guard.js +33 -0
- package/dist/core/runtime/tmux.d.ts +88 -0
- package/dist/core/runtime/tmux.js +198 -0
- package/dist/core/spawn.d.ts +17 -80
- package/dist/core/spawn.js +15 -219
- package/dist/daemon/crtrd-cli.d.ts +1 -0
- package/dist/daemon/crtrd-cli.js +4 -0
- package/dist/daemon/crtrd.d.ts +20 -0
- package/dist/daemon/crtrd.js +200 -0
- package/dist/daemon/manage.d.ts +17 -0
- package/dist/daemon/manage.js +57 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +16 -0
- package/dist/pi-extensions/canvas-inbox-watcher.js +229 -0
- package/dist/pi-extensions/canvas-nav.d.ts +32 -0
- package/dist/pi-extensions/canvas-nav.js +536 -0
- package/dist/pi-extensions/canvas-stophook.d.ts +17 -0
- package/dist/pi-extensions/canvas-stophook.js +373 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +3 -0
- package/package.json +6 -5
- package/dist/commands/agent.js +0 -384
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.js +0 -179
- package/dist/commands/job.js +0 -344
- package/dist/commands/plan.d.ts +0 -4
- package/dist/commands/plan.js +0 -309
- package/dist/commands/spec.d.ts +0 -3
- package/dist/commands/spec.js +0 -286
- package/dist/core/__tests__/flow-leaves.test.js +0 -248
- package/dist/core/__tests__/job.test.js +0 -310
- package/dist/core/__tests__/jobs.test.js +0 -66
- package/dist/core/jobs.d.ts +0 -101
- package/dist/core/jobs.js +0 -462
- package/dist/prompts/agent.d.ts +0 -18
- package/dist/prompts/agent.js +0 -153
- package/dist/prompts/debug.d.ts +0 -8
- package/dist/prompts/debug.js +0 -44
- /package/dist/core/__tests__/{flow-leaves.test.d.ts → canvas-inbox-watcher.test.d.ts} +0 -0
- /package/dist/core/__tests__/{job.test.d.ts → canvas.test.d.ts} +0 -0
- /package/dist/core/__tests__/{jobs.test.d.ts → reset.test.d.ts} +0 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
// `crtr node` — the canvas-native command surface.
|
|
2
|
+
//
|
|
3
|
+
// A node is the unit of the runtime: an agent with its own identity, context
|
|
4
|
+
// dir, and pi vehicle, pinned to a cwd. This subtree spawns terminal workers
|
|
5
|
+
// onto the canvas (`new`), inspects the graph (`inspect list|show`), and walks
|
|
6
|
+
// the spine (`focus`/`msg`). The push/feed half lives under `crtr push`.
|
|
7
|
+
import { defineLeaf, defineBranch } from '../core/command.js';
|
|
8
|
+
import { InputError } from '../core/io.js';
|
|
9
|
+
import { spawnChild, bootRoot } from '../core/runtime/spawn.js';
|
|
10
|
+
import { promote, requestYield } from '../core/runtime/promote.js';
|
|
11
|
+
import { writeYieldMessage } from '../core/runtime/kickoff.js';
|
|
12
|
+
import { reviveNode } from '../core/runtime/revive.js';
|
|
13
|
+
import { focusNodeInPlace } from '../core/runtime/presence.js';
|
|
14
|
+
import { windowAlive } from '../core/runtime/tmux.js';
|
|
15
|
+
import { appendInbox } from '../core/feed/inbox.js';
|
|
16
|
+
import { availableKinds } from '../core/personas/index.js';
|
|
17
|
+
import { getNode, listNodes, subscriptionsOf, subscribersOf, } from '../core/canvas/index.js';
|
|
18
|
+
/** Validate a `--kind` against the installed personas; throws a listing InputError. */
|
|
19
|
+
function assertKind(kind) {
|
|
20
|
+
const kinds = availableKinds();
|
|
21
|
+
if (!kinds.includes(kind)) {
|
|
22
|
+
throw new InputError({ error: 'unknown_kind', message: `unknown kind: ${kind}`, field: 'kind', next: `Valid kinds: ${kinds.join(', ')}.` });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// node new — spawn a terminal worker as a background window under the root
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
const nodeNew = defineLeaf({
|
|
29
|
+
name: 'new',
|
|
30
|
+
help: {
|
|
31
|
+
name: 'node new',
|
|
32
|
+
summary: 'spawn a terminal worker onto the canvas as a background window — returns its node id',
|
|
33
|
+
params: [
|
|
34
|
+
{ kind: 'stdin', name: 'prompt', required: true, constraint: 'First user message for the spawned node. Piped on stdin or passed as a positional.' },
|
|
35
|
+
{ kind: 'flag', name: 'kind', type: 'string', required: false, default: 'general', constraint: 'Persona kind — match the work: explore (map/investigate a codebase), spec (write a spec), design (architect a solution), plan (break work into steps), developer (implement a change), review (validate/critique), general (anything else).' },
|
|
36
|
+
{ kind: 'flag', name: 'mode', type: 'enum', choices: ['base', 'orchestrator'], required: false, default: 'base', constraint: 'Persona mode. Almost always base; orchestrator is reserved for promoted/resident nodes.' },
|
|
37
|
+
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Dir the node is pinned to. Defaults to the caller cwd.' },
|
|
38
|
+
{ kind: 'flag', name: 'name', type: 'string', required: false, constraint: 'Display name (tmux window + resume picker). Defaults to the kind.' },
|
|
39
|
+
{ kind: 'flag', name: 'parent', type: 'string', required: false, constraint: 'Parent node id. Defaults to the calling node (CRTR_NODE_ID).' },
|
|
40
|
+
],
|
|
41
|
+
output: [
|
|
42
|
+
{ name: 'node_id', type: 'string', required: true, constraint: 'The new node id.' },
|
|
43
|
+
{ name: 'name', type: 'string', required: true, constraint: 'Display name.' },
|
|
44
|
+
{ name: 'window', type: 'string', required: false, constraint: 'tmux window id of the background window.' },
|
|
45
|
+
{ name: 'session', type: 'string', required: true, constraint: 'Root tmux session the node was placed in.' },
|
|
46
|
+
{ name: 'status', type: 'string', required: true, constraint: 'Always "active" on spawn.' },
|
|
47
|
+
{ name: 'follow_up', type: 'string', required: true, constraint: 'A notification to the caller about the spawn: the child runs independently and its finish wakes you automatically, so treat it as fire-and-forget. Read it, then act.' },
|
|
48
|
+
],
|
|
49
|
+
outputKind: 'object',
|
|
50
|
+
effects: [
|
|
51
|
+
'Creates a node under ~/.crtr/nodes/<id>/ and indexes it in canvas.db.',
|
|
52
|
+
'Parent auto-subscribes (active) to the child so it is woken on the child\'s pushes.',
|
|
53
|
+
'Opens a background (non-focus-stealing) tmux window running pi.',
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
run: async (input) => {
|
|
57
|
+
const prompt = input['prompt'] ?? '';
|
|
58
|
+
if (prompt.trim() === '') {
|
|
59
|
+
throw new InputError({ error: 'empty_prompt', message: 'a prompt is required (stdin or positional)', next: 'Pipe a task on stdin or pass it as an argument.' });
|
|
60
|
+
}
|
|
61
|
+
const kind = input['kind'] ?? 'general';
|
|
62
|
+
const mode = (input['mode'] ?? 'base');
|
|
63
|
+
const cwd = input['cwd'] ?? process.cwd();
|
|
64
|
+
const name = input['name'];
|
|
65
|
+
const parent = input['parent'];
|
|
66
|
+
const res = spawnChild({ kind, mode, cwd, name, prompt, parent });
|
|
67
|
+
return {
|
|
68
|
+
node_id: res.node.node_id,
|
|
69
|
+
name: res.node.name,
|
|
70
|
+
window: res.window ?? undefined,
|
|
71
|
+
session: res.session,
|
|
72
|
+
status: res.node.status,
|
|
73
|
+
follow_up: "Notification only — you're auto-subscribed, so the child's finish wakes you automatically; treat it as fire-and-forget. Carry on with other independent work now, or stop and end your turn. On wake: `crtr feed read`.",
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
render: (r) => `<spawned name="${r['name']}" id="${r['node_id']}" status="${r['status']}">\n${r['follow_up']}\n</spawned>`,
|
|
77
|
+
});
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// node list — the active canvas (or a status slice)
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
const nodeList = defineLeaf({
|
|
82
|
+
name: 'list',
|
|
83
|
+
help: {
|
|
84
|
+
name: 'node inspect list',
|
|
85
|
+
summary: 'list nodes on the canvas, optionally by status',
|
|
86
|
+
params: [
|
|
87
|
+
{ kind: 'flag', name: 'status', type: 'string', required: false, constraint: 'Filter: active | idle | done | dead. Comma-separated for several.' },
|
|
88
|
+
],
|
|
89
|
+
output: [
|
|
90
|
+
{ name: 'nodes', type: 'object[]', required: true, constraint: 'Rows: {node_id, name, kind, mode, lifecycle, status, cwd, parent, created}.' },
|
|
91
|
+
],
|
|
92
|
+
outputKind: 'object',
|
|
93
|
+
effects: ['Read-only: queries canvas.db.'],
|
|
94
|
+
},
|
|
95
|
+
run: async (input) => {
|
|
96
|
+
const raw = input['status'];
|
|
97
|
+
const status = raw !== undefined && raw !== '' ? raw.split(',').map((s) => s.trim()) : undefined;
|
|
98
|
+
const nodes = listNodes(status !== undefined ? { status } : undefined);
|
|
99
|
+
return { nodes };
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// node show — a node + its place in the spine
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
const nodeShow = defineLeaf({
|
|
106
|
+
name: 'show',
|
|
107
|
+
help: {
|
|
108
|
+
name: 'node inspect show',
|
|
109
|
+
summary: 'show a node\'s meta plus its subscriptions (reports) and subscribers (managers)',
|
|
110
|
+
params: [
|
|
111
|
+
{ kind: 'positional', name: 'node', required: true, constraint: 'Node id.' },
|
|
112
|
+
],
|
|
113
|
+
output: [
|
|
114
|
+
{ name: 'node', type: 'object', required: true, constraint: 'The node meta.' },
|
|
115
|
+
{ name: 'reports', type: 'object[]', required: true, constraint: 'Who this node subscribes to (its reports/down).' },
|
|
116
|
+
{ name: 'managers', type: 'object[]', required: true, constraint: 'Who subscribes to this node (its managers/up).' },
|
|
117
|
+
],
|
|
118
|
+
outputKind: 'object',
|
|
119
|
+
effects: ['Read-only: reads the node meta + canvas.db edges.'],
|
|
120
|
+
},
|
|
121
|
+
run: async (input) => {
|
|
122
|
+
const id = input['node'];
|
|
123
|
+
const node = getNode(id);
|
|
124
|
+
if (node === null) {
|
|
125
|
+
throw new InputError({ error: 'not_found', message: `no node: ${id}`, next: 'List nodes with `crtr node inspect list`.' });
|
|
126
|
+
}
|
|
127
|
+
return { node, reports: subscriptionsOf(id), managers: subscribersOf(id) };
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// node inspect — read the graph (list + show)
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
const nodeInspect = defineBranch({
|
|
134
|
+
name: 'inspect',
|
|
135
|
+
help: {
|
|
136
|
+
name: 'node inspect',
|
|
137
|
+
summary: 'read the canvas graph — enumerate nodes or inspect one with its spine neighbors',
|
|
138
|
+
children: [
|
|
139
|
+
{ name: 'list', desc: 'list nodes on the canvas', useWhen: 'surveying what exists' },
|
|
140
|
+
{ name: 'show', desc: 'show a node + its spine neighbors', useWhen: 'inspecting one node' },
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
children: [nodeList, nodeShow],
|
|
144
|
+
});
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// node focus — bring a node's window forefront (across roots if needed)
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
const nodeFocus = defineLeaf({
|
|
149
|
+
name: 'focus',
|
|
150
|
+
help: {
|
|
151
|
+
name: 'node focus',
|
|
152
|
+
summary: 'bring a node into your CURRENT pane in place (swap-pane) — the agent appears where you are instead of navigating you to its window',
|
|
153
|
+
params: [
|
|
154
|
+
{ kind: 'positional', name: 'node', required: true, constraint: 'Node id to focus.' },
|
|
155
|
+
],
|
|
156
|
+
output: [
|
|
157
|
+
{ name: 'focused', type: 'boolean', required: true, constraint: 'True when the node was brought into view.' },
|
|
158
|
+
{ name: 'session', type: 'string', required: false, constraint: 'The tmux session the node lives in.' },
|
|
159
|
+
{ name: 'revived', type: 'boolean', required: true, constraint: 'True when a dormant node was revived to be focused.' },
|
|
160
|
+
{ name: 'in_place', type: 'boolean', required: true, constraint: 'True when the node was swapped into the caller pane; false when it fell back to window focus (no caller pane).' },
|
|
161
|
+
],
|
|
162
|
+
outputKind: 'object',
|
|
163
|
+
effects: ['Swaps the node\'s pane into the caller\'s current pane (tmux swap-pane -d) and updates the focus pointer.', 'Falls back to select-window (+ switch-client across roots) when there is no caller pane.', 'Revives a dormant node (resume) if it has no live window, then focuses it.'],
|
|
164
|
+
},
|
|
165
|
+
run: async (input) => {
|
|
166
|
+
const id = input['node'];
|
|
167
|
+
const node = getNode(id);
|
|
168
|
+
if (node === null)
|
|
169
|
+
throw new InputError({ error: 'not_found', message: `no node: ${id}`, next: 'List nodes with `crtr node inspect list`.' });
|
|
170
|
+
// A dormant node (done/dead/window released) has no live window — revive it
|
|
171
|
+
// (resume the saved conversation) so there is something to focus.
|
|
172
|
+
let revived = false;
|
|
173
|
+
if (!windowAlive(node.tmux_session, node.window)) {
|
|
174
|
+
try {
|
|
175
|
+
reviveNode(id, { resume: true });
|
|
176
|
+
revived = true;
|
|
177
|
+
}
|
|
178
|
+
catch { /* fall through; focus reports focused:false */ }
|
|
179
|
+
}
|
|
180
|
+
const res = focusNodeInPlace(id);
|
|
181
|
+
return { focused: res.focused, session: res.session, revived, in_place: res.inPlace };
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// node session — boot a NEW root in its own tmux session (the explicit form)
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
const nodeSession = defineLeaf({
|
|
188
|
+
name: 'session',
|
|
189
|
+
help: {
|
|
190
|
+
name: 'node session',
|
|
191
|
+
summary: 'start a fresh root node in its own tmux session and switch to it (use from inside a node to start a new root without taking your pane)',
|
|
192
|
+
params: [
|
|
193
|
+
{ kind: 'stdin', name: 'prompt', required: false, constraint: 'Optional starter prompt; a root needs none.' },
|
|
194
|
+
{ kind: 'flag', name: 'kind', type: 'string', required: false, default: 'general', constraint: 'Persona kind for the root.' },
|
|
195
|
+
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: 'Dir to pin the root to. Defaults to the caller cwd.' },
|
|
196
|
+
{ kind: 'flag', name: 'name', type: 'string', required: false, constraint: 'Display name.' },
|
|
197
|
+
],
|
|
198
|
+
output: [
|
|
199
|
+
{ name: 'node_id', type: 'string', required: true, constraint: 'The root node id.' },
|
|
200
|
+
{ name: 'session', type: 'string', required: true, constraint: 'The dedicated tmux session created for this root.' },
|
|
201
|
+
{ name: 'window', type: 'string', required: false, constraint: 'The root node\'s window id.' },
|
|
202
|
+
],
|
|
203
|
+
outputKind: 'object',
|
|
204
|
+
effects: ['Creates a detached tmux session and runs pi in it as a resident root node.'],
|
|
205
|
+
},
|
|
206
|
+
run: async (input) => {
|
|
207
|
+
const prompt = input['prompt'];
|
|
208
|
+
const kind = input['kind'] ?? 'general';
|
|
209
|
+
const cwd = input['cwd'] ?? process.cwd();
|
|
210
|
+
const name = input['name'];
|
|
211
|
+
const meta = bootRoot({ cwd, kind, name, prompt, placement: 'session' });
|
|
212
|
+
return { node_id: meta.node_id, session: meta.tmux_session ?? '', window: meta.window ?? undefined };
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// node msg — direct-address any node at a wake tier (wakes a dormant target)
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
const nodeMsg = defineLeaf({
|
|
219
|
+
name: 'msg',
|
|
220
|
+
help: {
|
|
221
|
+
name: 'node msg',
|
|
222
|
+
summary: 'send a direct message to any node\'s inbox at a wake tier — a direct message wakes the node regardless of subscriptions (reviving it if dormant)',
|
|
223
|
+
params: [
|
|
224
|
+
{ kind: 'positional', name: 'node', required: true, constraint: 'Target node id.' },
|
|
225
|
+
{ kind: 'stdin', name: 'body', required: true, constraint: 'Message body. Positional (after the node id is consumed) or stdin.' },
|
|
226
|
+
{ kind: 'flag', name: 'tier', type: 'enum', choices: ['critical', 'urgent', 'normal', 'deferred'], required: false, default: 'normal', constraint: 'How it lands: critical = interrupt + new turn; urgent = steer mid-turn; normal = follow-up; deferred = read on next cycle.' },
|
|
227
|
+
],
|
|
228
|
+
output: [
|
|
229
|
+
{ name: 'delivered', type: 'boolean', required: true, constraint: 'True when the message was appended to the target inbox.' },
|
|
230
|
+
{ name: 'node_id', type: 'string', required: true, constraint: 'Target node.' },
|
|
231
|
+
{ name: 'woke', type: 'boolean', required: true, constraint: 'True when a dormant target was revived to receive it.' },
|
|
232
|
+
],
|
|
233
|
+
outputKind: 'object',
|
|
234
|
+
effects: ['Appends a message entry to the target inbox.jsonl.', 'Revives the target (resume) if it has no live window.'],
|
|
235
|
+
},
|
|
236
|
+
run: async (input) => {
|
|
237
|
+
const id = input['node'];
|
|
238
|
+
const target = getNode(id);
|
|
239
|
+
if (target === null)
|
|
240
|
+
throw new InputError({ error: 'not_found', message: `no node: ${id}`, next: 'List nodes with `crtr node inspect list`.' });
|
|
241
|
+
const body = (input['body'] ?? '').trim();
|
|
242
|
+
if (body === '')
|
|
243
|
+
throw new InputError({ error: 'empty_body', message: 'a message body is required', field: 'body', next: 'Pass the message after the node id or on stdin.' });
|
|
244
|
+
const tier = (input['tier'] ?? 'normal');
|
|
245
|
+
const from = process.env['CRTR_NODE_ID'] ?? 'human';
|
|
246
|
+
appendInbox(id, { from, tier, kind: 'message', label: body.split('\n')[0].slice(0, 120), data: { body } });
|
|
247
|
+
// A direct message wakes any node: if the target has no live window
|
|
248
|
+
// (done/dead/idle-released), revive it so its inbox-watcher delivers this.
|
|
249
|
+
let woke = false;
|
|
250
|
+
if (!windowAlive(target.tmux_session, target.window)) {
|
|
251
|
+
try {
|
|
252
|
+
reviveNode(id, { resume: true });
|
|
253
|
+
woke = true;
|
|
254
|
+
}
|
|
255
|
+
catch { /* best-effort wake */ }
|
|
256
|
+
}
|
|
257
|
+
return { delivered: true, node_id: id, woke };
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// node promote — become a resident orchestrator (terminal → resident polymorph)
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
const nodePromote = defineLeaf({
|
|
264
|
+
name: 'promote',
|
|
265
|
+
help: {
|
|
266
|
+
name: 'node promote',
|
|
267
|
+
summary: 'promote yourself to a resident orchestrator of a chosen kind — flips to that kind\'s orchestrator persona on next revive, dumps its orchestration + roadmap-shaping guidance now, and seeds a roadmap scaffold for you to author',
|
|
268
|
+
params: [
|
|
269
|
+
{ kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Specialize as this kind of orchestrator: developer (own feature delivery), review, spec, design, plan, explore, general. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one — it picks the orchestrator persona you revive into and the roadmap-shaping skill dumped now.' },
|
|
270
|
+
{ kind: 'flag', name: 'node', type: 'string', required: false, constraint: 'Node to promote. Defaults to the caller (CRTR_NODE_ID).' },
|
|
271
|
+
],
|
|
272
|
+
output: [
|
|
273
|
+
{ name: 'node_id', type: 'string', required: true, constraint: 'The promoted node.' },
|
|
274
|
+
{ name: 'kind', type: 'string', required: true, constraint: 'The kind it now orchestrates as.' },
|
|
275
|
+
{ name: 'mode', type: 'string', required: true, constraint: 'Now "orchestrator".' },
|
|
276
|
+
{ name: 'roadmap_written', type: 'boolean', required: true, constraint: 'True if a roadmap scaffold was seeded by this call.' },
|
|
277
|
+
{ name: 'guidance', type: 'string', required: true, constraint: 'Kind-specific orchestration + roadmap-shaping guidance and your roadmap scaffold — read it, then AUTHOR your roadmap (goal, exit criteria, phases) this turn before delegating.' },
|
|
278
|
+
],
|
|
279
|
+
outputKind: 'object',
|
|
280
|
+
effects: ['Flips lifecycle→resident, mode→orchestrator, kind→chosen; rewrites the launch spec to that kind\'s orchestrator persona; seeds context/roadmap.md scaffold if absent.'],
|
|
281
|
+
},
|
|
282
|
+
run: async (input) => {
|
|
283
|
+
const id = input['node'] ?? process.env['CRTR_NODE_ID'];
|
|
284
|
+
if (id === undefined || id === '')
|
|
285
|
+
throw new InputError({ error: 'no_node', message: 'no node to promote (set CRTR_NODE_ID or pass --node)', next: 'Run from inside a node, or pass --node <id>.' });
|
|
286
|
+
const kind = input['kind'];
|
|
287
|
+
if (kind !== undefined)
|
|
288
|
+
assertKind(kind);
|
|
289
|
+
const res = promote(id, kind !== undefined ? { kind } : {});
|
|
290
|
+
return { node_id: res.meta.node_id, kind: res.meta.kind, mode: res.meta.mode, roadmap_written: res.roadmapWritten, guidance: res.guidance };
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// node yield — refresh: discard context, revive fresh against the roadmap
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
const nodeYield = defineLeaf({
|
|
297
|
+
name: 'yield',
|
|
298
|
+
help: {
|
|
299
|
+
name: 'node yield',
|
|
300
|
+
summary: 'request a context refresh — you will be respawned fresh against your roadmap on your next stop (a terminal node auto-promotes to resident first)',
|
|
301
|
+
params: [
|
|
302
|
+
{ kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'If this yield auto-promotes a terminal node, specialize it as this kind of orchestrator (developer, review, spec, design, plan, explore, general). Defaults to your current kind.' },
|
|
303
|
+
{ kind: 'stdin', name: 'message', required: false, constraint: 'A short note to your future self — what to do (if anything) the moment you wake fresh. Surfaced as <yield-message> in the next revive. Pass as a positional or pipe via heredoc.' },
|
|
304
|
+
],
|
|
305
|
+
output: [
|
|
306
|
+
{ name: 'node_id', type: 'string', required: true, constraint: 'The yielding node.' },
|
|
307
|
+
{ name: 'promoted', type: 'boolean', required: true, constraint: 'True if this yield promoted a terminal node to resident.' },
|
|
308
|
+
{ name: 'will_refresh', type: 'boolean', required: true, constraint: 'Always true: end your turn and you will revive fresh against your roadmap.' },
|
|
309
|
+
],
|
|
310
|
+
outputKind: 'object',
|
|
311
|
+
effects: ['Sets intent=refresh; the stophook shuts the process down on next stop and the daemon revives it fresh.'],
|
|
312
|
+
},
|
|
313
|
+
run: async (input) => {
|
|
314
|
+
const id = process.env['CRTR_NODE_ID'];
|
|
315
|
+
if (id === undefined || id === '')
|
|
316
|
+
throw new InputError({ error: 'no_node', message: 'no node to yield (CRTR_NODE_ID unset)', next: 'Run from inside a node.' });
|
|
317
|
+
const kind = input['kind'];
|
|
318
|
+
if (kind !== undefined)
|
|
319
|
+
assertKind(kind);
|
|
320
|
+
const message = typeof input['message'] === 'string' ? input['message'].trim() : '';
|
|
321
|
+
if (message !== '')
|
|
322
|
+
writeYieldMessage(id, message);
|
|
323
|
+
const res = requestYield(id, kind !== undefined ? { kind } : {});
|
|
324
|
+
return { node_id: res.meta.node_id, promoted: res.promoted, will_refresh: res.willRefresh };
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
export function registerNode() {
|
|
328
|
+
return defineBranch({
|
|
329
|
+
name: 'node',
|
|
330
|
+
rootEntry: {
|
|
331
|
+
concept: 'a unit of the agent runtime — an agent on the canvas with its own context dir and pi vehicle. Spawning nodes is how work gets done here, not an optimization you reach for late',
|
|
332
|
+
desc: 'spawn, inspect, and navigate nodes on the canvas',
|
|
333
|
+
useWhen: 'delegating work, or walking the subscription graph. CRITICAL, and your default reflex — spawning a child is the first move on almost any task, not a fallback. Your own context window is the scarce resource: before you read files into it to understand, implement, debug, or change code you do not already know, hand that to a fresh node and get back just the conclusion (`crtr node new "<task>" --kind <explore|spec|design|plan|developer|review>`). Keep inline only a fact you already know. Fan out independent units as CONCURRENT children — a wake with idle workers is wasted; serialize only true dependencies, and never let two live children edit the same files. Once you delegate a unit, do not also run it yourself: you auto-subscribe on spawn, so its finish wakes you. Spawn + collect mechanics: `crtr node -h`.',
|
|
334
|
+
},
|
|
335
|
+
help: {
|
|
336
|
+
name: 'node',
|
|
337
|
+
summary: 'spawn and navigate canvas nodes',
|
|
338
|
+
model: 'Every agent is a node in one directed graph (the canvas); `subscribes_to` is the spine — spawn a child and you auto-subscribe (active) to it, so its finish wakes you.\n\n' +
|
|
339
|
+
'WHEN TO DELEGATE: hand any self-contained unit of work to a child instead of doing it inline — it keeps your own context window (your scarce resource) free for steering. Spawn independent units in PARALLEL; a wake with idle workers is wasted. Serialize only true dependencies, and never let two live children edit the same files.\n\n' +
|
|
340
|
+
'HOW: `crtr node new "<task>" --kind <kind>` returns a node id immediately and runs the worker in a background window. Match the kind to the work (see `node new -h`). You are woken when a child finishes; absorb what your children reported with `crtr feed read` (coalesced pointers — dereference the report paths that matter, don\'t act on a one-line summary). Integrate, then either delegate the next units or finish.\n\n' +
|
|
341
|
+
'FINISH: a worker ends its own work with `crtr push final "<result>"` (writes the canonical result, marks done, closes the window) — stopping without it is not finishing. For a job too big for one context window, `node promote` to a resident orchestrator (holds a roadmap, delegates phases); when context fills, `node yield` to refresh against that roadmap.',
|
|
342
|
+
children: [
|
|
343
|
+
{ name: 'new', desc: 'spawn a terminal worker as a background window', useWhen: 'delegating a self-contained unit of work' },
|
|
344
|
+
{ name: 'inspect', desc: 'read the graph (list nodes / show one)', useWhen: 'surveying the canvas or inspecting a node' },
|
|
345
|
+
{ name: 'focus', desc: 'bring a node window forefront', useWhen: 'jumping to a node to watch or steer it' },
|
|
346
|
+
{ name: 'session', desc: 'open a fresh root in its own tmux session', useWhen: 'starting a new top-level session from inside a node' },
|
|
347
|
+
{ name: 'msg', desc: 'direct-message any node at a wake tier', useWhen: 'steering or pinging a specific node (wakes it)' },
|
|
348
|
+
{ name: 'promote', desc: 'become a resident orchestrator of a chosen kind', useWhen: 'your task is bigger than one context window and you must delegate + persist' },
|
|
349
|
+
{ name: 'yield', desc: 'refresh your context against your roadmap', useWhen: 'your context window is filling up' },
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
children: [nodeNew, nodeInspect, nodeFocus, nodeSession, nodeMsg, nodePromote, nodeYield],
|
|
353
|
+
});
|
|
354
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const marketInspectBranch: import("../../core/command.js").BranchDef;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
2
|
+
import { notFound, usage } from '../../core/errors.js';
|
|
3
|
+
import { paginate } from '../../core/pagination.js';
|
|
4
|
+
import { listInstalledPlugins, listInstalledMarketplaces, listAllMarketplaces, findMarketplaceByName, } from '../../core/resolver.js';
|
|
5
|
+
import { resolveScopeArg, projectScopeRoot } from '../../core/scope.js';
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// market.inspect.list
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
const marketList = defineLeaf({
|
|
10
|
+
name: 'list',
|
|
11
|
+
help: {
|
|
12
|
+
name: 'pkg market inspect list',
|
|
13
|
+
summary: 'list registered marketplaces',
|
|
14
|
+
params: [
|
|
15
|
+
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project', 'all'], required: false, constraint: 'One of: user, project, all. Default: all.' },
|
|
16
|
+
{ kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: 'Default 50, max 200.' },
|
|
17
|
+
{ kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: 'Opaque token from next_cursor. Omit on first call.' },
|
|
18
|
+
],
|
|
19
|
+
output: [
|
|
20
|
+
{ name: 'items', type: 'object[]', required: true, constraint: 'Each: {name, scope, url, ref, path, last_updated?}. Sorted by scope then name ascending.' },
|
|
21
|
+
{ name: 'next_cursor', type: 'string | null', required: true, constraint: 'null means no more items.' },
|
|
22
|
+
{ name: 'total', type: 'integer | null', required: true, constraint: 'Total count.' },
|
|
23
|
+
],
|
|
24
|
+
outputKind: 'object',
|
|
25
|
+
effects: ['None. Read-only.'],
|
|
26
|
+
},
|
|
27
|
+
run: async (input) => {
|
|
28
|
+
const scopeInput = input['scope'];
|
|
29
|
+
const limitRaw = input['limit'];
|
|
30
|
+
const limit = limitRaw !== undefined ? Math.min(Math.max(1, limitRaw), 200) : 50;
|
|
31
|
+
const cursor = input['cursor'];
|
|
32
|
+
let all;
|
|
33
|
+
if (scopeInput !== undefined) {
|
|
34
|
+
const resolved = resolveScopeArg(scopeInput);
|
|
35
|
+
if (resolved === 'all') {
|
|
36
|
+
all = listAllMarketplaces();
|
|
37
|
+
}
|
|
38
|
+
else if (resolved === 'builtin') {
|
|
39
|
+
all = [];
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
all = listInstalledMarketplaces(resolved);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
all = listAllMarketplaces();
|
|
47
|
+
}
|
|
48
|
+
const sorted = [...all].sort((a, b) => {
|
|
49
|
+
if (a.scope === 'project' && b.scope !== 'project')
|
|
50
|
+
return -1;
|
|
51
|
+
if (a.scope !== 'project' && b.scope === 'project')
|
|
52
|
+
return 1;
|
|
53
|
+
return a.name.localeCompare(b.name);
|
|
54
|
+
});
|
|
55
|
+
const result = paginate(sorted, { limit, cursor: cursor !== undefined ? cursor : undefined }, {
|
|
56
|
+
defaultLimit: 50,
|
|
57
|
+
maxLimit: 200,
|
|
58
|
+
keyOf: (m) => `${m.scope}:${m.name}`,
|
|
59
|
+
total: 'count',
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
items: result.items.map((m) => ({
|
|
63
|
+
name: m.name,
|
|
64
|
+
scope: m.scope,
|
|
65
|
+
url: m.url,
|
|
66
|
+
ref: m.ref,
|
|
67
|
+
path: m.root,
|
|
68
|
+
})),
|
|
69
|
+
next_cursor: result.next_cursor,
|
|
70
|
+
total: result.total,
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// market.inspect.browse
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
const marketBrowse = defineLeaf({
|
|
78
|
+
name: 'browse',
|
|
79
|
+
help: {
|
|
80
|
+
name: 'pkg market inspect browse',
|
|
81
|
+
summary: 'list plugins available in a marketplace',
|
|
82
|
+
params: [
|
|
83
|
+
{ kind: 'flag', name: 'marketplace', type: 'string', required: false, constraint: 'Marketplace name. Omit to browse all registered marketplaces.' },
|
|
84
|
+
{ kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: 'Default 50, max 200.' },
|
|
85
|
+
{ kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: 'Opaque token from next_cursor. Omit on first call.' },
|
|
86
|
+
],
|
|
87
|
+
output: [
|
|
88
|
+
{ name: 'marketplace', type: 'string', required: true, constraint: 'Echo of the input marketplace name.' },
|
|
89
|
+
{ name: 'items', type: 'object[]', required: true, constraint: 'Each: {name, source, version?, description?, keywords?, installed, installed_scope?}. Sorted by name ascending.' },
|
|
90
|
+
{ name: 'next_cursor', type: 'string | null', required: true, constraint: 'null means no more items.' },
|
|
91
|
+
{ name: 'total', type: 'integer | null', required: true, constraint: 'Total plugins in the marketplace; null if unavailable.' },
|
|
92
|
+
],
|
|
93
|
+
outputKind: 'object',
|
|
94
|
+
effects: ['None. Read-only.'],
|
|
95
|
+
},
|
|
96
|
+
run: async (input) => {
|
|
97
|
+
const mktName = input['marketplace'];
|
|
98
|
+
const limitRaw = input['limit'];
|
|
99
|
+
const limit = limitRaw !== undefined ? Math.min(Math.max(1, limitRaw), 200) : 50;
|
|
100
|
+
const cursor = input['cursor'];
|
|
101
|
+
if (mktName === undefined) {
|
|
102
|
+
throw usage('--marketplace is required for browse. Use `pkg market inspect list` to see registered marketplaces.');
|
|
103
|
+
}
|
|
104
|
+
const mkt = findMarketplaceByName(mktName);
|
|
105
|
+
if (!mkt)
|
|
106
|
+
throw notFound(`marketplace not found: ${mktName}`);
|
|
107
|
+
const allScopes = ['project', 'user'].filter((s) => s !== 'project' || projectScopeRoot() !== null);
|
|
108
|
+
const installedMap = new Map();
|
|
109
|
+
for (const scope of allScopes) {
|
|
110
|
+
for (const p of listInstalledPlugins(scope)) {
|
|
111
|
+
if (!installedMap.has(p.name)) {
|
|
112
|
+
installedMap.set(p.name, p.scope);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const pluginsSorted = [...mkt.manifest.plugins].sort((a, b) => a.name.localeCompare(b.name));
|
|
117
|
+
const result = paginate(pluginsSorted, { limit, cursor: cursor !== undefined ? cursor : undefined }, {
|
|
118
|
+
defaultLimit: 50,
|
|
119
|
+
maxLimit: 200,
|
|
120
|
+
keyOf: (p) => p.name,
|
|
121
|
+
total: 'count',
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
marketplace: mkt.name,
|
|
125
|
+
items: result.items.map((entry) => {
|
|
126
|
+
const installedScope = installedMap.get(entry.name);
|
|
127
|
+
const installed = installedScope !== undefined;
|
|
128
|
+
const item = {
|
|
129
|
+
name: entry.name,
|
|
130
|
+
source: entry.source,
|
|
131
|
+
version: entry.version,
|
|
132
|
+
description: entry.description,
|
|
133
|
+
keywords: entry.keywords,
|
|
134
|
+
installed,
|
|
135
|
+
};
|
|
136
|
+
if (installed) {
|
|
137
|
+
item.installed_scope = installedScope;
|
|
138
|
+
}
|
|
139
|
+
return item;
|
|
140
|
+
}),
|
|
141
|
+
next_cursor: result.next_cursor,
|
|
142
|
+
total: result.total,
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
export const marketInspectBranch = defineBranch({
|
|
147
|
+
name: 'inspect',
|
|
148
|
+
help: {
|
|
149
|
+
name: 'pkg market inspect',
|
|
150
|
+
summary: 'read marketplace metadata without modifying state',
|
|
151
|
+
children: [
|
|
152
|
+
{ name: 'list', desc: 'list registered marketplaces', useWhen: 'seeing which marketplaces are configured' },
|
|
153
|
+
{ name: 'browse', desc: 'list plugins available in a marketplace', useWhen: 'exploring what a marketplace offers before installing' },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
children: [marketList, marketBrowse],
|
|
157
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const marketManageBranch: import("../../core/command.js").BranchDef;
|