@fitlab-ai/agent-infra 0.5.10 → 0.6.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 +13 -13
- package/README.zh-CN.md +13 -13
- package/bin/{cli.js → cli.ts} +26 -18
- package/dist/bin/cli.js +121 -0
- package/dist/lib/defaults.json +62 -0
- package/dist/lib/init.js +238 -0
- package/dist/lib/log.js +18 -0
- package/dist/lib/merge.js +747 -0
- package/dist/lib/paths.js +18 -0
- package/dist/lib/prompt.js +85 -0
- package/dist/lib/render.js +139 -0
- package/dist/lib/sandbox/commands/create.js +1173 -0
- package/dist/lib/sandbox/commands/enter.js +98 -0
- package/dist/lib/sandbox/commands/ls.js +93 -0
- package/dist/lib/sandbox/commands/rebuild.js +101 -0
- package/dist/lib/sandbox/commands/refresh.js +85 -0
- package/dist/lib/sandbox/commands/rm.js +226 -0
- package/dist/lib/sandbox/commands/vm.js +144 -0
- package/dist/lib/sandbox/config.js +85 -0
- package/dist/lib/sandbox/constants.js +104 -0
- package/dist/lib/sandbox/credentials.js +437 -0
- package/dist/lib/sandbox/dockerfile.js +76 -0
- package/dist/lib/sandbox/dotfiles.js +170 -0
- package/dist/lib/sandbox/engine.js +155 -0
- package/dist/lib/sandbox/engines/colima.js +64 -0
- package/dist/lib/sandbox/engines/docker-desktop.js +27 -0
- package/dist/lib/sandbox/engines/index.js +25 -0
- package/dist/lib/sandbox/engines/native.js +96 -0
- package/dist/lib/sandbox/engines/orbstack.js +63 -0
- package/dist/lib/sandbox/engines/selinux.js +48 -0
- package/dist/lib/sandbox/engines/wsl2-paths.js +47 -0
- package/dist/lib/sandbox/engines/wsl2.js +57 -0
- package/dist/lib/sandbox/index.js +70 -0
- package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +39 -0
- package/dist/lib/sandbox/runtimes/base.dockerfile +178 -0
- package/dist/lib/sandbox/runtimes/java17.dockerfile +3 -0
- package/dist/lib/sandbox/runtimes/java21.dockerfile +3 -0
- package/dist/lib/sandbox/runtimes/node20.dockerfile +3 -0
- package/dist/lib/sandbox/runtimes/node22.dockerfile +3 -0
- package/dist/lib/sandbox/runtimes/python3.dockerfile +3 -0
- package/dist/lib/sandbox/shell.js +148 -0
- package/dist/lib/sandbox/task-resolver.js +35 -0
- package/dist/lib/sandbox/tools.js +115 -0
- package/dist/lib/update.js +186 -0
- package/dist/lib/version.js +5 -0
- package/dist/package.json +5 -0
- package/lib/defaults.json +4 -3
- package/lib/{init.js → init.ts} +48 -18
- package/lib/{log.js → log.ts} +4 -4
- package/lib/{merge.js → merge.ts} +129 -63
- package/lib/paths.ts +18 -0
- package/lib/{prompt.js → prompt.ts} +12 -12
- package/lib/{render.js → render.ts} +30 -17
- package/lib/sandbox/commands/{create.js → create.ts} +224 -118
- package/lib/sandbox/commands/{enter.js → enter.ts} +17 -14
- package/lib/sandbox/commands/{ls.js → ls.ts} +10 -10
- package/lib/sandbox/commands/{rebuild.js → rebuild.ts} +38 -21
- package/lib/sandbox/commands/{refresh.js → refresh.ts} +16 -7
- package/lib/sandbox/commands/{rm.js → rm.ts} +15 -13
- package/lib/sandbox/commands/{vm.js → vm.ts} +14 -11
- package/lib/sandbox/{config.js → config.ts} +56 -11
- package/lib/sandbox/{constants.js → constants.ts} +30 -18
- package/lib/sandbox/{credentials.js → credentials.ts} +160 -46
- package/lib/sandbox/{dockerfile.js → dockerfile.ts} +13 -6
- package/lib/sandbox/{dotfiles.js → dotfiles.ts} +66 -19
- package/lib/sandbox/{engine.js → engine.ts} +57 -25
- package/lib/sandbox/engines/{colima.js → colima.ts} +9 -7
- package/lib/sandbox/engines/{docker-desktop.js → docker-desktop.ts} +5 -3
- package/lib/sandbox/engines/index.ts +74 -0
- package/lib/sandbox/engines/{native.js → native.ts} +25 -6
- package/lib/sandbox/engines/{orbstack.js → orbstack.ts} +7 -5
- package/lib/sandbox/engines/{selinux.js → selinux.ts} +11 -5
- package/lib/sandbox/engines/{wsl2-paths.js → wsl2-paths.ts} +15 -9
- package/lib/sandbox/engines/{wsl2.js → wsl2.ts} +9 -7
- package/lib/sandbox/{index.js → index.ts} +8 -8
- package/lib/sandbox/{shell.js → shell.ts} +30 -17
- package/lib/sandbox/{task-resolver.js → task-resolver.ts} +6 -6
- package/lib/sandbox/{tools.js → tools.ts} +30 -26
- package/lib/{update.js → update.ts} +33 -10
- package/package.json +17 -9
- package/templates/.agents/README.en.md +8 -8
- package/templates/.agents/README.zh-CN.md +8 -8
- package/templates/{.claude → .agents}/hooks/check-version-format.sh +3 -3
- package/templates/.agents/rules/create-issue.github.en.md +6 -0
- package/templates/.agents/rules/create-issue.github.zh-CN.md +6 -0
- package/templates/.agents/rules/issue-fields.github.en.md +155 -0
- package/templates/.agents/rules/issue-fields.github.zh-CN.md +155 -0
- package/templates/.agents/rules/issue-pr-commands.github.en.md +1 -0
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +1 -0
- package/templates/.agents/rules/issue-sync.github.en.md +2 -1
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +2 -1
- package/templates/.agents/rules/task-management.en.md +17 -9
- package/templates/.agents/rules/task-management.zh-CN.md +17 -9
- package/templates/.agents/rules/testing-discipline.en.md +40 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +40 -0
- package/templates/.agents/rules/version-stamp.en.md +29 -0
- package/templates/.agents/rules/version-stamp.zh-CN.md +29 -0
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +143 -6
- package/templates/.agents/scripts/validate-artifact.js +32 -5
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -0
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/analyze-task/config/verify.json +2 -0
- package/templates/.agents/skills/block-task/SKILL.en.md +3 -0
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/block-task/config/verify.json +1 -0
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -0
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -0
- package/templates/.agents/skills/commit/SKILL.en.md +10 -0
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/commit/config/verify.json +1 -0
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +5 -0
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +5 -0
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/complete-task/config/verify.json +2 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +5 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-task/SKILL.en.md +9 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +9 -0
- package/templates/.agents/skills/create-task/config/verify.json +1 -0
- package/templates/.agents/skills/implement-task/SKILL.en.md +16 -1
- package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +16 -1
- package/templates/.agents/skills/implement-task/config/verify.json +2 -0
- package/templates/.agents/skills/import-codescan/config/verify.json +1 -0
- package/templates/.agents/skills/import-dependabot/config/verify.json +1 -0
- package/templates/.agents/skills/import-issue/SKILL.en.md +10 -0
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +10 -0
- package/templates/.agents/skills/import-issue/config/verify.json +1 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +3 -0
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/plan-task/config/verify.json +2 -0
- package/templates/.agents/skills/refine-task/SKILL.en.md +15 -1
- package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +15 -1
- package/templates/.agents/skills/refine-task/config/verify.json +2 -0
- package/templates/.agents/skills/refine-task/reference/fix-workflow.en.md +9 -0
- package/templates/.agents/skills/refine-task/reference/fix-workflow.zh-CN.md +9 -0
- package/templates/.agents/skills/refine-task/reference/report-template.en.md +11 -0
- package/templates/.agents/skills/refine-task/reference/report-template.zh-CN.md +11 -0
- package/templates/.agents/skills/restore-task/SKILL.en.md +3 -0
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/restore-task/config/verify.json +1 -0
- package/templates/.agents/skills/review-task/SKILL.en.md +16 -1
- package/templates/.agents/skills/review-task/SKILL.zh-CN.md +16 -1
- package/templates/.agents/skills/review-task/config/verify.json +3 -0
- package/templates/.agents/skills/review-task/reference/output-templates.en.md +20 -5
- package/templates/.agents/skills/review-task/reference/output-templates.zh-CN.md +20 -5
- package/templates/.agents/skills/review-task/reference/report-template.en.md +13 -0
- package/templates/.agents/skills/review-task/reference/report-template.zh-CN.md +13 -0
- package/templates/.agents/skills/review-task/reference/review-criteria.en.md +18 -0
- package/templates/.agents/skills/review-task/reference/review-criteria.zh-CN.md +18 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +4 -3
- package/templates/.agents/templates/task.en.md +5 -0
- package/templates/.agents/templates/task.zh-CN.md +5 -0
- package/templates/.claude/settings.json +1 -1
- package/templates/.codex/hooks.json +17 -0
- package/lib/paths.js +0 -9
- package/lib/sandbox/engines/index.js +0 -27
- /package/lib/{version.js → version.ts} +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { loadConfig } from "../config.js";
|
|
2
|
+
import { assertValidBranchName, containerNameCandidates } from "../constants.js";
|
|
3
|
+
import { detectEngine } from "../engine.js";
|
|
4
|
+
import { formatCredentialWarnings, formatRemaining, reconcileClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from "../credentials.js";
|
|
5
|
+
import { runInteractiveEngine, runSafeEngine } from "../shell.js";
|
|
6
|
+
import { resolveTaskBranch } from "../task-resolver.js";
|
|
7
|
+
import { dotfilesCacheDir, materializeDotfiles } from "../dotfiles.js";
|
|
8
|
+
const USAGE = `Usage: ai sandbox exec <branch> [cmd...]`;
|
|
9
|
+
const TMUX_ENTRY_PATH = '/usr/local/bin/sandbox-tmux-entry';
|
|
10
|
+
// Terminal-detection variables that interactive TUIs (e.g. claude-code)
|
|
11
|
+
// inspect to enable progressive enhancements such as the kitty keyboard
|
|
12
|
+
// protocol, which is what makes Shift+Enter distinguishable from Enter.
|
|
13
|
+
// `docker exec` does not forward these by default, so we must pass them
|
|
14
|
+
// through explicitly.
|
|
15
|
+
const FORWARDED_TERMINAL_ENV = [
|
|
16
|
+
'TERM_PROGRAM',
|
|
17
|
+
'TERM_PROGRAM_VERSION',
|
|
18
|
+
'LC_TERMINAL',
|
|
19
|
+
'LC_TERMINAL_VERSION'
|
|
20
|
+
];
|
|
21
|
+
export function terminalEnvFlags(env = process.env) {
|
|
22
|
+
const flags = [];
|
|
23
|
+
for (const name of FORWARDED_TERMINAL_ENV) {
|
|
24
|
+
const value = env[name];
|
|
25
|
+
if (value) {
|
|
26
|
+
flags.push('-e', `${name}=${value}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return flags;
|
|
30
|
+
}
|
|
31
|
+
export function formatCredentialSyncStatus(result, isTTY = process.stderr.isTTY) {
|
|
32
|
+
if (result.status === 'STALE_ACCESS') {
|
|
33
|
+
return 'Warning: Claude Code credentials on host appear stale. Run "ai sandbox refresh" or "claude /login" to renew.\n';
|
|
34
|
+
}
|
|
35
|
+
if (result.status === 'MISSING') {
|
|
36
|
+
return 'Warning: Claude Code credentials missing on host. Run "claude /login" to authenticate.\n';
|
|
37
|
+
}
|
|
38
|
+
if (result.status === 'KEYCHAIN_WRITE_FAILED') {
|
|
39
|
+
return `Warning: A sandbox refresh produced newer credentials but host Keychain write failed (${formatCredentialWarnings(result.warnings)}). Run "ai sandbox refresh" again or "claude /status" on the host to retry.\n`;
|
|
40
|
+
}
|
|
41
|
+
if (result.status === 'KEYCHAIN_LOCKED' || result.status === 'KEYCHAIN_ERROR') {
|
|
42
|
+
return 'Warning: Host keychain is unavailable; Claude credential sync skipped. Run "ai sandbox refresh" for details.\n';
|
|
43
|
+
}
|
|
44
|
+
if (result.status === 'OK' && result.authoritative !== 'host') {
|
|
45
|
+
const message = `Synced Claude Code credentials from sandbox refresh back to host (expires in ${formatRemaining(result.expiresAt)})`;
|
|
46
|
+
return isTTY ? `\x1b[2m${message}\x1b[0m\n` : `${message}\n`;
|
|
47
|
+
}
|
|
48
|
+
if (result.status === 'OK' && result.filesWritten.length > 0) {
|
|
49
|
+
const message = `Synced Claude Code credentials from host Keychain (expires in ${formatRemaining(result.expiresAt)})`;
|
|
50
|
+
return isTTY ? `\x1b[2m${message}\x1b[0m\n` : `${message}\n`;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
export function enter(args) {
|
|
55
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
56
|
+
process.stdout.write(`${USAGE}\n`);
|
|
57
|
+
if (args.length === 0) {
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
const config = loadConfig();
|
|
63
|
+
validateClaudeCredentialsEnvOverride();
|
|
64
|
+
const engine = detectEngine(config);
|
|
65
|
+
const [branchOrTaskId = '', ...cmd] = args;
|
|
66
|
+
const branch = resolveTaskBranch(branchOrTaskId, config.repoRoot);
|
|
67
|
+
assertValidBranchName(branch);
|
|
68
|
+
const running = runSafeEngine(engine, 'docker', ['ps', '--format', '{{.Names}}']).split('\n');
|
|
69
|
+
const container = containerNameCandidates(config, branch).find((name) => running.includes(name));
|
|
70
|
+
if (!container) {
|
|
71
|
+
throw new Error(`No running sandbox found for branch '${branch}'`);
|
|
72
|
+
}
|
|
73
|
+
if (config.tools.includes('claude-code')) {
|
|
74
|
+
try {
|
|
75
|
+
// Scan all projects so a refresh from a neighbouring sandbox can still flow back to the host.
|
|
76
|
+
const result = reconcileClaudeCredentials(config.home);
|
|
77
|
+
const message = formatCredentialSyncStatus(result);
|
|
78
|
+
if (message) {
|
|
79
|
+
process.stderr.write(message);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
process.stderr.write(`Warning: Failed to sync Claude Code credentials: ${redactCommandError(error instanceof Error ? error.message : 'unknown error')}\n`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const envFlags = terminalEnvFlags();
|
|
87
|
+
if (cmd.length === 0) {
|
|
88
|
+
try {
|
|
89
|
+
materializeDotfiles(config.dotfilesDir, dotfilesCacheDir(config.home, config.project));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
process.stderr.write(`Warning: dotfiles snapshot rebuild failed: ${redactCommandError(error instanceof Error ? error.message : 'unknown error')}\n`);
|
|
93
|
+
}
|
|
94
|
+
return runInteractiveEngine(engine, 'docker', ['exec', '-it', ...envFlags, container, 'bash', TMUX_ENTRY_PATH]);
|
|
95
|
+
}
|
|
96
|
+
return runInteractiveEngine(engine, 'docker', ['exec', '-it', ...envFlags, container, ...cmd]);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=enter.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { loadConfig } from "../config.js";
|
|
6
|
+
import { sandboxBranchLabel, sandboxLabel } from "../constants.js";
|
|
7
|
+
import { detectEngine } from "../engine.js";
|
|
8
|
+
import { runSafeEngine } from "../shell.js";
|
|
9
|
+
import { resolveTools, toolProjectDirCandidates } from "../tools.js";
|
|
10
|
+
const USAGE = 'Usage: ai sandbox ls';
|
|
11
|
+
const CONTAINER_LIST_HEADER = 'NAMES\tSTATUS\tBRANCH';
|
|
12
|
+
// Exported to lock the docker/podman-compatible format in unit tests.
|
|
13
|
+
export function containerListFormat() {
|
|
14
|
+
return '{{.Names}}\t{{.Status}}\t{{.Labels}}';
|
|
15
|
+
}
|
|
16
|
+
export function parseLabels(csv) {
|
|
17
|
+
if (!csv) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const labels = {};
|
|
21
|
+
for (const pair of csv.split(',')) {
|
|
22
|
+
if (!pair) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const eq = pair.indexOf('=');
|
|
26
|
+
if (eq < 0) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
30
|
+
}
|
|
31
|
+
return labels;
|
|
32
|
+
}
|
|
33
|
+
function listChildren(dir) {
|
|
34
|
+
if (!fs.existsSync(dir)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
return fs.readdirSync(dir).sort().map((entry) => path.join(dir, entry));
|
|
38
|
+
}
|
|
39
|
+
export function ls(args = []) {
|
|
40
|
+
if (args.length > 0 && (args[0] === '--help' || args[0] === '-h')) {
|
|
41
|
+
process.stdout.write(`${USAGE}\n`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const config = loadConfig();
|
|
45
|
+
const engine = detectEngine(config);
|
|
46
|
+
const tools = resolveTools(config);
|
|
47
|
+
const label = sandboxLabel(config);
|
|
48
|
+
const containers = runSafeEngine(engine, 'docker', [
|
|
49
|
+
'ps',
|
|
50
|
+
'-a',
|
|
51
|
+
'--filter',
|
|
52
|
+
`label=${label}`,
|
|
53
|
+
'--format',
|
|
54
|
+
containerListFormat()
|
|
55
|
+
]);
|
|
56
|
+
p.intro(pc.cyan(`Sandbox status for ${config.project}`));
|
|
57
|
+
p.log.step('Containers');
|
|
58
|
+
if (!containers) {
|
|
59
|
+
p.log.warn(' No sandbox containers');
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const branchKey = sandboxBranchLabel(config);
|
|
63
|
+
process.stdout.write(` ${CONTAINER_LIST_HEADER}\n`);
|
|
64
|
+
for (const line of containers.split('\n')) {
|
|
65
|
+
const [name = '', status = '', labelsCsv = ''] = line.split('\t');
|
|
66
|
+
const branch = parseLabels(labelsCsv)[branchKey] ?? '';
|
|
67
|
+
process.stdout.write(` ${name}\t${status}\t${branch}\n`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
p.log.step('Worktrees');
|
|
71
|
+
const worktrees = listChildren(config.worktreeBase);
|
|
72
|
+
if (worktrees.length === 0) {
|
|
73
|
+
p.log.warn(' No sandbox worktrees');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
for (const worktree of worktrees) {
|
|
77
|
+
process.stdout.write(` ${worktree}\n`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const tool of tools) {
|
|
81
|
+
p.log.step(`${tool.name} state`);
|
|
82
|
+
const entries = toolProjectDirCandidates(tool, config.project)
|
|
83
|
+
.flatMap((dir) => listChildren(dir));
|
|
84
|
+
if (entries.length === 0) {
|
|
85
|
+
p.log.warn(` No ${tool.name} sandbox state`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
process.stdout.write(` ${entry}\n`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=ls.js.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { loadConfig } from "../config.js";
|
|
6
|
+
import { prepareDockerfile } from "../dockerfile.js";
|
|
7
|
+
import { sandboxImageConfigLabel, sandboxLabel } from "../constants.js";
|
|
8
|
+
import { detectEngine, ensureDocker } from "../engine.js";
|
|
9
|
+
import { runEngine, runOkEngine, runSafeEngine, runVerboseEngine } from "../shell.js";
|
|
10
|
+
import { resolveTools, toolNpmPackagesArg } from "../tools.js";
|
|
11
|
+
import { toEnginePath } from "../engines/wsl2-paths.js";
|
|
12
|
+
import { resolveBuildUid } from "../engines/native.js";
|
|
13
|
+
const USAGE = `Usage: ai sandbox rebuild [--quiet]`;
|
|
14
|
+
function buildSignature(preparedDockerfile, tools) {
|
|
15
|
+
return createHash('sha256')
|
|
16
|
+
.update(JSON.stringify({
|
|
17
|
+
dockerfile: preparedDockerfile.signature,
|
|
18
|
+
tools: tools.map((tool) => tool.npmPackage)
|
|
19
|
+
}))
|
|
20
|
+
.digest('hex')
|
|
21
|
+
.slice(0, 12);
|
|
22
|
+
}
|
|
23
|
+
export function buildArgs(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, env = process.env } = {}) {
|
|
24
|
+
const selectedEngine = engine ?? detectEngine(config);
|
|
25
|
+
const { uid: hostUid, gid: hostGid } = resolveBuildUid({
|
|
26
|
+
engine: selectedEngine,
|
|
27
|
+
runFn,
|
|
28
|
+
runSafeFn,
|
|
29
|
+
env
|
|
30
|
+
});
|
|
31
|
+
return [
|
|
32
|
+
'build',
|
|
33
|
+
'-t',
|
|
34
|
+
config.imageName,
|
|
35
|
+
'--build-arg',
|
|
36
|
+
`HOST_UID=${hostUid}`,
|
|
37
|
+
'--build-arg',
|
|
38
|
+
`HOST_GID=${hostGid}`,
|
|
39
|
+
'--build-arg',
|
|
40
|
+
`AI_TOOL_PACKAGES=${toolNpmPackagesArg(tools)}`,
|
|
41
|
+
'--label',
|
|
42
|
+
sandboxLabel(config),
|
|
43
|
+
'--label',
|
|
44
|
+
`${sandboxImageConfigLabel(config)}=${imageSignature}`,
|
|
45
|
+
'-f',
|
|
46
|
+
toEnginePath(selectedEngine, dockerfilePath),
|
|
47
|
+
toEnginePath(selectedEngine, config.repoRoot)
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
function removeImageIfPresent(imageName, engine) {
|
|
51
|
+
if (runOkEngine(engine, 'docker', ['image', 'inspect', imageName])) {
|
|
52
|
+
runEngine(engine, 'docker', ['rmi', imageName]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function rebuild(args) {
|
|
56
|
+
const { values } = parseArgs({
|
|
57
|
+
args,
|
|
58
|
+
allowPositionals: true,
|
|
59
|
+
strict: true,
|
|
60
|
+
options: {
|
|
61
|
+
quiet: { type: 'boolean', short: 'q' },
|
|
62
|
+
help: { type: 'boolean', short: 'h' }
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
if (values.help) {
|
|
66
|
+
process.stdout.write(`${USAGE}\n`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const config = loadConfig();
|
|
70
|
+
const tools = resolveTools(config);
|
|
71
|
+
const preparedDockerfile = prepareDockerfile(config);
|
|
72
|
+
const imageSignature = buildSignature(preparedDockerfile, tools);
|
|
73
|
+
const quiet = values.quiet ?? false;
|
|
74
|
+
const engine = detectEngine(config);
|
|
75
|
+
await ensureDocker(config, undefined);
|
|
76
|
+
p.intro(pc.cyan('Rebuilding sandbox image'));
|
|
77
|
+
try {
|
|
78
|
+
if (quiet) {
|
|
79
|
+
const spinner = p.spinner();
|
|
80
|
+
spinner.start(`Removing old image ${config.imageName}...`);
|
|
81
|
+
removeImageIfPresent(config.imageName, engine);
|
|
82
|
+
spinner.stop('Old image removed');
|
|
83
|
+
spinner.start('Building image...');
|
|
84
|
+
runEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, { engine }), {
|
|
85
|
+
cwd: config.repoRoot
|
|
86
|
+
});
|
|
87
|
+
spinner.stop(pc.green('Sandbox image rebuilt'));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
p.log.step(`Removing old image ${config.imageName}`);
|
|
91
|
+
removeImageIfPresent(config.imageName, engine);
|
|
92
|
+
p.log.step('Building image');
|
|
93
|
+
runVerboseEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, { engine }), { cwd: config.repoRoot });
|
|
94
|
+
p.log.success(pc.green('Sandbox image rebuilt'));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
preparedDockerfile.cleanup();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=rebuild.js.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { buildLockedGuidance, discoverProjects, formatCredentialWarnings, formatRemaining, reconcileClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from "../credentials.js";
|
|
4
|
+
import { runProbe } from "../shell.js";
|
|
5
|
+
const USAGE = 'Usage: ai sandbox refresh';
|
|
6
|
+
export function probeClaudeStatus(spawnFn = runProbe) {
|
|
7
|
+
const result = spawnFn('claude', ['/status'], {
|
|
8
|
+
encoding: 'utf8',
|
|
9
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
10
|
+
timeout: 30_000
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
ok: result.status === 0,
|
|
14
|
+
stderr: result.stderr?.toString() ?? '',
|
|
15
|
+
error: result.error?.message ?? null
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export async function refresh(args, deps = {}) {
|
|
19
|
+
const { spawnFn = runProbe, execFn, readFn, existsFn, writeFn, writeHostFn, discoverFn = discoverProjects, writeStdout = (chunk) => process.stdout.write(chunk), writeStderr = (chunk) => process.stderr.write(chunk) } = deps;
|
|
20
|
+
if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
|
|
21
|
+
writeStdout(`${USAGE}\n`);
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
const { positionals } = parseArgs({ args, allowPositionals: true, strict: true });
|
|
25
|
+
if (positionals.length > 0) {
|
|
26
|
+
throw new Error(USAGE);
|
|
27
|
+
}
|
|
28
|
+
validateClaudeCredentialsEnvOverride();
|
|
29
|
+
const home = homedir();
|
|
30
|
+
if (!home) {
|
|
31
|
+
throw new Error('sandbox: home directory is required');
|
|
32
|
+
}
|
|
33
|
+
const projects = discoverFn(home);
|
|
34
|
+
if (projects.length === 0) {
|
|
35
|
+
writeStdout('No project credentials to refresh.\n');
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
const reconcileOptions = { execFn, readFn, existsFn, writeFn, writeHostFn, projects };
|
|
39
|
+
let result = reconcileClaudeCredentials(home, reconcileOptions);
|
|
40
|
+
if (result.status === 'STALE_ACCESS' && result.authoritative === null) {
|
|
41
|
+
writeStdout('Host credentials appear stale; probing claude /status to trigger refresh...\n');
|
|
42
|
+
const probe = probeClaudeStatus(spawnFn);
|
|
43
|
+
if (!probe.ok) {
|
|
44
|
+
writeStderr(`Probe failed: ${redactCommandError(probe.stderr || probe.error || 'unknown error')}\n`);
|
|
45
|
+
writeStderr('Run "claude /login" on the host to renew credentials.\n');
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
writeStdout('Probe succeeded; re-inspecting host credentials.\n');
|
|
49
|
+
result = reconcileClaudeCredentials(home, reconcileOptions);
|
|
50
|
+
}
|
|
51
|
+
if (result.status === 'MISSING') {
|
|
52
|
+
writeStderr('No Claude Code credentials found on host.\n');
|
|
53
|
+
writeStderr('Run "claude /login" on the host to authenticate.\n');
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
if (result.status === 'KEYCHAIN_LOCKED') {
|
|
57
|
+
writeStderr(`${buildLockedGuidance()}\n`);
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
if (result.status === 'KEYCHAIN_ERROR') {
|
|
61
|
+
writeStderr(`Host keychain error: ${redactCommandError(result.detail || 'unknown error')}\n`);
|
|
62
|
+
writeStderr(`${buildLockedGuidance()}\n`);
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
if (result.status === 'KEYCHAIN_WRITE_FAILED') {
|
|
66
|
+
writeStderr(`[host] keychain write failed: ${formatCredentialWarnings(result.warnings) || 'unknown error'}\n`);
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
if (result.status !== 'OK') {
|
|
70
|
+
writeStderr('Host credentials still invalid after probe; run "claude /login".\n');
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
if (result.authoritative && result.authoritative !== 'host' && result.hostWritten) {
|
|
74
|
+
writeStdout(`[host] reconciled from ${result.authoritative}\n`);
|
|
75
|
+
}
|
|
76
|
+
for (const project of projects) {
|
|
77
|
+
const action = result.filesWritten.includes(project) ? 'updated' : 'unchanged';
|
|
78
|
+
writeStdout(`[${project}] ${action}; expires in ${formatRemaining(result.expiresAt)}\n`);
|
|
79
|
+
}
|
|
80
|
+
for (const failure of result.fileErrors) {
|
|
81
|
+
writeStderr(`[${failure.project}] sync failed: ${failure.error}\n`);
|
|
82
|
+
}
|
|
83
|
+
return result.fileErrors.length > 0 ? 1 : 0;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=refresh.js.map
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
import * as p from '@clack/prompts';
|
|
5
|
+
import pc from 'picocolors';
|
|
6
|
+
import { loadConfig } from "../config.js";
|
|
7
|
+
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel, shareBranchDir, worktreeDirCandidates } from "../constants.js";
|
|
8
|
+
import { ENGINES, detectEngine, engineDisplayName, isManagedEngine, stopManagedVm } from "../engine.js";
|
|
9
|
+
import { run, runOk, runSafe, runSafeEngine } from "../shell.js";
|
|
10
|
+
import { resolveTaskBranch } from "../task-resolver.js";
|
|
11
|
+
import { resolveTools, toolConfigDirCandidates, toolProjectDirCandidates } from "../tools.js";
|
|
12
|
+
const USAGE = `Usage: ai sandbox rm <branch> [--all]`;
|
|
13
|
+
function projectToolDirs(config, tools) {
|
|
14
|
+
return tools.flatMap((tool) => toolProjectDirCandidates(tool, config.project));
|
|
15
|
+
}
|
|
16
|
+
export function assertManagedPath(root, target) {
|
|
17
|
+
const resolvedRoot = path.resolve(root);
|
|
18
|
+
const resolvedTarget = path.resolve(target);
|
|
19
|
+
const relative = path.relative(resolvedRoot, resolvedTarget);
|
|
20
|
+
if (relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
throw new Error(`Refusing to remove path outside managed sandbox root: ${target}`);
|
|
24
|
+
}
|
|
25
|
+
async function rmOne(config, tools, branch) {
|
|
26
|
+
assertValidBranchName(branch);
|
|
27
|
+
const engine = detectEngine(config);
|
|
28
|
+
let effectiveBranch = branch;
|
|
29
|
+
let worktreeCandidates = worktreeDirCandidates(config, branch);
|
|
30
|
+
let toolCandidates = tools.map((tool) => ({
|
|
31
|
+
tool,
|
|
32
|
+
candidates: toolConfigDirCandidates(tool, config.project, branch)
|
|
33
|
+
}));
|
|
34
|
+
p.intro(pc.cyan(`Removing sandbox for ${branch}`));
|
|
35
|
+
const existing = runSafeEngine(engine, 'docker', ['ps', '-a', '--format', '{{.Names}}']).split('\n').filter(Boolean);
|
|
36
|
+
const matchedContainers = containerNameCandidates(config, branch)
|
|
37
|
+
.filter((name) => existing.includes(name));
|
|
38
|
+
if (matchedContainers.length > 0) {
|
|
39
|
+
const resolvedBranch = runSafeEngine(engine, 'docker', [
|
|
40
|
+
'inspect',
|
|
41
|
+
'-f',
|
|
42
|
+
`{{ index .Config.Labels "${sandboxBranchLabel(config)}" }}`,
|
|
43
|
+
matchedContainers[0] ?? ''
|
|
44
|
+
]);
|
|
45
|
+
if (resolvedBranch) {
|
|
46
|
+
effectiveBranch = resolvedBranch;
|
|
47
|
+
worktreeCandidates = worktreeDirCandidates(config, effectiveBranch);
|
|
48
|
+
toolCandidates = tools.map((tool) => ({
|
|
49
|
+
tool,
|
|
50
|
+
candidates: toolConfigDirCandidates(tool, config.project, effectiveBranch)
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
const spinner = p.spinner();
|
|
54
|
+
spinner.start(`Stopping container(s): ${matchedContainers.join(', ')}`);
|
|
55
|
+
for (const name of matchedContainers) {
|
|
56
|
+
runSafeEngine(engine, 'docker', ['stop', name]);
|
|
57
|
+
runSafeEngine(engine, 'docker', ['rm', name]);
|
|
58
|
+
}
|
|
59
|
+
spinner.stop(pc.green(`Removed container(s): ${matchedContainers.join(', ')}`));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
p.log.warn(`No sandbox container found for '${branch}'`);
|
|
63
|
+
}
|
|
64
|
+
const existingWorktrees = worktreeCandidates.filter((candidate) => fs.existsSync(candidate));
|
|
65
|
+
if (existingWorktrees.length > 0) {
|
|
66
|
+
const shouldRemoveWorktree = await p.confirm({
|
|
67
|
+
message: `Remove worktree(s): ${existingWorktrees.join(', ')}?`,
|
|
68
|
+
initialValue: true
|
|
69
|
+
});
|
|
70
|
+
if (p.isCancel(shouldRemoveWorktree)) {
|
|
71
|
+
p.outro('Cancelled');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (shouldRemoveWorktree) {
|
|
75
|
+
for (const worktree of existingWorktrees) {
|
|
76
|
+
try {
|
|
77
|
+
run('git', ['-C', config.repoRoot, 'worktree', 'remove', worktree, '--force']);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
assertManagedPath(config.worktreeBase, worktree);
|
|
81
|
+
fs.rmSync(worktree, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const shouldDeleteBranch = await p.confirm({
|
|
85
|
+
message: `Also delete local branch '${effectiveBranch}'?`,
|
|
86
|
+
initialValue: true
|
|
87
|
+
});
|
|
88
|
+
if (!p.isCancel(shouldDeleteBranch) && shouldDeleteBranch) {
|
|
89
|
+
if (!runOk('git', ['-C', config.repoRoot, 'branch', '-D', effectiveBranch])) {
|
|
90
|
+
p.log.warn(`Local branch '${effectiveBranch}' was not deleted`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const { tool, candidates } of toolCandidates) {
|
|
96
|
+
for (const dir of candidates.filter((candidate) => fs.existsSync(candidate))) {
|
|
97
|
+
assertManagedPath(tool.sandboxBase, dir);
|
|
98
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
99
|
+
p.log.success(`${tool.name} state removed: ${dir}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const shareBranch = shareBranchDir(config, effectiveBranch);
|
|
103
|
+
if (fs.existsSync(shareBranch)) {
|
|
104
|
+
const shouldRemoveShare = await p.confirm({
|
|
105
|
+
message: `Remove share dir for branch '${effectiveBranch}' (${shareBranch})?`,
|
|
106
|
+
initialValue: true
|
|
107
|
+
});
|
|
108
|
+
if (!p.isCancel(shouldRemoveShare) && shouldRemoveShare) {
|
|
109
|
+
assertManagedPath(config.shareBase, shareBranch);
|
|
110
|
+
fs.rmSync(shareBranch, { recursive: true, force: true });
|
|
111
|
+
p.log.success(`Share dir removed: ${shareBranch}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
p.outro(pc.green('Sandbox removed'));
|
|
115
|
+
}
|
|
116
|
+
async function rmAll(config, tools) {
|
|
117
|
+
const engine = detectEngine(config);
|
|
118
|
+
p.intro(pc.cyan(`Removing all sandboxes for ${config.project}`));
|
|
119
|
+
const containers = runSafeEngine(engine, 'docker', [
|
|
120
|
+
'ps',
|
|
121
|
+
'-a',
|
|
122
|
+
'--filter',
|
|
123
|
+
`label=${sandboxLabel(config)}`,
|
|
124
|
+
'--format',
|
|
125
|
+
'{{.Names}}'
|
|
126
|
+
]);
|
|
127
|
+
if (containers) {
|
|
128
|
+
const spinner = p.spinner();
|
|
129
|
+
spinner.start('Stopping project sandbox containers...');
|
|
130
|
+
for (const name of containers.split('\n').filter(Boolean)) {
|
|
131
|
+
runSafeEngine(engine, 'docker', ['stop', name]);
|
|
132
|
+
runSafeEngine(engine, 'docker', ['rm', name]);
|
|
133
|
+
}
|
|
134
|
+
spinner.stop(pc.green('Project sandbox containers removed'));
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
p.log.warn('No project sandbox containers found');
|
|
138
|
+
}
|
|
139
|
+
if (fs.existsSync(config.worktreeBase) && fs.readdirSync(config.worktreeBase).length > 0) {
|
|
140
|
+
const shouldRemoveWorktrees = await p.confirm({
|
|
141
|
+
message: `Remove all worktrees in ${config.worktreeBase}?`,
|
|
142
|
+
initialValue: true
|
|
143
|
+
});
|
|
144
|
+
if (!p.isCancel(shouldRemoveWorktrees) && shouldRemoveWorktrees) {
|
|
145
|
+
for (const entry of fs.readdirSync(config.worktreeBase)) {
|
|
146
|
+
const dir = path.join(config.worktreeBase, entry);
|
|
147
|
+
try {
|
|
148
|
+
run('git', ['-C', config.repoRoot, 'worktree', 'remove', dir, '--force']);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
assertManagedPath(config.worktreeBase, dir);
|
|
152
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
runSafe('git', ['-C', config.repoRoot, 'worktree', 'prune']);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const dir of projectToolDirs(config, tools)) {
|
|
159
|
+
if (fs.existsSync(dir)) {
|
|
160
|
+
assertManagedPath(path.dirname(dir), dir);
|
|
161
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
162
|
+
p.log.success(`Removed tool state: ${dir}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (fs.existsSync(config.shareBase) && fs.readdirSync(config.shareBase).length > 0) {
|
|
166
|
+
const shouldRemoveAllShares = await p.confirm({
|
|
167
|
+
message: `Remove all share dirs for project (${config.shareBase})?`,
|
|
168
|
+
initialValue: true
|
|
169
|
+
});
|
|
170
|
+
if (!p.isCancel(shouldRemoveAllShares) && shouldRemoveAllShares) {
|
|
171
|
+
assertManagedPath(path.dirname(config.shareBase), config.shareBase);
|
|
172
|
+
fs.rmSync(config.shareBase, { recursive: true, force: true });
|
|
173
|
+
p.log.success(`Project share dirs removed: ${config.shareBase}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const shouldRemoveImage = await p.confirm({
|
|
177
|
+
message: `Remove image ${config.imageName}?`,
|
|
178
|
+
initialValue: false
|
|
179
|
+
});
|
|
180
|
+
if (!p.isCancel(shouldRemoveImage) && shouldRemoveImage) {
|
|
181
|
+
runSafeEngine(engine, 'docker', ['rmi', config.imageName]);
|
|
182
|
+
}
|
|
183
|
+
if (isManagedEngine(engine)) {
|
|
184
|
+
if (engine === ENGINES.WSL2) {
|
|
185
|
+
p.log.warn('Windows uses Docker Desktop with WSL2. Stop it from Docker Desktop or run "wsl --shutdown" manually.');
|
|
186
|
+
p.outro(pc.green('All project sandboxes removed'));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const name = engineDisplayName(engine);
|
|
190
|
+
const shouldStopVm = await p.confirm({
|
|
191
|
+
message: `Stop ${name} VM?`,
|
|
192
|
+
initialValue: false
|
|
193
|
+
});
|
|
194
|
+
if (!p.isCancel(shouldStopVm) && shouldStopVm) {
|
|
195
|
+
stopManagedVm(config);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
p.outro(pc.green('All project sandboxes removed'));
|
|
199
|
+
}
|
|
200
|
+
export async function rm(args) {
|
|
201
|
+
const { values, positionals } = parseArgs({
|
|
202
|
+
args,
|
|
203
|
+
allowPositionals: true,
|
|
204
|
+
strict: true,
|
|
205
|
+
options: {
|
|
206
|
+
all: { type: 'boolean' },
|
|
207
|
+
help: { type: 'boolean', short: 'h' }
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
if (values.help) {
|
|
211
|
+
process.stdout.write(`${USAGE}\n`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!values.all && positionals.length !== 1) {
|
|
215
|
+
throw new Error(USAGE);
|
|
216
|
+
}
|
|
217
|
+
const config = loadConfig();
|
|
218
|
+
const tools = resolveTools(config);
|
|
219
|
+
if (values.all) {
|
|
220
|
+
await rmAll(config, tools);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const branch = resolveTaskBranch(positionals[0] ?? '', config.repoRoot);
|
|
224
|
+
await rmOne(config, tools, branch);
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=rm.js.map
|