delimit-cli 4.1.2 → 4.1.4
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/bin/delimit-cli.js +117 -39
- package/package.json +1 -1
package/bin/delimit-cli.js
CHANGED
|
@@ -2333,9 +2333,36 @@ program
|
|
|
2333
2333
|
.command('scan [path]')
|
|
2334
2334
|
.description('Scan a project or OpenAPI spec for governance insights')
|
|
2335
2335
|
.action(async (specPath) => {
|
|
2336
|
-
const target = specPath
|
|
2336
|
+
const target = specPath ? path.resolve(specPath) : process.cwd();
|
|
2337
2337
|
console.log(chalk.bold('\n Delimit Scan\n'));
|
|
2338
2338
|
|
|
2339
|
+
// Resolve gateway dir: installed server > bundled in npm package
|
|
2340
|
+
const bundledGateway = path.join(__dirname, '..', 'gateway');
|
|
2341
|
+
const serverDir = (continuityContext.serverDir && continuityContext.serverDir !== 'undefined' && fs.existsSync(continuityContext.serverDir))
|
|
2342
|
+
? continuityContext.serverDir
|
|
2343
|
+
: fs.existsSync(bundledGateway) ? bundledGateway : null;
|
|
2344
|
+
|
|
2345
|
+
if (!serverDir) {
|
|
2346
|
+
console.log(chalk.yellow(' Gateway not found. Installing...\n'));
|
|
2347
|
+
try {
|
|
2348
|
+
execSync('npx delimit-cli setup --yes', { stdio: 'inherit', timeout: 60000 });
|
|
2349
|
+
// Retry after setup
|
|
2350
|
+
console.log(chalk.green('\n Setup complete. Re-running scan...\n'));
|
|
2351
|
+
execSync(`npx delimit-cli scan ${specPath || ''}`, { stdio: 'inherit', timeout: 30000 });
|
|
2352
|
+
} catch {
|
|
2353
|
+
console.log(chalk.red('\n Auto-setup failed. Run manually: npx delimit-cli setup'));
|
|
2354
|
+
}
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// Check Python + yaml dependency
|
|
2359
|
+
try {
|
|
2360
|
+
execSync('python3 -c "import yaml"', { stdio: 'ignore', timeout: 5000 });
|
|
2361
|
+
} catch {
|
|
2362
|
+
console.log(chalk.yellow(' Installing Python dependency (pyyaml)...\n'));
|
|
2363
|
+
try { execSync('pip3 install pyyaml -q', { stdio: 'ignore', timeout: 30000 }); } catch {}
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2339
2366
|
// Detect if target is a spec file or a project directory
|
|
2340
2367
|
const isFile = fs.existsSync(target) && fs.statSync(target).isFile();
|
|
2341
2368
|
|
|
@@ -2344,8 +2371,8 @@ program
|
|
|
2344
2371
|
console.log(chalk.gray(` Analyzing ${target}...\n`));
|
|
2345
2372
|
try {
|
|
2346
2373
|
const result = execSync(
|
|
2347
|
-
`python3 -c "import sys,json; sys.path.insert(0,'${
|
|
2348
|
-
{ encoding: 'utf-8', timeout: 15000, cwd:
|
|
2374
|
+
`python3 -c "import sys,json; sys.path.insert(0,'${serverDir}'); from core.spec_health import score_spec; import yaml; spec=yaml.safe_load(open('${target}')); r=score_spec(spec); print(json.dumps(r))"`,
|
|
2375
|
+
{ encoding: 'utf-8', timeout: 15000, cwd: serverDir }
|
|
2349
2376
|
);
|
|
2350
2377
|
const health = JSON.parse(result);
|
|
2351
2378
|
const gradeColors = { A: 'green', B: 'blue', C: 'yellow', D: 'red', F: 'red' };
|
|
@@ -2364,55 +2391,106 @@ program
|
|
|
2364
2391
|
console.log(` ${chalk.yellow('→')} ${text}`);
|
|
2365
2392
|
});
|
|
2366
2393
|
}
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2394
|
+
// Interactive next step picker
|
|
2395
|
+
try {
|
|
2396
|
+
const { next } = await inquirer.prompt([{
|
|
2397
|
+
type: 'list',
|
|
2398
|
+
name: 'next',
|
|
2399
|
+
message: 'What next?',
|
|
2400
|
+
choices: [
|
|
2401
|
+
{ name: `Lint this spec for breaking changes`, value: 'lint' },
|
|
2402
|
+
{ name: 'Set up governance for this project', value: 'init' },
|
|
2403
|
+
{ name: 'Configure AI assistants (Claude, Codex, Gemini)', value: 'setup' },
|
|
2404
|
+
{ name: 'Exit', value: 'exit' },
|
|
2405
|
+
],
|
|
2406
|
+
}]);
|
|
2407
|
+
if (next === 'lint') {
|
|
2408
|
+
execSync(`npx delimit-cli lint ${target}`, { stdio: 'inherit' });
|
|
2409
|
+
} else if (next === 'init') {
|
|
2410
|
+
execSync('npx delimit-cli init', { stdio: 'inherit' });
|
|
2411
|
+
} else if (next === 'setup') {
|
|
2412
|
+
execSync('npx delimit-cli setup', { stdio: 'inherit' });
|
|
2413
|
+
}
|
|
2414
|
+
} catch {}
|
|
2370
2415
|
} catch (e) {
|
|
2371
2416
|
console.log(chalk.red(` Error: ${e.message}`));
|
|
2372
2417
|
}
|
|
2373
2418
|
} else {
|
|
2374
|
-
// Project directory — find specs
|
|
2375
|
-
console.log(chalk.gray(` Scanning ${
|
|
2419
|
+
// Project directory — find specs using simple glob, no server.py needed
|
|
2420
|
+
console.log(chalk.gray(` Scanning ${target}...\n`));
|
|
2376
2421
|
try {
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
)
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
console.log(chalk.gray(`\n Running spec health on ${specs.files[0]}...`));
|
|
2422
|
+
// Find OpenAPI/Swagger specs
|
|
2423
|
+
const specPatterns = ['openapi.yaml', 'openapi.yml', 'openapi.json', 'swagger.yaml', 'swagger.yml', 'swagger.json'];
|
|
2424
|
+
const found = [];
|
|
2425
|
+
const _findSpecs = (dir, depth) => {
|
|
2426
|
+
if (depth > 4) return;
|
|
2427
|
+
try {
|
|
2428
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
2429
|
+
if (entry.name === 'node_modules' || entry.name === '.next' || entry.name === 'venv' || entry.name === '.git') continue;
|
|
2430
|
+
const full = path.join(dir, entry.name);
|
|
2431
|
+
if (entry.isFile() && specPatterns.includes(entry.name.toLowerCase())) {
|
|
2432
|
+
found.push(path.relative(target, full));
|
|
2433
|
+
} else if (entry.isDirectory()) {
|
|
2434
|
+
_findSpecs(full, depth + 1);
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
} catch {}
|
|
2438
|
+
};
|
|
2439
|
+
_findSpecs(target, 0);
|
|
2440
|
+
|
|
2441
|
+
if (found.length > 0) {
|
|
2442
|
+
console.log(` ${chalk.green('✓')} Found ${found.length} OpenAPI spec(s): ${found.slice(0, 3).join(', ')}`);
|
|
2443
|
+
// Run health on the first spec
|
|
2444
|
+
const specFile = path.join(target, found[0]);
|
|
2445
|
+
console.log(chalk.gray(`\n Scoring ${found[0]}...\n`));
|
|
2402
2446
|
try {
|
|
2403
2447
|
const healthResult = execSync(
|
|
2404
|
-
`python3 -c "import sys,json; sys.path.insert(0,'${
|
|
2405
|
-
{ encoding: 'utf-8', timeout: 15000, cwd:
|
|
2448
|
+
`python3 -c "import sys,json; sys.path.insert(0,'${serverDir}'); from core.spec_health import score_spec; import yaml; spec=yaml.safe_load(open('${specFile}')); r=score_spec(spec); print(json.dumps(r))"`,
|
|
2449
|
+
{ encoding: 'utf-8', timeout: 15000, cwd: serverDir }
|
|
2406
2450
|
);
|
|
2407
2451
|
const health = JSON.parse(healthResult);
|
|
2408
2452
|
const gradeColors = { A: 'green', B: 'blue', C: 'yellow', D: 'red', F: 'red' };
|
|
2409
2453
|
const gradeColor = gradeColors[health.grade] || 'white';
|
|
2410
|
-
console.log(
|
|
2454
|
+
console.log(` ${chalk[gradeColor].bold(health.grade)} ${chalk.white.bold(health.overall_score + '/100')} ${chalk.gray('Spec Health Score')}\n`);
|
|
2455
|
+
for (const [dim, data] of Object.entries(health.dimensions || {})) {
|
|
2456
|
+
const score = data.score || 0;
|
|
2457
|
+
const bar = '\u2588'.repeat(Math.round(score / 5)) + '\u2591'.repeat(20 - Math.round(score / 5));
|
|
2458
|
+
const color = score >= 70 ? 'green' : score >= 40 ? 'yellow' : 'red';
|
|
2459
|
+
console.log(` ${chalk.gray(dim.padEnd(16))} ${chalk[color](bar)} ${score}`);
|
|
2460
|
+
}
|
|
2461
|
+
if (health.recommendations && health.recommendations.length > 0) {
|
|
2462
|
+
console.log(chalk.bold('\n Recommendations:\n'));
|
|
2463
|
+
health.recommendations.slice(0, 5).forEach(r => {
|
|
2464
|
+
const text = typeof r === 'object' ? (r.recommendation || r.text || JSON.stringify(r)) : r;
|
|
2465
|
+
console.log(` ${chalk.yellow('\u2192')} ${text}`);
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2411
2468
|
} catch {}
|
|
2469
|
+
} else {
|
|
2470
|
+
console.log(` ${chalk.yellow('\u2014')} No OpenAPI specs found in this directory`);
|
|
2471
|
+
console.log(chalk.gray(' Tip: point at a spec file directly: npx delimit-cli scan openapi.yaml'));
|
|
2412
2472
|
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2473
|
+
// Interactive next step picker
|
|
2474
|
+
try {
|
|
2475
|
+
const { next } = await inquirer.prompt([{
|
|
2476
|
+
type: 'list',
|
|
2477
|
+
name: 'next',
|
|
2478
|
+
message: 'What next?',
|
|
2479
|
+
choices: [
|
|
2480
|
+
{ name: 'Set up governance for this project', value: 'init' },
|
|
2481
|
+
{ name: 'Configure AI assistants (Claude, Codex, Gemini)', value: 'setup' },
|
|
2482
|
+
{ name: 'Run a breaking change demo', value: 'try' },
|
|
2483
|
+
{ name: 'Exit', value: 'exit' },
|
|
2484
|
+
],
|
|
2485
|
+
}]);
|
|
2486
|
+
if (next === 'init') {
|
|
2487
|
+
execSync('npx delimit-cli init', { stdio: 'inherit' });
|
|
2488
|
+
} else if (next === 'setup') {
|
|
2489
|
+
execSync('npx delimit-cli setup', { stdio: 'inherit' });
|
|
2490
|
+
} else if (next === 'try') {
|
|
2491
|
+
execSync('npx delimit-cli try', { stdio: 'inherit' });
|
|
2492
|
+
}
|
|
2493
|
+
} catch {}
|
|
2416
2494
|
} catch (e) {
|
|
2417
2495
|
console.log(chalk.red(` Error: ${e.message}`));
|
|
2418
2496
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "delimit-cli",
|
|
3
3
|
"mcpName": "io.github.delimit-ai/delimit-mcp-server",
|
|
4
|
-
"version": "4.1.
|
|
4
|
+
"version": "4.1.4",
|
|
5
5
|
"description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|