@crouton-kit/crouter 0.3.11 → 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 +14 -6
- package/dist/commands/{mode.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/daemon.d.ts +2 -0
- 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 +10 -454
- 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 +3 -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 +6 -691
- 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 +4 -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/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 +5 -0
- package/dist/core/command.js +35 -10
- 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/help.js +5 -3
- 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 +109 -1
- 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 -197
- package/dist/core/spawn.js +16 -539
- 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/package.json +6 -5
- package/dist/commands/agent.d.ts +0 -6
- package/dist/commands/agent.js +0 -585
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.js +0 -192
- package/dist/commands/job.d.ts +0 -11
- package/dist/commands/job.js +0 -384
- package/dist/commands/mode.js +0 -231
- package/dist/commands/plan.d.ts +0 -4
- package/dist/commands/plan.js +0 -322
- package/dist/commands/spec.d.ts +0 -3
- package/dist/commands/spec.js +0 -299
- 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 -98
- package/dist/core/__tests__/spawn.test.js +0 -138
- package/dist/core/__tests__/subagents.test.d.ts +0 -1
- package/dist/core/__tests__/subagents.test.js +0 -75
- package/dist/core/jobs.d.ts +0 -107
- package/dist/core/jobs.js +0 -565
- package/dist/core/subagents.d.ts +0 -18
- package/dist/core/subagents.js +0 -163
- package/dist/prompts/agent.d.ts +0 -27
- package/dist/prompts/agent.js +0 -184
- 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
- /package/dist/{core/__tests__/spawn.test.d.ts → daemon/crtrd-cli.d.ts} +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona file loader.
|
|
3
|
+
*
|
|
4
|
+
* Discovers and parses persona markdown files with YAML frontmatter.
|
|
5
|
+
* Resolution order (highest → lowest precedence): project > user > builtin.
|
|
6
|
+
*
|
|
7
|
+
* Layout on disk:
|
|
8
|
+
* <root>/personas/<kind>/base.md
|
|
9
|
+
* <root>/personas/<kind>/orchestrator.md
|
|
10
|
+
* <root>/personas/orchestration-kernel.md
|
|
11
|
+
*
|
|
12
|
+
* The builtin root is src/builtin-personas (or dist/builtin-personas in the
|
|
13
|
+
* compiled build), resolved relative to this module — mirrors the pattern used
|
|
14
|
+
* for STOPHOOK_PATH in spawn.ts.
|
|
15
|
+
*/
|
|
16
|
+
export interface LoadedPersona {
|
|
17
|
+
/** Raw, uncoerced frontmatter key/value record (null when no frontmatter). */
|
|
18
|
+
frontmatter: Record<string, unknown> | null;
|
|
19
|
+
/** Body text with any @include directives inlined. */
|
|
20
|
+
body: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Load and parse a persona file for the given `kind` and `mode`.
|
|
24
|
+
*
|
|
25
|
+
* Returns `null` when no file is found in any scope (project/user/builtin).
|
|
26
|
+
* On success, `@include` directives in the body are resolved and inlined.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadPersona(kind: string, mode: 'base' | 'orchestrator'): LoadedPersona | null;
|
|
29
|
+
/**
|
|
30
|
+
* Load the raw text of the orchestration kernel (no frontmatter, body only).
|
|
31
|
+
* Returns an empty string if the kernel file cannot be found.
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadKernel(): string;
|
|
34
|
+
/**
|
|
35
|
+
* Load the base runtime prompt — the node operating protocol prepended to
|
|
36
|
+
* EVERY persona (push/finish/delegate/feed/ask). Returns '' if not found.
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadRuntimeBase(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Enumerate the kinds with at least one persona file (base.md or
|
|
41
|
+
* orchestrator.md) across all scope roots (project/user/builtin). Used to
|
|
42
|
+
* validate a requested `--kind` and to list the valid choices.
|
|
43
|
+
*/
|
|
44
|
+
export declare function availableKinds(): string[];
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona file loader.
|
|
3
|
+
*
|
|
4
|
+
* Discovers and parses persona markdown files with YAML frontmatter.
|
|
5
|
+
* Resolution order (highest → lowest precedence): project > user > builtin.
|
|
6
|
+
*
|
|
7
|
+
* Layout on disk:
|
|
8
|
+
* <root>/personas/<kind>/base.md
|
|
9
|
+
* <root>/personas/<kind>/orchestrator.md
|
|
10
|
+
* <root>/personas/orchestration-kernel.md
|
|
11
|
+
*
|
|
12
|
+
* The builtin root is src/builtin-personas (or dist/builtin-personas in the
|
|
13
|
+
* compiled build), resolved relative to this module — mirrors the pattern used
|
|
14
|
+
* for STOPHOOK_PATH in spawn.ts.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { parseFrontmatterGeneric } from '../frontmatter.js';
|
|
20
|
+
import { userScopeRoot, findProjectScopeRoot } from '../scope.js';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Builtin root resolution
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the builtin-personas directory from the location of this compiled
|
|
26
|
+
* or source module. Works from both `dist/core/personas/loader.js` and
|
|
27
|
+
* `src/core/personas/loader.ts` (tsx dev runs).
|
|
28
|
+
*/
|
|
29
|
+
function builtinPersonasRoot() {
|
|
30
|
+
const here = dirname(fileURLToPath(import.meta.url)); // dist/core/personas or src/core/personas
|
|
31
|
+
const coreDir = dirname(here); // dist/core or src/core
|
|
32
|
+
const pkgDir = dirname(coreDir); // dist/ or src/
|
|
33
|
+
const distPath = join(pkgDir, 'builtin-personas');
|
|
34
|
+
const srcPath = join(dirname(pkgDir), 'src', 'builtin-personas');
|
|
35
|
+
// Prefer the path that exists; fall back to the dist-sibling (which may not
|
|
36
|
+
// exist yet if the package is being run pre-build, but that is the caller's
|
|
37
|
+
// problem).
|
|
38
|
+
if (existsSync(distPath))
|
|
39
|
+
return distPath;
|
|
40
|
+
if (existsSync(srcPath))
|
|
41
|
+
return srcPath;
|
|
42
|
+
return distPath;
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Scope roots for personas
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/** Returns the ordered list of roots to search, highest precedence first. */
|
|
48
|
+
function personaSearchRoots() {
|
|
49
|
+
const roots = [];
|
|
50
|
+
const projectRoot = findProjectScopeRoot();
|
|
51
|
+
if (projectRoot)
|
|
52
|
+
roots.push(join(projectRoot, 'personas'));
|
|
53
|
+
roots.push(join(userScopeRoot(), 'personas'));
|
|
54
|
+
roots.push(builtinPersonasRoot());
|
|
55
|
+
return roots;
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// File resolution helpers
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* Find the first existing file across the scope roots.
|
|
62
|
+
* `relativePath` is relative to each root (e.g. 'general/base.md').
|
|
63
|
+
*/
|
|
64
|
+
function resolveFile(relativePath) {
|
|
65
|
+
for (const root of personaSearchRoots()) {
|
|
66
|
+
const candidate = join(root, relativePath);
|
|
67
|
+
if (existsSync(candidate))
|
|
68
|
+
return candidate;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// @include inlining
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
const INCLUDE_RE = /^@include\s+(\S+)\s*$/m;
|
|
76
|
+
/**
|
|
77
|
+
* Inline any `@include <filename>` directive found in `body`.
|
|
78
|
+
* The included file is looked up via the same scope-resolution chain; if it
|
|
79
|
+
* cannot be found, the directive line is replaced with an empty string rather
|
|
80
|
+
* than throwing — callers that need the kernel should use `loadKernel()`
|
|
81
|
+
* directly and can assert it themselves.
|
|
82
|
+
*/
|
|
83
|
+
function inlineIncludes(body) {
|
|
84
|
+
return body.replace(INCLUDE_RE, (_match, filename) => {
|
|
85
|
+
const path = resolveFile(filename);
|
|
86
|
+
if (!path)
|
|
87
|
+
return '';
|
|
88
|
+
const src = readFileSync(path, 'utf8');
|
|
89
|
+
// The kernel file has no frontmatter, but run through the parser just in
|
|
90
|
+
// case someone added one in a user/project override.
|
|
91
|
+
const { body: kernelBody } = parseFrontmatterGeneric(src);
|
|
92
|
+
return kernelBody.trim();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Load and parse a persona file for the given `kind` and `mode`.
|
|
97
|
+
*
|
|
98
|
+
* Returns `null` when no file is found in any scope (project/user/builtin).
|
|
99
|
+
* On success, `@include` directives in the body are resolved and inlined.
|
|
100
|
+
*/
|
|
101
|
+
export function loadPersona(kind, mode) {
|
|
102
|
+
const relativePath = `${kind}/${mode}.md`;
|
|
103
|
+
const filePath = resolveFile(relativePath);
|
|
104
|
+
if (!filePath)
|
|
105
|
+
return null;
|
|
106
|
+
const src = readFileSync(filePath, 'utf8');
|
|
107
|
+
const { data, body } = parseFrontmatterGeneric(src);
|
|
108
|
+
return {
|
|
109
|
+
frontmatter: data,
|
|
110
|
+
body: inlineIncludes(body).trim(),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Load the raw text of the orchestration kernel (no frontmatter, body only).
|
|
115
|
+
* Returns an empty string if the kernel file cannot be found.
|
|
116
|
+
*/
|
|
117
|
+
export function loadKernel() {
|
|
118
|
+
const filePath = resolveFile('orchestration-kernel.md');
|
|
119
|
+
if (!filePath)
|
|
120
|
+
return '';
|
|
121
|
+
const src = readFileSync(filePath, 'utf8');
|
|
122
|
+
const { body } = parseFrontmatterGeneric(src);
|
|
123
|
+
return body.trim();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Load the base runtime prompt — the node operating protocol prepended to
|
|
127
|
+
* EVERY persona (push/finish/delegate/feed/ask). Returns '' if not found.
|
|
128
|
+
*/
|
|
129
|
+
export function loadRuntimeBase() {
|
|
130
|
+
const filePath = resolveFile('runtime-base.md');
|
|
131
|
+
if (!filePath)
|
|
132
|
+
return '';
|
|
133
|
+
const src = readFileSync(filePath, 'utf8');
|
|
134
|
+
const { body } = parseFrontmatterGeneric(src);
|
|
135
|
+
return body.trim();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Enumerate the kinds with at least one persona file (base.md or
|
|
139
|
+
* orchestrator.md) across all scope roots (project/user/builtin). Used to
|
|
140
|
+
* validate a requested `--kind` and to list the valid choices.
|
|
141
|
+
*/
|
|
142
|
+
export function availableKinds() {
|
|
143
|
+
const kinds = new Set();
|
|
144
|
+
for (const root of personaSearchRoots()) {
|
|
145
|
+
if (!existsSync(root))
|
|
146
|
+
continue;
|
|
147
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
148
|
+
if (!entry.isDirectory())
|
|
149
|
+
continue;
|
|
150
|
+
const dir = join(root, entry.name);
|
|
151
|
+
if (existsSync(join(dir, 'base.md')) || existsSync(join(dir, 'orchestrator.md'))) {
|
|
152
|
+
kinds.add(entry.name);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return [...kinds].sort();
|
|
157
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona resolver — composes base + orchestrator personas into a
|
|
3
|
+
* ResolvedPersona ready for use when spawning a canvas node.
|
|
4
|
+
*
|
|
5
|
+
* Composition rules:
|
|
6
|
+
* mode==='base'
|
|
7
|
+
* → load <kind>/base.md; if missing fall back to general defaults.
|
|
8
|
+
*
|
|
9
|
+
* mode==='orchestrator'
|
|
10
|
+
* → prefer <kind>/orchestrator.md (which must embed the kernel via
|
|
11
|
+
* @include orchestration-kernel.md — inlined by the loader).
|
|
12
|
+
* If no orchestrator.md exists for this kind, compose:
|
|
13
|
+
* <kind>/base.md body + '\n\n' + kernel body
|
|
14
|
+
* If even the base is missing, fall back to general defaults + kernel.
|
|
15
|
+
*
|
|
16
|
+
* Frontmatter from whichever file is the primary source (orchestrator.md >
|
|
17
|
+
* base.md) supplies model/lifecycle/skills/extensions/tools.
|
|
18
|
+
*
|
|
19
|
+
* Lifecycle defaults:
|
|
20
|
+
* base → 'terminal'
|
|
21
|
+
* orchestrator → 'resident'
|
|
22
|
+
*/
|
|
23
|
+
export interface ResolvedPersona {
|
|
24
|
+
systemPrompt: string;
|
|
25
|
+
extensions: string[];
|
|
26
|
+
skills: string[];
|
|
27
|
+
model?: string;
|
|
28
|
+
lifecycle: 'terminal' | 'resident';
|
|
29
|
+
tools?: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a fully composed persona for the given `kind` and `mode`.
|
|
33
|
+
*
|
|
34
|
+
* Never throws for missing files — missing personas produce sensible defaults.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolve(kind: string, mode: 'base' | 'orchestrator'): ResolvedPersona;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona resolver — composes base + orchestrator personas into a
|
|
3
|
+
* ResolvedPersona ready for use when spawning a canvas node.
|
|
4
|
+
*
|
|
5
|
+
* Composition rules:
|
|
6
|
+
* mode==='base'
|
|
7
|
+
* → load <kind>/base.md; if missing fall back to general defaults.
|
|
8
|
+
*
|
|
9
|
+
* mode==='orchestrator'
|
|
10
|
+
* → prefer <kind>/orchestrator.md (which must embed the kernel via
|
|
11
|
+
* @include orchestration-kernel.md — inlined by the loader).
|
|
12
|
+
* If no orchestrator.md exists for this kind, compose:
|
|
13
|
+
* <kind>/base.md body + '\n\n' + kernel body
|
|
14
|
+
* If even the base is missing, fall back to general defaults + kernel.
|
|
15
|
+
*
|
|
16
|
+
* Frontmatter from whichever file is the primary source (orchestrator.md >
|
|
17
|
+
* base.md) supplies model/lifecycle/skills/extensions/tools.
|
|
18
|
+
*
|
|
19
|
+
* Lifecycle defaults:
|
|
20
|
+
* base → 'terminal'
|
|
21
|
+
* orchestrator → 'resident'
|
|
22
|
+
*/
|
|
23
|
+
import { loadPersona, loadKernel, loadRuntimeBase } from './loader.js';
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
function toStringArray(v) {
|
|
28
|
+
if (!Array.isArray(v))
|
|
29
|
+
return [];
|
|
30
|
+
return v.filter((x) => typeof x === 'string');
|
|
31
|
+
}
|
|
32
|
+
function toOptionalString(v) {
|
|
33
|
+
return typeof v === 'string' ? v : undefined;
|
|
34
|
+
}
|
|
35
|
+
function toLifecycle(v, defaultValue) {
|
|
36
|
+
if (v === 'terminal' || v === 'resident')
|
|
37
|
+
return v;
|
|
38
|
+
return defaultValue;
|
|
39
|
+
}
|
|
40
|
+
/** The bare-minimum system prompt used when no persona file is found at all. */
|
|
41
|
+
function fallbackBasePrompt(kind) {
|
|
42
|
+
return `You are a ${kind} agent. Complete the task you have been given.`;
|
|
43
|
+
}
|
|
44
|
+
/** Prepend the base runtime protocol (push/finish/delegate/feed/ask) to a
|
|
45
|
+
* persona's prompt — every node, every kind, every mode gets it first. */
|
|
46
|
+
function withBase(personaPrompt) {
|
|
47
|
+
const base = loadRuntimeBase();
|
|
48
|
+
return base ? `${base}\n\n---\n\n${personaPrompt}` : personaPrompt;
|
|
49
|
+
}
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Public API
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a fully composed persona for the given `kind` and `mode`.
|
|
55
|
+
*
|
|
56
|
+
* Never throws for missing files — missing personas produce sensible defaults.
|
|
57
|
+
*/
|
|
58
|
+
export function resolve(kind, mode) {
|
|
59
|
+
if (mode === 'base') {
|
|
60
|
+
const persona = loadPersona(kind, 'base');
|
|
61
|
+
if (!persona) {
|
|
62
|
+
// No persona file for this kind — use minimal defaults.
|
|
63
|
+
return {
|
|
64
|
+
systemPrompt: withBase(fallbackBasePrompt(kind)),
|
|
65
|
+
extensions: [],
|
|
66
|
+
skills: [],
|
|
67
|
+
lifecycle: 'terminal',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const fm = persona.frontmatter ?? {};
|
|
71
|
+
return {
|
|
72
|
+
systemPrompt: withBase(persona.body || fallbackBasePrompt(kind)),
|
|
73
|
+
extensions: toStringArray(fm['extensions']),
|
|
74
|
+
skills: toStringArray(fm['skills']),
|
|
75
|
+
model: toOptionalString(fm['model']),
|
|
76
|
+
lifecycle: toLifecycle(fm['lifecycle'], 'terminal'),
|
|
77
|
+
tools: fm['tools'] !== undefined ? toStringArray(fm['tools']) : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// mode === 'orchestrator'
|
|
81
|
+
const orchestratorPersona = loadPersona(kind, 'orchestrator');
|
|
82
|
+
if (orchestratorPersona) {
|
|
83
|
+
// Orchestrator file exists; @include was already inlined by the loader.
|
|
84
|
+
const fm = orchestratorPersona.frontmatter ?? {};
|
|
85
|
+
return {
|
|
86
|
+
systemPrompt: withBase(orchestratorPersona.body || fallbackBasePrompt(kind)),
|
|
87
|
+
extensions: toStringArray(fm['extensions']),
|
|
88
|
+
skills: toStringArray(fm['skills']),
|
|
89
|
+
model: toOptionalString(fm['model']),
|
|
90
|
+
lifecycle: toLifecycle(fm['lifecycle'], 'resident'),
|
|
91
|
+
tools: fm['tools'] !== undefined ? toStringArray(fm['tools']) : undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// No orchestrator.md for this kind — compose base + bare kernel.
|
|
95
|
+
const kernel = loadKernel();
|
|
96
|
+
const basePersona = loadPersona(kind, 'base');
|
|
97
|
+
const baseBody = basePersona?.body || fallbackBasePrompt(kind);
|
|
98
|
+
const fm = basePersona?.frontmatter ?? {};
|
|
99
|
+
// Append the kernel to the base body (with separator if kernel is non-empty).
|
|
100
|
+
const systemPrompt = kernel ? `${baseBody}\n\n${kernel}` : baseBody;
|
|
101
|
+
return {
|
|
102
|
+
systemPrompt: withBase(systemPrompt),
|
|
103
|
+
extensions: toStringArray(fm['extensions']),
|
|
104
|
+
skills: toStringArray(fm['skills']),
|
|
105
|
+
model: toOptionalString(fm['model']),
|
|
106
|
+
// Override lifecycle to 'resident' — this node is being used as an orchestrator.
|
|
107
|
+
lifecycle: 'resident',
|
|
108
|
+
tools: fm['tools'] !== undefined ? toStringArray(fm['tools']) : undefined,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LeafHelp } from './help.js';
|
|
2
|
+
import type { ErrorPayload } from './io.js';
|
|
3
|
+
/** Schema-driven fallback: turn a result object into agent-ready XML+markdown.
|
|
4
|
+
* Field order follows the leaf's declared output schema; any extra keys append
|
|
5
|
+
* after. Scalars become attributes or a bullet list, prose and collections
|
|
6
|
+
* become their own fenced blocks. */
|
|
7
|
+
export declare function renderResult(result: Record<string, unknown>, help: LeafHelp): string;
|
|
8
|
+
/** Render a structured failure as an instruction-shaped block: what broke, what
|
|
9
|
+
* was received, and the recovery road sign — the same recovery info the JSON
|
|
10
|
+
* payload carries, shaped for the model to act on. */
|
|
11
|
+
export declare function renderError(p: ErrorPayload): string;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Default stdout rendering: the result a leaf returns is written FOR the model
|
|
2
|
+
// to act on, not as data to parse. Output is a continuation of the agent's
|
|
3
|
+
// prompt — light XML fences around markdown, prose where prose belongs. The
|
|
4
|
+
// raw JSON object is still available behind the `--json` global for tooling.
|
|
5
|
+
//
|
|
6
|
+
// A leaf may hand `defineLeaf` a bespoke `render(result)` for instruction-shaped
|
|
7
|
+
// output (see `node new`, `push`, `feed read`). Everything else falls through to
|
|
8
|
+
// the schema-driven generic renderer here, so every command obeys the paradigm
|
|
9
|
+
// without a hand-written renderer.
|
|
10
|
+
// Field names whose value is prose the agent reads, not a scalar it keys on —
|
|
11
|
+
// these always render as their own fenced block, never as a tag attribute.
|
|
12
|
+
const PROSE_FIELDS = new Set([
|
|
13
|
+
'follow_up', 'digest', 'message', 'next', 'result', 'content', 'body', 'guide',
|
|
14
|
+
'summary', 'note', 'reason', 'detail', 'details', 'hint', 'instruction',
|
|
15
|
+
'instructions', 'prompt', 'roadmap', 'report', 'answer', 'response', 'text',
|
|
16
|
+
]);
|
|
17
|
+
/** Minimal attribute-value sanitization: values here are controlled (ids,
|
|
18
|
+
* statuses, counts), so collapse quotes rather than entity-escape. */
|
|
19
|
+
function attrEsc(s) {
|
|
20
|
+
return String(s).replace(/"/g, "'").replace(/\n/g, ' ');
|
|
21
|
+
}
|
|
22
|
+
function scalarStr(v) {
|
|
23
|
+
if (v === null || v === undefined)
|
|
24
|
+
return '';
|
|
25
|
+
if (Array.isArray(v))
|
|
26
|
+
return v.map(scalarStr).join(', ');
|
|
27
|
+
if (typeof v === 'object')
|
|
28
|
+
return JSON.stringify(v);
|
|
29
|
+
return String(v);
|
|
30
|
+
}
|
|
31
|
+
/** A string field is prose when it is named as such, spans lines, or is long
|
|
32
|
+
* enough that it reads as a sentence rather than an identifier. */
|
|
33
|
+
function isProse(name, v) {
|
|
34
|
+
if (typeof v !== 'string')
|
|
35
|
+
return false;
|
|
36
|
+
if (PROSE_FIELDS.has(name))
|
|
37
|
+
return true;
|
|
38
|
+
return v.includes('\n') || v.length > 80;
|
|
39
|
+
}
|
|
40
|
+
/** Short, space-free scalars ride as attributes on the open tag (ids, statuses,
|
|
41
|
+
* counts); everything else that isn't prose lists as a markdown bullet. */
|
|
42
|
+
function isAttr(v) {
|
|
43
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
44
|
+
return true;
|
|
45
|
+
return typeof v === 'string' && v.length <= 40 && !v.includes(' ') && !v.includes('\n');
|
|
46
|
+
}
|
|
47
|
+
/** Root element name = the command path with spaces hyphenated. */
|
|
48
|
+
function rootTag(help) {
|
|
49
|
+
return help.name.trim().split(/\s+/).join('-').replace(/[^a-zA-Z0-9_-]/g, '') || 'result';
|
|
50
|
+
}
|
|
51
|
+
function renderArray(name, arr) {
|
|
52
|
+
if (arr.length === 0)
|
|
53
|
+
return `<${name} count="0"></${name}>`;
|
|
54
|
+
const allObjects = arr.every((x) => x !== null && typeof x === 'object' && !Array.isArray(x));
|
|
55
|
+
if (allObjects) {
|
|
56
|
+
const rows = arr;
|
|
57
|
+
const cols = [];
|
|
58
|
+
for (const r of rows)
|
|
59
|
+
for (const k of Object.keys(r))
|
|
60
|
+
if (!cols.includes(k))
|
|
61
|
+
cols.push(k);
|
|
62
|
+
const head = `| ${cols.join(' | ')} |`;
|
|
63
|
+
const sep = `| ${cols.map(() => '---').join(' | ')} |`;
|
|
64
|
+
const body = rows.map((r) => `| ${cols.map((c) => scalarStr(r[c])).join(' | ')} |`).join('\n');
|
|
65
|
+
return `<${name} count="${arr.length}">\n${head}\n${sep}\n${body}\n</${name}>`;
|
|
66
|
+
}
|
|
67
|
+
const items = arr.map((x) => `- ${scalarStr(x)}`).join('\n');
|
|
68
|
+
return `<${name} count="${arr.length}">\n${items}\n</${name}>`;
|
|
69
|
+
}
|
|
70
|
+
/** Schema-driven fallback: turn a result object into agent-ready XML+markdown.
|
|
71
|
+
* Field order follows the leaf's declared output schema; any extra keys append
|
|
72
|
+
* after. Scalars become attributes or a bullet list, prose and collections
|
|
73
|
+
* become their own fenced blocks. */
|
|
74
|
+
export function renderResult(result, help) {
|
|
75
|
+
const order = help.output.map((f) => f.name);
|
|
76
|
+
for (const k of Object.keys(result))
|
|
77
|
+
if (!order.includes(k))
|
|
78
|
+
order.push(k);
|
|
79
|
+
const attrs = [];
|
|
80
|
+
const bullets = [];
|
|
81
|
+
const blocks = [];
|
|
82
|
+
for (const name of order) {
|
|
83
|
+
const v = result[name];
|
|
84
|
+
if (v === undefined || v === null)
|
|
85
|
+
continue;
|
|
86
|
+
if (Array.isArray(v)) {
|
|
87
|
+
blocks.push(renderArray(name, v));
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (typeof v === 'object') {
|
|
91
|
+
blocks.push(`<${name}>\n${JSON.stringify(v, null, 2)}\n</${name}>`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (isProse(name, v)) {
|
|
95
|
+
blocks.push(`<${name}>\n${v}\n</${name}>`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (isAttr(v)) {
|
|
99
|
+
attrs.push(`${name}="${attrEsc(v)}"`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
bullets.push(`- ${name}: ${scalarStr(v)}`);
|
|
103
|
+
}
|
|
104
|
+
const tag = rootTag(help);
|
|
105
|
+
const open = attrs.length > 0 ? `<${tag} ${attrs.join(' ')}>` : `<${tag}>`;
|
|
106
|
+
const parts = [];
|
|
107
|
+
if (bullets.length > 0)
|
|
108
|
+
parts.push(bullets.join('\n'));
|
|
109
|
+
if (blocks.length > 0)
|
|
110
|
+
parts.push(blocks.join('\n\n'));
|
|
111
|
+
const inner = parts.join('\n\n');
|
|
112
|
+
return inner !== '' ? `${open}\n${inner}\n</${tag}>` : `${open}</${tag}>`;
|
|
113
|
+
}
|
|
114
|
+
/** Render a structured failure as an instruction-shaped block: what broke, what
|
|
115
|
+
* was received, and the recovery road sign — the same recovery info the JSON
|
|
116
|
+
* payload carries, shaped for the model to act on. */
|
|
117
|
+
export function renderError(p) {
|
|
118
|
+
const lines = [p.message];
|
|
119
|
+
if (p.received !== undefined && p.received !== null && p.received !== '') {
|
|
120
|
+
lines.push(`received: ${scalarStr(p.received)}`);
|
|
121
|
+
}
|
|
122
|
+
if (p.field !== undefined)
|
|
123
|
+
lines.push(`field: ${p.field}`);
|
|
124
|
+
lines.push(`Next: ${p.next}`);
|
|
125
|
+
return `<error code="${attrEsc(p.error)}">\n${lines.join('\n')}\n</error>`;
|
|
126
|
+
}
|
package/dist/core/resolver.d.ts
CHANGED
|
@@ -28,5 +28,15 @@ export declare function parseSkillQualifier(raw: string): ParsedSkillQualifier;
|
|
|
28
28
|
export declare function listInstalledMarketplaces(scope: Scope): InstalledMarketplace[];
|
|
29
29
|
export declare function listAllMarketplaces(): InstalledMarketplace[];
|
|
30
30
|
export declare function findMarketplaceByName(name: string, scope?: Scope): InstalledMarketplace | null;
|
|
31
|
+
export interface CategoryResolution {
|
|
32
|
+
id: string;
|
|
33
|
+
plugin: string | undefined;
|
|
34
|
+
scope: Scope | undefined;
|
|
35
|
+
dir: string;
|
|
36
|
+
indexPath: string | undefined;
|
|
37
|
+
skills: Skill[];
|
|
38
|
+
}
|
|
39
|
+
export declare function resolveCategory(name: string, opts?: SkillResolutionOpts): CategoryResolution | null;
|
|
40
|
+
export declare function buildCategoryIndex(cat: CategoryResolution): string;
|
|
31
41
|
export declare function scopeRootsLabel(): string;
|
|
32
42
|
export {};
|
package/dist/core/resolver.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join, relative, sep, dirname } from 'node:path';
|
|
2
2
|
import { SCOPE_SKILL_PLUGIN, SKILL_ENTRY_FILE, SKILLS_DIR, skillConfigKey, } from '../types.js';
|
|
3
3
|
import { readConfig } from './config.js';
|
|
4
|
-
import { listDirs, pathExists, readText, walkFiles, } from './fs-utils.js';
|
|
4
|
+
import { listDirs, pathExists, readText, readTextIfExists, walkFiles, } from './fs-utils.js';
|
|
5
5
|
import { readMarketplaceManifest, readPluginManifest } from './manifest.js';
|
|
6
6
|
import { parseFrontmatter } from './frontmatter.js';
|
|
7
7
|
import { ambiguous, notFound, usage } from './errors.js';
|
|
@@ -534,6 +534,114 @@ export function findMarketplaceByName(name, scope) {
|
|
|
534
534
|
}
|
|
535
535
|
return null;
|
|
536
536
|
}
|
|
537
|
+
export function resolveCategory(name, opts = {}) {
|
|
538
|
+
const parsed = parseSkillQualifier(name);
|
|
539
|
+
if (parsed.segments.length === 0)
|
|
540
|
+
return null;
|
|
541
|
+
const effectiveScope = opts.scope ?? parsed.scope;
|
|
542
|
+
let pluginQualifier;
|
|
543
|
+
let subpath;
|
|
544
|
+
if (opts.pluginFilter !== undefined) {
|
|
545
|
+
pluginQualifier = opts.pluginFilter;
|
|
546
|
+
const sub = parsed.segments.join('/');
|
|
547
|
+
subpath = sub || undefined;
|
|
548
|
+
}
|
|
549
|
+
else if (parsed.segments.length > 1) {
|
|
550
|
+
const maybePlugin = parsed.segments[0];
|
|
551
|
+
const pluginMatch = findPluginByName(maybePlugin, effectiveScope) ??
|
|
552
|
+
(effectiveScope === undefined ? null : findPluginByName(maybePlugin));
|
|
553
|
+
if (pluginMatch !== null) {
|
|
554
|
+
pluginQualifier = maybePlugin;
|
|
555
|
+
subpath = parsed.segments.slice(1).join('/');
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
pluginQualifier = undefined;
|
|
559
|
+
subpath = parsed.segments.join('/');
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
const maybePlugin = parsed.segments[0];
|
|
564
|
+
const pluginMatch = findPluginByName(maybePlugin, effectiveScope) ??
|
|
565
|
+
(effectiveScope === undefined ? null : findPluginByName(maybePlugin));
|
|
566
|
+
if (pluginMatch !== null) {
|
|
567
|
+
pluginQualifier = maybePlugin;
|
|
568
|
+
subpath = undefined;
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
pluginQualifier = undefined;
|
|
572
|
+
subpath = maybePlugin;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
let skills;
|
|
576
|
+
let dir;
|
|
577
|
+
let id;
|
|
578
|
+
let resolvedScope;
|
|
579
|
+
if (pluginQualifier !== undefined) {
|
|
580
|
+
const plugin = findPluginByName(pluginQualifier, effectiveScope) ??
|
|
581
|
+
(effectiveScope === undefined ? null : findPluginByName(pluginQualifier));
|
|
582
|
+
if (!plugin)
|
|
583
|
+
return null;
|
|
584
|
+
resolvedScope = plugin.scope;
|
|
585
|
+
const allPluginSkills = listSkillsInPlugin(plugin);
|
|
586
|
+
if (subpath === undefined) {
|
|
587
|
+
skills = allPluginSkills;
|
|
588
|
+
dir = join(plugin.root, SKILLS_DIR);
|
|
589
|
+
id = pluginQualifier;
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
skills = allPluginSkills.filter((s) => s.name.startsWith(subpath + '/'));
|
|
593
|
+
dir = join(plugin.root, SKILLS_DIR, ...subpath.split('/'));
|
|
594
|
+
id = `${pluginQualifier}/${subpath}`;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
else if (subpath !== undefined) {
|
|
598
|
+
const scope = effectiveScope ?? 'user';
|
|
599
|
+
resolvedScope = scope;
|
|
600
|
+
const skillsRoot = scopeSkillsDir(scope);
|
|
601
|
+
if (!skillsRoot)
|
|
602
|
+
return null;
|
|
603
|
+
const allScopeSkills = listScopeRootSkills(scope);
|
|
604
|
+
skills = allScopeSkills.filter((s) => s.name.startsWith(subpath + '/'));
|
|
605
|
+
dir = join(skillsRoot, ...subpath.split('/'));
|
|
606
|
+
id = `${scope}/${subpath}`;
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
if (skills.length === 0)
|
|
612
|
+
return null;
|
|
613
|
+
const indexMd = join(dir, 'index.md');
|
|
614
|
+
const indexPath = pathExists(indexMd) ? indexMd : undefined;
|
|
615
|
+
return { id, plugin: pluginQualifier, scope: resolvedScope, dir, indexPath, skills };
|
|
616
|
+
}
|
|
617
|
+
export function buildCategoryIndex(cat) {
|
|
618
|
+
const lines = [];
|
|
619
|
+
lines.push(`# ${cat.id} — ${cat.skills.length} skills`);
|
|
620
|
+
lines.push('');
|
|
621
|
+
if (cat.indexPath !== undefined) {
|
|
622
|
+
const authored = readTextIfExists(cat.indexPath);
|
|
623
|
+
if (authored !== null) {
|
|
624
|
+
const { body } = parseFrontmatter(authored);
|
|
625
|
+
const trimmed = body.trim();
|
|
626
|
+
if (trimmed.length > 0) {
|
|
627
|
+
lines.push(trimmed);
|
|
628
|
+
lines.push('');
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
lines.push('## Skills');
|
|
633
|
+
const sorted = [...cat.skills].sort((a, b) => a.name.localeCompare(b.name));
|
|
634
|
+
for (const skill of sorted) {
|
|
635
|
+
const fullId = skill.plugin === SCOPE_SKILL_PLUGIN
|
|
636
|
+
? `${skill.scope}/${skill.name}`
|
|
637
|
+
: `${skill.plugin}/${skill.name}`;
|
|
638
|
+
const desc = skill.frontmatter.description ?? '(no description)';
|
|
639
|
+
lines.push(`- \`${fullId}\` — ${desc}`);
|
|
640
|
+
}
|
|
641
|
+
lines.push('');
|
|
642
|
+
lines.push(`Read one with \`crtr skill read <full-id>\`. Narrow with \`crtr skill find list --plugin ${cat.plugin ?? cat.id}\`.`);
|
|
643
|
+
return lines.join('\n');
|
|
644
|
+
}
|
|
537
645
|
export function scopeRootsLabel() {
|
|
538
646
|
const proj = projectScopeRoot();
|
|
539
647
|
return proj ? `project=${proj}, user=${userScopeRoot()}` : `user=${userScopeRoot()}`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RootDef } from './../command.js';
|
|
2
|
+
/** Env marker set on every pi the front door boots. Its presence means we are
|
|
3
|
+
* already inside a front-door-booted root, so a nested front-door launch must
|
|
4
|
+
* be refused — otherwise a removed/renamed subcommand that a child pi re-runs
|
|
5
|
+
* (e.g. `crtr node -h`) fork-bombs pi until the machine must be rebooted. */
|
|
6
|
+
export declare const FRONT_DOOR_ENV = "CRTR_FRONT_DOOR";
|
|
7
|
+
/** If this invocation is a front-door (root) launch, boot it and never return.
|
|
8
|
+
* Returns false when it's a recognized subcommand / help / unknown token (let
|
|
9
|
+
* the dispatcher handle it — for unknown tokens it errors cleanly). */
|
|
10
|
+
export declare function maybeBootRoot(root: RootDef, argv: string[]): boolean;
|