@crouton-kit/crouter 0.1.3 → 0.1.6
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/README.md +14 -1
- package/dist/cli.js +5 -0
- package/dist/commands/agent.d.ts +2 -0
- package/dist/commands/agent.js +265 -0
- package/dist/commands/config.js +13 -1
- package/dist/commands/plan.js +17 -1
- package/dist/commands/skill.js +62 -16
- package/dist/commands/spec.js +4 -0
- package/dist/core/artifact.d.ts +12 -0
- package/dist/core/artifact.js +68 -6
- package/dist/core/bootstrap.d.ts +5 -0
- package/dist/core/bootstrap.js +149 -0
- package/dist/core/config.js +6 -1
- package/dist/core/resolver.d.ts +1 -0
- package/dist/core/resolver.js +66 -6
- package/dist/core/scope.d.ts +1 -0
- package/dist/core/scope.js +4 -0
- package/dist/core/spawn.d.ts +95 -0
- package/dist/core/spawn.js +309 -0
- package/dist/prompts/agent.d.ts +13 -0
- package/dist/prompts/agent.js +114 -0
- package/dist/prompts/plan.js +39 -3
- package/dist/prompts/review.d.ts +2 -0
- package/dist/prompts/review.js +103 -0
- package/dist/prompts/skill.d.ts +1 -0
- package/dist/prompts/skill.js +240 -8
- package/dist/prompts/spec.js +28 -3
- package/dist/types.d.ts +4 -0
- package/dist/types.js +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { userScopeRoot } from './scope.js';
|
|
5
|
+
import { ensureDir, pathExists, readText, removePath, nowIso } from './fs-utils.js';
|
|
6
|
+
import { readConfig, readState, updateConfig, updateState, ensureScopeInitialized } from './config.js';
|
|
7
|
+
import { clone } from './git.js';
|
|
8
|
+
import { readMarketplaceManifest } from './manifest.js';
|
|
9
|
+
export const OFFICIAL_MARKETPLACE_NAME = 'crouter-official-marketplace';
|
|
10
|
+
export const OFFICIAL_MARKETPLACE_URL = 'https://github.com/crouton-labs/crouter-official-marketplace.git';
|
|
11
|
+
export const OFFICIAL_MARKETPLACE_REF = 'main';
|
|
12
|
+
const SKIP_SUBCOMMANDS = new Set([
|
|
13
|
+
'help',
|
|
14
|
+
'--help',
|
|
15
|
+
'-h',
|
|
16
|
+
'--version',
|
|
17
|
+
'-v',
|
|
18
|
+
]);
|
|
19
|
+
function shouldSkipForArgv(argv) {
|
|
20
|
+
const sub = argv[2];
|
|
21
|
+
if (sub === undefined)
|
|
22
|
+
return true;
|
|
23
|
+
return SKIP_SUBCOMMANDS.has(sub);
|
|
24
|
+
}
|
|
25
|
+
const BOOT_SKILL_NAME = 'crtr-skills';
|
|
26
|
+
const BOOT_SKILL_MARKER = '<!-- crtr-boot-skill v1 -->';
|
|
27
|
+
function bootSkillBody() {
|
|
28
|
+
return `---
|
|
29
|
+
name: crtr-skills
|
|
30
|
+
description: Capture, list, search, and load skills via the crtr CLI. Use when the user wants to remember something, save knowledge, build a context primer, or recall a previously saved skill. Triggers: "save", "remember", "build context for", "what skills do we have", "skill for X".
|
|
31
|
+
argument-hint: [topic or verb]
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
${BOOT_SKILL_MARKER}
|
|
35
|
+
|
|
36
|
+
# /crtr:skills — the skill router
|
|
37
|
+
|
|
38
|
+
\`crtr\` is the source of truth for skills on this machine. Every skill the
|
|
39
|
+
agent should know about is discoverable via \`crtr skill\`. This file is a
|
|
40
|
+
thin router; the CLI is the index.
|
|
41
|
+
|
|
42
|
+
## What the user is asking for
|
|
43
|
+
|
|
44
|
+
- **Capture new knowledge** ("save this", "remember", "build context for X",
|
|
45
|
+
"make a skill that…"): run \`crtr skill create $ARGUMENTS\` and follow the
|
|
46
|
+
walkthrough it prints. It picks a template (primer/preference/runbook/
|
|
47
|
+
glossary/decision/freeform) and walks you through scoping, researching,
|
|
48
|
+
and scaffolding.
|
|
49
|
+
- **Find a relevant skill** ("what do we have on X"): run
|
|
50
|
+
\`crtr skill search "$ARGUMENTS"\` and load the best hit with
|
|
51
|
+
\`crtr skill show <name>\`.
|
|
52
|
+
- **Load a known skill by name**: run \`crtr skill show <name>\`.
|
|
53
|
+
- **List everything**: run \`crtr skill list\`.
|
|
54
|
+
- **Anything else skill-related**: run \`crtr skill\` (no args) — it prints
|
|
55
|
+
the full skill workflow guide. Follow it.
|
|
56
|
+
|
|
57
|
+
\`$ARGUMENTS\` is the user's request as a string. Use it to seed the topic for
|
|
58
|
+
\`create\` or the query for \`search\`. If it's empty, ask the user what they
|
|
59
|
+
want before running anything.
|
|
60
|
+
|
|
61
|
+
## Output rules
|
|
62
|
+
|
|
63
|
+
The CLI's stdout is the prompt. Read it, then act on it. Don't paraphrase the
|
|
64
|
+
guidance back at the user — just do the work it describes.
|
|
65
|
+
|
|
66
|
+
If \`crtr\` isn't on PATH, tell the user and stop. This skill assumes
|
|
67
|
+
\`@crouton-kit/crouter\` is installed globally.
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
export function ensureBootSkill(argv) {
|
|
71
|
+
try {
|
|
72
|
+
if (process.env.CRTR_NO_BOOT_SKILL === '1')
|
|
73
|
+
return;
|
|
74
|
+
if (shouldSkipForArgv(argv))
|
|
75
|
+
return;
|
|
76
|
+
const claudeSkillsRoot = join(homedir(), '.claude', 'skills');
|
|
77
|
+
// Only install if the user actually uses Claude Code (the dir exists or is
|
|
78
|
+
// creatable). We won't create ~/.claude itself; that's not our directory.
|
|
79
|
+
if (!pathExists(join(homedir(), '.claude')))
|
|
80
|
+
return;
|
|
81
|
+
const skillDir = join(claudeSkillsRoot, BOOT_SKILL_NAME);
|
|
82
|
+
const skillFile = join(skillDir, 'SKILL.md');
|
|
83
|
+
if (pathExists(skillFile)) {
|
|
84
|
+
// Idempotent: only rewrite if it's still our marker version.
|
|
85
|
+
const existing = readText(skillFile);
|
|
86
|
+
if (!existing.includes(BOOT_SKILL_MARKER))
|
|
87
|
+
return;
|
|
88
|
+
// Same marker — check if body needs update, otherwise skip.
|
|
89
|
+
if (existing === bootSkillBody())
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
ensureDir(skillDir);
|
|
93
|
+
writeFileSync(skillFile, bootSkillBody(), 'utf8');
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
if (process.env.CRTR_DEBUG === '1') {
|
|
97
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
98
|
+
process.stderr.write(`crtr: boot-skill error: ${msg}\n`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export function ensureOfficialMarketplace(argv) {
|
|
103
|
+
try {
|
|
104
|
+
if (process.env.CRTR_NO_BOOTSTRAP === '1')
|
|
105
|
+
return;
|
|
106
|
+
if (shouldSkipForArgv(argv))
|
|
107
|
+
return;
|
|
108
|
+
const state = readState('user');
|
|
109
|
+
if (state.bootstrap_done === true)
|
|
110
|
+
return;
|
|
111
|
+
const cfg = readConfig('user');
|
|
112
|
+
if (cfg.marketplaces[OFFICIAL_MARKETPLACE_NAME] !== undefined) {
|
|
113
|
+
updateState('user', (s) => {
|
|
114
|
+
s.bootstrap_done = true;
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const root = userScopeRoot();
|
|
119
|
+
ensureScopeInitialized('user', root);
|
|
120
|
+
const mktsDir = join(root, 'marketplaces');
|
|
121
|
+
ensureDir(mktsDir);
|
|
122
|
+
const dest = join(mktsDir, OFFICIAL_MARKETPLACE_NAME);
|
|
123
|
+
if (pathExists(dest)) {
|
|
124
|
+
removePath(dest);
|
|
125
|
+
}
|
|
126
|
+
clone(OFFICIAL_MARKETPLACE_URL, dest, { depth: 1, ref: OFFICIAL_MARKETPLACE_REF });
|
|
127
|
+
const manifest = readMarketplaceManifest(dest);
|
|
128
|
+
if (manifest === null) {
|
|
129
|
+
removePath(dest);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
updateConfig('user', (c) => {
|
|
133
|
+
c.marketplaces[OFFICIAL_MARKETPLACE_NAME] = {
|
|
134
|
+
url: OFFICIAL_MARKETPLACE_URL,
|
|
135
|
+
ref: OFFICIAL_MARKETPLACE_REF,
|
|
136
|
+
installed_at: nowIso(),
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
updateState('user', (s) => {
|
|
140
|
+
s.bootstrap_done = true;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
if (process.env.CRTR_DEBUG === '1') {
|
|
145
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
146
|
+
process.stderr.write(`crtr: bootstrap error: ${msg}\n`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
package/dist/core/config.js
CHANGED
|
@@ -36,6 +36,7 @@ export function readState(scope) {
|
|
|
36
36
|
marketplaces: existing.marketplaces ?? {},
|
|
37
37
|
plugins: existing.plugins ?? {},
|
|
38
38
|
last_self_check: existing.last_self_check,
|
|
39
|
+
bootstrap_done: existing.bootstrap_done,
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
export function writeConfig(scope, config) {
|
|
@@ -80,7 +81,11 @@ function mergeConfig(partial) {
|
|
|
80
81
|
content: normalizeMode(au?.content, defaults.auto_update.content),
|
|
81
82
|
interval_hours,
|
|
82
83
|
};
|
|
83
|
-
|
|
84
|
+
const rawMaxPanes = partial.max_panes_per_window;
|
|
85
|
+
const max_panes_per_window = typeof rawMaxPanes === 'number' && Number.isFinite(rawMaxPanes) && rawMaxPanes >= 1
|
|
86
|
+
? Math.floor(rawMaxPanes)
|
|
87
|
+
: defaults.max_panes_per_window;
|
|
88
|
+
return { schema_version, marketplaces, plugins, skills, auto_update, max_panes_per_window };
|
|
84
89
|
}
|
|
85
90
|
export function updateConfig(scope, mutate) {
|
|
86
91
|
const cfg = readConfig(scope);
|
package/dist/core/resolver.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare function effectiveSkillEnabled(pluginName: string, skillName: str
|
|
|
11
11
|
disabledIn?: Scope;
|
|
12
12
|
};
|
|
13
13
|
export declare function listSkillsInPlugin(plugin: InstalledPlugin, cfgs?: ScopeConfigs): Skill[];
|
|
14
|
+
export declare function listScopeRootSkills(scope: Scope, cfgs?: ScopeConfigs): Skill[];
|
|
14
15
|
export declare function listAllSkills(scopeFilter?: Scope): Skill[];
|
|
15
16
|
export interface SkillResolutionOpts {
|
|
16
17
|
scope?: Scope;
|
package/dist/core/resolver.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { join, relative, sep, dirname } from 'node:path';
|
|
2
|
-
import { SKILL_ENTRY_FILE, SKILLS_DIR, skillConfigKey, } from '../types.js';
|
|
2
|
+
import { SCOPE_SKILL_PLUGIN, SKILL_ENTRY_FILE, SKILLS_DIR, skillConfigKey, } from '../types.js';
|
|
3
3
|
import { readConfig } from './config.js';
|
|
4
4
|
import { listDirs, pathExists, readText, 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 } from './errors.js';
|
|
8
|
-
import { marketplacesDir, pluginsDir, projectScopeRoot, userScopeRoot } from './scope.js';
|
|
8
|
+
import { marketplacesDir, pluginsDir, projectScopeRoot, scopeSkillsDir, userScopeRoot, } from './scope.js';
|
|
9
9
|
export function listInstalledPlugins(scope) {
|
|
10
10
|
const dir = pluginsDir(scope);
|
|
11
11
|
if (!dir || !pathExists(dir))
|
|
@@ -99,20 +99,80 @@ export function listSkillsInPlugin(plugin, cfgs) {
|
|
|
99
99
|
}
|
|
100
100
|
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
101
101
|
}
|
|
102
|
+
export function listScopeRootSkills(scope, cfgs) {
|
|
103
|
+
const skillsRoot = scopeSkillsDir(scope);
|
|
104
|
+
if (!skillsRoot || !pathExists(skillsRoot))
|
|
105
|
+
return [];
|
|
106
|
+
const configs = cfgs === undefined ? loadScopeConfigs() : cfgs;
|
|
107
|
+
const skills = [];
|
|
108
|
+
const skillFiles = walkFiles(skillsRoot, (n) => n === SKILL_ENTRY_FILE);
|
|
109
|
+
for (const file of skillFiles) {
|
|
110
|
+
const rel = relative(skillsRoot, dirname(file));
|
|
111
|
+
const name = rel.split(sep).join('/');
|
|
112
|
+
if (!name)
|
|
113
|
+
continue;
|
|
114
|
+
const source = readText(file);
|
|
115
|
+
const { data } = parseFrontmatter(source);
|
|
116
|
+
const { enabled, disabledIn } = effectiveSkillEnabled(SCOPE_SKILL_PLUGIN, name, configs);
|
|
117
|
+
skills.push({
|
|
118
|
+
name,
|
|
119
|
+
plugin: SCOPE_SKILL_PLUGIN,
|
|
120
|
+
scope,
|
|
121
|
+
path: file,
|
|
122
|
+
pluginRoot: skillsRoot,
|
|
123
|
+
frontmatter: data === null ? { name } : data,
|
|
124
|
+
enabled,
|
|
125
|
+
disabledIn,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
129
|
+
}
|
|
102
130
|
export function listAllSkills(scopeFilter) {
|
|
103
131
|
const plugins = scopeFilter ? listInstalledPlugins(scopeFilter) : listAllPlugins();
|
|
104
132
|
const cfgs = loadScopeConfigs();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
133
|
+
const scopes = scopeFilter
|
|
134
|
+
? [scopeFilter]
|
|
135
|
+
: [projectScopeRoot() ? 'project' : null, 'user'].filter(Boolean);
|
|
136
|
+
return [
|
|
137
|
+
...scopes.flatMap((s) => listScopeRootSkills(s, cfgs)),
|
|
138
|
+
...plugins.filter((p) => p.enabled).flatMap((p) => listSkillsInPlugin(p, cfgs)),
|
|
139
|
+
];
|
|
108
140
|
}
|
|
109
141
|
export function resolveSkill(rawName, opts = {}) {
|
|
110
142
|
const { plugin: pluginQualifier, name } = parseSkillQualifier(rawName);
|
|
111
143
|
const plugins = opts.scope ? listInstalledPlugins(opts.scope) : listAllPlugins();
|
|
112
144
|
const enabledPlugins = plugins.filter((p) => p.enabled);
|
|
113
145
|
const cfgs = loadScopeConfigs();
|
|
114
|
-
const ordered = orderPluginsByResolution(enabledPlugins);
|
|
115
146
|
const matches = [];
|
|
147
|
+
// Scope-root skills first — they're the user's own captured knowledge.
|
|
148
|
+
if (!opts.pluginFilter &&
|
|
149
|
+
(pluginQualifier === undefined || pluginQualifier === SCOPE_SKILL_PLUGIN)) {
|
|
150
|
+
const scopes = opts.scope
|
|
151
|
+
? [opts.scope]
|
|
152
|
+
: [projectScopeRoot() ? 'project' : null, 'user'].filter(Boolean);
|
|
153
|
+
for (const s of scopes) {
|
|
154
|
+
const skillsRoot = scopeSkillsDir(s);
|
|
155
|
+
if (!skillsRoot)
|
|
156
|
+
continue;
|
|
157
|
+
const skillPath = join(skillsRoot, ...name.split('/'), SKILL_ENTRY_FILE);
|
|
158
|
+
if (!pathExists(skillPath))
|
|
159
|
+
continue;
|
|
160
|
+
const source = readText(skillPath);
|
|
161
|
+
const { data } = parseFrontmatter(source);
|
|
162
|
+
const { enabled, disabledIn } = effectiveSkillEnabled(SCOPE_SKILL_PLUGIN, name, cfgs);
|
|
163
|
+
matches.push({
|
|
164
|
+
name,
|
|
165
|
+
plugin: SCOPE_SKILL_PLUGIN,
|
|
166
|
+
scope: s,
|
|
167
|
+
path: skillPath,
|
|
168
|
+
pluginRoot: skillsRoot,
|
|
169
|
+
frontmatter: data === null ? { name } : data,
|
|
170
|
+
enabled,
|
|
171
|
+
disabledIn,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const ordered = orderPluginsByResolution(enabledPlugins);
|
|
116
176
|
for (const plugin of ordered) {
|
|
117
177
|
if (pluginQualifier && plugin.name !== pluginQualifier)
|
|
118
178
|
continue;
|
package/dist/core/scope.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare function requireScopeRoot(scope: Scope): string;
|
|
|
7
7
|
export declare function ensureProjectScopeRoot(startDir?: string): string;
|
|
8
8
|
export declare function pluginsDir(scope: Scope): string | null;
|
|
9
9
|
export declare function marketplacesDir(scope: Scope): string | null;
|
|
10
|
+
export declare function scopeSkillsDir(scope: Scope): string | null;
|
|
10
11
|
export declare function resolveScopeArg(scopeArg: string | undefined): Scope | 'all';
|
|
11
12
|
export declare function listScopes(scopeArg: string | undefined): Scope[];
|
|
12
13
|
export declare function resetScopeCache(): void;
|
package/dist/core/scope.js
CHANGED
|
@@ -63,6 +63,10 @@ export function marketplacesDir(scope) {
|
|
|
63
63
|
const root = scopeRoot(scope);
|
|
64
64
|
return root ? join(root, 'marketplaces') : null;
|
|
65
65
|
}
|
|
66
|
+
export function scopeSkillsDir(scope) {
|
|
67
|
+
const root = scopeRoot(scope);
|
|
68
|
+
return root ? join(root, 'skills') : null;
|
|
69
|
+
}
|
|
66
70
|
export function resolveScopeArg(scopeArg) {
|
|
67
71
|
if (scopeArg === undefined)
|
|
68
72
|
return 'all';
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export interface SidePaneOptions {
|
|
2
|
+
/** Full first user message — task + checklist + submit instructions all in one. */
|
|
3
|
+
prompt: string;
|
|
4
|
+
cwd: string;
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const DEFAULT_PANE_OPTS: {
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
};
|
|
10
|
+
export type SidePaneStatus = 'submitted' | 'timeout' | 'pane-closed' | 'spawn-failed';
|
|
11
|
+
export interface SidePaneResult {
|
|
12
|
+
status: SidePaneStatus;
|
|
13
|
+
content: string;
|
|
14
|
+
paneId?: string;
|
|
15
|
+
sessionDir: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function createSession(): {
|
|
18
|
+
id: string;
|
|
19
|
+
dir: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function submitToSession(sessionDir: string, content: string): void;
|
|
22
|
+
export interface DetachOptions {
|
|
23
|
+
/** Full first user message for the new claude session. No custom system prompt. */
|
|
24
|
+
prompt: string;
|
|
25
|
+
cwd: string;
|
|
26
|
+
/** Where to open the new pane. */
|
|
27
|
+
placement: 'split-h' | 'split-v' | 'new-window';
|
|
28
|
+
/** Seconds to wait before killing the originating pane so the caller can finish. */
|
|
29
|
+
killAfterSeconds: number;
|
|
30
|
+
}
|
|
31
|
+
export interface DetachResult {
|
|
32
|
+
status: 'spawned' | 'spawn-failed' | 'not-in-tmux';
|
|
33
|
+
paneId?: string;
|
|
34
|
+
message: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Fire-and-forget: launch an interactive `claude` in a new pane (or window),
|
|
38
|
+
* then schedule the originating pane to be killed after `killAfterSeconds`.
|
|
39
|
+
*
|
|
40
|
+
* No custom system prompt — the task is delivered as the first user message
|
|
41
|
+
* so the user can `/clear` to fall back to a normal default Claude session.
|
|
42
|
+
*
|
|
43
|
+
* Returns as soon as the new pane is up; does NOT wait for claude to finish.
|
|
44
|
+
*/
|
|
45
|
+
export declare function spawnAndDetach(opts: DetachOptions): DetachResult;
|
|
46
|
+
/**
|
|
47
|
+
* Spawn a side-pane `claude` reviewer. Blocks until the reviewer calls
|
|
48
|
+
* `crtr agent submit <content>`, the 10-min budget elapses, or the pane is closed.
|
|
49
|
+
*
|
|
50
|
+
* No custom system prompt — the task is delivered as the first user message
|
|
51
|
+
* so the reviewer is a normal Claude session running a single task.
|
|
52
|
+
*/
|
|
53
|
+
export declare function spawnSidePaneReview(opts: SidePaneOptions): Promise<SidePaneResult>;
|
|
54
|
+
export interface SpawnAgentOptions {
|
|
55
|
+
/** First user message for the new claude session. */
|
|
56
|
+
prompt: string;
|
|
57
|
+
cwd: string;
|
|
58
|
+
/** If set, resume this Claude Code session with --fork-session (new session id). */
|
|
59
|
+
fork?: {
|
|
60
|
+
sessionId: string;
|
|
61
|
+
};
|
|
62
|
+
/** Max panes per tmux window before overflowing to a new window. */
|
|
63
|
+
maxPanesPerWindow: number;
|
|
64
|
+
}
|
|
65
|
+
export interface SpawnAgentResult {
|
|
66
|
+
status: 'spawned' | 'spawn-failed' | 'not-in-tmux';
|
|
67
|
+
/** crtr session UUID — pass to `crtr agent await` to receive the result. */
|
|
68
|
+
sessionId?: string;
|
|
69
|
+
/** tmux pane id of the spawned pane. */
|
|
70
|
+
paneId?: string;
|
|
71
|
+
/** How the pane was placed. */
|
|
72
|
+
placement?: 'split-window' | 'new-window';
|
|
73
|
+
message: string;
|
|
74
|
+
}
|
|
75
|
+
export declare function sessionDirForId(sessionId: string): string;
|
|
76
|
+
export declare function countPanesInCurrentWindow(): number;
|
|
77
|
+
/**
|
|
78
|
+
* Async sibling spawn. Launches a claude session in a new tmux pane or window
|
|
79
|
+
* (depending on current pane count vs maxPanesPerWindow). Returns immediately
|
|
80
|
+
* with the crtr session id; the parent stays alive.
|
|
81
|
+
*
|
|
82
|
+
* If `fork` is set, uses `claude --resume <id> --fork-session` so the child
|
|
83
|
+
* gets a fresh session id and does not contend with the parent's JSONL.
|
|
84
|
+
*/
|
|
85
|
+
export declare function spawnAgent(opts: SpawnAgentOptions): SpawnAgentResult;
|
|
86
|
+
export interface AwaitOptions {
|
|
87
|
+
timeoutMs: number;
|
|
88
|
+
/** Kill the child pane after content is received. Default true. */
|
|
89
|
+
killPane: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Block until the agent identified by `sessionId` calls `crtr agent submit`.
|
|
93
|
+
* Returns content + status. Cleans up the session dir on completion.
|
|
94
|
+
*/
|
|
95
|
+
export declare function awaitSession(sessionId: string, opts: AwaitOptions): Promise<SidePaneResult>;
|