swarm-orchestrator 7.0.0-alpha.0
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/LICENSE +15 -0
- package/README.md +199 -0
- package/agents/reviewer.agent.md +50 -0
- package/agents/worker.agent.md +41 -0
- package/config/default-agents.yaml +56 -0
- package/config/pm-agent.yaml +36 -0
- package/config/quality-gates.yaml +88 -0
- package/config/repair-agent.yaml +34 -0
- package/config/semgrep-rules/complexity-mismatch.yaml +6 -0
- package/config/semgrep-rules/exception-swallowing.yaml +13 -0
- package/config/semgrep-rules/hardcoded-answer.yaml +6 -0
- package/config/semgrep-rules/mock-mutation.yaml +6 -0
- package/config/semgrep-rules/test-modification.yaml +6 -0
- package/config/user-agents.yaml +23 -0
- package/dist/src/adapters/adapter-factory.js +29 -0
- package/dist/src/adapters/agent-adapter.js +32 -0
- package/dist/src/adapters/claude-code-adapter.js +126 -0
- package/dist/src/adapters/claude-code-teams.js +119 -0
- package/dist/src/adapters/codex-adapter.js +118 -0
- package/dist/src/adapters/copilot-adapter.js +224 -0
- package/dist/src/adapters/fatal-error-classifier.js +88 -0
- package/dist/src/adapters/index.js +44 -0
- package/dist/src/adapters/persistent-session.js +204 -0
- package/dist/src/adapters/process-supervisor.js +199 -0
- package/dist/src/agents-exporter.js +398 -0
- package/dist/src/analytics-log.js +161 -0
- package/dist/src/baseline-scanner.js +65 -0
- package/dist/src/bootstrap-evidence.js +88 -0
- package/dist/src/bootstrap-orchestrator.js +205 -0
- package/dist/src/bootstrap-types.js +5 -0
- package/dist/src/branch-merger.js +427 -0
- package/dist/src/cli/attest-handlers.js +44 -0
- package/dist/src/cli/cost-prompt.js +51 -0
- package/dist/src/cli/demo-handlers.js +274 -0
- package/dist/src/cli/flags.js +152 -0
- package/dist/src/cli/index.js +57 -0
- package/dist/src/cli/live-status.js +186 -0
- package/dist/src/cli/misc-handlers.js +242 -0
- package/dist/src/cli/plan-handlers.js +350 -0
- package/dist/src/cli/share-handlers.js +163 -0
- package/dist/src/cli/status-handlers.js +457 -0
- package/dist/src/cli/swarm-handlers.js +638 -0
- package/dist/src/cli/usage.js +72 -0
- package/dist/src/cli.js +217 -0
- package/dist/src/commit-pattern-detector.js +143 -0
- package/dist/src/commit-quality-analyzer.js +38 -0
- package/dist/src/config-loader.js +339 -0
- package/dist/src/context-broker.js +358 -0
- package/dist/src/copilot-cli-wrapper.js +258 -0
- package/dist/src/cost-estimator.js +241 -0
- package/dist/src/defaults.js +18 -0
- package/dist/src/demo-mode.js +180 -0
- package/dist/src/deployment-handler.js +78 -0
- package/dist/src/deployment-manager.js +228 -0
- package/dist/src/execution-queue.js +196 -0
- package/dist/src/external-tool-manager.js +184 -0
- package/dist/src/gate-prompt-builder.js +117 -0
- package/dist/src/gate-remediation.js +173 -0
- package/dist/src/github-issues-ingester.js +152 -0
- package/dist/src/github-mcp-integrator.js +164 -0
- package/dist/src/hook-generator.js +229 -0
- package/dist/src/index.js +48 -0
- package/dist/src/knowledge-base.js +300 -0
- package/dist/src/logger.js +111 -0
- package/dist/src/meta-analyzer.js +208 -0
- package/dist/src/metrics-collector.js +209 -0
- package/dist/src/metrics-types.js +5 -0
- package/dist/src/multi-repo-coordinator.js +179 -0
- package/dist/src/orchestrator/async-meta-analysis.js +84 -0
- package/dist/src/orchestrator/fatal-run-error.js +43 -0
- package/dist/src/orchestrator/final-gates-remediation.js +258 -0
- package/dist/src/orchestrator/git-state-utils.js +119 -0
- package/dist/src/orchestrator/pause-controller.js +55 -0
- package/dist/src/orchestrator/replan-runner.js +258 -0
- package/dist/src/orchestrator/step-executor.js +391 -0
- package/dist/src/orchestrator/wave-scheduler-loop.js +204 -0
- package/dist/src/owasp-mapper.js +153 -0
- package/dist/src/owasp-report-renderer.js +39 -0
- package/dist/src/plan-files.js +142 -0
- package/dist/src/plan-generator.js +1207 -0
- package/dist/src/pm-agent.js +296 -0
- package/dist/src/post-run-reporter.js +203 -0
- package/dist/src/pr-automation.js +103 -0
- package/dist/src/pr-manager.js +318 -0
- package/dist/src/presenter/index.js +209 -0
- package/dist/src/prompt-builder.js +173 -0
- package/dist/src/quality-gates/config-loader.js +196 -0
- package/dist/src/quality-gates/default-config.js +243 -0
- package/dist/src/quality-gates/file-utils.js +152 -0
- package/dist/src/quality-gates/gate-runner.js +171 -0
- package/dist/src/quality-gates/gates/accessibility.js +355 -0
- package/dist/src/quality-gates/gates/duplicate-blocks.js +144 -0
- package/dist/src/quality-gates/gates/hardcoded-config.js +52 -0
- package/dist/src/quality-gates/gates/readme-claims.js +62 -0
- package/dist/src/quality-gates/gates/runtime-checks.js +265 -0
- package/dist/src/quality-gates/gates/scaffold-defaults.js +108 -0
- package/dist/src/quality-gates/gates/test-coverage.js +240 -0
- package/dist/src/quality-gates/gates/test-file-protection.js +85 -0
- package/dist/src/quality-gates/gates/test-isolation.js +74 -0
- package/dist/src/quality-gates/index.js +17 -0
- package/dist/src/quality-gates/registry.js +205 -0
- package/dist/src/quality-gates/types.js +2 -0
- package/dist/src/quick-fix-mode.js +302 -0
- package/dist/src/recipe-loader.js +122 -0
- package/dist/src/repair-agent.js +396 -0
- package/dist/src/repo-analyzer.js +342 -0
- package/dist/src/report-generator.js +146 -0
- package/dist/src/report-renderer.js +90 -0
- package/dist/src/requirement-filter.js +52 -0
- package/dist/src/sarif-formatter.js +140 -0
- package/dist/src/scheduling/dependency-analyzer.js +178 -0
- package/dist/src/scheduling/work-stealing-queue.js +123 -0
- package/dist/src/secret-redactor.js +90 -0
- package/dist/src/session-executor.js +587 -0
- package/dist/src/session-manager.js +221 -0
- package/dist/src/share/transcript-verification.js +128 -0
- package/dist/src/share-parser.js +477 -0
- package/dist/src/spinner.js +146 -0
- package/dist/src/step-runner.js +365 -0
- package/dist/src/swarm-orchestrator.js +686 -0
- package/dist/src/task-classifier.js +134 -0
- package/dist/src/test-command-discovery.js +123 -0
- package/dist/src/text-similarity.js +24 -0
- package/dist/src/tier-maps.js +158 -0
- package/dist/src/types.js +5 -0
- package/dist/src/url-shortener.js +135 -0
- package/dist/src/verification/attestation.js +166 -0
- package/dist/src/verification/cheat-detector.js +240 -0
- package/dist/src/verification/command-runner.js +70 -0
- package/dist/src/verification/composite-score.js +157 -0
- package/dist/src/verification/cosign-attestation.js +137 -0
- package/dist/src/verification/diff-analysis.js +89 -0
- package/dist/src/verification/differential-gate.js +173 -0
- package/dist/src/verification/index.js +41 -0
- package/dist/src/verification/mutation-gate.js +242 -0
- package/dist/src/verification/property-gate.js +201 -0
- package/dist/src/verification/test-synthesizer.js +243 -0
- package/dist/src/verifier/outcome-checks.js +396 -0
- package/dist/src/verifier/transcript-checks.js +207 -0
- package/dist/src/verifier/verification-reporters.js +148 -0
- package/dist/src/verifier-engine.js +491 -0
- package/dist/src/wave-resizer.js +131 -0
- package/dist/src/wave-scheduler.js +44 -0
- package/dist/src/worktree-manager.js +368 -0
- package/dist/src/worktree-reserved-paths.js +97 -0
- package/package.json +80 -0
- package/plugin/agents/.gitkeep +0 -0
- package/plugin/agents/backend_master.agent.md +72 -0
- package/plugin/agents/devops_pro.agent.md +45 -0
- package/plugin/agents/frontend_expert.agent.md +59 -0
- package/plugin/agents/integrator_finalizer.agent.md +44 -0
- package/plugin/agents/meta_reviewer.agent.md +44 -0
- package/plugin/agents/security_auditor.agent.md +43 -0
- package/plugin/agents/tester_elite.agent.md +43 -0
- package/plugin/hooks/hooks.json +19 -0
- package/plugin/hooks/post-tool-use.json +9 -0
- package/plugin/hooks/pre-tool-use.json +9 -0
- package/plugin/plugin.json +47 -0
- package/plugin/skills/swarm-gates/SKILL.md +53 -0
- package/plugin/skills/swarm-orchestrate/SKILL.md +34 -0
- package/plugin/skills/swarm-verify/SKILL.md +44 -0
- package/templates/README.md +35 -0
- package/templates/cli-tool.json +31 -0
- package/templates/fullstack.json +52 -0
- package/templates/library.json +38 -0
- package/templates/react-app.json +45 -0
- package/templates/recipes/add-api-docs.json +35 -0
- package/templates/recipes/add-auth.json +47 -0
- package/templates/recipes/add-ci.json +39 -0
- package/templates/recipes/add-tests.json +39 -0
- package/templates/recipes/migrate-to-ts.json +42 -0
- package/templates/recipes/refactor-modularize.json +44 -0
- package/templates/recipes/security-audit.json +35 -0
- package/templates/rest-api.json +38 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Demo and template CLI command handlers.
|
|
4
|
+
* Extracted from cli-handlers.ts for modularity.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.installDemoDependencies = installDemoDependencies;
|
|
41
|
+
exports.handleDemoCommand = handleDemoCommand;
|
|
42
|
+
exports.runDemo = runDemo;
|
|
43
|
+
exports.handleTemplatesCommand = handleTemplatesCommand;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const demo_mode_1 = require("../demo-mode");
|
|
47
|
+
const plan_files_1 = require("../plan-files");
|
|
48
|
+
const flags_1 = require("./flags");
|
|
49
|
+
const logger_1 = require("../logger");
|
|
50
|
+
// Minimal ANSI helpers for a cleaner demo UX. No chalk dep to keep the
|
|
51
|
+
// dist footprint unchanged. NO_COLOR env disables coloring for CI / pipes.
|
|
52
|
+
const colorOn = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
53
|
+
const c = {
|
|
54
|
+
bold: (s) => colorOn ? `\x1b[1m${s}\x1b[22m` : s,
|
|
55
|
+
dim: (s) => colorOn ? `\x1b[2m${s}\x1b[22m` : s,
|
|
56
|
+
cyan: (s) => colorOn ? `\x1b[36m${s}\x1b[39m` : s,
|
|
57
|
+
magenta: (s) => colorOn ? `\x1b[35m${s}\x1b[39m` : s,
|
|
58
|
+
green: (s) => colorOn ? `\x1b[32m${s}\x1b[39m` : s,
|
|
59
|
+
};
|
|
60
|
+
const logger = (0, logger_1.getLogger)('cli:demo');
|
|
61
|
+
/**
|
|
62
|
+
* Install npm dependencies for all package.json files in demo output.
|
|
63
|
+
* Makes the demo runnable immediately after completion.
|
|
64
|
+
*/
|
|
65
|
+
async function installDemoDependencies(demoDir) {
|
|
66
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
67
|
+
const packageJsonPaths = [];
|
|
68
|
+
function findPackageJsons(dir, depth = 0) {
|
|
69
|
+
if (depth > 3)
|
|
70
|
+
return;
|
|
71
|
+
try {
|
|
72
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'runs')
|
|
75
|
+
continue;
|
|
76
|
+
const fullPath = path.join(dir, entry.name);
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
findPackageJsons(fullPath, depth + 1);
|
|
79
|
+
}
|
|
80
|
+
else if (entry.name === 'package.json') {
|
|
81
|
+
packageJsonPaths.push(dir);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Permission denied or broken symlink; skip this subtree
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
findPackageJsons(demoDir);
|
|
90
|
+
if (packageJsonPaths.length === 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
packageJsonPaths.sort((a, b) => a.length - b.length);
|
|
94
|
+
const colorOn = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
95
|
+
const dim = (s) => colorOn ? `\x1b[2m${s}\x1b[22m` : s;
|
|
96
|
+
const green = (s) => colorOn ? `\x1b[32m${s}\x1b[39m` : s;
|
|
97
|
+
const red = (s) => colorOn ? `\x1b[31m${s}\x1b[39m` : s;
|
|
98
|
+
logger.info(`\n ${dim(`installing dependencies (${packageJsonPaths.length} location${packageJsonPaths.length === 1 ? '' : 's'})`)}`);
|
|
99
|
+
let successCount = 0;
|
|
100
|
+
let failCount = 0;
|
|
101
|
+
for (const pkgDir of packageJsonPaths) {
|
|
102
|
+
const relativePath = path.relative(demoDir, pkgDir) || '.';
|
|
103
|
+
try {
|
|
104
|
+
execSync('npm install --loglevel=error', {
|
|
105
|
+
cwd: pkgDir,
|
|
106
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
107
|
+
timeout: 120000
|
|
108
|
+
});
|
|
109
|
+
logger.info(` ${green('✓')} ${relativePath}`);
|
|
110
|
+
successCount++;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
logger.warn(` ${red('✗')} ${relativePath} ${dim('(run npm install manually)')}`);
|
|
114
|
+
failCount++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (failCount > 0) {
|
|
118
|
+
logger.warn(` ${dim(`installed ${successCount}/${packageJsonPaths.length}; resolve failures before running`)}`);
|
|
119
|
+
}
|
|
120
|
+
logger.info(`\n ${dim('to run:')}`);
|
|
121
|
+
logger.info(` cd ${demoDir}`);
|
|
122
|
+
const rootPkgPath = path.join(demoDir, 'package.json');
|
|
123
|
+
if (fs.existsSync(rootPkgPath)) {
|
|
124
|
+
try {
|
|
125
|
+
const pkg = JSON.parse(fs.readFileSync(rootPkgPath, 'utf8'));
|
|
126
|
+
if (pkg.scripts?.['start:server']) {
|
|
127
|
+
logger.info(` npm run start:server ${dim('# backend')}`);
|
|
128
|
+
}
|
|
129
|
+
else if (pkg.scripts?.start) {
|
|
130
|
+
logger.info(` npm start ${dim('# server')}`);
|
|
131
|
+
}
|
|
132
|
+
if (pkg.scripts?.dev) {
|
|
133
|
+
logger.info(` npm run dev ${dim('# dev server')}`);
|
|
134
|
+
}
|
|
135
|
+
if (pkg.scripts?.test) {
|
|
136
|
+
logger.info(` npm test ${dim('# tests')}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
logger.info(' npm start');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const frontendPkgPath = path.join(demoDir, 'frontend', 'package.json');
|
|
144
|
+
if (fs.existsSync(frontendPkgPath)) {
|
|
145
|
+
logger.info(`\n ${dim('# frontend (separate terminal)')}`);
|
|
146
|
+
logger.info(' cd frontend && npm run dev');
|
|
147
|
+
}
|
|
148
|
+
logger.info('');
|
|
149
|
+
}
|
|
150
|
+
async function handleDemoCommand(args) {
|
|
151
|
+
const subcommand = args[1];
|
|
152
|
+
if (!subcommand || subcommand === '--help') {
|
|
153
|
+
(0, logger_1.setPrettyMode)(true);
|
|
154
|
+
logger.info('\n demo scenarios');
|
|
155
|
+
logger.info(' demo-fast two independent utility files, one wave');
|
|
156
|
+
logger.info(' api-quick REST API + tests + Dockerfile, two waves');
|
|
157
|
+
logger.info('\n run: swarm demo <name>');
|
|
158
|
+
logger.info(' list: swarm demo list\n');
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
if (subcommand === 'list') {
|
|
162
|
+
(0, logger_1.setPrettyMode)(true);
|
|
163
|
+
const demoMode = new demo_mode_1.DemoMode();
|
|
164
|
+
const scenarios = demoMode.getAvailableScenarios();
|
|
165
|
+
logger.info(`\n ${c.bold('demo scenarios')}\n`);
|
|
166
|
+
scenarios.forEach(scenario => {
|
|
167
|
+
logger.info(` ${c.bold(scenario.name)}`);
|
|
168
|
+
logger.info(` ${c.dim(scenario.description)}`);
|
|
169
|
+
logger.info(` ${c.dim(`${scenario.steps.length} step${scenario.steps.length === 1 ? '' : 's'} · ${scenario.expectedDuration}`)}\n`);
|
|
170
|
+
});
|
|
171
|
+
logger.info(` ${c.dim('run:')} swarm demo <name>\n`);
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
// Run a demo scenario
|
|
175
|
+
try {
|
|
176
|
+
return await runDemo(subcommand);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
logger.error('Error running demo:', error instanceof Error ? error.message : error);
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function runDemo(scenarioName) {
|
|
184
|
+
// Demos get a cleaner UX: drop `[scope]` log prefixes.
|
|
185
|
+
(0, logger_1.setPrettyMode)(true);
|
|
186
|
+
const { executeSwarm } = await Promise.resolve().then(() => __importStar(require('./swarm-handlers')));
|
|
187
|
+
const demoMode = new demo_mode_1.DemoMode();
|
|
188
|
+
const scenario = demoMode.getScenario(scenarioName);
|
|
189
|
+
// Parse all CLI flags (including --target, --pr, --hooks, etc.)
|
|
190
|
+
const cliArgs = process.argv.slice(2);
|
|
191
|
+
const parsedFlags = (0, flags_1.parseSwarmFlags)(cliArgs);
|
|
192
|
+
const os = require('os');
|
|
193
|
+
let demoDir;
|
|
194
|
+
let isExternalTarget = false;
|
|
195
|
+
if (parsedFlags.targetDir) {
|
|
196
|
+
demoDir = path.resolve(parsedFlags.targetDir);
|
|
197
|
+
isExternalTarget = true;
|
|
198
|
+
if (!fs.existsSync(demoDir)) {
|
|
199
|
+
logger.error(`${c.bold('error:')} target directory does not exist: ${demoDir}`);
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
demoDir = fs.mkdtempSync(path.join(os.tmpdir(), `swarm-demo-${scenarioName}-`));
|
|
205
|
+
const { execSync } = require('child_process');
|
|
206
|
+
execSync('git init', { cwd: demoDir, stdio: 'pipe' });
|
|
207
|
+
execSync('git config user.email "demo@swarm.local"', { cwd: demoDir, stdio: 'pipe' });
|
|
208
|
+
execSync('git config user.name "Swarm Demo"', { cwd: demoDir, stdio: 'pipe' });
|
|
209
|
+
fs.writeFileSync(path.join(demoDir, 'README.md'), `# Swarm Demo: ${scenarioName}\n`);
|
|
210
|
+
execSync('git add . && git commit -m "init demo"', { cwd: demoDir, stdio: 'pipe' });
|
|
211
|
+
}
|
|
212
|
+
// Run the demo in the target directory; restore cwd when done
|
|
213
|
+
const originalDir = process.cwd();
|
|
214
|
+
process.chdir(demoDir);
|
|
215
|
+
try {
|
|
216
|
+
if (!scenario) {
|
|
217
|
+
logger.error(`${c.bold('error:')} demo scenario "${scenarioName}" not found`);
|
|
218
|
+
logger.info('');
|
|
219
|
+
logger.info('Available scenarios:');
|
|
220
|
+
demoMode.getAvailableScenarios().forEach(s => {
|
|
221
|
+
logger.info(` ${c.cyan(s.name.padEnd(12))} ${c.dim(s.description)}`);
|
|
222
|
+
});
|
|
223
|
+
logger.info('');
|
|
224
|
+
logger.info(`Run: ${c.bold('swarm demo list')}`);
|
|
225
|
+
return 1;
|
|
226
|
+
}
|
|
227
|
+
logger.info('');
|
|
228
|
+
logger.info(` ${c.bold('swarm')} ${c.dim('·')} ${scenario.name}`);
|
|
229
|
+
logger.info(` ${c.dim(scenario.description)}`);
|
|
230
|
+
logger.info('');
|
|
231
|
+
logger.info(` steps ${scenario.steps.length}`);
|
|
232
|
+
logger.info(` duration ${c.dim(scenario.expectedDuration)}`);
|
|
233
|
+
logger.info(` folder ${demoDir} ${c.dim(isExternalTarget ? '(target)' : '(temp)')}`);
|
|
234
|
+
logger.info('');
|
|
235
|
+
const plan = demoMode.scenarioToPlan(scenario);
|
|
236
|
+
const planPath = (0, plan_files_1.savePlanFile)(plan);
|
|
237
|
+
const execOpts = {
|
|
238
|
+
...parsedFlags,
|
|
239
|
+
noQualityGates: true,
|
|
240
|
+
};
|
|
241
|
+
const exitCode = await executeSwarm(path.basename(planPath), execOpts);
|
|
242
|
+
await installDemoDependencies(demoDir);
|
|
243
|
+
return exitCode;
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
process.chdir(originalDir);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async function handleTemplatesCommand() {
|
|
250
|
+
const templatesDir = path.join(__dirname, '..', '..', 'templates');
|
|
251
|
+
const resolvedDir = fs.existsSync(templatesDir) ? templatesDir : path.join(__dirname, '..', '..', '..', 'templates');
|
|
252
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
253
|
+
logger.error('Templates directory not found.');
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
const files = fs.readdirSync(resolvedDir).filter((f) => f.endsWith('.json'));
|
|
257
|
+
logger.info('\n Available Plan Templates\n');
|
|
258
|
+
logger.info(' ' + '-'.repeat(60));
|
|
259
|
+
for (const file of files) {
|
|
260
|
+
try {
|
|
261
|
+
const plan = JSON.parse(fs.readFileSync(path.join(resolvedDir, file), 'utf8'));
|
|
262
|
+
const steps = plan.steps?.length || 0;
|
|
263
|
+
const duration = plan.metadata?.estimatedDuration || 'unknown';
|
|
264
|
+
logger.info(` ${file.padEnd(20)} ${String(steps).padStart(2)} steps ${duration}`);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// Template file is malformed; show it in the listing with an error indicator
|
|
268
|
+
logger.info(` ${file.padEnd(20)} (invalid JSON)`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
logger.info(' ' + '-'.repeat(60));
|
|
272
|
+
logger.info(`\n Usage: swarm swarm templates/<template>.json\n`);
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseOutputFormat = parseOutputFormat;
|
|
4
|
+
exports.extractPositionalArgs = extractPositionalArgs;
|
|
5
|
+
exports.normalizeLeadingGlobalFlags = normalizeLeadingGlobalFlags;
|
|
6
|
+
exports.parseSwarmFlags = parseSwarmFlags;
|
|
7
|
+
function parseOutputFormat(args) {
|
|
8
|
+
if (args.includes('--json'))
|
|
9
|
+
return 'json';
|
|
10
|
+
const outputIndex = args.indexOf('--output');
|
|
11
|
+
if (outputIndex !== -1 && args[outputIndex + 1] === 'json')
|
|
12
|
+
return 'json';
|
|
13
|
+
return 'text';
|
|
14
|
+
}
|
|
15
|
+
function extractPositionalArgs(args, options = {}) {
|
|
16
|
+
const booleanFlags = new Set(options.booleanFlags ?? []);
|
|
17
|
+
const valueFlags = new Set(options.valueFlags ?? []);
|
|
18
|
+
const positional = [];
|
|
19
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
20
|
+
const arg = args[i];
|
|
21
|
+
if (booleanFlags.has(arg))
|
|
22
|
+
continue;
|
|
23
|
+
if (valueFlags.has(arg)) {
|
|
24
|
+
i += 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (arg.startsWith('--'))
|
|
28
|
+
continue;
|
|
29
|
+
positional.push(arg);
|
|
30
|
+
}
|
|
31
|
+
return positional;
|
|
32
|
+
}
|
|
33
|
+
function normalizeLeadingGlobalFlags(args) {
|
|
34
|
+
const reorderedFlags = [];
|
|
35
|
+
let index = 0;
|
|
36
|
+
while (index < args.length) {
|
|
37
|
+
const arg = args[index];
|
|
38
|
+
if (arg === '--verbose' || arg === '--quiet' || arg === '-q' || arg === '--json' || arg === '--help' || arg === '-h') {
|
|
39
|
+
reorderedFlags.push(arg);
|
|
40
|
+
index += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === '--output') {
|
|
44
|
+
reorderedFlags.push(arg);
|
|
45
|
+
if (args[index + 1]) {
|
|
46
|
+
reorderedFlags.push(args[index + 1]);
|
|
47
|
+
index += 2;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
index += 1;
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
if (reorderedFlags.length === 0) {
|
|
57
|
+
return args;
|
|
58
|
+
}
|
|
59
|
+
if (index >= args.length) {
|
|
60
|
+
if (reorderedFlags.includes('--help') || reorderedFlags.includes('-h')) {
|
|
61
|
+
const trailingFlags = reorderedFlags.filter((flag) => flag !== '--help' && flag !== '-h');
|
|
62
|
+
return ['--help', ...trailingFlags];
|
|
63
|
+
}
|
|
64
|
+
return args;
|
|
65
|
+
}
|
|
66
|
+
return [args[index], ...reorderedFlags, ...args.slice(index + 1)];
|
|
67
|
+
}
|
|
68
|
+
function parseSwarmFlags(args) {
|
|
69
|
+
const opts = {};
|
|
70
|
+
if (parseOutputFormat(args) === 'json') {
|
|
71
|
+
opts.outputFormat = 'json';
|
|
72
|
+
}
|
|
73
|
+
const modelIndex = args.indexOf('--model');
|
|
74
|
+
if (modelIndex !== -1 && args[modelIndex + 1])
|
|
75
|
+
opts.model = args[modelIndex + 1];
|
|
76
|
+
if (args.includes('--confirm-deploy'))
|
|
77
|
+
opts.confirmDeploy = true;
|
|
78
|
+
if (args.includes('--no-quality-gates'))
|
|
79
|
+
opts.noQualityGates = true;
|
|
80
|
+
if (args.includes('--pm'))
|
|
81
|
+
opts.pm = true;
|
|
82
|
+
if (args.includes('--strict-isolation'))
|
|
83
|
+
opts.strictIsolation = true;
|
|
84
|
+
if (args.includes('--lean'))
|
|
85
|
+
opts.lean = true;
|
|
86
|
+
if (args.includes('--verbose'))
|
|
87
|
+
opts.verbose = true;
|
|
88
|
+
if (args.includes('--useInnerFleet') || args.includes('--wrap-fleet'))
|
|
89
|
+
opts.useInnerFleet = true;
|
|
90
|
+
if (args.includes('--cost-estimate-only'))
|
|
91
|
+
opts.costEstimateOnly = true;
|
|
92
|
+
if (args.includes('--yes') || args.includes('-y'))
|
|
93
|
+
opts.yes = true;
|
|
94
|
+
if (args.includes('--quiet') || args.includes('-q'))
|
|
95
|
+
opts.quiet = true;
|
|
96
|
+
if (args.includes('--stream-agent'))
|
|
97
|
+
opts.streamAgent = true;
|
|
98
|
+
if (args.includes('--no-hooks')) {
|
|
99
|
+
opts.hooksEnabled = false;
|
|
100
|
+
}
|
|
101
|
+
else if (args.includes('--hooks')) {
|
|
102
|
+
opts.hooksEnabled = true;
|
|
103
|
+
}
|
|
104
|
+
const maxPremIdx = args.indexOf('--max-premium-requests');
|
|
105
|
+
if (maxPremIdx !== -1 && args[maxPremIdx + 1]) {
|
|
106
|
+
const parsed = parseInt(args[maxPremIdx + 1], 10);
|
|
107
|
+
if (isNaN(parsed) || parsed < 0) {
|
|
108
|
+
throw new Error(`--max-premium-requests requires a non-negative integer, got "${args[maxPremIdx + 1]}"`);
|
|
109
|
+
}
|
|
110
|
+
opts.maxPremiumRequests = parsed;
|
|
111
|
+
}
|
|
112
|
+
const maxRetriesIdx = args.indexOf('--max-retries');
|
|
113
|
+
if (maxRetriesIdx !== -1 && args[maxRetriesIdx + 1]) {
|
|
114
|
+
const parsed = parseInt(args[maxRetriesIdx + 1], 10);
|
|
115
|
+
if (isNaN(parsed) || parsed < 0) {
|
|
116
|
+
throw new Error(`--max-retries requires a non-negative integer, got "${args[maxRetriesIdx + 1]}"`);
|
|
117
|
+
}
|
|
118
|
+
opts.maxRetries = parsed;
|
|
119
|
+
}
|
|
120
|
+
const resumeIndex = args.indexOf('--resume');
|
|
121
|
+
if (resumeIndex !== -1 && args[resumeIndex + 1])
|
|
122
|
+
opts.session = args[resumeIndex + 1];
|
|
123
|
+
const qgConfigIndex = args.indexOf('--quality-gates-config');
|
|
124
|
+
if (qgConfigIndex !== -1 && args[qgConfigIndex + 1]) {
|
|
125
|
+
opts.qualityGatesConfigPath = args[qgConfigIndex + 1];
|
|
126
|
+
}
|
|
127
|
+
const qgOutIndex = args.indexOf('--quality-gates-out');
|
|
128
|
+
if (qgOutIndex !== -1 && args[qgOutIndex + 1]) {
|
|
129
|
+
opts.qualityGatesOutDir = args[qgOutIndex + 1];
|
|
130
|
+
}
|
|
131
|
+
const prIndex = args.indexOf('--pr');
|
|
132
|
+
if (prIndex !== -1 && args[prIndex + 1]) {
|
|
133
|
+
const mode = args[prIndex + 1];
|
|
134
|
+
if (mode !== 'auto' && mode !== 'review') {
|
|
135
|
+
throw new Error(`--pr requires "auto" or "review", got "${mode}"`);
|
|
136
|
+
}
|
|
137
|
+
opts.prMode = mode;
|
|
138
|
+
}
|
|
139
|
+
const targetIndex = args.indexOf('--target') !== -1
|
|
140
|
+
? args.indexOf('--target')
|
|
141
|
+
: args.indexOf('--dir');
|
|
142
|
+
if (targetIndex !== -1 && args[targetIndex + 1]) {
|
|
143
|
+
opts.targetDir = args[targetIndex + 1];
|
|
144
|
+
}
|
|
145
|
+
if (args.includes('--owasp-report'))
|
|
146
|
+
opts.owaspReport = true;
|
|
147
|
+
const toolIndex = args.indexOf('--tool');
|
|
148
|
+
if (toolIndex !== -1 && args[toolIndex + 1]) {
|
|
149
|
+
opts.cliAgent = args[toolIndex + 1];
|
|
150
|
+
}
|
|
151
|
+
return opts;
|
|
152
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI command handlers – barrel re-export module.
|
|
4
|
+
*
|
|
5
|
+
* The actual handler implementations live in focused sub-modules under src/cli/.
|
|
6
|
+
* This file re-exports every public symbol so that existing consumers
|
|
7
|
+
* (cli.ts, tests, etc.) continue to work unchanged.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.parseSwarmFlags = exports.showUsage = exports.handleAttestCommand = exports.handleAgentsCommand = exports.handleRecipeInfoCommand = exports.handleRecipesCommand = exports.handleUseCommand = exports.handleShareCommand = exports.handleTemplatesCommand = exports.runDemo = exports.handleDemoCommand = exports.installDemoDependencies = exports.handleReportCommand = exports.handleMetricsCommand = exports.handleAuditCommand = exports.handleGatesCommand = exports.handleStatusCommand = exports.showStatus = exports.handleRunCommand = exports.handleQuickCommand = exports.handleSwarmCommand = exports.handleBootstrapCommand = exports.writeCIOutputs = exports.executeSwarm = exports.validateAdapterSecrets = exports.handleExecuteCommand = exports.handlePlanCommand = exports.executePlan = exports.importPlanFromTranscript = exports.generatePlan = void 0;
|
|
11
|
+
// ── Plan handlers ──────────────────────────────────────────────────────────
|
|
12
|
+
var plan_handlers_1 = require("./plan-handlers");
|
|
13
|
+
Object.defineProperty(exports, "generatePlan", { enumerable: true, get: function () { return plan_handlers_1.generatePlan; } });
|
|
14
|
+
Object.defineProperty(exports, "importPlanFromTranscript", { enumerable: true, get: function () { return plan_handlers_1.importPlanFromTranscript; } });
|
|
15
|
+
Object.defineProperty(exports, "executePlan", { enumerable: true, get: function () { return plan_handlers_1.executePlan; } });
|
|
16
|
+
Object.defineProperty(exports, "handlePlanCommand", { enumerable: true, get: function () { return plan_handlers_1.handlePlanCommand; } });
|
|
17
|
+
Object.defineProperty(exports, "handleExecuteCommand", { enumerable: true, get: function () { return plan_handlers_1.handleExecuteCommand; } });
|
|
18
|
+
// ── Swarm / execution handlers ────────────────────────────────────────────
|
|
19
|
+
var swarm_handlers_1 = require("./swarm-handlers");
|
|
20
|
+
Object.defineProperty(exports, "validateAdapterSecrets", { enumerable: true, get: function () { return swarm_handlers_1.validateAdapterSecrets; } });
|
|
21
|
+
Object.defineProperty(exports, "executeSwarm", { enumerable: true, get: function () { return swarm_handlers_1.executeSwarm; } });
|
|
22
|
+
Object.defineProperty(exports, "writeCIOutputs", { enumerable: true, get: function () { return swarm_handlers_1.writeCIOutputs; } });
|
|
23
|
+
Object.defineProperty(exports, "handleBootstrapCommand", { enumerable: true, get: function () { return swarm_handlers_1.handleBootstrapCommand; } });
|
|
24
|
+
Object.defineProperty(exports, "handleSwarmCommand", { enumerable: true, get: function () { return swarm_handlers_1.handleSwarmCommand; } });
|
|
25
|
+
Object.defineProperty(exports, "handleQuickCommand", { enumerable: true, get: function () { return swarm_handlers_1.handleQuickCommand; } });
|
|
26
|
+
Object.defineProperty(exports, "handleRunCommand", { enumerable: true, get: function () { return swarm_handlers_1.handleRunCommand; } });
|
|
27
|
+
// ── Status / reporting handlers ───────────────────────────────────────────
|
|
28
|
+
var status_handlers_1 = require("./status-handlers");
|
|
29
|
+
Object.defineProperty(exports, "showStatus", { enumerable: true, get: function () { return status_handlers_1.showStatus; } });
|
|
30
|
+
Object.defineProperty(exports, "handleStatusCommand", { enumerable: true, get: function () { return status_handlers_1.handleStatusCommand; } });
|
|
31
|
+
Object.defineProperty(exports, "handleGatesCommand", { enumerable: true, get: function () { return status_handlers_1.handleGatesCommand; } });
|
|
32
|
+
Object.defineProperty(exports, "handleAuditCommand", { enumerable: true, get: function () { return status_handlers_1.handleAuditCommand; } });
|
|
33
|
+
Object.defineProperty(exports, "handleMetricsCommand", { enumerable: true, get: function () { return status_handlers_1.handleMetricsCommand; } });
|
|
34
|
+
Object.defineProperty(exports, "handleReportCommand", { enumerable: true, get: function () { return status_handlers_1.handleReportCommand; } });
|
|
35
|
+
// ── Demo / template handlers ─────────────────────────────────────────────
|
|
36
|
+
var demo_handlers_1 = require("./demo-handlers");
|
|
37
|
+
Object.defineProperty(exports, "installDemoDependencies", { enumerable: true, get: function () { return demo_handlers_1.installDemoDependencies; } });
|
|
38
|
+
Object.defineProperty(exports, "handleDemoCommand", { enumerable: true, get: function () { return demo_handlers_1.handleDemoCommand; } });
|
|
39
|
+
Object.defineProperty(exports, "runDemo", { enumerable: true, get: function () { return demo_handlers_1.runDemo; } });
|
|
40
|
+
Object.defineProperty(exports, "handleTemplatesCommand", { enumerable: true, get: function () { return demo_handlers_1.handleTemplatesCommand; } });
|
|
41
|
+
// ── Share handlers ───────────────────────────────────────────────────────
|
|
42
|
+
var share_handlers_1 = require("./share-handlers");
|
|
43
|
+
Object.defineProperty(exports, "handleShareCommand", { enumerable: true, get: function () { return share_handlers_1.handleShareCommand; } });
|
|
44
|
+
// ── Misc handlers (recipes, agents, use) ─────────────────────────────────
|
|
45
|
+
var misc_handlers_1 = require("./misc-handlers");
|
|
46
|
+
Object.defineProperty(exports, "handleUseCommand", { enumerable: true, get: function () { return misc_handlers_1.handleUseCommand; } });
|
|
47
|
+
Object.defineProperty(exports, "handleRecipesCommand", { enumerable: true, get: function () { return misc_handlers_1.handleRecipesCommand; } });
|
|
48
|
+
Object.defineProperty(exports, "handleRecipeInfoCommand", { enumerable: true, get: function () { return misc_handlers_1.handleRecipeInfoCommand; } });
|
|
49
|
+
Object.defineProperty(exports, "handleAgentsCommand", { enumerable: true, get: function () { return misc_handlers_1.handleAgentsCommand; } });
|
|
50
|
+
// ── Attestation handlers ─────────────────────────────────────────────────
|
|
51
|
+
var attest_handlers_1 = require("./attest-handlers");
|
|
52
|
+
Object.defineProperty(exports, "handleAttestCommand", { enumerable: true, get: function () { return attest_handlers_1.handleAttestCommand; } });
|
|
53
|
+
// ── Already-split modules (re-export for convenience) ────────────────────
|
|
54
|
+
var usage_1 = require("./usage");
|
|
55
|
+
Object.defineProperty(exports, "showUsage", { enumerable: true, get: function () { return usage_1.showUsage; } });
|
|
56
|
+
var flags_1 = require("./flags");
|
|
57
|
+
Object.defineProperty(exports, "parseSwarmFlags", { enumerable: true, get: function () { return flags_1.parseSwarmFlags; } });
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Live status block: a fixed-position region pinned to the bottom of stdout
|
|
4
|
+
* that re-renders in place via ANSI cursor moves. Running steps each occupy
|
|
5
|
+
* one line (spinner, label, elapsed, current action). Static output (agent
|
|
6
|
+
* stream lines, completed-step summaries, banners) prints above the block;
|
|
7
|
+
* the block redraws after each insertion so it stays at the bottom.
|
|
8
|
+
*
|
|
9
|
+
* Non-TTY fallback: append a single line on each state transition. Pipes,
|
|
10
|
+
* CI logs, and NO_COLOR/CI envs degrade gracefully without ANSI escapes.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.LiveStatus = void 0;
|
|
14
|
+
exports.getLiveStatus = getLiveStatus;
|
|
15
|
+
exports._setLiveStatusForTest = _setLiveStatusForTest;
|
|
16
|
+
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
17
|
+
const RENDER_INTERVAL_MS = 100;
|
|
18
|
+
const ESC = '\x1b[';
|
|
19
|
+
const HIDE_CURSOR = `${ESC}?25l`;
|
|
20
|
+
const SHOW_CURSOR = `${ESC}?25h`;
|
|
21
|
+
const CLEAR_LINE = `${ESC}2K`;
|
|
22
|
+
const CURSOR_UP_ONE = `${ESC}1A`;
|
|
23
|
+
class LiveStatus {
|
|
24
|
+
out;
|
|
25
|
+
tty;
|
|
26
|
+
color;
|
|
27
|
+
steps = new Map();
|
|
28
|
+
order = [];
|
|
29
|
+
renderedLines = 0;
|
|
30
|
+
spinnerFrame = 0;
|
|
31
|
+
renderTimer;
|
|
32
|
+
active = false;
|
|
33
|
+
constructor(opts = {}) {
|
|
34
|
+
this.out = opts.out ?? process.stdout;
|
|
35
|
+
const detectedTty = Boolean(this.out.isTTY) && !process.env.CI;
|
|
36
|
+
this.tty = opts.tty ?? detectedTty;
|
|
37
|
+
this.color = opts.color ?? (this.tty && !process.env.NO_COLOR);
|
|
38
|
+
}
|
|
39
|
+
start() {
|
|
40
|
+
if (this.active)
|
|
41
|
+
return;
|
|
42
|
+
this.active = true;
|
|
43
|
+
if (this.tty) {
|
|
44
|
+
this.out.write(HIDE_CURSOR);
|
|
45
|
+
this.renderTimer = setInterval(() => this.render(), RENDER_INTERVAL_MS);
|
|
46
|
+
this.renderTimer.unref?.();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
stop() {
|
|
50
|
+
if (!this.active)
|
|
51
|
+
return;
|
|
52
|
+
this.active = false;
|
|
53
|
+
if (this.renderTimer) {
|
|
54
|
+
clearInterval(this.renderTimer);
|
|
55
|
+
this.renderTimer = undefined;
|
|
56
|
+
}
|
|
57
|
+
if (this.tty) {
|
|
58
|
+
this.clearLiveBlock();
|
|
59
|
+
this.out.write(SHOW_CURSOR);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
addStep(id, label) {
|
|
63
|
+
if (!this.steps.has(id)) {
|
|
64
|
+
this.order.push(id);
|
|
65
|
+
}
|
|
66
|
+
this.steps.set(id, { id, label, startedAt: Date.now() });
|
|
67
|
+
if (!this.tty) {
|
|
68
|
+
this.out.write(` ${this.dim('›')} ${label} ${this.dim('started')}\n`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.render();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
setAction(id, action) {
|
|
75
|
+
const step = this.steps.get(id);
|
|
76
|
+
if (!step)
|
|
77
|
+
return;
|
|
78
|
+
step.currentAction = action;
|
|
79
|
+
}
|
|
80
|
+
finishStep(id, state, summary) {
|
|
81
|
+
const step = this.steps.get(id);
|
|
82
|
+
if (!step)
|
|
83
|
+
return;
|
|
84
|
+
const elapsed = elapsedSeconds(step.startedAt);
|
|
85
|
+
const glyph = state === 'done' ? this.green('✓') : this.red('✗');
|
|
86
|
+
const tail = summary ? ` ${this.dim('·')} ${summary}` : '';
|
|
87
|
+
const line = ` ${glyph} ${step.label} ${this.dim(formatDuration(elapsed))}${tail}`;
|
|
88
|
+
this.printAbove(line);
|
|
89
|
+
this.steps.delete(id);
|
|
90
|
+
const idx = this.order.indexOf(id);
|
|
91
|
+
if (idx >= 0)
|
|
92
|
+
this.order.splice(idx, 1);
|
|
93
|
+
}
|
|
94
|
+
/** Print a line as static output above the live block. */
|
|
95
|
+
print(line) {
|
|
96
|
+
this.printAbove(line);
|
|
97
|
+
}
|
|
98
|
+
/** True when running in a real TTY where cursor moves work. */
|
|
99
|
+
isTTY() {
|
|
100
|
+
return this.tty;
|
|
101
|
+
}
|
|
102
|
+
printAbove(line) {
|
|
103
|
+
if (!this.tty) {
|
|
104
|
+
this.out.write(line + '\n');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.clearLiveBlock();
|
|
108
|
+
this.out.write(line + '\n');
|
|
109
|
+
this.render();
|
|
110
|
+
}
|
|
111
|
+
clearLiveBlock() {
|
|
112
|
+
if (this.renderedLines === 0)
|
|
113
|
+
return;
|
|
114
|
+
let buf = '';
|
|
115
|
+
for (let i = 0; i < this.renderedLines; i++) {
|
|
116
|
+
buf += CURSOR_UP_ONE + CLEAR_LINE;
|
|
117
|
+
}
|
|
118
|
+
this.out.write(buf);
|
|
119
|
+
this.renderedLines = 0;
|
|
120
|
+
}
|
|
121
|
+
render() {
|
|
122
|
+
if (!this.tty)
|
|
123
|
+
return;
|
|
124
|
+
this.clearLiveBlock();
|
|
125
|
+
const frame = SPINNER[this.spinnerFrame % SPINNER.length];
|
|
126
|
+
this.spinnerFrame++;
|
|
127
|
+
const lines = [];
|
|
128
|
+
for (const id of this.order) {
|
|
129
|
+
const step = this.steps.get(id);
|
|
130
|
+
if (!step)
|
|
131
|
+
continue;
|
|
132
|
+
const elapsed = elapsedSeconds(step.startedAt);
|
|
133
|
+
const action = step.currentAction
|
|
134
|
+
? ` ${this.dim('·')} ${this.dim(truncate(step.currentAction, 56))}`
|
|
135
|
+
: '';
|
|
136
|
+
lines.push(` ${this.cyan(frame)} ${step.label} ${this.dim(formatDuration(elapsed))}${action}`);
|
|
137
|
+
}
|
|
138
|
+
if (lines.length === 0)
|
|
139
|
+
return;
|
|
140
|
+
this.out.write(lines.join('\n') + '\n');
|
|
141
|
+
this.renderedLines = lines.length;
|
|
142
|
+
}
|
|
143
|
+
cyan(s) {
|
|
144
|
+
return this.color ? `${ESC}36m${s}${ESC}39m` : s;
|
|
145
|
+
}
|
|
146
|
+
green(s) {
|
|
147
|
+
return this.color ? `${ESC}32m${s}${ESC}39m` : s;
|
|
148
|
+
}
|
|
149
|
+
red(s) {
|
|
150
|
+
return this.color ? `${ESC}31m${s}${ESC}39m` : s;
|
|
151
|
+
}
|
|
152
|
+
dim(s) {
|
|
153
|
+
return this.color ? `${ESC}2m${s}${ESC}22m` : s;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.LiveStatus = LiveStatus;
|
|
157
|
+
function elapsedSeconds(startedAt) {
|
|
158
|
+
return Math.max(0, Math.round((Date.now() - startedAt) / 1000));
|
|
159
|
+
}
|
|
160
|
+
function formatDuration(secs) {
|
|
161
|
+
if (secs < 60)
|
|
162
|
+
return `${secs}s`;
|
|
163
|
+
const m = Math.floor(secs / 60);
|
|
164
|
+
const s = secs % 60;
|
|
165
|
+
return s === 0 ? `${m}m` : `${m}m${s}s`;
|
|
166
|
+
}
|
|
167
|
+
function truncate(s, max) {
|
|
168
|
+
if (s.length <= max)
|
|
169
|
+
return s;
|
|
170
|
+
return s.slice(0, max - 1) + '…';
|
|
171
|
+
}
|
|
172
|
+
let singleton;
|
|
173
|
+
/**
|
|
174
|
+
* Returns the process-wide LiveStatus instance, creating one on first call.
|
|
175
|
+
* The instance is inert until start() is invoked, so importing this getter
|
|
176
|
+
* from code paths that never run a swarm has no side effect.
|
|
177
|
+
*/
|
|
178
|
+
function getLiveStatus() {
|
|
179
|
+
if (!singleton)
|
|
180
|
+
singleton = new LiveStatus();
|
|
181
|
+
return singleton;
|
|
182
|
+
}
|
|
183
|
+
/** Test hook: replace the singleton with an instance configured for capture. */
|
|
184
|
+
function _setLiveStatusForTest(instance) {
|
|
185
|
+
singleton = instance;
|
|
186
|
+
}
|