delimit-cli 4.1.40 → 4.1.42
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 +1 -0
- package/bin/delimit-cli.js +188 -80
- package/lib/delimit-template.js +37 -2
- package/package.json +1 -1
- package/server.json +2 -2
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
|
```
|
package/bin/delimit-cli.js
CHANGED
|
@@ -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
|
|
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
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
338
|
-
|
|
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
|
-
|
|
342
|
-
console.log(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
//
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
// Git hooks
|
|
478
|
+
|
|
479
|
+
// --- Last session ---
|
|
480
|
+
const sessionsDir = path.join(delimitHome, 'sessions');
|
|
380
481
|
try {
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
//
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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(
|
|
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/lib/delimit-template.js
CHANGED
|
@@ -27,8 +27,8 @@ These rules fire automatically. The AI MUST call the specified tools without ask
|
|
|
27
27
|
The founder reviews and approves via email. The AI's job is to draft, send, and chain — not to ask.
|
|
28
28
|
|
|
29
29
|
### Session Lifecycle
|
|
30
|
-
- **Session start**: call \`delimit_ledger_context\` + \`delimit_gov_health\` + \`delimit_inbox_daemon(action="start")\` + \`delimit_notify_routing(action="status")\`
|
|
31
|
-
- **Session end or idle**: call \`delimit_session_handoff\` to preserve context for next session
|
|
30
|
+
- **Session start**: call \`delimit_revive\` + \`delimit_ledger_context\` + \`delimit_gov_health\` + \`delimit_inbox_daemon(action="start")\` + \`delimit_notify_routing(action="status")\`
|
|
31
|
+
- **Session end or idle**: call \`delimit_soul_capture\` + \`delimit_session_handoff\` to preserve context for next session
|
|
32
32
|
- If returning user, summarize what's pending. If new project, call \`delimit_scan\`.
|
|
33
33
|
|
|
34
34
|
### Code Development (debounced per edit batch)
|
|
@@ -82,6 +82,18 @@ The founder reviews and approves via email. The AI's job is to draft, send, and
|
|
|
82
82
|
- Max 5 immediate notifications per hour (overflow to digest)
|
|
83
83
|
- Per-scan fan-out: max 10 targets per notification
|
|
84
84
|
|
|
85
|
+
## Memory Rules (CRITICAL)
|
|
86
|
+
|
|
87
|
+
Routine code changes are auto-captured from git on session exit. You do NOT need to call memory_store for normal work.
|
|
88
|
+
|
|
89
|
+
ONLY use \`memory_store\` to capture:
|
|
90
|
+
- **Failed approaches** — "tried X, it didn't work because Y" (git only shows what survived, not dead ends)
|
|
91
|
+
- **Architecture decisions** — "chose PostgreSQL over Redis for sessions because Z"
|
|
92
|
+
- **Key context** — customer requirements, API constraints, deployment gotchas
|
|
93
|
+
- **Things that will confuse the next session** — non-obvious setups, workarounds, tech debt
|
|
94
|
+
|
|
95
|
+
Do NOT store: routine code changes, file lists, commit messages, status updates — these are in git.
|
|
96
|
+
|
|
85
97
|
## What Delimit does:
|
|
86
98
|
- **API governance** -- lint, diff, semver classification, migration guides
|
|
87
99
|
- **Persistent context** -- memory and ledger survive across sessions and models
|
|
@@ -97,6 +109,29 @@ Add breaking change detection to any repo:
|
|
|
97
109
|
spec: api/openapi.yaml
|
|
98
110
|
\`\`\`
|
|
99
111
|
|
|
112
|
+
## Paying Customers (CRITICAL — Read Before Any Change)
|
|
113
|
+
|
|
114
|
+
Delimit has paying Pro customers. Every code change, MCP tool modification, server update, or API change MUST consider impact on existing users.
|
|
115
|
+
|
|
116
|
+
### Customer Protection Rules
|
|
117
|
+
- **Before modifying any MCP tool signature** (params, return schema): check if it would break existing Pro users' workflows
|
|
118
|
+
- **Before renaming/removing CLI commands**: these are documented and users depend on them
|
|
119
|
+
- **Before changing license validation**: customers have active license keys (Lemon Squeezy)
|
|
120
|
+
- **Before modifying server.py tool definitions**: Pro users have the MCP server installed locally at ~/.delimit/server/
|
|
121
|
+
- **Before changing JSONL/JSON storage formats**: memory, ledger, evidence files may exist on customer machines
|
|
122
|
+
- **npm publish is a production deploy**: every publish goes to real users, not just us
|
|
123
|
+
- **Gateway → npm sync**: when syncing server.py to the npm bundle, verify no breaking tool changes
|
|
124
|
+
- **Test with \`delimit doctor\`** before any publish to catch config/setup breaks
|
|
125
|
+
- **Backwards compatibility**: new features must not break existing installations. Add, don't remove.
|
|
126
|
+
|
|
127
|
+
### What Constitutes a Breaking Change for Users
|
|
128
|
+
- MCP tool parameter renamed or removed
|
|
129
|
+
- CLI command renamed or removed
|
|
130
|
+
- Storage format change (memories.jsonl, ledger, evidence, license.json)
|
|
131
|
+
- Python import path changes in server.py
|
|
132
|
+
- Hook format changes in settings.json
|
|
133
|
+
- Default behavior changes (e.g., changing what \`delimit scan\` does with no args)
|
|
134
|
+
|
|
100
135
|
## Links
|
|
101
136
|
- Docs: https://delimit.ai/docs
|
|
102
137
|
- GitHub: https://github.com/delimit-ai/delimit-mcp-server
|
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.42",
|
|
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.
|
|
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.
|
|
16
|
+
"version": "4.1.40",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
}
|