@fitlab-ai/agent-infra 0.6.5 → 0.7.1
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 +51 -25
- package/README.zh-CN.md +49 -23
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +1 -1
- package/dist/lib/builtin-tuis.js +45 -0
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/init.js +65 -23
- package/dist/lib/prompt.js +49 -1
- package/dist/lib/sandbox/commands/create.js +4 -2
- package/dist/lib/sandbox/commands/enter.js +15 -4
- package/dist/lib/sandbox/commands/list-running.js +153 -0
- package/dist/lib/sandbox/commands/ls.js +24 -45
- package/dist/lib/sandbox/commands/rebuild.js +7 -13
- package/dist/lib/sandbox/commands/rm.js +2 -0
- package/dist/lib/sandbox/config.js +3 -0
- package/dist/lib/sandbox/image-prune.js +18 -0
- package/dist/lib/sandbox/index.js +2 -1
- package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +10 -6
- package/dist/lib/sandbox/task-resolver.js +18 -0
- package/dist/lib/sandbox/tools.js +213 -8
- package/dist/lib/update.js +70 -18
- package/lib/builtin-tuis.ts +55 -0
- package/lib/defaults.json +4 -0
- package/lib/init.ts +97 -35
- package/lib/prompt.ts +54 -1
- package/lib/sandbox/commands/create.ts +10 -2
- package/lib/sandbox/commands/enter.ts +14 -4
- package/lib/sandbox/commands/list-running.ts +188 -0
- package/lib/sandbox/commands/ls.ts +28 -49
- package/lib/sandbox/commands/rebuild.ts +12 -14
- package/lib/sandbox/commands/rm.ts +3 -0
- package/lib/sandbox/config.ts +7 -0
- package/lib/sandbox/image-prune.ts +23 -0
- package/lib/sandbox/index.ts +2 -1
- package/lib/sandbox/runtimes/ai-tools.dockerfile +10 -6
- package/lib/sandbox/task-resolver.ts +23 -1
- package/lib/sandbox/tools.ts +248 -9
- package/lib/update.ts +85 -30
- package/package.json +1 -1
- package/templates/.agents/QUICKSTART.en.md +1 -1
- package/templates/.agents/QUICKSTART.zh-CN.md +1 -1
- package/templates/.agents/README.en.md +111 -2
- package/templates/.agents/README.zh-CN.md +111 -2
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.github.en.md +6 -5
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +6 -5
- package/templates/.agents/rules/milestone-inference.github.en.md +2 -2
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +2 -2
- package/templates/.agents/rules/no-mid-flow-questions.en.md +57 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +57 -0
- package/templates/.agents/rules/pr-sync.github.en.md +4 -5
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +4 -5
- package/templates/.agents/rules/task-management.en.md +9 -6
- package/templates/.agents/rules/task-management.zh-CN.md +9 -6
- package/templates/.agents/rules/task-short-id.en.md +141 -0
- package/templates/.agents/rules/task-short-id.zh-CN.md +124 -0
- package/templates/.agents/rules/testing-discipline.en.md +2 -2
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -2
- package/templates/.agents/scripts/task-short-id.js +713 -0
- package/templates/.agents/scripts/validate-artifact.js +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.en.md +20 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +20 -5
- package/templates/.agents/skills/block-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +47 -32
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +46 -32
- package/templates/.agents/skills/close-codescan/SKILL.en.md +11 -0
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +11 -0
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +121 -0
- package/templates/.agents/skills/{implement-task → code-task}/SKILL.zh-CN.md +55 -25
- package/templates/.agents/skills/{implement-task → code-task}/config/verify.en.json +4 -4
- package/templates/.agents/skills/{implement-task → code-task}/config/verify.zh-CN.json +4 -4
- package/templates/.agents/skills/{implement-task → code-task}/reference/branch-management.zh-CN.md +2 -2
- package/templates/.agents/skills/{implement-task/reference/implementation-rules.en.md → code-task/reference/code-rules.en.md} +6 -6
- package/templates/.agents/skills/{implement-task/reference/implementation-rules.zh-CN.md → code-task/reference/code-rules.zh-CN.md} +3 -3
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +69 -0
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +69 -0
- package/templates/.agents/skills/{refine-task/reference/fix-workflow.en.md → code-task/reference/fix-mode.en.md} +12 -12
- package/templates/.agents/skills/{refine-task/reference/fix-workflow.zh-CN.md → code-task/reference/fix-mode.zh-CN.md} +8 -8
- package/templates/.agents/skills/code-task/reference/output-template.en.md +20 -0
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +20 -0
- package/templates/.agents/skills/{implement-task → code-task}/reference/report-template.en.md +4 -4
- package/templates/.agents/skills/{implement-task → code-task}/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/scripts/detect-mode.js +370 -0
- package/templates/.agents/skills/commit/SKILL.en.md +6 -2
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +10 -6
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +10 -6
- package/templates/.agents/skills/complete-task/SKILL.en.md +17 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +17 -4
- package/templates/.agents/skills/create-pr/SKILL.en.md +21 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +21 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +14 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +15 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +15 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +16 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +16 -2
- package/templates/.agents/skills/import-issue/SKILL.en.md +17 -3
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +17 -3
- package/templates/.agents/skills/plan-task/SKILL.en.md +8 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
- package/templates/.agents/skills/restore-task/SKILL.en.md +16 -3
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +16 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +80 -0
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +105 -0
- package/templates/.agents/skills/review-analysis/config/verify.en.json +51 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +51 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +87 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +87 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +90 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +91 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +47 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +47 -0
- package/templates/.agents/skills/{review-task → review-code}/SKILL.en.md +15 -9
- package/templates/.agents/skills/{review-task → review-code}/SKILL.zh-CN.md +19 -10
- package/templates/.agents/skills/{review-task → review-code}/config/verify.en.json +7 -5
- package/templates/.agents/skills/{review-task → review-code}/config/verify.zh-CN.json +6 -4
- package/templates/.agents/skills/{review-task → review-code}/reference/output-templates.en.md +21 -17
- package/templates/.agents/skills/{review-task → review-code}/reference/output-templates.zh-CN.md +19 -15
- package/templates/.agents/skills/{review-task → review-code}/reference/report-template.en.md +5 -6
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +91 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +48 -0
- package/templates/.agents/skills/{review-task → review-code}/reference/review-criteria.zh-CN.md +10 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +80 -0
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +105 -0
- package/templates/.agents/skills/{refine-task → review-plan}/config/verify.en.json +14 -10
- package/templates/.agents/skills/{refine-task → review-plan}/config/verify.zh-CN.json +14 -10
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +87 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +87 -0
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +90 -0
- package/templates/.agents/skills/{review-task → review-plan}/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +47 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +47 -0
- package/templates/.agents/skills/test/SKILL.en.md +2 -2
- package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -31
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +113 -21
- package/templates/.agents/templates/task.en.md +4 -3
- package/templates/.agents/templates/task.zh-CN.md +3 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +126 -80
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +90 -44
- package/templates/.agents/workflows/feature-development.en.yaml +115 -70
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +92 -47
- package/templates/.agents/workflows/refactoring.en.yaml +123 -78
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +89 -44
- package/templates/.claude/commands/code-task.en.md +8 -0
- package/templates/.claude/commands/code-task.zh-CN.md +8 -0
- package/templates/.claude/commands/review-analysis.en.md +8 -0
- package/templates/.claude/commands/review-analysis.zh-CN.md +8 -0
- package/templates/.claude/commands/review-code.en.md +8 -0
- package/templates/.claude/commands/review-code.zh-CN.md +8 -0
- package/templates/.claude/commands/review-plan.en.md +8 -0
- package/templates/.claude/commands/review-plan.zh-CN.md +8 -0
- package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +1 -1
- package/templates/.gemini/commands/_project_/code-task.en.toml +8 -0
- package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +8 -0
- package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +1 -1
- package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +1 -1
- package/templates/.gemini/commands/_project_/review-analysis.en.toml +8 -0
- package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +8 -0
- package/templates/.gemini/commands/_project_/review-code.en.toml +8 -0
- package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +8 -0
- package/templates/.gemini/commands/_project_/review-plan.en.toml +8 -0
- package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +8 -0
- package/templates/.opencode/commands/code-task.en.md +11 -0
- package/templates/.opencode/commands/code-task.zh-CN.md +11 -0
- package/templates/.opencode/commands/review-analysis.en.md +11 -0
- package/templates/.opencode/commands/review-analysis.zh-CN.md +11 -0
- package/templates/.opencode/commands/review-code.en.md +11 -0
- package/templates/.opencode/commands/review-code.zh-CN.md +11 -0
- package/templates/.opencode/commands/review-plan.en.md +11 -0
- package/templates/.opencode/commands/review-plan.zh-CN.md +11 -0
- package/templates/.agents/skills/implement-task/SKILL.en.md +0 -173
- package/templates/.agents/skills/implement-task/reference/output-template.en.md +0 -20
- package/templates/.agents/skills/implement-task/reference/output-template.zh-CN.md +0 -20
- package/templates/.agents/skills/refine-task/SKILL.en.md +0 -153
- package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +0 -153
- package/templates/.agents/skills/refine-task/reference/report-template.en.md +0 -64
- package/templates/.agents/skills/refine-task/reference/report-template.zh-CN.md +0 -64
- package/templates/.agents/skills/review-task/reference/review-criteria.en.md +0 -42
- package/templates/.claude/commands/implement-task.en.md +0 -8
- package/templates/.claude/commands/implement-task.zh-CN.md +0 -8
- package/templates/.claude/commands/refine-task.en.md +0 -8
- package/templates/.claude/commands/refine-task.zh-CN.md +0 -8
- package/templates/.claude/commands/review-task.en.md +0 -8
- package/templates/.claude/commands/review-task.zh-CN.md +0 -8
- package/templates/.gemini/commands/_project_/implement-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/implement-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/refine-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/refine-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/review-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/review-task.zh-CN.toml +0 -8
- package/templates/.opencode/commands/implement-task.en.md +0 -11
- package/templates/.opencode/commands/implement-task.zh-CN.md +0 -11
- package/templates/.opencode/commands/refine-task.en.md +0 -11
- package/templates/.opencode/commands/refine-task.zh-CN.md +0 -11
- package/templates/.opencode/commands/review-task.en.md +0 -11
- package/templates/.opencode/commands/review-task.zh-CN.md +0 -11
- /package/templates/.agents/skills/{implement-task → code-task}/reference/branch-management.en.md +0 -0
package/lib/init.ts
CHANGED
|
@@ -3,11 +3,18 @@ import path from 'node:path';
|
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
4
|
import { platform } from 'node:os';
|
|
5
5
|
import { info, ok, err } from './log.ts';
|
|
6
|
-
import { prompt, select, closePrompt } from './prompt.ts';
|
|
6
|
+
import { prompt, select, multiSelect, closePrompt } from './prompt.ts';
|
|
7
7
|
import { resolveTemplateDir } from './paths.ts';
|
|
8
8
|
import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.ts';
|
|
9
9
|
import { enginesForPlatform } from './sandbox/engines/index.ts';
|
|
10
10
|
import { VERSION } from './version.ts';
|
|
11
|
+
import {
|
|
12
|
+
BUILTIN_TUI_IDS,
|
|
13
|
+
BUILTIN_TUI_DISPLAY,
|
|
14
|
+
isPathOwnedByDisabledTUI,
|
|
15
|
+
resolveEnabledTUIs
|
|
16
|
+
} from './builtin-tuis.ts';
|
|
17
|
+
import type { BuiltinTUIId } from './builtin-tuis.ts';
|
|
11
18
|
|
|
12
19
|
type FileRegistry = {
|
|
13
20
|
managed: string[];
|
|
@@ -23,7 +30,9 @@ type SourceEntry = {
|
|
|
23
30
|
type Defaults = {
|
|
24
31
|
files: FileRegistry;
|
|
25
32
|
sandbox: Record<string, unknown>;
|
|
33
|
+
task: { shortIdLength: number };
|
|
26
34
|
labels: Record<string, unknown>;
|
|
35
|
+
requiresPullRequest: boolean;
|
|
27
36
|
};
|
|
28
37
|
|
|
29
38
|
type AgentConfig = {
|
|
@@ -31,10 +40,13 @@ type AgentConfig = {
|
|
|
31
40
|
org: string;
|
|
32
41
|
language: string;
|
|
33
42
|
platform: { type: string };
|
|
43
|
+
requiresPullRequest: boolean;
|
|
34
44
|
templateVersion: string;
|
|
35
45
|
sandbox: Record<string, unknown>;
|
|
46
|
+
task: { shortIdLength: number };
|
|
36
47
|
labels: Record<string, unknown>;
|
|
37
48
|
files: FileRegistry;
|
|
49
|
+
tuis: string[];
|
|
38
50
|
templates?: { sources: SourceEntry[] };
|
|
39
51
|
skills?: { sources: SourceEntry[] };
|
|
40
52
|
};
|
|
@@ -58,10 +70,15 @@ function isPathOwnedByOtherPlatform(relativePath: string, platformType: string):
|
|
|
58
70
|
return candidate !== platformType;
|
|
59
71
|
}
|
|
60
72
|
|
|
61
|
-
function buildDefaultFiles(platformType: string): FileRegistry {
|
|
73
|
+
function buildDefaultFiles(platformType: string, enabledTUIs: Set<BuiltinTUIId>): FileRegistry {
|
|
74
|
+
const ownedByDisabled = (entry: string) => isPathOwnedByDisabledTUI(entry, enabledTUIs);
|
|
62
75
|
return {
|
|
63
|
-
managed: (defaults.files.managed || []).filter(
|
|
64
|
-
|
|
76
|
+
managed: (defaults.files.managed || []).filter(
|
|
77
|
+
(entry) => !isPathOwnedByOtherPlatform(entry, platformType) && !ownedByDisabled(entry)
|
|
78
|
+
),
|
|
79
|
+
merged: (defaults.files.merged || []).filter(
|
|
80
|
+
(entry) => !isPathOwnedByOtherPlatform(entry, platformType) && !ownedByDisabled(entry)
|
|
81
|
+
),
|
|
65
82
|
ejected: structuredClone(defaults.files.ejected || [])
|
|
66
83
|
};
|
|
67
84
|
}
|
|
@@ -207,6 +224,27 @@ async function cmdInit(): Promise<void> {
|
|
|
207
224
|
);
|
|
208
225
|
}
|
|
209
226
|
|
|
227
|
+
const requiresPRChoice = await select(
|
|
228
|
+
'Require Pull Request flow?',
|
|
229
|
+
['yes', 'no'],
|
|
230
|
+
'yes'
|
|
231
|
+
);
|
|
232
|
+
const requiresPullRequest = requiresPRChoice !== 'no';
|
|
233
|
+
|
|
234
|
+
let enabledTUIs: string[];
|
|
235
|
+
try {
|
|
236
|
+
enabledTUIs = await multiSelect(
|
|
237
|
+
'Built-in TUI command files to install/manage',
|
|
238
|
+
BUILTIN_TUI_IDS.map((id) => ({ id, label: BUILTIN_TUI_DISPLAY[id] }))
|
|
239
|
+
);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
err(e instanceof Error ? e.message : String(e));
|
|
242
|
+
closePrompt();
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const enabledTUISet = resolveEnabledTUIs(enabledTUIs);
|
|
247
|
+
|
|
210
248
|
const templateSources = parseLocalSources(await prompt(
|
|
211
249
|
'Template sources (optional, comma-separated local paths, e.g. ~/my-templates; Enter to skip)',
|
|
212
250
|
''
|
|
@@ -250,29 +288,35 @@ async function cmdInit(): Promise<void> {
|
|
|
250
288
|
);
|
|
251
289
|
ok('Installed .agents/skills/update-agent-infra/');
|
|
252
290
|
|
|
253
|
-
// install Claude command
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
291
|
+
// install Claude command (only if enabled)
|
|
292
|
+
if (enabledTUISet.has('claude-code')) {
|
|
293
|
+
renderFile(
|
|
294
|
+
path.join(templateDir, '.claude', 'commands', claudeSrc),
|
|
295
|
+
path.join('.claude', 'commands', 'update-agent-infra.md'),
|
|
296
|
+
replacements
|
|
297
|
+
);
|
|
298
|
+
ok('Installed .claude/commands/update-agent-infra.md');
|
|
299
|
+
}
|
|
260
300
|
|
|
261
|
-
// install Gemini command
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
301
|
+
// install Gemini command (only if enabled)
|
|
302
|
+
if (enabledTUISet.has('gemini-cli')) {
|
|
303
|
+
renderFile(
|
|
304
|
+
path.join(templateDir, '.gemini', 'commands', '_project_', geminiSrc),
|
|
305
|
+
path.join('.gemini', 'commands', project, 'update-agent-infra.toml'),
|
|
306
|
+
replacements
|
|
307
|
+
);
|
|
308
|
+
ok(`Installed .gemini/commands/${project}/update-agent-infra.toml`);
|
|
309
|
+
}
|
|
268
310
|
|
|
269
|
-
// install OpenCode command
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
311
|
+
// install OpenCode command (only if enabled)
|
|
312
|
+
if (enabledTUISet.has('opencode')) {
|
|
313
|
+
renderFile(
|
|
314
|
+
path.join(templateDir, '.opencode', 'commands', opencodeSrc),
|
|
315
|
+
path.join('.opencode', 'commands', 'update-agent-infra.md'),
|
|
316
|
+
replacements
|
|
317
|
+
);
|
|
318
|
+
ok('Installed .opencode/commands/update-agent-infra.md');
|
|
319
|
+
}
|
|
276
320
|
|
|
277
321
|
// generate .agents/.airc.json
|
|
278
322
|
const config: AgentConfig = {
|
|
@@ -280,10 +324,13 @@ async function cmdInit(): Promise<void> {
|
|
|
280
324
|
org: orgName,
|
|
281
325
|
language,
|
|
282
326
|
platform: { type: platformType },
|
|
327
|
+
requiresPullRequest,
|
|
283
328
|
templateVersion: VERSION,
|
|
284
329
|
sandbox: structuredClone(defaults.sandbox),
|
|
330
|
+
task: structuredClone(defaults.task),
|
|
285
331
|
labels: structuredClone(defaults.labels),
|
|
286
|
-
files: buildDefaultFiles(platformType)
|
|
332
|
+
files: buildDefaultFiles(platformType, enabledTUISet),
|
|
333
|
+
tuis: enabledTUIs
|
|
287
334
|
};
|
|
288
335
|
|
|
289
336
|
if (sandboxEngine) {
|
|
@@ -310,15 +357,30 @@ async function cmdInit(): Promise<void> {
|
|
|
310
357
|
console.log('');
|
|
311
358
|
ok('Project initialized successfully!');
|
|
312
359
|
console.log('');
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
360
|
+
if (enabledTUISet.size === 0) {
|
|
361
|
+
console.log(' No built-in TUI selected.');
|
|
362
|
+
console.log(` Configure "customTUIs" in ${configPath} before running update-agent-infra.`);
|
|
363
|
+
console.log('');
|
|
364
|
+
} else {
|
|
365
|
+
console.log(' Next step: open this project in any AI TUI and run:');
|
|
366
|
+
console.log('');
|
|
367
|
+
const claudeOrOpencode: string[] = [];
|
|
368
|
+
if (enabledTUISet.has('claude-code')) claudeOrOpencode.push('Claude Code');
|
|
369
|
+
if (enabledTUISet.has('opencode')) claudeOrOpencode.push('OpenCode');
|
|
370
|
+
if (claudeOrOpencode.length > 0) {
|
|
371
|
+
console.log(` ${claudeOrOpencode.join(' / ')}: /update-agent-infra`);
|
|
372
|
+
}
|
|
373
|
+
if (enabledTUISet.has('gemini-cli')) {
|
|
374
|
+
console.log(` Gemini CLI: /${project}:update-agent-infra`);
|
|
375
|
+
}
|
|
376
|
+
if (enabledTUISet.has('codex')) {
|
|
377
|
+
console.log(' Codex CLI: $update-agent-infra');
|
|
378
|
+
}
|
|
379
|
+
console.log('');
|
|
380
|
+
console.log(' This will render all templates and set up the full');
|
|
381
|
+
console.log(' AI collaboration infrastructure.');
|
|
382
|
+
console.log('');
|
|
383
|
+
}
|
|
322
384
|
}
|
|
323
385
|
|
|
324
386
|
export { cmdInit };
|
package/lib/prompt.ts
CHANGED
|
@@ -86,6 +86,59 @@ async function select(question: string, choices: string[], defaultValue?: string
|
|
|
86
86
|
return trimmed;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
async function multiSelect(
|
|
90
|
+
question: string,
|
|
91
|
+
choices: { id: string; label: string }[]
|
|
92
|
+
): Promise<string[]> {
|
|
93
|
+
process.stdout.write(` ${question}:\n`);
|
|
94
|
+
const idWidth = Math.max(...choices.map((c) => c.id.length));
|
|
95
|
+
choices.forEach((c, i) => {
|
|
96
|
+
process.stdout.write(` ${i + 1}) ${c.id.padEnd(idWidth)} (${c.label})\n`);
|
|
97
|
+
});
|
|
98
|
+
ask('Enter comma-separated numbers or ids to keep, or "none" to select nothing [default: all]: ');
|
|
99
|
+
|
|
100
|
+
setupInterface();
|
|
101
|
+
|
|
102
|
+
const line = await nextLine();
|
|
103
|
+
// Strictly distinguish bare Enter (null/empty string) from whitespace input.
|
|
104
|
+
if (line === null || line === '') return choices.map((c) => c.id);
|
|
105
|
+
// Explicit empty selection: "none" means deliberately zero built-in choices.
|
|
106
|
+
if (line.trim().toLowerCase() === 'none') return [];
|
|
107
|
+
|
|
108
|
+
const tokens = line.split(',').map((t) => t.trim());
|
|
109
|
+
if (tokens.some((t) => t === '')) {
|
|
110
|
+
throw new Error(`Invalid selection input: "${line}" (empty token)`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const idSet = new Set(choices.map((c) => c.id));
|
|
114
|
+
const seenIds = new Set<string>();
|
|
115
|
+
for (const t of tokens) {
|
|
116
|
+
let resolvedId: string | undefined;
|
|
117
|
+
if (/^[0-9]+$/.test(t)) {
|
|
118
|
+
const n = Number.parseInt(t, 10);
|
|
119
|
+
if (n < 1 || n > choices.length) {
|
|
120
|
+
throw new Error(`Selection out of range: "${t}" (expected 1..${choices.length})`);
|
|
121
|
+
}
|
|
122
|
+
resolvedId = choices[n - 1]!.id;
|
|
123
|
+
} else if (idSet.has(t)) {
|
|
124
|
+
resolvedId = t;
|
|
125
|
+
} else {
|
|
126
|
+
throw new Error(`Unknown TUI selection token: "${t}"`);
|
|
127
|
+
}
|
|
128
|
+
if (seenIds.has(resolvedId)) {
|
|
129
|
+
throw new Error(`Duplicate selection: "${t}" resolves to already-selected "${resolvedId}"`);
|
|
130
|
+
}
|
|
131
|
+
seenIds.add(resolvedId);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Normalize to prompt order: users can type tokens in any order, but the
|
|
135
|
+
// persisted array follows the canonical choices order to keep .airc.json
|
|
136
|
+
// diffs stable. An empty result here is impossible (tokens.length > 0 and
|
|
137
|
+
// every token resolves to an id), so no separate empty guard is needed —
|
|
138
|
+
// explicit "none" handled above.
|
|
139
|
+
return choices.map((c) => c.id).filter((id) => seenIds.has(id));
|
|
140
|
+
}
|
|
141
|
+
|
|
89
142
|
function closePrompt(): void {
|
|
90
143
|
if (_rl) {
|
|
91
144
|
_rl.close();
|
|
@@ -94,4 +147,4 @@ function closePrompt(): void {
|
|
|
94
147
|
}
|
|
95
148
|
}
|
|
96
149
|
|
|
97
|
-
export { prompt, select, closePrompt };
|
|
150
|
+
export { prompt, select, multiSelect, closePrompt };
|
|
@@ -36,7 +36,13 @@ import {
|
|
|
36
36
|
runVerboseEngine
|
|
37
37
|
} from '../shell.ts';
|
|
38
38
|
import { resolveTaskBranch } from '../task-resolver.ts';
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
imageSignatureFields,
|
|
41
|
+
resolveTools,
|
|
42
|
+
toolConfigDirCandidates,
|
|
43
|
+
toolNpmPackagesArg,
|
|
44
|
+
toolShellInstallScriptBase64
|
|
45
|
+
} from '../tools.ts';
|
|
40
46
|
import type { SandboxTool } from '../tools.ts';
|
|
41
47
|
import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.ts';
|
|
42
48
|
import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.ts';
|
|
@@ -113,7 +119,7 @@ function buildSignature(preparedDockerfile: PreparedDockerfile, tools: SandboxTo
|
|
|
113
119
|
return createHash('sha256')
|
|
114
120
|
.update(JSON.stringify({
|
|
115
121
|
dockerfile: preparedDockerfile.signature,
|
|
116
|
-
tools: tools
|
|
122
|
+
tools: imageSignatureFields(tools)
|
|
117
123
|
}))
|
|
118
124
|
.digest('hex')
|
|
119
125
|
.slice(0, 12);
|
|
@@ -1063,6 +1069,8 @@ export function buildImage(
|
|
|
1063
1069
|
`HOST_GID=${hostGid}`,
|
|
1064
1070
|
'--build-arg',
|
|
1065
1071
|
`AI_TOOL_PACKAGES=${toolNpmPackagesArg(tools)}`,
|
|
1072
|
+
'--build-arg',
|
|
1073
|
+
`AI_TOOLS_SHELL_INSTALL_B64=${toolShellInstallScriptBase64(tools)}`,
|
|
1066
1074
|
'--label',
|
|
1067
1075
|
sandboxLabel(config),
|
|
1068
1076
|
'--label',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from '../config.ts';
|
|
2
|
-
import { assertValidBranchName, containerNameCandidates } from '../constants.ts';
|
|
2
|
+
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel } from '../constants.ts';
|
|
3
3
|
import { detectEngine } from '../engine.ts';
|
|
4
4
|
import {
|
|
5
5
|
formatCredentialWarnings,
|
|
@@ -13,8 +13,12 @@ import { resolveTaskBranch } from '../task-resolver.ts';
|
|
|
13
13
|
import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.ts';
|
|
14
14
|
import { runInteractiveWithClipboardBridge } from '../clipboard/bridge.ts';
|
|
15
15
|
import { detectHostTimezone } from '../host-timezone.ts';
|
|
16
|
+
import { fetchSandboxRows, isTaskShortRef, resolveTaskShortRef } from './list-running.ts';
|
|
16
17
|
|
|
17
|
-
const USAGE = `Usage: ai sandbox exec <branch> [cmd...]
|
|
18
|
+
const USAGE = `Usage: ai sandbox exec <branch | TASK-id | '#N'> [cmd...]
|
|
19
|
+
|
|
20
|
+
'#N' references the N-th running sandbox in 'ai sandbox ls' order (1-based).
|
|
21
|
+
Quote it as '#N' to avoid shell '#' comment handling.`;
|
|
18
22
|
const TMUX_ENTRY_PATH = '/usr/local/bin/sandbox-tmux-entry';
|
|
19
23
|
|
|
20
24
|
// Terminal-detection variables that interactive TUIs (e.g. claude-code)
|
|
@@ -115,8 +119,14 @@ export async function enter(args: string[]): Promise<number> {
|
|
|
115
119
|
const config = loadConfig();
|
|
116
120
|
validateClaudeCredentialsEnvOverride();
|
|
117
121
|
const engine = detectEngine(config);
|
|
118
|
-
const [
|
|
119
|
-
|
|
122
|
+
const [firstArg = '', ...cmd] = args;
|
|
123
|
+
let branch: string;
|
|
124
|
+
if (isTaskShortRef(firstArg)) {
|
|
125
|
+
const { running } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
|
|
126
|
+
branch = resolveTaskShortRef(firstArg, { running, repoRoot: config.repoRoot });
|
|
127
|
+
} else {
|
|
128
|
+
branch = resolveTaskBranch(firstArg, config.repoRoot);
|
|
129
|
+
}
|
|
120
130
|
assertValidBranchName(branch);
|
|
121
131
|
const running = runSafeEngine(engine, 'docker', ['ps', '--format', '{{.Names}}']).split('\n');
|
|
122
132
|
const container = containerNameCandidates(config, branch).find((name) => running.includes(name));
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { runSafeEngine } from '../shell.ts';
|
|
5
|
+
|
|
6
|
+
export type SandboxRow = {
|
|
7
|
+
name: string;
|
|
8
|
+
status: string;
|
|
9
|
+
branch: string;
|
|
10
|
+
running: boolean;
|
|
11
|
+
index: number | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function containerListFormat(): string {
|
|
15
|
+
return '{{.Names}}\t{{.Status}}\t{{.Labels}}';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseLabels(csv: string): Record<string, string> {
|
|
19
|
+
if (!csv) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const labels: Record<string, string> = {};
|
|
24
|
+
for (const pair of csv.split(',')) {
|
|
25
|
+
if (!pair) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const eq = pair.indexOf('=');
|
|
29
|
+
if (eq < 0) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
33
|
+
}
|
|
34
|
+
return labels;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseSandboxRows(rawOutput: string, branchKey: string): SandboxRow[] {
|
|
38
|
+
if (!rawOutput) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
return rawOutput.split('\n').map((line) => {
|
|
42
|
+
const [name = '', status = '', labelsCsv = ''] = line.split('\t');
|
|
43
|
+
const branch = parseLabels(labelsCsv)[branchKey] ?? '';
|
|
44
|
+
return {
|
|
45
|
+
name,
|
|
46
|
+
status,
|
|
47
|
+
branch,
|
|
48
|
+
running: status.startsWith('Up '),
|
|
49
|
+
index: null
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function sortAndIndexSandboxRows(rows: SandboxRow[]): {
|
|
55
|
+
running: SandboxRow[];
|
|
56
|
+
nonRunning: SandboxRow[];
|
|
57
|
+
} {
|
|
58
|
+
const byName = (a: SandboxRow, b: SandboxRow): number => {
|
|
59
|
+
if (a.name < b.name) return -1;
|
|
60
|
+
if (a.name > b.name) return 1;
|
|
61
|
+
return 0;
|
|
62
|
+
};
|
|
63
|
+
const running = rows.filter((row) => row.running).sort(byName).map((row, i) => ({
|
|
64
|
+
...row,
|
|
65
|
+
index: i + 1
|
|
66
|
+
}));
|
|
67
|
+
const nonRunning = rows.filter((row) => !row.running).sort(byName).map((row) => ({
|
|
68
|
+
...row,
|
|
69
|
+
index: null
|
|
70
|
+
}));
|
|
71
|
+
return { running, nonRunning };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function fetchSandboxRows(
|
|
75
|
+
engine: string,
|
|
76
|
+
label: string,
|
|
77
|
+
branchKey: string
|
|
78
|
+
): { running: SandboxRow[]; nonRunning: SandboxRow[] } {
|
|
79
|
+
const raw = runSafeEngine(engine, 'docker', [
|
|
80
|
+
'ps',
|
|
81
|
+
'-a',
|
|
82
|
+
'--filter',
|
|
83
|
+
`label=${label}`,
|
|
84
|
+
'--format',
|
|
85
|
+
containerListFormat()
|
|
86
|
+
]);
|
|
87
|
+
return sortAndIndexSandboxRows(parseSandboxRows(raw, branchKey));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Returns true iff `arg` is a syntactically valid task short reference ('#N').
|
|
92
|
+
* Zero IO. Callers MUST use this as the gate before constructing any context
|
|
93
|
+
* for resolveTaskShortRef — that way non-matching arguments (e.g. '#abc',
|
|
94
|
+
* '#1.5', '#') never trigger sandbox list IO.
|
|
95
|
+
*/
|
|
96
|
+
export function isTaskShortRef(arg: string): boolean {
|
|
97
|
+
return /^#\d+$/.test(arg);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
type RegistryLookup =
|
|
101
|
+
| { status: 'miss' }
|
|
102
|
+
| { status: 'hit'; branch: string };
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Try to resolve a short ref against the global task-short-id registry.
|
|
106
|
+
*
|
|
107
|
+
* Tri-state semantics (review-code Round 1 M-1 fix):
|
|
108
|
+
* - 'miss' → script reports no entry (or registry script missing). Caller may fall back.
|
|
109
|
+
* - 'hit' → registry resolved to a task id and branch is found in task.md.
|
|
110
|
+
* - throws → registry hit but task.md is missing or branch metadata is unparseable;
|
|
111
|
+
* surfacing this error is critical — never silently fall back to running index.
|
|
112
|
+
*/
|
|
113
|
+
function tryResolveFromRegistry(arg: string, repoRoot: string): RegistryLookup {
|
|
114
|
+
const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
|
|
115
|
+
if (!fs.existsSync(scriptPath)) return { status: 'miss' };
|
|
116
|
+
const result = spawnSync('node', [scriptPath, 'resolve', arg], { encoding: 'utf8', cwd: repoRoot });
|
|
117
|
+
if (result.status !== 0) return { status: 'miss' };
|
|
118
|
+
const taskId = (result.stdout || '').trim();
|
|
119
|
+
if (!/^TASK-\d{8}-\d{6}$/.test(taskId)) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Registry returned malformed task id for '${arg}': ${JSON.stringify(taskId)}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
for (const sub of ['active', 'completed', 'blocked', 'archive']) {
|
|
125
|
+
const taskMdPath = path.join(repoRoot, '.agents', 'workspace', sub, taskId, 'task.md');
|
|
126
|
+
if (!fs.existsSync(taskMdPath)) continue;
|
|
127
|
+
const content = fs.readFileSync(taskMdPath, 'utf8');
|
|
128
|
+
const fm = content.match(/^branch:\s*(.+)$/m);
|
|
129
|
+
if (fm?.[1]?.trim()) {
|
|
130
|
+
return { status: 'hit', branch: fm[1].trim().replace(/^(["'])(.*)\1$/, '$2') };
|
|
131
|
+
}
|
|
132
|
+
const ctx = content.match(/^- \*\*(?:分支|Branch)\*\*:[ \t]*`?([^`\n]+)`?$/m);
|
|
133
|
+
if (ctx?.[1]?.trim()) {
|
|
134
|
+
return { status: 'hit', branch: ctx[1].trim().replace(/^(["'])(.*)\1$/, '$2') };
|
|
135
|
+
}
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Short ref '${arg}' resolved to task ${taskId} but task.md has no branch field`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Short ref '${arg}' resolved to task ${taskId} but task.md was not found under any workspace dir`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveByRunningIndex(arg: string, running: SandboxRow[]): string {
|
|
146
|
+
const n = Number(arg.slice(1));
|
|
147
|
+
if (n < 1) {
|
|
148
|
+
throw new Error(`Invalid sandbox index '${arg}': must be >= 1`);
|
|
149
|
+
}
|
|
150
|
+
if (running.length === 0) {
|
|
151
|
+
throw new Error(`No running sandbox to reference with '${arg}'`);
|
|
152
|
+
}
|
|
153
|
+
if (n > running.length) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`No running sandbox at index '${arg}' (only ${running.length} running)`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const row = running[n - 1]!;
|
|
159
|
+
if (!row.branch) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Cannot resolve branch for sandbox '${arg}' (container '${row.name}' missing branch label)`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return row.branch;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Resolve a task short reference ('#N') to a branch name for the sandbox entrypoint.
|
|
169
|
+
*
|
|
170
|
+
* Resolution order (sandbox fallback mode, plan-r7 C2):
|
|
171
|
+
* 1. Try the global task-short-id registry under repoRoot. If hit, look up the
|
|
172
|
+
* branch from the matching task.md.
|
|
173
|
+
* 2. Fallback to the running-sandbox list index (preserves the #414 ls-index
|
|
174
|
+
* behaviour; long-term contract per analysis-r5).
|
|
175
|
+
*
|
|
176
|
+
* Precondition: callers MUST gate on isTaskShortRef(arg) === true.
|
|
177
|
+
*/
|
|
178
|
+
export function resolveTaskShortRef(
|
|
179
|
+
arg: string,
|
|
180
|
+
ctx: { running: SandboxRow[]; repoRoot?: string }
|
|
181
|
+
): string {
|
|
182
|
+
if (ctx.repoRoot) {
|
|
183
|
+
const lookup = tryResolveFromRegistry(arg, ctx.repoRoot);
|
|
184
|
+
if (lookup.status === 'hit') return lookup.branch;
|
|
185
|
+
// 'miss' falls through to ls-index fallback (preserves #414 behaviour); 'hit-but-invalid' already threw above.
|
|
186
|
+
}
|
|
187
|
+
return resolveByRunningIndex(arg, ctx.running);
|
|
188
|
+
}
|
|
@@ -5,51 +5,36 @@ import pc from 'picocolors';
|
|
|
5
5
|
import { loadConfig } from '../config.ts';
|
|
6
6
|
import { sandboxBranchLabel, sandboxLabel } from '../constants.ts';
|
|
7
7
|
import { detectEngine } from '../engine.ts';
|
|
8
|
-
import { runSafeEngine } from '../shell.ts';
|
|
9
8
|
import { resolveTools, toolProjectDirCandidates } from '../tools.ts';
|
|
9
|
+
import { fetchSandboxRows } from './list-running.ts';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
export { containerListFormat, parseLabels } from './list-running.ts';
|
|
12
|
+
|
|
13
|
+
const USAGE = `Usage: ai sandbox ls
|
|
14
|
+
|
|
15
|
+
Lists all containers for the current project. The leftmost '#' column
|
|
16
|
+
numbers running sandboxes; use it as "ai sandbox exec '#N'" to enter one.
|
|
17
|
+
Quote '#N' to avoid shell '#' comment handling.`;
|
|
18
|
+
|
|
19
|
+
const CONTAINER_TABLE_HEADERS = ['#', 'NAMES', 'STATUS', 'BRANCH'] as const;
|
|
13
20
|
|
|
14
21
|
type ContainerTableRow = {
|
|
22
|
+
index: string;
|
|
15
23
|
name: string;
|
|
16
24
|
status: string;
|
|
17
25
|
branch: string;
|
|
18
26
|
};
|
|
19
27
|
|
|
20
|
-
// Exported to lock the docker/podman-compatible format in unit tests.
|
|
21
|
-
export function containerListFormat(): string {
|
|
22
|
-
return '{{.Names}}\t{{.Status}}\t{{.Labels}}';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function parseLabels(csv: string): Record<string, string> {
|
|
26
|
-
if (!csv) {
|
|
27
|
-
return {};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const labels: Record<string, string> = {};
|
|
31
|
-
for (const pair of csv.split(',')) {
|
|
32
|
-
if (!pair) {
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
const eq = pair.indexOf('=');
|
|
36
|
-
if (eq < 0) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
40
|
-
}
|
|
41
|
-
return labels;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
28
|
export function formatContainerTable(rows: ContainerTableRow[]): string[] {
|
|
45
|
-
const columns = rows.map((row) => [row.name, row.status, row.branch] as const);
|
|
29
|
+
const columns = rows.map((row) => [row.index, row.name, row.status, row.branch] as const);
|
|
46
30
|
const widths = [
|
|
47
|
-
Math.max(CONTAINER_TABLE_HEADERS[0].length, ...rows.map((row) => row.
|
|
48
|
-
Math.max(CONTAINER_TABLE_HEADERS[1].length, ...rows.map((row) => row.
|
|
49
|
-
Math.max(CONTAINER_TABLE_HEADERS[2].length, ...rows.map((row) => row.
|
|
31
|
+
Math.max(CONTAINER_TABLE_HEADERS[0].length, ...rows.map((row) => row.index.length)),
|
|
32
|
+
Math.max(CONTAINER_TABLE_HEADERS[1].length, ...rows.map((row) => row.name.length)),
|
|
33
|
+
Math.max(CONTAINER_TABLE_HEADERS[2].length, ...rows.map((row) => row.status.length)),
|
|
34
|
+
Math.max(CONTAINER_TABLE_HEADERS[3].length, ...rows.map((row) => row.branch.length))
|
|
50
35
|
] as const;
|
|
51
|
-
const renderRow = (values: readonly [string, string, string]): string =>
|
|
52
|
-
`${values[0].padEnd(widths[0])} ${values[1].padEnd(widths[1])} ${values[2]}`.trimEnd();
|
|
36
|
+
const renderRow = (values: readonly [string, string, string, string]): string =>
|
|
37
|
+
`${values[0].padEnd(widths[0])} ${values[1].padEnd(widths[1])} ${values[2].padEnd(widths[2])} ${values[3]}`.trimEnd();
|
|
53
38
|
|
|
54
39
|
return [
|
|
55
40
|
renderRow(CONTAINER_TABLE_HEADERS),
|
|
@@ -75,28 +60,22 @@ export function ls(args: string[] = []): void {
|
|
|
75
60
|
const engine = detectEngine(config);
|
|
76
61
|
const tools = resolveTools(config);
|
|
77
62
|
const label = sandboxLabel(config);
|
|
78
|
-
const
|
|
79
|
-
'ps',
|
|
80
|
-
'-a',
|
|
81
|
-
'--filter',
|
|
82
|
-
`label=${label}`,
|
|
83
|
-
'--format',
|
|
84
|
-
containerListFormat()
|
|
85
|
-
]);
|
|
63
|
+
const { running, nonRunning } = fetchSandboxRows(engine, label, sandboxBranchLabel(config));
|
|
86
64
|
|
|
87
65
|
p.intro(pc.cyan(`Sandbox status for ${config.project}`));
|
|
88
66
|
|
|
89
67
|
p.log.step('Containers');
|
|
90
|
-
|
|
68
|
+
const ordered = [...running, ...nonRunning];
|
|
69
|
+
if (ordered.length === 0) {
|
|
91
70
|
p.log.warn(' No sandbox containers');
|
|
92
71
|
} else {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
});
|
|
99
|
-
for (const line of formatContainerTable(
|
|
72
|
+
const tableRows: ContainerTableRow[] = ordered.map((row) => ({
|
|
73
|
+
index: row.index === null ? '' : String(row.index),
|
|
74
|
+
name: row.name,
|
|
75
|
+
status: row.status,
|
|
76
|
+
branch: row.branch
|
|
77
|
+
}));
|
|
78
|
+
for (const line of formatContainerTable(tableRows)) {
|
|
100
79
|
process.stdout.write(` ${line}\n`);
|
|
101
80
|
}
|
|
102
81
|
}
|