delimit-cli 4.1.40 → 4.1.41

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 CHANGED
@@ -169,6 +169,7 @@ npx delimit-cli recall --export # Export as markdown
169
169
  npx delimit-cli forget abc123 # Delete a memory by ID
170
170
  npx delimit-cli models # Configure deliberation API keys (BYOK wizard)
171
171
  npx delimit-cli models --status # Show current model config
172
+ npx delimit-cli status # Compact dashboard of your Delimit setup
172
173
  npx delimit-cli doctor # Check setup health
173
174
  npx delimit-cli uninstall --dry-run # Preview removal
174
175
  ```
@@ -128,6 +128,20 @@ if (process.env.DELIMIT_DEBUG_CONTINUITY === '1') {
128
128
  console.log('');
129
129
  }
130
130
 
131
+ // Helper to format a timestamp as relative time (e.g. "2h ago", "3d ago")
132
+ function _relativeTime(ts) {
133
+ const diff = Date.now() - ts;
134
+ const mins = Math.floor(diff / 60000);
135
+ if (mins < 1) return 'just now';
136
+ if (mins < 60) return mins + 'm ago';
137
+ const hrs = Math.floor(mins / 60);
138
+ if (hrs < 24) return hrs + 'h ago';
139
+ const days = Math.floor(hrs / 24);
140
+ if (days < 30) return days + 'd ago';
141
+ const months = Math.floor(days / 30);
142
+ return months + 'mo ago';
143
+ }
144
+
131
145
  // Helper to check if agent is running
132
146
  async function checkAgent() {
133
147
  try {
@@ -311,99 +325,193 @@ program
311
325
  // Status command
312
326
  program
313
327
  .command('status')
314
- .description('Show governance status')
328
+ .description('Show a compact dashboard of your Delimit setup')
315
329
 
316
330
  .option('--verbose', 'Show detailed status')
317
331
  .action(async (options) => {
318
- const agentRunning = await checkAgent();
319
-
320
- console.log(chalk.blue.bold('\nDelimit Governance Status\n'));
321
- console.log('Agent:', agentRunning ? chalk.green('✓ Running') : chalk.red('✗ Not running'));
332
+ const homedir = os.homedir();
333
+ const delimitHome = path.join(homedir, '.delimit');
334
+ const target = process.cwd();
335
+
336
+ console.log(chalk.bold('\n Delimit Status\n'));
337
+
338
+ // --- Memory stats ---
339
+ const memoryDir = path.join(delimitHome, 'memory');
340
+ let memTotal = 0;
341
+ let memRecent = 0;
342
+ let recentMemories = [];
343
+ const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
344
+ try {
345
+ const memFiles = fs.readdirSync(memoryDir).filter(f => f.startsWith('mem-') && f.endsWith('.json'));
346
+ memTotal = memFiles.length;
347
+ for (const f of memFiles) {
348
+ try {
349
+ const data = JSON.parse(fs.readFileSync(path.join(memoryDir, f), 'utf-8'));
350
+ const ts = new Date(data.created_at || data.timestamp || data.created || 0).getTime();
351
+ if (ts > oneWeekAgo) memRecent++;
352
+ recentMemories.push({ text: data.text || data.content || '', tags: data.tags || [], ts });
353
+ } catch {}
354
+ }
355
+ recentMemories.sort((a, b) => b.ts - a.ts);
356
+ recentMemories = recentMemories.slice(0, 3);
357
+ } catch {}
358
+ console.log(` Memory: ${chalk.white.bold(memTotal)} memories${memRecent > 0 ? ` (${memRecent} this week)` : ''}`);
322
359
 
323
- if (options.verbose) {
324
- console.log('\n' + chalk.bold('Continuity Context:'));
325
- console.log(formatContinuityReport(continuityContext).split('\n').slice(1).map(line => ' ' + line.trimStart()).join('\n'));
326
- }
327
-
328
- if (agentRunning) {
329
- const { data } = await axios.get(`${AGENT_URL}/status`);
330
-
331
- // Mode information
332
- console.log('\n' + chalk.bold('Mode Configuration:'));
333
- console.log(` Current Mode: ${chalk.bold(data.sessionMode)}`);
334
- if (data.defaultMode) {
335
- console.log(` Default Mode: ${data.defaultMode}`);
360
+ // --- Governance / Policy ---
361
+ const policyPath = path.join(target, '.delimit', 'policies.yml');
362
+ let policyLabel = chalk.gray('none');
363
+ let hasPolicy = false;
364
+ if (fs.existsSync(policyPath)) {
365
+ hasPolicy = true;
366
+ try {
367
+ const policyContent = yaml.load(fs.readFileSync(policyPath, 'utf-8'));
368
+ const preset = policyContent?.preset || policyContent?.name || 'custom';
369
+ policyLabel = chalk.green(preset + ' policy');
370
+ } catch {
371
+ policyLabel = chalk.green('custom policy');
336
372
  }
337
- if (data.effectiveMode && data.effectiveMode !== data.sessionMode) {
338
- console.log(` Effective Mode: ${chalk.yellow(data.effectiveMode)} (escalated)`);
373
+ }
374
+ // Count tracked specs
375
+ const specPatterns = ['openapi.yaml', 'openapi.yml', 'openapi.json', 'swagger.yaml', 'swagger.yml', 'swagger.json'];
376
+ let specCount = 0;
377
+ const _countSpecs = (dir, depth) => {
378
+ if (depth > 3) return;
379
+ try {
380
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
381
+ if (['node_modules', '.next', 'venv', '.git'].includes(entry.name)) continue;
382
+ const full = path.join(dir, entry.name);
383
+ if (entry.isFile() && specPatterns.includes(entry.name.toLowerCase())) {
384
+ specCount++;
385
+ } else if (entry.isDirectory()) {
386
+ _countSpecs(full, depth + 1);
387
+ }
388
+ }
389
+ } catch {}
390
+ };
391
+ _countSpecs(target, 0);
392
+ const specLabel = specCount > 0 ? `${specCount} spec${specCount > 1 ? 's' : ''} tracked` : chalk.gray('no specs');
393
+ console.log(` Governance: ${policyLabel}${hasPolicy ? ' | ' : ' | '}${specLabel}`);
394
+
395
+ // --- Git hooks ---
396
+ const preCommitPath = path.join(target, '.git', 'hooks', 'pre-commit');
397
+ let hasGitHooks = false;
398
+ try {
399
+ const hookContent = fs.readFileSync(preCommitPath, 'utf-8');
400
+ hasGitHooks = hookContent.includes('delimit');
401
+ } catch {}
402
+ console.log(` Git Hooks: ${hasGitHooks ? chalk.green('pre-commit installed') : chalk.gray('not installed')}`);
403
+
404
+ // --- CI ---
405
+ const workflowPath = path.join(target, '.github', 'workflows', 'api-governance.yml');
406
+ const hasCI = fs.existsSync(workflowPath);
407
+ console.log(` CI: ${hasCI ? chalk.green('GitHub Action active') : chalk.gray('not configured')}`);
408
+
409
+ // --- MCP ---
410
+ const mcpConfigPath = path.join(homedir, '.mcp.json');
411
+ let hasMcp = false;
412
+ let toolCount = 0;
413
+ try {
414
+ const mcpContent = fs.readFileSync(mcpConfigPath, 'utf-8');
415
+ hasMcp = mcpContent.includes('delimit');
416
+ } catch {}
417
+ if (hasMcp) {
418
+ // Count tools from server.py if available
419
+ const serverPyPaths = [
420
+ path.join(delimitHome, 'server', 'ai', 'server.py'),
421
+ path.join(delimitHome, 'server', 'server.py'),
422
+ ];
423
+ for (const sp of serverPyPaths) {
424
+ try {
425
+ const serverContent = fs.readFileSync(sp, 'utf-8');
426
+ const toolMatches = serverContent.match(/@mcp\.tool/g);
427
+ if (toolMatches) {
428
+ toolCount = toolMatches.length;
429
+ break;
430
+ }
431
+ } catch {}
339
432
  }
340
-
341
- // Policies
342
- console.log('\n' + chalk.bold('Policies:'));
343
- if (data.policiesLoaded.length > 0) {
344
- data.policiesLoaded.forEach(policy => {
345
- console.log(` • ${policy}`);
346
- });
347
- if (data.totalRules) {
348
- console.log(` Total Rules: ${data.totalRules}`);
433
+ console.log(` MCP: ${chalk.green('connected')}${toolCount > 0 ? ` (${toolCount} tools)` : ''}`);
434
+ } else {
435
+ console.log(` MCP: ${chalk.gray('not configured')}`);
436
+ }
437
+
438
+ // --- Models ---
439
+ const modelsPath = path.join(delimitHome, 'models.json');
440
+ let modelsLabel = chalk.gray('none configured');
441
+ try {
442
+ const modelsData = JSON.parse(fs.readFileSync(modelsPath, 'utf-8'));
443
+ const modelNames = [];
444
+ for (const [key, val] of Object.entries(modelsData)) {
445
+ if (val && typeof val === 'object' && (val.api_key || val.enabled !== false)) {
446
+ modelNames.push(key.charAt(0).toUpperCase() + key.slice(1));
349
447
  }
350
- } else {
351
- console.log(' No policies loaded');
352
448
  }
353
-
354
- // Recent activity
355
- console.log('\n' + chalk.bold('Activity:'));
356
- console.log(` Audit Log Entries: ${data.auditLogSize}`);
357
- if (data.lastDecision) {
358
- const timeSince = Date.now() - new Date(data.lastDecision.timestamp);
359
- const minutes = Math.floor(timeSince / 60000);
360
- console.log(` Last Decision: ${minutes} minutes ago (${data.lastDecision.action})`);
449
+ if (modelNames.length > 0) {
450
+ modelsLabel = chalk.white(modelNames.join(' + ')) + chalk.gray(' (BYOK)');
361
451
  }
362
- console.log(` Uptime: ${Math.floor(data.uptime / 60)} minutes`);
363
-
364
- // Verbose mode shows recent decisions
365
- if (options.verbose && data.recentDecisions) {
366
- console.log('\n' + chalk.bold('Recent Decisions:'));
367
- data.recentDecisions.forEach(decision => {
368
- const color = decision.action === 'block' ? chalk.red :
369
- decision.action === 'prompt' ? chalk.yellow :
370
- chalk.green;
371
- console.log(` ${decision.timestamp} | ${color(decision.mode)} | ${decision.rule || 'no rule'}`);
372
- });
452
+ } catch {}
453
+ console.log(` Models: ${modelsLabel}`);
454
+
455
+ // --- License ---
456
+ const licensePath = path.join(delimitHome, 'license.json');
457
+ let licenseLabel = chalk.gray('Free');
458
+ try {
459
+ const licenseData = JSON.parse(fs.readFileSync(licensePath, 'utf-8'));
460
+ const tier = licenseData.tier || licenseData.plan || 'Free';
461
+ const active = licenseData.status === 'active' || licenseData.valid === true;
462
+ if (tier.toLowerCase() !== 'free') {
463
+ licenseLabel = active ? chalk.green(`${tier} (active)`) : chalk.yellow(`${tier} (${licenseData.status || 'unknown'})`);
464
+ }
465
+ } catch {}
466
+ console.log(` License: ${licenseLabel}`);
467
+
468
+ // --- Recent memories ---
469
+ if (recentMemories.length > 0) {
470
+ console.log(chalk.bold('\n Recent memories:'));
471
+ for (const mem of recentMemories) {
472
+ const ago = _relativeTime(mem.ts);
473
+ const tagStr = mem.tags.length > 0 ? ' ' + chalk.gray(mem.tags.map(t => '#' + t).join(' ')) : '';
474
+ const text = mem.text.length > 55 ? mem.text.slice(0, 55) + '...' : mem.text;
475
+ console.log(` ${chalk.gray('[' + ago + ']')} ${text}${tagStr}`);
373
476
  }
374
477
  }
375
-
376
- // System integration
377
- console.log('\n' + chalk.bold('System Integration:'));
378
-
379
- // Git hooks
478
+
479
+ // --- Last session ---
480
+ const sessionsDir = path.join(delimitHome, 'sessions');
380
481
  try {
381
- const hooksPath = execSync('git config --global core.hooksPath').toString().trim();
382
- const hooksActive = hooksPath.includes('.delimit');
383
- console.log(` Git Hooks: ${hooksActive ? chalk.green('✓ Active') : chalk.yellow('⚠ Not configured')}`);
384
- } catch (e) {
385
- console.log(` Git Hooks: ${chalk.red('✗ Not configured')}`);
386
- }
387
-
388
- // PATH
389
- if (process.env.PATH.includes('.delimit/shims')) {
390
- console.log(` AI Tool Interception: ${chalk.green(' Active')}`);
391
- } else {
392
- console.log(` AI Tool Interception: ${chalk.gray('Not active')}`);
393
- }
394
-
395
- // Policy files
396
- const policyFiles = [];
397
- if (fs.existsSync('delimit.yml')) {
398
- policyFiles.push('project');
399
- }
400
- if (fs.existsSync(path.join(process.env.HOME, '.config', 'delimit', 'delimit.yml'))) {
401
- policyFiles.push('user');
402
- }
403
- console.log(` Policy Files: ${policyFiles.length > 0 ? policyFiles.join(', ') : chalk.gray('none')}`);
404
-
482
+ const sessFiles = fs.readdirSync(sessionsDir)
483
+ .filter(f => f.startsWith('session_') && f.endsWith('.json'))
484
+ .sort()
485
+ .reverse();
486
+ if (sessFiles.length > 0) {
487
+ const latest = JSON.parse(fs.readFileSync(path.join(sessionsDir, sessFiles[0]), 'utf-8'));
488
+ const summary = latest.summary || latest.description || latest.title || null;
489
+ if (summary) {
490
+ const truncated = summary.length > 60 ? summary.slice(0, 60) + '...' : summary;
491
+ console.log(chalk.bold('\n Last session: ') + truncated);
492
+ }
493
+ }
494
+ } catch {}
495
+
496
+ // --- Governance readiness ---
497
+ const hasSpecs = specCount > 0;
498
+ const checks = [
499
+ { name: 'API spec', done: hasSpecs },
500
+ { name: 'Policy', done: hasPolicy },
501
+ { name: 'CI gate', done: hasCI },
502
+ { name: 'Git hooks', done: hasGitHooks },
503
+ { name: 'MCP', done: hasMcp },
504
+ ];
505
+ const score = checks.filter(c => c.done).length;
506
+
507
+ console.log(chalk.bold(`\n Governance readiness: ${score}/${checks.length}`));
508
+ console.log(' ' + checks.map(c => c.done ? chalk.green('\u25cf') + ' ' + c.name : chalk.gray('\u25cb') + ' ' + chalk.gray(c.name)).join(' '));
509
+ console.log('');
510
+
405
511
  if (options.verbose) {
406
- console.log('\n' + chalk.gray('Run "delimit doctor" for detailed diagnostics'));
512
+ console.log(chalk.bold(' Continuity Context:'));
513
+ console.log(formatContinuityReport(continuityContext).split('\n').slice(1).map(line => ' ' + line.trimStart()).join('\n'));
514
+ console.log('');
407
515
  }
408
516
  });
409
517
 
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.40",
4
+ "version": "4.1.41",
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": [
package/server.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "url": "https://github.com/delimit-ai/delimit-mcp-server",
8
8
  "source": "github"
9
9
  },
10
- "version": "4.1.38",
10
+ "version": "4.1.40",
11
11
  "websiteUrl": "https://delimit.ai",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "delimit-cli",
16
- "version": "4.1.38",
16
+ "version": "4.1.40",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  }