delimit-cli 4.1.37 → 4.1.40

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
@@ -167,6 +167,8 @@ 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
170
172
  npx delimit-cli doctor # Check setup health
171
173
  npx delimit-cli uninstall --dry-run # Preview removal
172
174
  ```
@@ -270,6 +272,22 @@ rules:
270
272
 
271
273
  ---
272
274
 
275
+ ## FAQ
276
+
277
+ **How does this compare to Obsidian Mind?**
278
+
279
+ 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.
280
+
281
+ **Does this work without Claude Code?**
282
+
283
+ 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.
284
+
285
+ **Is this free?**
286
+
287
+ 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.
288
+
289
+ ---
290
+
273
291
  ## Links
274
292
 
275
293
  - [delimit.ai](https://delimit.ai) -- homepage
@@ -4159,6 +4159,267 @@ if result.get('summary'):
4159
4159
  }
4160
4160
  });
4161
4161
 
4162
+ // ---------------------------------------------------------------------------
4163
+ // Models command: BYOK deliberation key management wizard
4164
+ // ---------------------------------------------------------------------------
4165
+
4166
+ const MODELS_CONFIG_PATH = path.join(os.homedir(), '.delimit', 'models.json');
4167
+ const DELIBERATION_USAGE_PATH = path.join(os.homedir(), '.delimit', 'deliberation_usage.json');
4168
+
4169
+ const DEFAULT_MODELS = {
4170
+ grok: { enabled: false, api_key: '', model: 'grok-4-0709', name: 'Grok 4' },
4171
+ gemini: { enabled: false, api_key: '', model: 'gemini-2.5-pro', name: 'Gemini Pro' },
4172
+ openai: { enabled: false, api_key: '', model: 'gpt-4o', name: 'Codex (GPT-4o)' },
4173
+ };
4174
+
4175
+ const MODEL_PROVIDERS = {
4176
+ 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'] },
4177
+ 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'] },
4178
+ 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'] },
4179
+ };
4180
+
4181
+ function loadModelsConfig() {
4182
+ try {
4183
+ if (fs.existsSync(MODELS_CONFIG_PATH)) {
4184
+ return JSON.parse(fs.readFileSync(MODELS_CONFIG_PATH, 'utf-8'));
4185
+ }
4186
+ } catch {}
4187
+ return {};
4188
+ }
4189
+
4190
+ function saveModelsConfig(config) {
4191
+ const dir = path.dirname(MODELS_CONFIG_PATH);
4192
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
4193
+ fs.writeFileSync(MODELS_CONFIG_PATH, JSON.stringify(config, null, 2));
4194
+ }
4195
+
4196
+ function loadDeliberationUsage() {
4197
+ try {
4198
+ if (fs.existsSync(DELIBERATION_USAGE_PATH)) {
4199
+ return JSON.parse(fs.readFileSync(DELIBERATION_USAGE_PATH, 'utf-8'));
4200
+ }
4201
+ } catch {}
4202
+ return { used: 0, limit: 3 };
4203
+ }
4204
+
4205
+ function getModelStatus(config, key) {
4206
+ const entry = config[key];
4207
+ if (entry && entry.enabled && entry.api_key) {
4208
+ return { configured: true, model: entry.model || DEFAULT_MODELS[key].model };
4209
+ }
4210
+ return { configured: false, model: null };
4211
+ }
4212
+
4213
+ function printModelStatus(config) {
4214
+ const usage = loadDeliberationUsage();
4215
+ const remaining = Math.max(0, (usage.limit || 3) - (usage.used || 0));
4216
+ let configuredCount = 0;
4217
+
4218
+ console.log(chalk.bold.blue('\n Delimit Models -- Deliberation Config\n'));
4219
+ console.log(chalk.bold(' Current models:'));
4220
+
4221
+ for (const [key, defaults] of Object.entries(DEFAULT_MODELS)) {
4222
+ const status = getModelStatus(config, key);
4223
+ if (status.configured) {
4224
+ configuredCount++;
4225
+ console.log(` ${chalk.green('*')} ${defaults.name.split(' ')[0].padEnd(10)} -- configured (${status.model})`);
4226
+ } else {
4227
+ const extra = key === 'openai' ? '' : '';
4228
+ console.log(` ${chalk.gray('o')} ${defaults.name.split(' ')[0].padEnd(10)} -- ${chalk.gray('not configured')}${extra}`);
4229
+ }
4230
+ }
4231
+ console.log(` ${chalk.gray('o')} ${'Claude'.padEnd(10)} -- ${chalk.gray('not configured (uses your Claude Code subscription)')}`);
4232
+
4233
+ console.log('');
4234
+ console.log(` ${remaining} free deliberation${remaining === 1 ? '' : 's'} remaining (of ${usage.limit || 3}).`);
4235
+ if (configuredCount > 0) {
4236
+ console.log(` Mode: ${chalk.green('BYOK')} (${configuredCount} model${configuredCount === 1 ? '' : 's'})`);
4237
+ } else {
4238
+ console.log(' Add API keys for unlimited deliberation with your own models.');
4239
+ }
4240
+ console.log('');
4241
+
4242
+ return { configuredCount, remaining };
4243
+ }
4244
+
4245
+ async function testModelKey(providerKey, apiKey, model) {
4246
+ const provider = MODEL_PROVIDERS[providerKey];
4247
+ const prompt = 'What is 2+2? Reply with just the number.';
4248
+
4249
+ try {
4250
+ if (providerKey === 'gemini') {
4251
+ const url = provider.endpoint.replace('{model}', model) + `?key=${apiKey}`;
4252
+ const resp = await axios.post(url, {
4253
+ contents: [{ parts: [{ text: prompt }] }],
4254
+ }, { timeout: 15000, headers: { 'Content-Type': 'application/json' } });
4255
+ const text = resp.data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
4256
+ return { ok: true, response: text.trim() };
4257
+ } else {
4258
+ const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` };
4259
+ const body = { model, messages: [{ role: 'user', content: prompt }], max_tokens: 10 };
4260
+ const resp = await axios.post(provider.endpoint, body, { timeout: 15000, headers });
4261
+ const text = resp.data?.choices?.[0]?.message?.content || '';
4262
+ return { ok: true, response: text.trim() };
4263
+ }
4264
+ } catch (err) {
4265
+ const status = err.response?.status;
4266
+ const msg = err.response?.data?.error?.message || err.message || 'Unknown error';
4267
+ return { ok: false, error: `${status ? `HTTP ${status}: ` : ''}${msg}` };
4268
+ }
4269
+ }
4270
+
4271
+ program
4272
+ .command('models')
4273
+ .description('Configure deliberation model API keys (BYOK)')
4274
+ .option('--status', 'Show current model configuration (non-interactive)')
4275
+ .action(async (options) => {
4276
+ const config = loadModelsConfig();
4277
+
4278
+ // --status: non-interactive output
4279
+ if (options.status) {
4280
+ let configuredCount = 0;
4281
+ console.log('');
4282
+ for (const [key, defaults] of Object.entries(DEFAULT_MODELS)) {
4283
+ const status = getModelStatus(config, key);
4284
+ const label = defaults.name.split(' ')[0] + ':';
4285
+ if (status.configured) {
4286
+ configuredCount++;
4287
+ console.log(` ${label.padEnd(10)} configured (${status.model})`);
4288
+ } else {
4289
+ console.log(` ${label.padEnd(10)} ${chalk.gray('not configured')}`);
4290
+ }
4291
+ }
4292
+ console.log(` ${'Mode:'.padEnd(10)} ${configuredCount > 0 ? `BYOK (${configuredCount} model${configuredCount === 1 ? '' : 's'})` : 'free tier'}`);
4293
+ console.log('');
4294
+ return;
4295
+ }
4296
+
4297
+ // Interactive wizard
4298
+ printModelStatus(config);
4299
+
4300
+ let running = true;
4301
+ while (running) {
4302
+ const choices = [
4303
+ { name: 'Add Grok (xAI)', value: 'add_grok' },
4304
+ { name: 'Add Gemini (Google)', value: 'add_gemini' },
4305
+ { name: 'Add Codex/GPT-4o (OpenAI)', value: 'add_openai' },
4306
+ new inquirer.Separator(),
4307
+ { name: 'Remove a model', value: 'remove' },
4308
+ { name: 'Test deliberation', value: 'test' },
4309
+ { name: 'Exit', value: 'exit' },
4310
+ ];
4311
+
4312
+ const { action } = await inquirer.prompt([{
4313
+ type: 'list',
4314
+ name: 'action',
4315
+ message: 'Configure a model:',
4316
+ choices,
4317
+ }]);
4318
+
4319
+ if (action === 'exit') {
4320
+ running = false;
4321
+ break;
4322
+ }
4323
+
4324
+ if (action.startsWith('add_')) {
4325
+ const providerKey = action.replace('add_', '');
4326
+ const provider = MODEL_PROVIDERS[providerKey];
4327
+ const existing = config[providerKey];
4328
+
4329
+ // Warn if already configured
4330
+ if (existing && existing.enabled && existing.api_key) {
4331
+ const { overwrite } = await inquirer.prompt([{
4332
+ type: 'confirm',
4333
+ name: 'overwrite',
4334
+ message: `${provider.label} is already configured. Overwrite?`,
4335
+ default: false,
4336
+ }]);
4337
+ if (!overwrite) continue;
4338
+ }
4339
+
4340
+ // Prompt for API key
4341
+ const { apiKey } = await inquirer.prompt([{
4342
+ type: 'password',
4343
+ name: 'apiKey',
4344
+ message: `Enter your ${provider.label} API key:`,
4345
+ mask: '*',
4346
+ validate: (input) => {
4347
+ if (!input || input.trim().length === 0) return 'API key cannot be empty.';
4348
+ if (!input.startsWith(provider.prefix)) {
4349
+ return `Key should start with "${provider.prefix}". Got: "${input.slice(0, 6)}..."`;
4350
+ }
4351
+ return true;
4352
+ },
4353
+ }]);
4354
+
4355
+ // Optionally choose model variant
4356
+ const { modelChoice } = await inquirer.prompt([{
4357
+ type: 'list',
4358
+ name: 'modelChoice',
4359
+ message: 'Select model:',
4360
+ choices: provider.variants.map(v => ({ name: v === provider.defaultModel ? `${v} (default)` : v, value: v })),
4361
+ default: provider.defaultModel,
4362
+ }]);
4363
+
4364
+ config[providerKey] = {
4365
+ enabled: true,
4366
+ api_key: apiKey.trim(),
4367
+ model: modelChoice,
4368
+ name: provider.defaultName,
4369
+ };
4370
+ saveModelsConfig(config);
4371
+ console.log(chalk.green(`\n ${provider.label} configured with model ${modelChoice}.\n`));
4372
+ }
4373
+
4374
+ if (action === 'remove') {
4375
+ const configuredModels = Object.entries(config)
4376
+ .filter(([, v]) => v && v.enabled && v.api_key)
4377
+ .map(([k]) => ({ name: `${DEFAULT_MODELS[k]?.name || k} (${config[k].model})`, value: k }));
4378
+
4379
+ if (configuredModels.length === 0) {
4380
+ console.log(chalk.yellow('\n No models configured to remove.\n'));
4381
+ continue;
4382
+ }
4383
+
4384
+ const { toRemove } = await inquirer.prompt([{
4385
+ type: 'list',
4386
+ name: 'toRemove',
4387
+ message: 'Select model to remove:',
4388
+ choices: configuredModels,
4389
+ }]);
4390
+
4391
+ config[toRemove] = { enabled: false, api_key: '', model: DEFAULT_MODELS[toRemove].model, name: DEFAULT_MODELS[toRemove].name };
4392
+ saveModelsConfig(config);
4393
+ console.log(chalk.green(`\n ${DEFAULT_MODELS[toRemove].name} removed.\n`));
4394
+ }
4395
+
4396
+ if (action === 'test') {
4397
+ const configuredModels = Object.entries(config)
4398
+ .filter(([, v]) => v && v.enabled && v.api_key);
4399
+
4400
+ if (configuredModels.length === 0) {
4401
+ console.log(chalk.yellow('\n No models configured. Add a model first.\n'));
4402
+ continue;
4403
+ }
4404
+
4405
+ console.log(chalk.blue('\n Testing deliberation models...\n'));
4406
+ console.log(chalk.gray(' Prompt: "What is 2+2?"\n'));
4407
+
4408
+ for (const [key, entry] of configuredModels) {
4409
+ const label = (entry.name || key).padEnd(18);
4410
+ process.stdout.write(` ${label} `);
4411
+ const result = await testModelKey(key, entry.api_key, entry.model);
4412
+ if (result.ok) {
4413
+ console.log(chalk.green(`pass`) + chalk.gray(` -- "${result.response}"`));
4414
+ } else {
4415
+ console.log(chalk.red(`fail`) + chalk.gray(` -- ${result.error}`));
4416
+ }
4417
+ }
4418
+ console.log('');
4419
+ }
4420
+ }
4421
+ });
4422
+
4162
4423
  // Version subcommand alias (users type 'delimit version' not 'delimit -V')
4163
4424
  program
4164
4425
  .command('version')
@@ -4297,18 +4558,80 @@ function extractTags(text) {
4297
4558
  }
4298
4559
 
4299
4560
  function readMemories() {
4300
- if (!fs.existsSync(MEMORY_FILE)) return [];
4301
- const lines = fs.readFileSync(MEMORY_FILE, 'utf-8').split('\n').filter(l => l.trim());
4561
+ if (!fs.existsSync(MEMORY_DIR)) return [];
4302
4562
  const memories = [];
4303
- for (const line of lines) {
4304
- try { memories.push(JSON.parse(line)); } catch {}
4563
+
4564
+ // Read individual .json files (MCP format primary)
4565
+ try {
4566
+ const files = fs.readdirSync(MEMORY_DIR).filter(f => f.endsWith('.json') && f.startsWith('mem-'));
4567
+ for (const f of files) {
4568
+ try {
4569
+ const entry = JSON.parse(fs.readFileSync(path.join(MEMORY_DIR, f), 'utf-8'));
4570
+ // Normalize: MCP uses "content", CLI used "text"
4571
+ if (entry.content && !entry.text) entry.text = entry.content;
4572
+ if (entry.text && !entry.content) entry.content = entry.text;
4573
+ if (entry.created_at && !entry.created) entry.created = entry.created_at;
4574
+ if (entry.created && !entry.created_at) entry.created_at = entry.created;
4575
+ memories.push(entry);
4576
+ } catch {}
4577
+ }
4578
+ } catch {}
4579
+
4580
+ // Also read legacy .jsonl file (CLI format — backwards compat)
4581
+ if (fs.existsSync(MEMORY_FILE)) {
4582
+ const lines = fs.readFileSync(MEMORY_FILE, 'utf-8').split('\n').filter(l => l.trim());
4583
+ for (const line of lines) {
4584
+ try {
4585
+ const entry = JSON.parse(line);
4586
+ // Skip if already loaded from .json file
4587
+ if (!memories.find(m => m.id === entry.id)) {
4588
+ if (entry.text && !entry.content) entry.content = entry.text;
4589
+ if (entry.created && !entry.created_at) entry.created_at = entry.created;
4590
+ memories.push(entry);
4591
+ }
4592
+ } catch {}
4593
+ }
4305
4594
  }
4595
+
4596
+ // Sort by created date, newest first
4597
+ memories.sort((a, b) => new Date(b.created_at || b.created || 0) - new Date(a.created_at || a.created || 0));
4306
4598
  return memories;
4307
4599
  }
4308
4600
 
4309
- function writeMemories(memories) {
4601
+ function writeMemory(entry) {
4602
+ // Write in MCP-compatible format (individual .json files)
4310
4603
  fs.mkdirSync(MEMORY_DIR, { recursive: true });
4311
- fs.writeFileSync(MEMORY_FILE, memories.map(m => JSON.stringify(m)).join('\n') + (memories.length ? '\n' : ''));
4604
+ const memId = 'mem-' + require('crypto').createHash('sha256').update(entry.text.slice(0, 100)).digest('hex').slice(0, 12);
4605
+ const mcpEntry = {
4606
+ id: memId,
4607
+ content: entry.text,
4608
+ tags: entry.tags || [],
4609
+ context: entry.source || 'cli',
4610
+ created_at: entry.created || new Date().toISOString(),
4611
+ };
4612
+ fs.writeFileSync(path.join(MEMORY_DIR, `${memId}.json`), JSON.stringify(mcpEntry, null, 2));
4613
+ return memId;
4614
+ }
4615
+
4616
+ function deleteMemory(id) {
4617
+ // Delete from .json files
4618
+ const jsonFile = path.join(MEMORY_DIR, `${id}.json`);
4619
+ if (fs.existsSync(jsonFile)) {
4620
+ fs.unlinkSync(jsonFile);
4621
+ return true;
4622
+ }
4623
+ // Also check legacy .jsonl
4624
+ if (fs.existsSync(MEMORY_FILE)) {
4625
+ const lines = fs.readFileSync(MEMORY_FILE, 'utf-8').split('\n').filter(l => l.trim());
4626
+ const filtered = lines.filter(l => {
4627
+ try { return JSON.parse(l).id !== id; } catch { return true; }
4628
+ });
4629
+ if (filtered.length < lines.length) {
4630
+ fs.writeFileSync(MEMORY_FILE, filtered.join('\n') + (filtered.length ? '\n' : ''));
4631
+ return true;
4632
+ }
4633
+ }
4634
+ return false;
4312
4635
  }
4313
4636
 
4314
4637
  function relativeTime(isoDate) {
@@ -4349,18 +4672,16 @@ program
4349
4672
  const manualTags = (options.tag || []).map(t => t.toLowerCase());
4350
4673
  const allTags = [...new Set([...autoTags, ...manualTags])];
4351
4674
 
4352
- const memories = readMemories();
4353
4675
  const entry = {
4354
- id: generateShortId(),
4355
4676
  text,
4356
4677
  tags: allTags,
4357
4678
  created: new Date().toISOString(),
4358
4679
  source: 'cli',
4359
4680
  };
4360
- memories.push(entry);
4361
- writeMemories(memories);
4681
+ writeMemory(entry);
4682
+ const total = readMemories().length;
4362
4683
 
4363
- console.log(chalk.green(`\n Remembered.`) + chalk.gray(` (${memories.length} memor${memories.length === 1 ? 'y' : 'ies'} total)\n`));
4684
+ console.log(chalk.green(`\n Remembered.`) + chalk.gray(` (${total} memor${total === 1 ? 'y' : 'ies'} total)\n`));
4364
4685
  });
4365
4686
 
4366
4687
  program
@@ -4375,14 +4696,13 @@ program
4375
4696
 
4376
4697
  // --forget mode
4377
4698
  if (options.forget) {
4378
- const idx = memories.findIndex(m => m.id === options.forget);
4379
- if (idx === -1) {
4699
+ if (deleteMemory(options.forget)) {
4700
+ const remaining = readMemories().length;
4701
+ console.log(chalk.green(`\n Forgotten.`) + chalk.gray(` (${remaining} memor${remaining === 1 ? 'y' : 'ies'} remaining)\n`));
4702
+ } else {
4380
4703
  console.log(chalk.red(`\n No memory found with ID: ${options.forget}\n`));
4381
4704
  process.exit(1);
4382
4705
  }
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
4706
  return;
4387
4707
  }
4388
4708
 
@@ -4454,15 +4774,13 @@ program
4454
4774
  .command('forget <id>')
4455
4775
  .description('Delete a memory by ID (alias for recall --forget)')
4456
4776
  .action((id) => {
4457
- const memories = readMemories();
4458
- const idx = memories.findIndex(m => m.id === id);
4459
- if (idx === -1) {
4777
+ if (deleteMemory(id)) {
4778
+ const remaining = readMemories().length;
4779
+ console.log(chalk.green(`\n Forgotten.`) + chalk.gray(` (${remaining} memor${remaining === 1 ? 'y' : 'ies'} remaining)\n`));
4780
+ } else {
4460
4781
  console.log(chalk.red(`\n No memory found with ID: ${id}\n`));
4461
4782
  process.exit(1);
4462
4783
  }
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
4784
  });
4467
4785
 
4468
4786
  const normalizedArgs = normalizeNaturalLanguageArgs(process.argv);
@@ -342,7 +342,8 @@ function installClaudeHooks(tool, hookConfig) {
342
342
  const scriptContent = '#!/bin/bash\n' + `
343
343
  # Delimit SessionStart — generated by delimit-cli setup
344
344
  DELIMIT_HOME="\${DELIMIT_HOME:-${delimitHome}}"
345
- echo "=== Delimit Status ==="
345
+ echo " <"
346
+ echo " === Delimit Status ==="
346
347
  # Governance
347
348
  if [ -f "./delimit.yml" ] || [ -f "./.delimit/policies.yml" ]; then
348
349
  echo "Governance: active | policy=project"
@@ -378,7 +379,7 @@ if [ -d "$SESSIONS" ]; then
378
379
  LATEST=$(ls -t "$SESSIONS"/session_*.json 2>/dev/null | head -1)
379
380
  [ -n "$LATEST" ] && python3 -c "import json; d=json.load(open('$LATEST')); s=d.get('summary','')[:150]; print(f'Last session: {s}')" 2>/dev/null
380
381
  fi
381
- echo "=== Delimit Ready ==="
382
+ echo " === Delimit Ready ==="
382
383
  # Self-heal: re-wrap claude binary if an update overwrote our shim
383
384
  CLAUDE_BIN=$(PATH=$(echo "$PATH" | tr ':' '\\n' | grep -v '.delimit/shims' | tr '\\n' ':') command -v claude 2>/dev/null)
384
385
  if [ -n "$CLAUDE_BIN" ] && [ -f "$CLAUDE_BIN" ]; then
@@ -541,8 +542,7 @@ fi
541
542
  const stopScript = path.join(hooksDir, 'delimit-stop');
542
543
  const delimitHome = path.join(home, '.delimit');
543
544
  const stopContent = '#!/bin/bash\n' + `
544
- # Delimit Stop — session handoff on exit
545
- # Preserves context for next session across all AI assistants
545
+ # Delimit Stop — the > in </>
546
546
  DELIMIT_HOME="\${DELIMIT_HOME:-${delimitHome}}"
547
547
  LEDGER_DIR="$DELIMIT_HOME/ledger"
548
548
 
@@ -556,7 +556,10 @@ fi
556
556
 
557
557
  # Save session timestamp
558
558
  echo "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$DELIMIT_HOME/.last_session_end"
559
- echo "[Delimit] Session context saved."
559
+ echo ""
560
+ echo " />"
561
+ echo " Session context saved. Next session picks up where you left off."
562
+ echo ""
560
563
  `;
561
564
  fs.writeFileSync(stopScript, stopContent);
562
565
  fs.chmodSync(stopScript, '755');
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.37",
4
+ "version": "4.1.40",
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.34",
10
+ "version": "4.1.38",
11
11
  "websiteUrl": "https://delimit.ai",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "delimit-cli",
16
- "version": "4.1.34",
16
+ "version": "4.1.38",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  }