@voiden/runner 2.1.0 → 2.2.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/CHANGELOG.md +26 -0
- package/bundled-runners/package.json +3 -0
- package/bundled-runners/simple-assertions-runner.js +1 -0
- package/bundled-runners/versions.json +9 -0
- package/bundled-runners/voiden-advanced-auth-runner.js +1 -0
- package/bundled-runners/voiden-faker-runner.js +9 -0
- package/bundled-runners/voiden-graphql-runner.js +1 -0
- package/bundled-runners/voiden-rest-api-runner.js +1 -0
- package/bundled-runners/voiden-scripting-runner.js +397 -0
- package/bundled-runners/voiden-sockets-grpcs-runner.js +1 -0
- package/dist/discovery.d.ts +11 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +54 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.js +384 -112
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +19 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +19 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcpInstall.d.ts +48 -0
- package/dist/mcpInstall.d.ts.map +1 -0
- package/dist/mcpInstall.js +194 -0
- package/dist/mcpInstall.js.map +1 -0
- package/dist/plugins/community.d.ts.map +1 -1
- package/dist/plugins/community.js +9 -2
- package/dist/plugins/community.js.map +1 -1
- package/dist/plugins/loader.d.ts +1 -0
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +32 -18
- package/dist/plugins/loader.js.map +1 -1
- package/dist/plugins/registry.d.ts +11 -0
- package/dist/plugins/registry.d.ts.map +1 -1
- package/dist/plugins/registry.js +63 -10
- package/dist/plugins/registry.js.map +1 -1
- package/dist/plugins/registryCache.d.ts +2 -0
- package/dist/plugins/registryCache.d.ts.map +1 -1
- package/dist/plugins/registryCache.js.map +1 -1
- package/dist/plugins/store.d.ts +28 -1
- package/dist/plugins/store.d.ts.map +1 -1
- package/dist/plugins/store.js +46 -9
- package/dist/plugins/store.js.map +1 -1
- package/dist/plugins/updateCheck.d.ts +9 -4
- package/dist/plugins/updateCheck.d.ts.map +1 -1
- package/dist/plugins/updateCheck.js +29 -15
- package/dist/plugins/updateCheck.js.map +1 -1
- package/dist/plugins/versionInfo.d.ts +15 -0
- package/dist/plugins/versionInfo.d.ts.map +1 -0
- package/dist/plugins/versionInfo.js +18 -0
- package/dist/plugins/versionInfo.js.map +1 -0
- package/dist/resultBlock.d.ts +31 -0
- package/dist/resultBlock.d.ts.map +1 -0
- package/dist/resultBlock.js +121 -0
- package/dist/resultBlock.js.map +1 -0
- package/dist/runner.d.ts +11 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +61 -2
- package/dist/runner.js.map +1 -1
- package/dist/runtimeVars.js +3 -3
- package/dist/runtimeVars.js.map +1 -1
- package/dist/skillContent.d.ts +8 -0
- package/dist/skillContent.d.ts.map +1 -0
- package/dist/skillContent.js +42 -0
- package/dist/skillContent.js.map +1 -0
- package/package.json +13 -2
package/dist/index.js
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { program } from 'commander';
|
|
3
|
-
import { readFileSync, existsSync,
|
|
4
|
-
import { resolve, basename, join, dirname } from 'path';
|
|
3
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
|
|
4
|
+
import { resolve, relative, basename, join, dirname } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import { readdir } from 'fs/promises';
|
|
7
6
|
import chalk from 'chalk';
|
|
8
7
|
import { runVoidFile } from './runner.js';
|
|
8
|
+
import { resolveFiles } from './discovery.js';
|
|
9
9
|
import { loadEnabledPlugins } from './plugins/loader.js';
|
|
10
10
|
import { exportToCsv } from './report/csv.js';
|
|
11
11
|
import { sendMailReport } from './report/mail.js';
|
|
12
|
-
import { getCorePlugins, findPlugin } from './plugins/registry.js';
|
|
12
|
+
import { getCorePlugins, findPlugin, hasCoreRunner } from './plugins/registry.js';
|
|
13
13
|
import { downloadCoreRunner } from './plugins/loader.js';
|
|
14
14
|
import { fetchCommunityPlugins, findCommunityPlugin, hasCommunityRunner, installCommunityRunner, } from './plugins/community.js';
|
|
15
15
|
import { installPlugin, uninstallPlugin, setPluginEnabled, setPluginVersion, getAllInstalledPlugins, readStore, STORE_DIR, } from './plugins/store.js';
|
|
16
16
|
import { checkForPluginUpdates } from './plugins/updateCheck.js';
|
|
17
|
+
import { getInstalledPluginInfo } from './plugins/versionInfo.js';
|
|
18
|
+
import { parseVoidFile } from './parser.js';
|
|
19
|
+
import { classifyBlockVersion } from '@voiden/executors';
|
|
20
|
+
import { installMcpIntegration, uninstallMcpIntegration, getMcpStatus } from './mcpInstall.js';
|
|
21
|
+
import { RUNNER_SKILL_MARKDOWN } from './skillContent.js';
|
|
17
22
|
import { appendSessionResults, loadSessionResults, clearSession, } from './session.js';
|
|
18
23
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
// Exit codes — a stable, documented contract CI pipelines can branch on.
|
|
25
|
+
//
|
|
26
|
+
// 0 success — all requests passed
|
|
27
|
+
// 1 one or more requests failed (assertions/errors), or --bail /
|
|
28
|
+
// --fail-on-error triggered — unchanged from prior releases
|
|
29
|
+
// 2 the runner could not execute the run at all: bad CLI args/flags,
|
|
30
|
+
// missing files, missing plugins, invalid env — a pipeline/config
|
|
31
|
+
// problem, not an API failure
|
|
32
|
+
//
|
|
33
|
+
// See CHANGELOG.md and docs.voiden.md/docs/developer-tools/voiden-runner/ci-cd
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
const EXIT_SUCCESS = 0;
|
|
36
|
+
const EXIT_RUN_FAILURE = 1;
|
|
37
|
+
const EXIT_USAGE_ERROR = 2;
|
|
38
|
+
/** JSON output schema version — bump whenever a field is renamed, removed, or
|
|
39
|
+
* reinterpreted (adding a field is not a breaking change and does not need a bump). */
|
|
40
|
+
const JSON_SCHEMA_VERSION = '1';
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
42
|
// Helpers
|
|
20
43
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
44
|
function loadEnvFile(envPath) {
|
|
@@ -49,50 +72,6 @@ function formatDuration(ms) {
|
|
|
49
72
|
return `${ms}ms`;
|
|
50
73
|
return `${(ms / 1000).toFixed(2)}s`;
|
|
51
74
|
}
|
|
52
|
-
/** Recursively collect all .void files under a directory. */
|
|
53
|
-
async function collectVoidFiles(inputPath) {
|
|
54
|
-
const abs = resolve(inputPath);
|
|
55
|
-
if (!existsSync(abs))
|
|
56
|
-
return [];
|
|
57
|
-
const stat = statSync(abs);
|
|
58
|
-
if (stat.isFile()) {
|
|
59
|
-
return abs.endsWith('.void') ? [abs] : [];
|
|
60
|
-
}
|
|
61
|
-
if (stat.isDirectory()) {
|
|
62
|
-
const entries = await readdir(abs, { withFileTypes: true });
|
|
63
|
-
const results = [];
|
|
64
|
-
for (const entry of entries) {
|
|
65
|
-
const full = resolve(abs, entry.name);
|
|
66
|
-
if (entry.isDirectory()) {
|
|
67
|
-
results.push(...(await collectVoidFiles(full)));
|
|
68
|
-
}
|
|
69
|
-
else if (entry.isFile() && entry.name.endsWith('.void')) {
|
|
70
|
-
results.push(full);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return results;
|
|
74
|
-
}
|
|
75
|
-
return [];
|
|
76
|
-
}
|
|
77
|
-
/** Expand a list of paths/globs into resolved .void file paths. */
|
|
78
|
-
async function resolveFiles(patterns) {
|
|
79
|
-
const resolved = [];
|
|
80
|
-
for (const pattern of patterns) {
|
|
81
|
-
if (pattern.includes('*')) {
|
|
82
|
-
const dir = resolve(pattern.replace(/\/?\*.*$/, '') || '.');
|
|
83
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
84
|
-
for (const entry of entries) {
|
|
85
|
-
if (entry.isFile() && entry.name.endsWith('.void')) {
|
|
86
|
-
resolved.push(resolve(dir, entry.name));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
resolved.push(...(await collectVoidFiles(pattern)));
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return resolved;
|
|
95
|
-
}
|
|
96
75
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
97
76
|
// Spinner
|
|
98
77
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -266,20 +245,71 @@ function printRunSummary(results, totalMs) {
|
|
|
266
245
|
console.log(DIVIDER);
|
|
267
246
|
console.log();
|
|
268
247
|
}
|
|
269
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Builds the `--json` / `--output-json` payload shape shared by `run` and
|
|
250
|
+
* `report generate`. `schemaVersion` is the stable contract external tooling
|
|
251
|
+
* codes against — see the exit-codes comment above for the versioning rule.
|
|
252
|
+
*/
|
|
253
|
+
function buildJsonReport(results, extra = {}) {
|
|
270
254
|
const passed = results.filter(r => r.result.success).length;
|
|
271
|
-
|
|
255
|
+
return {
|
|
256
|
+
schemaVersion: JSON_SCHEMA_VERSION,
|
|
272
257
|
summary: {
|
|
273
258
|
total: results.length,
|
|
274
259
|
passed,
|
|
275
260
|
failed: results.length - passed,
|
|
276
|
-
|
|
277
|
-
activePlugins,
|
|
261
|
+
...extra,
|
|
278
262
|
},
|
|
279
263
|
requests: results.map(r => ({ file: r.file, ...r.result })),
|
|
280
264
|
};
|
|
265
|
+
}
|
|
266
|
+
function printRunSummaryJson(results, totalMs, activePlugins) {
|
|
267
|
+
const output = buildJsonReport(results, { totalDurationMs: totalMs, activePlugins });
|
|
281
268
|
console.log(JSON.stringify(output, null, 2));
|
|
282
269
|
}
|
|
270
|
+
/** Split CHANGELOG.md into per-version entries on "## " headers (newest first, matching file order). */
|
|
271
|
+
function parseChangelog(content) {
|
|
272
|
+
const lines = content.split('\n');
|
|
273
|
+
const entries = [];
|
|
274
|
+
let current = null;
|
|
275
|
+
for (const line of lines) {
|
|
276
|
+
const headerMatch = line.match(/^##\s+(\S+)(?:\s+-\s+(.+))?$/);
|
|
277
|
+
if (headerMatch) {
|
|
278
|
+
if (current)
|
|
279
|
+
entries.push(current);
|
|
280
|
+
current = { version: headerMatch[1], date: headerMatch[2]?.trim(), body: [] };
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (current)
|
|
284
|
+
current.body.push(line);
|
|
285
|
+
}
|
|
286
|
+
if (current)
|
|
287
|
+
entries.push(current);
|
|
288
|
+
return entries;
|
|
289
|
+
}
|
|
290
|
+
function renderChangelogEntry(entry) {
|
|
291
|
+
const header = entry.date ? `${entry.version} ${chalk.gray(entry.date)}` : entry.version;
|
|
292
|
+
console.log(chalk.bold.white(` ${header}`));
|
|
293
|
+
console.log(DIVIDER);
|
|
294
|
+
for (const line of entry.body) {
|
|
295
|
+
const trimmed = line.trim();
|
|
296
|
+
if (!trimmed)
|
|
297
|
+
continue;
|
|
298
|
+
const sectionMatch = trimmed.match(/^###\s+(.+)$/);
|
|
299
|
+
if (sectionMatch) {
|
|
300
|
+
console.log();
|
|
301
|
+
console.log(chalk.bold.cyan(` ${sectionMatch[1]}`));
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const bulletMatch = trimmed.match(/^-\s+(.+)$/);
|
|
305
|
+
if (bulletMatch) {
|
|
306
|
+
console.log(chalk.gray(` · `) + bulletMatch[1]);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
console.log(` ${trimmed}`);
|
|
310
|
+
}
|
|
311
|
+
console.log();
|
|
312
|
+
}
|
|
283
313
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
284
314
|
// Plugin update notices — surfaced after `run` and `plugin` commands so users
|
|
285
315
|
// learn about newer registry versions without having to run `plugin list`.
|
|
@@ -303,6 +333,65 @@ async function notifyPluginUpdates() {
|
|
|
303
333
|
// Informational only — ignore failures (e.g. offline)
|
|
304
334
|
}
|
|
305
335
|
}
|
|
336
|
+
function scanProjectRequirements(files, cwd) {
|
|
337
|
+
// pluginId → version → files that declared it
|
|
338
|
+
const usages = new Map();
|
|
339
|
+
for (const file of files) {
|
|
340
|
+
let content;
|
|
341
|
+
try {
|
|
342
|
+
content = readFileSync(file, 'utf-8');
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
for (const block of parseVoidFile(content)) {
|
|
348
|
+
const pluginId = block.attrs?.pluginId;
|
|
349
|
+
const pluginVersion = block.attrs?.pluginVersion;
|
|
350
|
+
if (!pluginId || !pluginVersion)
|
|
351
|
+
continue;
|
|
352
|
+
if (!usages.has(pluginId))
|
|
353
|
+
usages.set(pluginId, new Map());
|
|
354
|
+
const versions = usages.get(pluginId);
|
|
355
|
+
if (!versions.has(pluginVersion))
|
|
356
|
+
versions.set(pluginVersion, new Set());
|
|
357
|
+
versions.get(pluginVersion).add(relative(cwd, file));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const issues = [];
|
|
361
|
+
for (const [pluginId, versions] of usages) {
|
|
362
|
+
for (const [version, fileSet] of versions) {
|
|
363
|
+
const installed = getInstalledPluginInfo(pluginId);
|
|
364
|
+
const status = classifyBlockVersion({ pluginId, pluginVersion: version, blockType: '' }, installed);
|
|
365
|
+
if (status === 'ok')
|
|
366
|
+
continue;
|
|
367
|
+
issues.push({ pluginId, requiredVersion: version, installedVersion: installed?.version, status, files: [...fileSet] });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return issues;
|
|
371
|
+
}
|
|
372
|
+
function printProjectStatus(issues) {
|
|
373
|
+
if (issues.length === 0)
|
|
374
|
+
return;
|
|
375
|
+
console.log();
|
|
376
|
+
console.log(chalk.yellow(` ⚠ ${issues.length} plugin${issues.length !== 1 ? 's' : ''} ${issues.length !== 1 ? "don't" : "doesn't"} match what this project needs`));
|
|
377
|
+
for (const issue of issues) {
|
|
378
|
+
const have = issue.status === 'not-installed' ? 'not installed'
|
|
379
|
+
: issue.status === 'disabled' ? 'installed but disabled'
|
|
380
|
+
: `v${issue.installedVersion} installed`;
|
|
381
|
+
console.log(chalk.gray(` ${chalk.bold(issue.pluginId.padEnd(24))} requires v${issue.requiredVersion} — ${have}`));
|
|
382
|
+
console.log(chalk.gray(` used in: ${issue.files.join(', ')}`));
|
|
383
|
+
}
|
|
384
|
+
console.log(chalk.gray(` Run: voiden-runner plugin install <name>@<version>`));
|
|
385
|
+
}
|
|
386
|
+
/** Best-effort — never throws, never blocks command output on failure. */
|
|
387
|
+
function notifyProjectStatus(files, cwd) {
|
|
388
|
+
try {
|
|
389
|
+
printProjectStatus(scanProjectRequirements(files, cwd));
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// Informational only
|
|
393
|
+
}
|
|
394
|
+
}
|
|
306
395
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
307
396
|
// CLI
|
|
308
397
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -312,6 +401,47 @@ program
|
|
|
312
401
|
.name('voiden-runner')
|
|
313
402
|
.description('Run .void files headlessly — REST, WebSocket, and gRPC')
|
|
314
403
|
.version(pkg.version);
|
|
404
|
+
// ── voiden-runner changelog ───────────────────────────────────────────────────
|
|
405
|
+
program
|
|
406
|
+
.command('changelog [version]')
|
|
407
|
+
.description('Show release notes for voiden-runner — versioned independently of the desktop app\n\n' +
|
|
408
|
+
' Examples:\n' +
|
|
409
|
+
' voiden-runner changelog # full history\n' +
|
|
410
|
+
' voiden-runner changelog --latest # most recent release only\n' +
|
|
411
|
+
' voiden-runner changelog 2.1.0 # a specific version\n')
|
|
412
|
+
.option('--latest', 'Show only the most recent release')
|
|
413
|
+
.action((version, opts) => {
|
|
414
|
+
const changelogPath = resolve(join(dirname(fileURLToPath(import.meta.url)), '../CHANGELOG.md'));
|
|
415
|
+
if (!existsSync(changelogPath)) {
|
|
416
|
+
console.error(chalk.red(' ✗ No CHANGELOG.md found for this install.'));
|
|
417
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
418
|
+
}
|
|
419
|
+
const entries = parseChangelog(readFileSync(changelogPath, 'utf-8'));
|
|
420
|
+
if (entries.length === 0) {
|
|
421
|
+
console.log(chalk.gray(' Changelog is empty.'));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
let toShow = entries;
|
|
425
|
+
if (version) {
|
|
426
|
+
const normalized = version.replace(/^v/, '');
|
|
427
|
+
const match = entries.find(e => e.version.replace(/^v/, '') === normalized);
|
|
428
|
+
if (!match) {
|
|
429
|
+
console.error(chalk.red(` ✗ No changelog entry found for version "${version}".`));
|
|
430
|
+
console.log(chalk.gray(` Available: ${entries.map(e => e.version).join(', ')}`));
|
|
431
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
432
|
+
}
|
|
433
|
+
toShow = [match];
|
|
434
|
+
}
|
|
435
|
+
else if (opts.latest) {
|
|
436
|
+
toShow = [entries[0]];
|
|
437
|
+
}
|
|
438
|
+
console.log();
|
|
439
|
+
console.log(chalk.bold.white(' voiden-runner changelog'));
|
|
440
|
+
console.log();
|
|
441
|
+
for (const entry of toShow) {
|
|
442
|
+
renderChangelogEntry(entry);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
315
445
|
// ── voiden-runner run ─────────────────────────────────────────────────────────
|
|
316
446
|
program
|
|
317
447
|
.command('run <paths...>')
|
|
@@ -358,14 +488,14 @@ program
|
|
|
358
488
|
const envPath = resolve(opts.env);
|
|
359
489
|
if (!existsSync(envPath)) {
|
|
360
490
|
console.error(chalk.red(`Env file not found: ${envPath}`));
|
|
361
|
-
process.exit(
|
|
491
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
362
492
|
}
|
|
363
493
|
try {
|
|
364
494
|
Object.assign(env, loadEnvFile(envPath));
|
|
365
495
|
}
|
|
366
496
|
catch (err) {
|
|
367
497
|
console.error(chalk.red(` ✗ ${err.message}`));
|
|
368
|
-
process.exit(
|
|
498
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
369
499
|
}
|
|
370
500
|
}
|
|
371
501
|
// 2. Individual --env-var overrides
|
|
@@ -374,13 +504,13 @@ program
|
|
|
374
504
|
const eq = pair.indexOf('=');
|
|
375
505
|
if (eq === -1) {
|
|
376
506
|
console.error(chalk.red(` ✗ Invalid --env-var format: "${pair}" (expected key=value)`));
|
|
377
|
-
process.exit(
|
|
507
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
378
508
|
}
|
|
379
509
|
const key = pair.slice(0, eq).trim();
|
|
380
510
|
const val = pair.slice(eq + 1).trim().replace(/^["']|["']$/g, '');
|
|
381
511
|
if (!key) {
|
|
382
512
|
console.error(chalk.red(` ✗ Invalid --env-var format: "${pair}" (key cannot be empty)`));
|
|
383
|
-
process.exit(
|
|
513
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
384
514
|
}
|
|
385
515
|
env[key] = val;
|
|
386
516
|
}
|
|
@@ -388,7 +518,7 @@ program
|
|
|
388
518
|
const resolvedFiles = await resolveFiles(paths);
|
|
389
519
|
if (resolvedFiles.length === 0) {
|
|
390
520
|
console.error(chalk.red('No .void files found at the given path(s)'));
|
|
391
|
-
process.exit(
|
|
521
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
392
522
|
}
|
|
393
523
|
// --stop-on-failure is a CI-friendly alias for --bail
|
|
394
524
|
const stopOnFailure = opts.bail || opts.stopOnFailure;
|
|
@@ -406,11 +536,11 @@ program
|
|
|
406
536
|
if (opts.mail || opts.mailTo) {
|
|
407
537
|
if (!mailTo) {
|
|
408
538
|
console.error(chalk.red(' ✗ Mail error: no recipient found. Please provide --mail-to or set VOIDEN_MAIL_TO.'));
|
|
409
|
-
process.exit(
|
|
539
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
410
540
|
}
|
|
411
541
|
if (!smtpHost) {
|
|
412
542
|
console.error(chalk.red(' ✗ Mail keys are missing. Please provide SMTP configuration (VOIDEN_SMTP_HOST).'));
|
|
413
|
-
process.exit(
|
|
543
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
414
544
|
}
|
|
415
545
|
}
|
|
416
546
|
const runStart = Date.now();
|
|
@@ -516,16 +646,7 @@ program
|
|
|
516
646
|
// ── Output JSON to file (before mail so it can be attached) ──────────────
|
|
517
647
|
let savedJsonPath;
|
|
518
648
|
if (opts.outputJson) {
|
|
519
|
-
const jsonData = {
|
|
520
|
-
summary: {
|
|
521
|
-
total: allResults.length,
|
|
522
|
-
passed: allResults.filter(r => r.result.success).length,
|
|
523
|
-
failed: allResults.filter(r => !r.result.success).length,
|
|
524
|
-
totalDurationMs: totalMs,
|
|
525
|
-
activePlugins,
|
|
526
|
-
},
|
|
527
|
-
requests: allResults.map(r => ({ file: r.file, ...r.result })),
|
|
528
|
-
};
|
|
649
|
+
const jsonData = buildJsonReport(allResults, { totalDurationMs: totalMs, activePlugins });
|
|
529
650
|
try {
|
|
530
651
|
mkdirSync(dirname(opts.outputJson), { recursive: true });
|
|
531
652
|
writeFileSync(opts.outputJson, JSON.stringify(jsonData, null, 2) + '\n', 'utf-8');
|
|
@@ -566,10 +687,12 @@ program
|
|
|
566
687
|
console.log(chalk.gray(' (use this exit code in your shell script to abort on failure)'));
|
|
567
688
|
console.log();
|
|
568
689
|
}
|
|
569
|
-
// Surface plugin update notices — skipped in --json mode so output stays machine-readable
|
|
570
|
-
if (!opts.json)
|
|
690
|
+
// Surface plugin update / project-requirements notices — skipped in --json mode so output stays machine-readable
|
|
691
|
+
if (!opts.json) {
|
|
571
692
|
await notifyPluginUpdates();
|
|
572
|
-
|
|
693
|
+
notifyProjectStatus(resolvedFiles, process.cwd());
|
|
694
|
+
}
|
|
695
|
+
process.exit(shouldFail ? EXIT_RUN_FAILURE : EXIT_SUCCESS);
|
|
573
696
|
});
|
|
574
697
|
// ── voiden-runner session ─────────────────────────────────────────────────────
|
|
575
698
|
const sessionCmd = program
|
|
@@ -684,7 +807,7 @@ reportCmd
|
|
|
684
807
|
const results = loadSessionResults();
|
|
685
808
|
if (results.length === 0) {
|
|
686
809
|
console.error(chalk.red(' ✗ No results found in session. Run some .void files first.'));
|
|
687
|
-
process.exit(
|
|
810
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
688
811
|
}
|
|
689
812
|
// Load optional .env for report SMTP settings
|
|
690
813
|
const env = { ...process.env };
|
|
@@ -703,12 +826,12 @@ reportCmd
|
|
|
703
826
|
if (opts.mail || opts.mailTo) {
|
|
704
827
|
if (!mailTo) {
|
|
705
828
|
console.error(chalk.red(' ✗ Mail error: no recipient found. Please provide --mail-to or set VOIDEN_MAIL_TO.'));
|
|
706
|
-
process.exit(
|
|
829
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
707
830
|
}
|
|
708
831
|
const smtpHost = opts.smtpHost || env.VOIDEN_SMTP_HOST;
|
|
709
832
|
if (!smtpHost) {
|
|
710
833
|
console.error(chalk.red(' ✗ Mail keys are missing. Please provide SMTP configuration (VOIDEN_SMTP_HOST).'));
|
|
711
|
-
process.exit(
|
|
834
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
712
835
|
}
|
|
713
836
|
}
|
|
714
837
|
if (!opts.csv && !opts.outputJson && !mailTo) {
|
|
@@ -727,14 +850,7 @@ reportCmd
|
|
|
727
850
|
}
|
|
728
851
|
let savedJsonPath;
|
|
729
852
|
if (opts.outputJson) {
|
|
730
|
-
const jsonData =
|
|
731
|
-
summary: {
|
|
732
|
-
total: results.length,
|
|
733
|
-
passed: results.filter(r => r.result.success).length,
|
|
734
|
-
failed: results.filter(r => !r.result.success).length,
|
|
735
|
-
},
|
|
736
|
-
requests: results.map(r => ({ file: r.file, ...r.result })),
|
|
737
|
-
};
|
|
853
|
+
const jsonData = buildJsonReport(results);
|
|
738
854
|
try {
|
|
739
855
|
mkdirSync(dirname(opts.outputJson), { recursive: true });
|
|
740
856
|
writeFileSync(opts.outputJson, JSON.stringify(jsonData, null, 2) + '\n', 'utf-8');
|
|
@@ -755,7 +871,7 @@ reportCmd
|
|
|
755
871
|
if (!smtpHost) {
|
|
756
872
|
console.error(chalk.red(' ✗ SMTP configuration required for email reports.'));
|
|
757
873
|
console.log(chalk.gray(' Set VOIDEN_SMTP_HOST in your environment or use --smtp-host.'));
|
|
758
|
-
process.exit(
|
|
874
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
759
875
|
}
|
|
760
876
|
console.log(chalk.gray(` ↑ Sending session report to ${mailTo} …`));
|
|
761
877
|
try {
|
|
@@ -778,6 +894,14 @@ reportCmd
|
|
|
778
894
|
}
|
|
779
895
|
}
|
|
780
896
|
});
|
|
897
|
+
// Splits a `name` or `name@version` CLI arg. Plugin ids are plain slugs (no
|
|
898
|
+
// leading `@`), so splitting on the first `@` is unambiguous.
|
|
899
|
+
function parsePluginTarget(raw) {
|
|
900
|
+
const at = raw.indexOf('@');
|
|
901
|
+
if (at <= 0)
|
|
902
|
+
return { name: raw };
|
|
903
|
+
return { name: raw.slice(0, at), pinVersion: raw.slice(at + 1) };
|
|
904
|
+
}
|
|
781
905
|
// ── voiden-runner plugin ──────────────────────────────────────────────────────
|
|
782
906
|
const pluginCmd = program
|
|
783
907
|
.command('plugin')
|
|
@@ -786,34 +910,58 @@ const pluginCmd = program
|
|
|
786
910
|
pluginCmd
|
|
787
911
|
.command('install [names...]')
|
|
788
912
|
.description('Install one or more plugins, or all core plugins\n\n' +
|
|
789
|
-
' --all installs all core plugins only. Community plugins must be installed by name.\n
|
|
913
|
+
' --all installs all core plugins only. Community plugins must be installed by name.\n' +
|
|
914
|
+
' Pin an exact version with name@version (e.g. after `voiden-runner lock`, or to\n' +
|
|
915
|
+
' match a "Block ... requires plugin X vY" error).\n\n' +
|
|
790
916
|
' Examples:\n' +
|
|
791
917
|
' voiden-runner plugin install --all\n' +
|
|
792
918
|
' voiden-runner plugin install voiden-scripting\n' +
|
|
793
|
-
' voiden-runner plugin install
|
|
919
|
+
' voiden-runner plugin install voiden-rest-api@1.4.7\n')
|
|
794
920
|
.option('--all', 'Install all core plugins (community plugins must be installed by name)')
|
|
795
|
-
.action(async (
|
|
921
|
+
.action(async (rawNames, opts) => {
|
|
796
922
|
const corePlugins = await getCorePlugins();
|
|
797
923
|
const communityPlugins = await fetchCommunityPlugins();
|
|
798
924
|
const targets = opts.all
|
|
799
|
-
? corePlugins.map(p => p.name)
|
|
800
|
-
:
|
|
925
|
+
? corePlugins.map(p => ({ name: p.name }))
|
|
926
|
+
: rawNames.map(parsePluginTarget);
|
|
801
927
|
if (targets.length === 0) {
|
|
802
928
|
console.error(chalk.red('Specify plugin name(s) or use --all'));
|
|
803
929
|
console.log(chalk.gray(' Core: ' + corePlugins.map(p => p.name).join(', ')));
|
|
804
930
|
if (communityPlugins.length > 0) {
|
|
805
931
|
console.log(chalk.gray(' Community (install by name): ' + communityPlugins.map(p => p.id).join(', ')));
|
|
806
932
|
}
|
|
807
|
-
process.exit(
|
|
933
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
808
934
|
}
|
|
809
935
|
let installedCount = 0;
|
|
810
|
-
for (const name of targets) {
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
if (!
|
|
936
|
+
for (const { name, pinVersion } of targets) {
|
|
937
|
+
const foundCoreDef = await findPlugin(name);
|
|
938
|
+
const foundCommDef = !foundCoreDef ? findCommunityPlugin(name, communityPlugins) : undefined;
|
|
939
|
+
if (!foundCoreDef && !foundCommDef) {
|
|
814
940
|
console.log(chalk.yellow(` ⚠ Unknown plugin "${name}" — skipped`));
|
|
815
941
|
continue;
|
|
816
942
|
}
|
|
943
|
+
// A pinned version overrides the registry's "latest" default — this is what
|
|
944
|
+
// makes the fix-it command in version-mismatch errors ("plugin install x@y")
|
|
945
|
+
// actually able to install the exact version a file declares.
|
|
946
|
+
const coreDef = foundCoreDef && pinVersion ? { ...foundCoreDef, version: pinVersion } : foundCoreDef;
|
|
947
|
+
const commDef = foundCommDef && pinVersion ? { ...foundCommDef, version: pinVersion } : foundCommDef;
|
|
948
|
+
// Core plugins: only download if not already bundled/cached — unless a
|
|
949
|
+
// specific version was pinned, in which case always fetch that version.
|
|
950
|
+
if (coreDef && (pinVersion || !hasCoreRunner(name))) {
|
|
951
|
+
process.stdout.write(` ↓ Downloading runner for ${chalk.bold(name)} …`);
|
|
952
|
+
try {
|
|
953
|
+
const ok = await downloadCoreRunner(coreDef.name, coreDef.repo, coreDef.runnerAsset, coreDef.version, false);
|
|
954
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
955
|
+
if (!ok) {
|
|
956
|
+
console.log(chalk.red(` ✗ No "${coreDef.runnerAsset}" asset in release v${coreDef.version} for "${name}"`));
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
catch (err) {
|
|
961
|
+
process.stdout.write('\r' + chalk.red(` ✗ Failed to download runner for "${name}": ${err?.message ?? String(err)}\n`));
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
817
965
|
// Community plugins: download runner.js from the GitHub release first
|
|
818
966
|
if (commDef) {
|
|
819
967
|
process.stdout.write(` ↓ Downloading runner for ${chalk.bold(name)} …`);
|
|
@@ -837,6 +985,13 @@ pluginCmd
|
|
|
837
985
|
console.log(chalk.green(` ✓ Installed`) + chalk.bold(` ${name}`) + chalk.gray(` — ${description}`));
|
|
838
986
|
installedCount++;
|
|
839
987
|
}
|
|
988
|
+
else if (pinVersion) {
|
|
989
|
+
// Re-running install with an explicit pin (e.g. to fix a version-mismatch)
|
|
990
|
+
// should still record the newly-downloaded version even if already "installed".
|
|
991
|
+
setPluginVersion(name, coreDef?.version ?? commDef?.version ?? pinVersion);
|
|
992
|
+
console.log(chalk.green(` ✓ Installed`) + chalk.bold(` ${name}@${pinVersion}`));
|
|
993
|
+
installedCount++;
|
|
994
|
+
}
|
|
840
995
|
else {
|
|
841
996
|
console.log(chalk.gray(` · Already installed`) + ` ${name}`);
|
|
842
997
|
}
|
|
@@ -920,17 +1075,44 @@ pluginCmd
|
|
|
920
1075
|
console.log(chalk.gray(` ${updatedCount} plugin(s) updated.`));
|
|
921
1076
|
}
|
|
922
1077
|
});
|
|
923
|
-
// voiden-runner plugin uninstall
|
|
1078
|
+
// voiden-runner plugin uninstall [names...] --all
|
|
924
1079
|
pluginCmd
|
|
925
|
-
.command('uninstall
|
|
926
|
-
.description('Remove
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1080
|
+
.command('uninstall [names...]')
|
|
1081
|
+
.description('Remove one or more installed plugins, or all installed plugins\n\n' +
|
|
1082
|
+
' Examples:\n' +
|
|
1083
|
+
' voiden-runner plugin uninstall voiden-scripting\n' +
|
|
1084
|
+
' voiden-runner plugin uninstall apyhub-explorer voiden-scripting\n' +
|
|
1085
|
+
' voiden-runner plugin uninstall --all\n')
|
|
1086
|
+
.option('--all', 'Uninstall all installed plugins (core and community)')
|
|
1087
|
+
.action(async (names, opts) => {
|
|
1088
|
+
// Core plugins are bundled and enabled by default — they may never have
|
|
1089
|
+
// an explicit store record even though they're clearly active, so --all
|
|
1090
|
+
// (and named uninstalls of a bundled plugin) must include the full core
|
|
1091
|
+
// registry, not just names that already happen to have a store record.
|
|
1092
|
+
const corePlugins = await getCorePlugins();
|
|
1093
|
+
const coreNames = new Set(corePlugins.map(p => p.name));
|
|
1094
|
+
const targets = opts.all
|
|
1095
|
+
? [...new Set([...coreNames, ...getAllInstalledPlugins().map(p => p.name)])]
|
|
1096
|
+
: names;
|
|
1097
|
+
if (targets.length === 0) {
|
|
1098
|
+
console.error(chalk.red(' Specify plugin name(s) or use --all'));
|
|
1099
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
1100
|
+
return;
|
|
931
1101
|
}
|
|
932
|
-
|
|
933
|
-
|
|
1102
|
+
let removedCount = 0;
|
|
1103
|
+
for (const name of targets) {
|
|
1104
|
+
const removed = uninstallPlugin(name, coreNames.has(name));
|
|
1105
|
+
if (removed) {
|
|
1106
|
+
console.log(chalk.green(` ✓ Uninstalled`) + ` ${name}`);
|
|
1107
|
+
removedCount++;
|
|
1108
|
+
}
|
|
1109
|
+
else {
|
|
1110
|
+
console.log(chalk.yellow(` ⚠ Plugin "${name}" is not installed`));
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (removedCount > 1) {
|
|
1114
|
+
console.log();
|
|
1115
|
+
console.log(chalk.gray(` ${removedCount} plugin(s) uninstalled.`));
|
|
934
1116
|
}
|
|
935
1117
|
});
|
|
936
1118
|
// voiden-runner plugin enable [name] --all
|
|
@@ -944,9 +1126,11 @@ pluginCmd
|
|
|
944
1126
|
.action(async (name, opts) => {
|
|
945
1127
|
if (opts.all) {
|
|
946
1128
|
const store = readStore();
|
|
947
|
-
// Re-enable all explicitly disabled plugins (core + community)
|
|
1129
|
+
// Re-enable all explicitly disabled plugins (core + community) — but not
|
|
1130
|
+
// uninstalled ones; bringing those back requires an explicit `plugin
|
|
1131
|
+
// install`, not a blanket --all enable.
|
|
948
1132
|
const disabled = Object.entries(store.installedPlugins)
|
|
949
|
-
.filter(([, r]) => !r.enabled)
|
|
1133
|
+
.filter(([, r]) => !r.enabled && !r.uninstalled)
|
|
950
1134
|
.map(([n]) => n);
|
|
951
1135
|
// Also ensure all core plugins that were never in the store are treated as enabled (default)
|
|
952
1136
|
const disabledCoreNotInStore = [];
|
|
@@ -963,14 +1147,14 @@ pluginCmd
|
|
|
963
1147
|
}
|
|
964
1148
|
if (!name) {
|
|
965
1149
|
console.error(chalk.red(' Specify a plugin name or use --all'));
|
|
966
|
-
process.exit(
|
|
1150
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
967
1151
|
}
|
|
968
1152
|
const communityPlugins = await fetchCommunityPlugins();
|
|
969
1153
|
const commDef = findCommunityPlugin(name, communityPlugins);
|
|
970
1154
|
if (commDef && !hasCommunityRunner(name)) {
|
|
971
1155
|
console.log(chalk.red(` ✗ Cannot enable "${name}" — runner not installed`));
|
|
972
1156
|
console.log(chalk.gray(` Run: voiden-runner plugin install ${name}`));
|
|
973
|
-
process.exit(
|
|
1157
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
974
1158
|
}
|
|
975
1159
|
setPluginEnabled(name, true);
|
|
976
1160
|
console.log(chalk.green(` ✓ Enabled`) + ` ${name}`);
|
|
@@ -1005,7 +1189,7 @@ pluginCmd
|
|
|
1005
1189
|
}
|
|
1006
1190
|
if (!name) {
|
|
1007
1191
|
console.error(chalk.red(' Specify a plugin name or use --all'));
|
|
1008
|
-
process.exit(
|
|
1192
|
+
process.exit(EXIT_USAGE_ERROR);
|
|
1009
1193
|
return;
|
|
1010
1194
|
}
|
|
1011
1195
|
setPluginEnabled(name, false);
|
|
@@ -1035,10 +1219,16 @@ pluginCmd
|
|
|
1035
1219
|
console.log(DIVIDER);
|
|
1036
1220
|
for (const def of corePlugins) {
|
|
1037
1221
|
const record = store.installedPlugins[def.name];
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1222
|
+
let statusBadge;
|
|
1223
|
+
if (record?.uninstalled) {
|
|
1224
|
+
statusBadge = chalk.gray(' not installed');
|
|
1225
|
+
}
|
|
1226
|
+
else if (record !== undefined && !record.enabled) {
|
|
1227
|
+
statusBadge = chalk.yellow(' · disabled');
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
statusBadge = chalk.green(' ✓ enabled');
|
|
1231
|
+
}
|
|
1042
1232
|
console.log(` ${chalk.bold(def.name.padEnd(24))}${statusBadge}${updateBadge(def.name, def.version)}`);
|
|
1043
1233
|
console.log(chalk.gray(` ${def.description}`));
|
|
1044
1234
|
}
|
|
@@ -1091,5 +1281,87 @@ pluginCmd
|
|
|
1091
1281
|
}
|
|
1092
1282
|
console.log();
|
|
1093
1283
|
});
|
|
1284
|
+
// ── voiden-runner mcp ─────────────────────────────────────────────────────────
|
|
1285
|
+
//
|
|
1286
|
+
// Enables the AI-agent loop for CLI-only users (no Voiden app installed):
|
|
1287
|
+
// registers @voiden/mcp-server with Claude Code / Codex, and installs a
|
|
1288
|
+
// standalone skill teaching the run/verify/write-back workflow. The Voiden
|
|
1289
|
+
// app's own Settings toggle does the equivalent for desktop users, reusing
|
|
1290
|
+
// the same registration helpers from mcpInstall.ts.
|
|
1291
|
+
function resolveMcpTargets(opts) {
|
|
1292
|
+
// Default to both when neither flag is given — a single command should be
|
|
1293
|
+
// enough to "just enable this".
|
|
1294
|
+
if (!opts.claude && !opts.codex)
|
|
1295
|
+
return { claude: true, codex: true };
|
|
1296
|
+
return { claude: Boolean(opts.claude), codex: Boolean(opts.codex) };
|
|
1297
|
+
}
|
|
1298
|
+
const mcpCmd = program
|
|
1299
|
+
.command('mcp')
|
|
1300
|
+
.description('Enable AI-agent integration — registers @voiden/mcp-server and installs a run/verify skill');
|
|
1301
|
+
mcpCmd
|
|
1302
|
+
.command('install')
|
|
1303
|
+
.description('Register @voiden/mcp-server with Claude Code and/or Codex, and install a skill teaching the run/verify/write-back loop.\n\n' +
|
|
1304
|
+
' Examples:\n' +
|
|
1305
|
+
' voiden-runner mcp install # both Claude Code and Codex\n' +
|
|
1306
|
+
' voiden-runner mcp install --claude # Claude Code only\n' +
|
|
1307
|
+
' voiden-runner mcp install -p ./my-project # register against a specific project dir (default: cwd)\n' +
|
|
1308
|
+
' voiden-runner mcp install --local-server ./dist/index.js # before publishing: point at a local build instead of npx\n')
|
|
1309
|
+
.option('--claude', 'Install for Claude Code only')
|
|
1310
|
+
.option('--codex', 'Install for Codex only')
|
|
1311
|
+
.option('-p, --project <path>', 'Project directory to register the MCP server against', '.')
|
|
1312
|
+
.option('--local-server <path>', 'Use `node <path>` instead of `npx -y @voiden/mcp-server` — for testing against a local build before it\'s published')
|
|
1313
|
+
.action((opts) => {
|
|
1314
|
+
const targets = resolveMcpTargets(opts);
|
|
1315
|
+
const serverCommand = opts.localServer
|
|
1316
|
+
? { command: 'node', args: [resolve(opts.localServer), resolve(opts.project)] }
|
|
1317
|
+
: undefined;
|
|
1318
|
+
const installed = installMcpIntegration(opts.project, targets, RUNNER_SKILL_MARKDOWN, serverCommand);
|
|
1319
|
+
if (installed.length === 0) {
|
|
1320
|
+
console.log(chalk.yellow(' Nothing to install.'));
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
console.log();
|
|
1324
|
+
for (const target of installed) {
|
|
1325
|
+
console.log(chalk.green(` ✓ ${target === 'claude' ? 'Claude Code' : 'Codex'}`) + chalk.gray(` — skill installed, @voiden/mcp-server registered for ${resolve(opts.project)}`));
|
|
1326
|
+
}
|
|
1327
|
+
if (serverCommand) {
|
|
1328
|
+
console.log(chalk.gray(` Using local build: node ${serverCommand.args[0]}`));
|
|
1329
|
+
}
|
|
1330
|
+
console.log();
|
|
1331
|
+
console.log(chalk.gray(' Restart Claude Code / Codex (or run /mcp) to pick up the new server.'));
|
|
1332
|
+
});
|
|
1333
|
+
mcpCmd
|
|
1334
|
+
.command('uninstall')
|
|
1335
|
+
.description('Remove the MCP server registration and skill installed by `mcp install`')
|
|
1336
|
+
.option('--claude', 'Remove Claude Code integration only')
|
|
1337
|
+
.option('--codex', 'Remove Codex integration only')
|
|
1338
|
+
.option('-p, --project <path>', 'Project directory to unregister the MCP server from', '.')
|
|
1339
|
+
.action((opts) => {
|
|
1340
|
+
const targets = resolveMcpTargets(opts);
|
|
1341
|
+
const removed = uninstallMcpIntegration(opts.project, targets);
|
|
1342
|
+
if (removed.length === 0) {
|
|
1343
|
+
console.log(chalk.yellow(' Nothing to remove.'));
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
for (const target of removed) {
|
|
1347
|
+
console.log(chalk.green(` ✓ Removed`) + chalk.gray(` ${target === 'claude' ? 'Claude Code' : 'Codex'} integration`));
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
mcpCmd
|
|
1351
|
+
.command('status')
|
|
1352
|
+
.description('Show whether the MCP server + skill are installed for this project')
|
|
1353
|
+
.option('-p, --project <path>', 'Project directory to check', '.')
|
|
1354
|
+
.action((opts) => {
|
|
1355
|
+
const status = getMcpStatus(opts.project);
|
|
1356
|
+
console.log();
|
|
1357
|
+
console.log(chalk.bold(' Claude Code'));
|
|
1358
|
+
console.log(` skill installed: ${status.claude.skillInstalled ? chalk.green('yes') : chalk.gray('no')}`);
|
|
1359
|
+
console.log(` server registered: ${status.claude.serverRegistered ? chalk.green('yes') : chalk.gray('no')}`);
|
|
1360
|
+
console.log();
|
|
1361
|
+
console.log(chalk.bold(' Codex'));
|
|
1362
|
+
console.log(` skill installed: ${status.codex.skillInstalled ? chalk.green('yes') : chalk.gray('no')}`);
|
|
1363
|
+
console.log(` server registered: ${status.codex.serverRegistered ? chalk.green('yes') : chalk.gray('no')}`);
|
|
1364
|
+
console.log();
|
|
1365
|
+
});
|
|
1094
1366
|
program.parse();
|
|
1095
1367
|
//# sourceMappingURL=index.js.map
|