delimit-cli 4.1.38 → 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 +19 -0
- package/bin/delimit-cli.js +528 -102
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -167,6 +167,9 @@ npx delimit-cli recall # Show recent memories
|
|
|
167
167
|
npx delimit-cli recall --tag deploy --all # Filter by tag, show all
|
|
168
168
|
npx delimit-cli recall --export # Export as markdown
|
|
169
169
|
npx delimit-cli forget abc123 # Delete a memory by ID
|
|
170
|
+
npx delimit-cli models # Configure deliberation API keys (BYOK wizard)
|
|
171
|
+
npx delimit-cli models --status # Show current model config
|
|
172
|
+
npx delimit-cli status # Compact dashboard of your Delimit setup
|
|
170
173
|
npx delimit-cli doctor # Check setup health
|
|
171
174
|
npx delimit-cli uninstall --dry-run # Preview removal
|
|
172
175
|
```
|
|
@@ -270,6 +273,22 @@ rules:
|
|
|
270
273
|
|
|
271
274
|
---
|
|
272
275
|
|
|
276
|
+
## FAQ
|
|
277
|
+
|
|
278
|
+
**How does this compare to Obsidian Mind?**
|
|
279
|
+
|
|
280
|
+
Obsidian Mind is a great Obsidian vault template for Claude Code users who want persistent memory via markdown files. Delimit takes a different approach: it's an MCP server that works across Claude Code, Codex, Gemini CLI, and Cursor. Your memory, ledger, and governance travel with you when you switch models. Delimit also adds API governance (27-type breaking change detection), CI gates, git hooks, and policy enforcement that Obsidian Mind doesn't cover. Use Obsidian Mind if you're all-in on Claude + Obsidian. Use Delimit if you switch between models or need governance.
|
|
281
|
+
|
|
282
|
+
**Does this work without Claude Code?**
|
|
283
|
+
|
|
284
|
+
Yes. Delimit works with Claude Code, Codex (OpenAI), Gemini CLI (Google), and Cursor. The `remember`/`recall` commands work standalone with zero config. The MCP server integrates with any client that supports the Model Context Protocol.
|
|
285
|
+
|
|
286
|
+
**Is this free?**
|
|
287
|
+
|
|
288
|
+
The free tier includes API governance, persistent memory, zero-spec extraction, project scanning, and 3 multi-model deliberations. Pro ($10/mo) adds unlimited deliberation, security audit, test verification, deploy pipeline, and agent orchestration.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
273
292
|
## Links
|
|
274
293
|
|
|
275
294
|
- [delimit.ai](https://delimit.ai) -- homepage
|
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
|
|
|
@@ -4159,6 +4267,267 @@ if result.get('summary'):
|
|
|
4159
4267
|
}
|
|
4160
4268
|
});
|
|
4161
4269
|
|
|
4270
|
+
// ---------------------------------------------------------------------------
|
|
4271
|
+
// Models command: BYOK deliberation key management wizard
|
|
4272
|
+
// ---------------------------------------------------------------------------
|
|
4273
|
+
|
|
4274
|
+
const MODELS_CONFIG_PATH = path.join(os.homedir(), '.delimit', 'models.json');
|
|
4275
|
+
const DELIBERATION_USAGE_PATH = path.join(os.homedir(), '.delimit', 'deliberation_usage.json');
|
|
4276
|
+
|
|
4277
|
+
const DEFAULT_MODELS = {
|
|
4278
|
+
grok: { enabled: false, api_key: '', model: 'grok-4-0709', name: 'Grok 4' },
|
|
4279
|
+
gemini: { enabled: false, api_key: '', model: 'gemini-2.5-pro', name: 'Gemini Pro' },
|
|
4280
|
+
openai: { enabled: false, api_key: '', model: 'gpt-4o', name: 'Codex (GPT-4o)' },
|
|
4281
|
+
};
|
|
4282
|
+
|
|
4283
|
+
const MODEL_PROVIDERS = {
|
|
4284
|
+
grok: { label: 'Grok (xAI)', prefix: 'xai-', endpoint: 'https://api.x.ai/v1/chat/completions', defaultModel: 'grok-4-0709', defaultName: 'Grok 4', variants: ['grok-4-0709', 'grok-3', 'grok-3-mini'] },
|
|
4285
|
+
gemini: { label: 'Gemini (Google)', prefix: 'AIza', endpoint: 'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent', defaultModel: 'gemini-2.5-pro', defaultName: 'Gemini Pro', variants: ['gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.0-flash'] },
|
|
4286
|
+
openai: { label: 'Codex/GPT-4o (OpenAI)', prefix: 'sk-', endpoint: 'https://api.openai.com/v1/chat/completions', defaultModel: 'gpt-4o', defaultName: 'Codex (GPT-4o)', variants: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o3-mini'] },
|
|
4287
|
+
};
|
|
4288
|
+
|
|
4289
|
+
function loadModelsConfig() {
|
|
4290
|
+
try {
|
|
4291
|
+
if (fs.existsSync(MODELS_CONFIG_PATH)) {
|
|
4292
|
+
return JSON.parse(fs.readFileSync(MODELS_CONFIG_PATH, 'utf-8'));
|
|
4293
|
+
}
|
|
4294
|
+
} catch {}
|
|
4295
|
+
return {};
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
function saveModelsConfig(config) {
|
|
4299
|
+
const dir = path.dirname(MODELS_CONFIG_PATH);
|
|
4300
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
4301
|
+
fs.writeFileSync(MODELS_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
function loadDeliberationUsage() {
|
|
4305
|
+
try {
|
|
4306
|
+
if (fs.existsSync(DELIBERATION_USAGE_PATH)) {
|
|
4307
|
+
return JSON.parse(fs.readFileSync(DELIBERATION_USAGE_PATH, 'utf-8'));
|
|
4308
|
+
}
|
|
4309
|
+
} catch {}
|
|
4310
|
+
return { used: 0, limit: 3 };
|
|
4311
|
+
}
|
|
4312
|
+
|
|
4313
|
+
function getModelStatus(config, key) {
|
|
4314
|
+
const entry = config[key];
|
|
4315
|
+
if (entry && entry.enabled && entry.api_key) {
|
|
4316
|
+
return { configured: true, model: entry.model || DEFAULT_MODELS[key].model };
|
|
4317
|
+
}
|
|
4318
|
+
return { configured: false, model: null };
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
function printModelStatus(config) {
|
|
4322
|
+
const usage = loadDeliberationUsage();
|
|
4323
|
+
const remaining = Math.max(0, (usage.limit || 3) - (usage.used || 0));
|
|
4324
|
+
let configuredCount = 0;
|
|
4325
|
+
|
|
4326
|
+
console.log(chalk.bold.blue('\n Delimit Models -- Deliberation Config\n'));
|
|
4327
|
+
console.log(chalk.bold(' Current models:'));
|
|
4328
|
+
|
|
4329
|
+
for (const [key, defaults] of Object.entries(DEFAULT_MODELS)) {
|
|
4330
|
+
const status = getModelStatus(config, key);
|
|
4331
|
+
if (status.configured) {
|
|
4332
|
+
configuredCount++;
|
|
4333
|
+
console.log(` ${chalk.green('*')} ${defaults.name.split(' ')[0].padEnd(10)} -- configured (${status.model})`);
|
|
4334
|
+
} else {
|
|
4335
|
+
const extra = key === 'openai' ? '' : '';
|
|
4336
|
+
console.log(` ${chalk.gray('o')} ${defaults.name.split(' ')[0].padEnd(10)} -- ${chalk.gray('not configured')}${extra}`);
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
console.log(` ${chalk.gray('o')} ${'Claude'.padEnd(10)} -- ${chalk.gray('not configured (uses your Claude Code subscription)')}`);
|
|
4340
|
+
|
|
4341
|
+
console.log('');
|
|
4342
|
+
console.log(` ${remaining} free deliberation${remaining === 1 ? '' : 's'} remaining (of ${usage.limit || 3}).`);
|
|
4343
|
+
if (configuredCount > 0) {
|
|
4344
|
+
console.log(` Mode: ${chalk.green('BYOK')} (${configuredCount} model${configuredCount === 1 ? '' : 's'})`);
|
|
4345
|
+
} else {
|
|
4346
|
+
console.log(' Add API keys for unlimited deliberation with your own models.');
|
|
4347
|
+
}
|
|
4348
|
+
console.log('');
|
|
4349
|
+
|
|
4350
|
+
return { configuredCount, remaining };
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4353
|
+
async function testModelKey(providerKey, apiKey, model) {
|
|
4354
|
+
const provider = MODEL_PROVIDERS[providerKey];
|
|
4355
|
+
const prompt = 'What is 2+2? Reply with just the number.';
|
|
4356
|
+
|
|
4357
|
+
try {
|
|
4358
|
+
if (providerKey === 'gemini') {
|
|
4359
|
+
const url = provider.endpoint.replace('{model}', model) + `?key=${apiKey}`;
|
|
4360
|
+
const resp = await axios.post(url, {
|
|
4361
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
4362
|
+
}, { timeout: 15000, headers: { 'Content-Type': 'application/json' } });
|
|
4363
|
+
const text = resp.data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
4364
|
+
return { ok: true, response: text.trim() };
|
|
4365
|
+
} else {
|
|
4366
|
+
const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` };
|
|
4367
|
+
const body = { model, messages: [{ role: 'user', content: prompt }], max_tokens: 10 };
|
|
4368
|
+
const resp = await axios.post(provider.endpoint, body, { timeout: 15000, headers });
|
|
4369
|
+
const text = resp.data?.choices?.[0]?.message?.content || '';
|
|
4370
|
+
return { ok: true, response: text.trim() };
|
|
4371
|
+
}
|
|
4372
|
+
} catch (err) {
|
|
4373
|
+
const status = err.response?.status;
|
|
4374
|
+
const msg = err.response?.data?.error?.message || err.message || 'Unknown error';
|
|
4375
|
+
return { ok: false, error: `${status ? `HTTP ${status}: ` : ''}${msg}` };
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
program
|
|
4380
|
+
.command('models')
|
|
4381
|
+
.description('Configure deliberation model API keys (BYOK)')
|
|
4382
|
+
.option('--status', 'Show current model configuration (non-interactive)')
|
|
4383
|
+
.action(async (options) => {
|
|
4384
|
+
const config = loadModelsConfig();
|
|
4385
|
+
|
|
4386
|
+
// --status: non-interactive output
|
|
4387
|
+
if (options.status) {
|
|
4388
|
+
let configuredCount = 0;
|
|
4389
|
+
console.log('');
|
|
4390
|
+
for (const [key, defaults] of Object.entries(DEFAULT_MODELS)) {
|
|
4391
|
+
const status = getModelStatus(config, key);
|
|
4392
|
+
const label = defaults.name.split(' ')[0] + ':';
|
|
4393
|
+
if (status.configured) {
|
|
4394
|
+
configuredCount++;
|
|
4395
|
+
console.log(` ${label.padEnd(10)} configured (${status.model})`);
|
|
4396
|
+
} else {
|
|
4397
|
+
console.log(` ${label.padEnd(10)} ${chalk.gray('not configured')}`);
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
console.log(` ${'Mode:'.padEnd(10)} ${configuredCount > 0 ? `BYOK (${configuredCount} model${configuredCount === 1 ? '' : 's'})` : 'free tier'}`);
|
|
4401
|
+
console.log('');
|
|
4402
|
+
return;
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
// Interactive wizard
|
|
4406
|
+
printModelStatus(config);
|
|
4407
|
+
|
|
4408
|
+
let running = true;
|
|
4409
|
+
while (running) {
|
|
4410
|
+
const choices = [
|
|
4411
|
+
{ name: 'Add Grok (xAI)', value: 'add_grok' },
|
|
4412
|
+
{ name: 'Add Gemini (Google)', value: 'add_gemini' },
|
|
4413
|
+
{ name: 'Add Codex/GPT-4o (OpenAI)', value: 'add_openai' },
|
|
4414
|
+
new inquirer.Separator(),
|
|
4415
|
+
{ name: 'Remove a model', value: 'remove' },
|
|
4416
|
+
{ name: 'Test deliberation', value: 'test' },
|
|
4417
|
+
{ name: 'Exit', value: 'exit' },
|
|
4418
|
+
];
|
|
4419
|
+
|
|
4420
|
+
const { action } = await inquirer.prompt([{
|
|
4421
|
+
type: 'list',
|
|
4422
|
+
name: 'action',
|
|
4423
|
+
message: 'Configure a model:',
|
|
4424
|
+
choices,
|
|
4425
|
+
}]);
|
|
4426
|
+
|
|
4427
|
+
if (action === 'exit') {
|
|
4428
|
+
running = false;
|
|
4429
|
+
break;
|
|
4430
|
+
}
|
|
4431
|
+
|
|
4432
|
+
if (action.startsWith('add_')) {
|
|
4433
|
+
const providerKey = action.replace('add_', '');
|
|
4434
|
+
const provider = MODEL_PROVIDERS[providerKey];
|
|
4435
|
+
const existing = config[providerKey];
|
|
4436
|
+
|
|
4437
|
+
// Warn if already configured
|
|
4438
|
+
if (existing && existing.enabled && existing.api_key) {
|
|
4439
|
+
const { overwrite } = await inquirer.prompt([{
|
|
4440
|
+
type: 'confirm',
|
|
4441
|
+
name: 'overwrite',
|
|
4442
|
+
message: `${provider.label} is already configured. Overwrite?`,
|
|
4443
|
+
default: false,
|
|
4444
|
+
}]);
|
|
4445
|
+
if (!overwrite) continue;
|
|
4446
|
+
}
|
|
4447
|
+
|
|
4448
|
+
// Prompt for API key
|
|
4449
|
+
const { apiKey } = await inquirer.prompt([{
|
|
4450
|
+
type: 'password',
|
|
4451
|
+
name: 'apiKey',
|
|
4452
|
+
message: `Enter your ${provider.label} API key:`,
|
|
4453
|
+
mask: '*',
|
|
4454
|
+
validate: (input) => {
|
|
4455
|
+
if (!input || input.trim().length === 0) return 'API key cannot be empty.';
|
|
4456
|
+
if (!input.startsWith(provider.prefix)) {
|
|
4457
|
+
return `Key should start with "${provider.prefix}". Got: "${input.slice(0, 6)}..."`;
|
|
4458
|
+
}
|
|
4459
|
+
return true;
|
|
4460
|
+
},
|
|
4461
|
+
}]);
|
|
4462
|
+
|
|
4463
|
+
// Optionally choose model variant
|
|
4464
|
+
const { modelChoice } = await inquirer.prompt([{
|
|
4465
|
+
type: 'list',
|
|
4466
|
+
name: 'modelChoice',
|
|
4467
|
+
message: 'Select model:',
|
|
4468
|
+
choices: provider.variants.map(v => ({ name: v === provider.defaultModel ? `${v} (default)` : v, value: v })),
|
|
4469
|
+
default: provider.defaultModel,
|
|
4470
|
+
}]);
|
|
4471
|
+
|
|
4472
|
+
config[providerKey] = {
|
|
4473
|
+
enabled: true,
|
|
4474
|
+
api_key: apiKey.trim(),
|
|
4475
|
+
model: modelChoice,
|
|
4476
|
+
name: provider.defaultName,
|
|
4477
|
+
};
|
|
4478
|
+
saveModelsConfig(config);
|
|
4479
|
+
console.log(chalk.green(`\n ${provider.label} configured with model ${modelChoice}.\n`));
|
|
4480
|
+
}
|
|
4481
|
+
|
|
4482
|
+
if (action === 'remove') {
|
|
4483
|
+
const configuredModels = Object.entries(config)
|
|
4484
|
+
.filter(([, v]) => v && v.enabled && v.api_key)
|
|
4485
|
+
.map(([k]) => ({ name: `${DEFAULT_MODELS[k]?.name || k} (${config[k].model})`, value: k }));
|
|
4486
|
+
|
|
4487
|
+
if (configuredModels.length === 0) {
|
|
4488
|
+
console.log(chalk.yellow('\n No models configured to remove.\n'));
|
|
4489
|
+
continue;
|
|
4490
|
+
}
|
|
4491
|
+
|
|
4492
|
+
const { toRemove } = await inquirer.prompt([{
|
|
4493
|
+
type: 'list',
|
|
4494
|
+
name: 'toRemove',
|
|
4495
|
+
message: 'Select model to remove:',
|
|
4496
|
+
choices: configuredModels,
|
|
4497
|
+
}]);
|
|
4498
|
+
|
|
4499
|
+
config[toRemove] = { enabled: false, api_key: '', model: DEFAULT_MODELS[toRemove].model, name: DEFAULT_MODELS[toRemove].name };
|
|
4500
|
+
saveModelsConfig(config);
|
|
4501
|
+
console.log(chalk.green(`\n ${DEFAULT_MODELS[toRemove].name} removed.\n`));
|
|
4502
|
+
}
|
|
4503
|
+
|
|
4504
|
+
if (action === 'test') {
|
|
4505
|
+
const configuredModels = Object.entries(config)
|
|
4506
|
+
.filter(([, v]) => v && v.enabled && v.api_key);
|
|
4507
|
+
|
|
4508
|
+
if (configuredModels.length === 0) {
|
|
4509
|
+
console.log(chalk.yellow('\n No models configured. Add a model first.\n'));
|
|
4510
|
+
continue;
|
|
4511
|
+
}
|
|
4512
|
+
|
|
4513
|
+
console.log(chalk.blue('\n Testing deliberation models...\n'));
|
|
4514
|
+
console.log(chalk.gray(' Prompt: "What is 2+2?"\n'));
|
|
4515
|
+
|
|
4516
|
+
for (const [key, entry] of configuredModels) {
|
|
4517
|
+
const label = (entry.name || key).padEnd(18);
|
|
4518
|
+
process.stdout.write(` ${label} `);
|
|
4519
|
+
const result = await testModelKey(key, entry.api_key, entry.model);
|
|
4520
|
+
if (result.ok) {
|
|
4521
|
+
console.log(chalk.green(`pass`) + chalk.gray(` -- "${result.response}"`));
|
|
4522
|
+
} else {
|
|
4523
|
+
console.log(chalk.red(`fail`) + chalk.gray(` -- ${result.error}`));
|
|
4524
|
+
}
|
|
4525
|
+
}
|
|
4526
|
+
console.log('');
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
});
|
|
4530
|
+
|
|
4162
4531
|
// Version subcommand alias (users type 'delimit version' not 'delimit -V')
|
|
4163
4532
|
program
|
|
4164
4533
|
.command('version')
|
|
@@ -4297,18 +4666,80 @@ function extractTags(text) {
|
|
|
4297
4666
|
}
|
|
4298
4667
|
|
|
4299
4668
|
function readMemories() {
|
|
4300
|
-
if (!fs.existsSync(
|
|
4301
|
-
const lines = fs.readFileSync(MEMORY_FILE, 'utf-8').split('\n').filter(l => l.trim());
|
|
4669
|
+
if (!fs.existsSync(MEMORY_DIR)) return [];
|
|
4302
4670
|
const memories = [];
|
|
4303
|
-
|
|
4304
|
-
|
|
4671
|
+
|
|
4672
|
+
// Read individual .json files (MCP format — primary)
|
|
4673
|
+
try {
|
|
4674
|
+
const files = fs.readdirSync(MEMORY_DIR).filter(f => f.endsWith('.json') && f.startsWith('mem-'));
|
|
4675
|
+
for (const f of files) {
|
|
4676
|
+
try {
|
|
4677
|
+
const entry = JSON.parse(fs.readFileSync(path.join(MEMORY_DIR, f), 'utf-8'));
|
|
4678
|
+
// Normalize: MCP uses "content", CLI used "text"
|
|
4679
|
+
if (entry.content && !entry.text) entry.text = entry.content;
|
|
4680
|
+
if (entry.text && !entry.content) entry.content = entry.text;
|
|
4681
|
+
if (entry.created_at && !entry.created) entry.created = entry.created_at;
|
|
4682
|
+
if (entry.created && !entry.created_at) entry.created_at = entry.created;
|
|
4683
|
+
memories.push(entry);
|
|
4684
|
+
} catch {}
|
|
4685
|
+
}
|
|
4686
|
+
} catch {}
|
|
4687
|
+
|
|
4688
|
+
// Also read legacy .jsonl file (CLI format — backwards compat)
|
|
4689
|
+
if (fs.existsSync(MEMORY_FILE)) {
|
|
4690
|
+
const lines = fs.readFileSync(MEMORY_FILE, 'utf-8').split('\n').filter(l => l.trim());
|
|
4691
|
+
for (const line of lines) {
|
|
4692
|
+
try {
|
|
4693
|
+
const entry = JSON.parse(line);
|
|
4694
|
+
// Skip if already loaded from .json file
|
|
4695
|
+
if (!memories.find(m => m.id === entry.id)) {
|
|
4696
|
+
if (entry.text && !entry.content) entry.content = entry.text;
|
|
4697
|
+
if (entry.created && !entry.created_at) entry.created_at = entry.created;
|
|
4698
|
+
memories.push(entry);
|
|
4699
|
+
}
|
|
4700
|
+
} catch {}
|
|
4701
|
+
}
|
|
4305
4702
|
}
|
|
4703
|
+
|
|
4704
|
+
// Sort by created date, newest first
|
|
4705
|
+
memories.sort((a, b) => new Date(b.created_at || b.created || 0) - new Date(a.created_at || a.created || 0));
|
|
4306
4706
|
return memories;
|
|
4307
4707
|
}
|
|
4308
4708
|
|
|
4309
|
-
function
|
|
4709
|
+
function writeMemory(entry) {
|
|
4710
|
+
// Write in MCP-compatible format (individual .json files)
|
|
4310
4711
|
fs.mkdirSync(MEMORY_DIR, { recursive: true });
|
|
4311
|
-
|
|
4712
|
+
const memId = 'mem-' + require('crypto').createHash('sha256').update(entry.text.slice(0, 100)).digest('hex').slice(0, 12);
|
|
4713
|
+
const mcpEntry = {
|
|
4714
|
+
id: memId,
|
|
4715
|
+
content: entry.text,
|
|
4716
|
+
tags: entry.tags || [],
|
|
4717
|
+
context: entry.source || 'cli',
|
|
4718
|
+
created_at: entry.created || new Date().toISOString(),
|
|
4719
|
+
};
|
|
4720
|
+
fs.writeFileSync(path.join(MEMORY_DIR, `${memId}.json`), JSON.stringify(mcpEntry, null, 2));
|
|
4721
|
+
return memId;
|
|
4722
|
+
}
|
|
4723
|
+
|
|
4724
|
+
function deleteMemory(id) {
|
|
4725
|
+
// Delete from .json files
|
|
4726
|
+
const jsonFile = path.join(MEMORY_DIR, `${id}.json`);
|
|
4727
|
+
if (fs.existsSync(jsonFile)) {
|
|
4728
|
+
fs.unlinkSync(jsonFile);
|
|
4729
|
+
return true;
|
|
4730
|
+
}
|
|
4731
|
+
// Also check legacy .jsonl
|
|
4732
|
+
if (fs.existsSync(MEMORY_FILE)) {
|
|
4733
|
+
const lines = fs.readFileSync(MEMORY_FILE, 'utf-8').split('\n').filter(l => l.trim());
|
|
4734
|
+
const filtered = lines.filter(l => {
|
|
4735
|
+
try { return JSON.parse(l).id !== id; } catch { return true; }
|
|
4736
|
+
});
|
|
4737
|
+
if (filtered.length < lines.length) {
|
|
4738
|
+
fs.writeFileSync(MEMORY_FILE, filtered.join('\n') + (filtered.length ? '\n' : ''));
|
|
4739
|
+
return true;
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
return false;
|
|
4312
4743
|
}
|
|
4313
4744
|
|
|
4314
4745
|
function relativeTime(isoDate) {
|
|
@@ -4349,18 +4780,16 @@ program
|
|
|
4349
4780
|
const manualTags = (options.tag || []).map(t => t.toLowerCase());
|
|
4350
4781
|
const allTags = [...new Set([...autoTags, ...manualTags])];
|
|
4351
4782
|
|
|
4352
|
-
const memories = readMemories();
|
|
4353
4783
|
const entry = {
|
|
4354
|
-
id: generateShortId(),
|
|
4355
4784
|
text,
|
|
4356
4785
|
tags: allTags,
|
|
4357
4786
|
created: new Date().toISOString(),
|
|
4358
4787
|
source: 'cli',
|
|
4359
4788
|
};
|
|
4360
|
-
|
|
4361
|
-
|
|
4789
|
+
writeMemory(entry);
|
|
4790
|
+
const total = readMemories().length;
|
|
4362
4791
|
|
|
4363
|
-
console.log(chalk.green(`\n Remembered.`) + chalk.gray(` (${
|
|
4792
|
+
console.log(chalk.green(`\n Remembered.`) + chalk.gray(` (${total} memor${total === 1 ? 'y' : 'ies'} total)\n`));
|
|
4364
4793
|
});
|
|
4365
4794
|
|
|
4366
4795
|
program
|
|
@@ -4375,14 +4804,13 @@ program
|
|
|
4375
4804
|
|
|
4376
4805
|
// --forget mode
|
|
4377
4806
|
if (options.forget) {
|
|
4378
|
-
|
|
4379
|
-
|
|
4807
|
+
if (deleteMemory(options.forget)) {
|
|
4808
|
+
const remaining = readMemories().length;
|
|
4809
|
+
console.log(chalk.green(`\n Forgotten.`) + chalk.gray(` (${remaining} memor${remaining === 1 ? 'y' : 'ies'} remaining)\n`));
|
|
4810
|
+
} else {
|
|
4380
4811
|
console.log(chalk.red(`\n No memory found with ID: ${options.forget}\n`));
|
|
4381
4812
|
process.exit(1);
|
|
4382
4813
|
}
|
|
4383
|
-
memories.splice(idx, 1);
|
|
4384
|
-
writeMemories(memories);
|
|
4385
|
-
console.log(chalk.green(`\n Forgotten.`) + chalk.gray(` (${memories.length} memor${memories.length === 1 ? 'y' : 'ies'} remaining)\n`));
|
|
4386
4814
|
return;
|
|
4387
4815
|
}
|
|
4388
4816
|
|
|
@@ -4454,15 +4882,13 @@ program
|
|
|
4454
4882
|
.command('forget <id>')
|
|
4455
4883
|
.description('Delete a memory by ID (alias for recall --forget)')
|
|
4456
4884
|
.action((id) => {
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4885
|
+
if (deleteMemory(id)) {
|
|
4886
|
+
const remaining = readMemories().length;
|
|
4887
|
+
console.log(chalk.green(`\n Forgotten.`) + chalk.gray(` (${remaining} memor${remaining === 1 ? 'y' : 'ies'} remaining)\n`));
|
|
4888
|
+
} else {
|
|
4460
4889
|
console.log(chalk.red(`\n No memory found with ID: ${id}\n`));
|
|
4461
4890
|
process.exit(1);
|
|
4462
4891
|
}
|
|
4463
|
-
memories.splice(idx, 1);
|
|
4464
|
-
writeMemories(memories);
|
|
4465
|
-
console.log(chalk.green(`\n Forgotten.`) + chalk.gray(` (${memories.length} memor${memories.length === 1 ? 'y' : 'ies'} remaining)\n`));
|
|
4466
4892
|
});
|
|
4467
4893
|
|
|
4468
4894
|
const normalizedArgs = normalizeNaturalLanguageArgs(process.argv);
|
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.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.
|
|
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
|
}
|