delimit-cli 4.7.4 → 4.7.5
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-setup.js +1 -114
- package/gateway/ai/tui.py +63 -1
- package/lib/chat-repl.js +4 -1
- package/package.json +1 -1
package/bin/delimit-setup.js
CHANGED
|
@@ -8,17 +8,14 @@
|
|
|
8
8
|
* 3. Installs default agents into ~/.claude/agents/
|
|
9
9
|
* 4. Prints next steps
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
11
|
const fs = require('fs');
|
|
13
12
|
const path = require('path');
|
|
14
13
|
const { execSync } = require('child_process');
|
|
15
14
|
const os = require('os');
|
|
16
|
-
|
|
17
15
|
const DELIMIT_HOME = path.join(os.homedir(), '.delimit');
|
|
18
16
|
const MCP_CONFIG = path.join(os.homedir(), '.mcp.json');
|
|
19
17
|
const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
20
18
|
const AGENTS_DIR = path.join(CLAUDE_DIR, 'agents');
|
|
21
|
-
|
|
22
19
|
// Colors
|
|
23
20
|
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
24
21
|
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
@@ -28,12 +25,10 @@ const magenta = (s) => `\x1b[1;35m${s}\x1b[0m`;
|
|
|
28
25
|
const orange = (s) => `\x1b[1;33m${s}\x1b[0m`;
|
|
29
26
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
30
27
|
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
31
|
-
|
|
32
28
|
function log(msg) { console.log(msg); }
|
|
33
29
|
function step(n, msg) { log(`\n${blue(`[${n}]`)} ${msg}`); }
|
|
34
30
|
function pause(ms = 150) { return new Promise(r => setTimeout(r, ms)); }
|
|
35
31
|
async function logp(msg, ms = 180) { console.log(msg); await pause(ms); }
|
|
36
|
-
|
|
37
32
|
function findGitDir(startDir) {
|
|
38
33
|
let dir = startDir;
|
|
39
34
|
while (dir !== path.dirname(dir)) {
|
|
@@ -51,7 +46,6 @@ function findGitDir(startDir) {
|
|
|
51
46
|
}
|
|
52
47
|
return null;
|
|
53
48
|
}
|
|
54
|
-
|
|
55
49
|
/**
|
|
56
50
|
* Recursively find OpenAPI/Swagger spec files, ignoring node_modules.
|
|
57
51
|
*/
|
|
@@ -72,7 +66,6 @@ function findSpecFiles(dir, depth = 0) {
|
|
|
72
66
|
} catch {}
|
|
73
67
|
return results;
|
|
74
68
|
}
|
|
75
|
-
|
|
76
69
|
async function main() {
|
|
77
70
|
// Self-update check: ensure we're running the latest version (skip if already re-execed)
|
|
78
71
|
const _pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
@@ -87,7 +80,6 @@ async function main() {
|
|
|
87
80
|
}
|
|
88
81
|
} catch { /* offline or timeout — continue with current version */ }
|
|
89
82
|
}
|
|
90
|
-
|
|
91
83
|
log('');
|
|
92
84
|
log(purple(' ____ ________ ______ _____________'));
|
|
93
85
|
log(purple(' / __ \\/ ____/ / / _/ |/ / _/_ __/'));
|
|
@@ -98,10 +90,8 @@ async function main() {
|
|
|
98
90
|
const _pkgNow = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
99
91
|
log(dim(` v${_pkgNow.version}`));
|
|
100
92
|
log('');
|
|
101
|
-
|
|
102
93
|
// Step 1: Check prerequisites
|
|
103
94
|
step(1, 'Checking prerequisites...');
|
|
104
|
-
|
|
105
95
|
// Python 3.9+
|
|
106
96
|
let python = null;
|
|
107
97
|
for (const cmd of ['python3', 'python']) {
|
|
@@ -119,7 +109,6 @@ async function main() {
|
|
|
119
109
|
log(` ${yellow('✗')} Python 3.9+ not found. Install Python first.`);
|
|
120
110
|
process.exit(1);
|
|
121
111
|
}
|
|
122
|
-
|
|
123
112
|
// Check if Claude Code is available
|
|
124
113
|
let hasClaude = false;
|
|
125
114
|
try {
|
|
@@ -129,7 +118,6 @@ async function main() {
|
|
|
129
118
|
} catch {
|
|
130
119
|
log(` ${yellow('!')} Claude Code not detected — MCP config will still be created`);
|
|
131
120
|
}
|
|
132
|
-
|
|
133
121
|
// Show what we're about to do and ask for confirmation
|
|
134
122
|
log('');
|
|
135
123
|
log(` ${blue('What Delimit will do:')}`);
|
|
@@ -145,7 +133,6 @@ async function main() {
|
|
|
145
133
|
log('');
|
|
146
134
|
log(` ${dim('Undo anytime:')} rm -rf ~/.delimit && delimit uninstall`);
|
|
147
135
|
log('');
|
|
148
|
-
|
|
149
136
|
const inquirerTop = (() => { try { return require('inquirer'); } catch { return null; } })();
|
|
150
137
|
if (inquirerTop && process.stdin.isTTY) {
|
|
151
138
|
try {
|
|
@@ -162,17 +149,14 @@ async function main() {
|
|
|
162
149
|
} catch {}
|
|
163
150
|
}
|
|
164
151
|
log('');
|
|
165
|
-
|
|
166
152
|
// Step 2: Install Delimit MCP server
|
|
167
153
|
step(2, 'Installing Delimit MCP server...');
|
|
168
|
-
|
|
169
154
|
// Create ~/.delimit directory
|
|
170
155
|
fs.mkdirSync(path.join(DELIMIT_HOME, 'server', 'core', 'zero_spec'), { recursive: true });
|
|
171
156
|
fs.mkdirSync(path.join(DELIMIT_HOME, 'server', 'tasks'), { recursive: true });
|
|
172
157
|
fs.mkdirSync(path.join(DELIMIT_HOME, 'deploys'), { recursive: true });
|
|
173
158
|
fs.mkdirSync(path.join(DELIMIT_HOME, 'ledger'), { recursive: true });
|
|
174
159
|
fs.mkdirSync(path.join(DELIMIT_HOME, 'evidence'), { recursive: true });
|
|
175
|
-
|
|
176
160
|
// Copy the gateway core from our bundled copy
|
|
177
161
|
// Skip if server dirs are symlinks (dev machine using gateway source directly)
|
|
178
162
|
const serverAiDir = path.join(DELIMIT_HOME, 'server', 'ai');
|
|
@@ -193,13 +177,11 @@ async function main() {
|
|
|
193
177
|
log(` ${yellow('!')} Could not download. Clone manually: git clone https://github.com/delimit-ai/delimit-gateway.git ~/.delimit/server`);
|
|
194
178
|
}
|
|
195
179
|
}
|
|
196
|
-
|
|
197
180
|
// Copy the MCP server file
|
|
198
181
|
const serverSource = path.join(__dirname, '..', 'mcp-server.py');
|
|
199
182
|
if (fs.existsSync(serverSource)) {
|
|
200
183
|
fs.copyFileSync(serverSource, path.join(DELIMIT_HOME, 'server', 'mcp-server.py'));
|
|
201
184
|
}
|
|
202
|
-
|
|
203
185
|
// Download compiled Pro modules (platform-specific)
|
|
204
186
|
const proDir = path.join(DELIMIT_HOME, 'server', 'ai');
|
|
205
187
|
const pyVer = (() => { try { return execSync(`${python} -c "import sys; print(f'cp{sys.version_info.major}{sys.version_info.minor}')"`, { encoding: 'utf-8' }).trim(); } catch { return 'cp310'; } })();
|
|
@@ -209,7 +191,6 @@ async function main() {
|
|
|
209
191
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
210
192
|
const proVersion = pkg.proModuleVersion || '3.8.2';
|
|
211
193
|
const proUrl = `https://delimit.ai/releases/v${proVersion}/delimit-pro-${artifact}.tar.gz`;
|
|
212
|
-
|
|
213
194
|
try {
|
|
214
195
|
const proTarball = path.join(DELIMIT_HOME, 'pro.tar.gz');
|
|
215
196
|
execSync(`curl -sL "${proUrl}" -o "${proTarball}" --fail`, { stdio: 'pipe', timeout: 30000 });
|
|
@@ -219,13 +200,11 @@ async function main() {
|
|
|
219
200
|
} catch {
|
|
220
201
|
log(` ${dim(' Pro modules not available for ${artifact} — free tools work fine')}`);
|
|
221
202
|
}
|
|
222
|
-
|
|
223
203
|
// Re-copy gateway source AFTER Pro modules to ensure full files aren't overwritten by stubs
|
|
224
204
|
// Skip if dev symlinks are in place
|
|
225
205
|
if (fs.existsSync(gatewaySource) && !isDevSymlink) {
|
|
226
206
|
copyDir(gatewaySource, path.join(DELIMIT_HOME, 'server'));
|
|
227
207
|
}
|
|
228
|
-
|
|
229
208
|
// Remove stale .so binaries that shadow updated .py source files
|
|
230
209
|
// Python loads .so before .py, so old compiled stubs block new source
|
|
231
210
|
const aiDir = path.join(DELIMIT_HOME, 'server', 'ai');
|
|
@@ -244,7 +223,6 @@ async function main() {
|
|
|
244
223
|
}
|
|
245
224
|
} catch { /* ignore cleanup errors */ }
|
|
246
225
|
}
|
|
247
|
-
|
|
248
226
|
// Install Python deps into isolated venv with pinned versions
|
|
249
227
|
log(` ${dim(' Installing Python dependencies...')}`);
|
|
250
228
|
const venvDir = path.join(DELIMIT_HOME, 'venv');
|
|
@@ -276,7 +254,6 @@ async function main() {
|
|
|
276
254
|
log(` ${yellow('!')} pip install failed — run manually: pip install fastmcp pyyaml pydantic packaging pytest`);
|
|
277
255
|
}
|
|
278
256
|
}
|
|
279
|
-
|
|
280
257
|
// LED-1084 week 2: build the content-grounding feature whitelist.
|
|
281
258
|
// Populates ~/.delimit/content/grounding/features.json with every
|
|
282
259
|
// @mcp.tool() entry + every CLI subcommand we ship. Drafters then
|
|
@@ -299,10 +276,8 @@ async function main() {
|
|
|
299
276
|
// which makes the gate strict but doesn't break anything. Customers
|
|
300
277
|
// can rebuild manually later: `python -m ai.content_grounding.features build`.
|
|
301
278
|
}
|
|
302
|
-
|
|
303
279
|
// Step 3: Configure Claude Code MCP
|
|
304
280
|
step(3, 'Configuring Claude Code MCP...');
|
|
305
|
-
|
|
306
281
|
const configuredTools = [];
|
|
307
282
|
let mcpConfig = {};
|
|
308
283
|
if (fs.existsSync(MCP_CONFIG)) {
|
|
@@ -311,14 +286,11 @@ async function main() {
|
|
|
311
286
|
} catch {}
|
|
312
287
|
}
|
|
313
288
|
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
314
|
-
|
|
315
289
|
const serverPath = path.join(DELIMIT_HOME, 'server', 'ai', 'server.py');
|
|
316
290
|
const serverPathAlt = path.join(DELIMIT_HOME, 'server', 'mcp-server.py');
|
|
317
291
|
const actualServer = fs.existsSync(serverPath) ? serverPath : serverPathAlt;
|
|
318
|
-
|
|
319
292
|
// Always update paths to match this machine's directory structure
|
|
320
293
|
const delimitMcp = {
|
|
321
|
-
type: 'stdio',
|
|
322
294
|
command: python,
|
|
323
295
|
args: [actualServer],
|
|
324
296
|
cwd: path.join(DELIMIT_HOME, 'server'),
|
|
@@ -337,7 +309,6 @@ async function main() {
|
|
|
337
309
|
await logp(` ${green('✓')} Added delimit to ${MCP_CONFIG}`);
|
|
338
310
|
configuredTools.push('Claude Code');
|
|
339
311
|
}
|
|
340
|
-
|
|
341
312
|
// Auto-approve all Delimit tools in Claude Code settings.json
|
|
342
313
|
const CLAUDE_SETTINGS = path.join(CLAUDE_DIR, 'settings.json');
|
|
343
314
|
try {
|
|
@@ -356,7 +327,6 @@ async function main() {
|
|
|
356
327
|
} catch (e) {
|
|
357
328
|
log(` ${yellow('!')} Could not set Claude Code permissions: ${e.message}`);
|
|
358
329
|
}
|
|
359
|
-
|
|
360
330
|
// Step 3b: Configure Codex MCP (if installed)
|
|
361
331
|
const CODEX_CONFIG = path.join(os.homedir(), '.codex', 'config.toml');
|
|
362
332
|
// Create config.toml if .codex dir exists or codex is in PATH
|
|
@@ -379,7 +349,6 @@ async function main() {
|
|
|
379
349
|
const serverDir = path.join(DELIMIT_HOME, 'server');
|
|
380
350
|
// approval_policy = "never" means auto-approve all tools from this server (no per-prompt confirmations)
|
|
381
351
|
const correctEntry = `\n[mcp_servers.delimit]\ncommand = "${python}"\nargs = ["${actualServer}"]\ncwd = "${serverDir}"\napproval_policy = "never"\n\n[mcp_servers.delimit.env]\nPYTHONPATH = "${serverDir}:${path.join(serverDir, 'ai')}"\n`;
|
|
382
|
-
|
|
383
352
|
// Remove ALL existing delimit MCP entries (prevents duplicates)
|
|
384
353
|
const existed = toml.includes('mcp_servers.delimit');
|
|
385
354
|
const lines = toml.split('\n');
|
|
@@ -410,7 +379,6 @@ async function main() {
|
|
|
410
379
|
log(` ${yellow('!')} Could not configure Codex: ${e.message}`);
|
|
411
380
|
}
|
|
412
381
|
}
|
|
413
|
-
|
|
414
382
|
// Step 3c: Configure Cursor MCP (if installed)
|
|
415
383
|
const CURSOR_CONFIG = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
416
384
|
if (fs.existsSync(path.join(os.homedir(), '.cursor'))) {
|
|
@@ -439,7 +407,6 @@ async function main() {
|
|
|
439
407
|
log(` ${yellow('!')} Could not configure Cursor: ${e.message}`);
|
|
440
408
|
}
|
|
441
409
|
}
|
|
442
|
-
|
|
443
410
|
// Step 3d: Configure Gemini CLI (if installed)
|
|
444
411
|
const GEMINI_DIR = path.join(os.homedir(), '.gemini');
|
|
445
412
|
const GEMINI_CONFIG = path.join(GEMINI_DIR, 'settings.json');
|
|
@@ -480,7 +447,6 @@ async function main() {
|
|
|
480
447
|
log(` ${yellow('!')} Could not configure Gemini CLI: ${e.message}`);
|
|
481
448
|
}
|
|
482
449
|
}
|
|
483
|
-
|
|
484
450
|
// Step 3e: Configure Antigravity CLI (if installed)
|
|
485
451
|
const ANTIGRAVITY_DIR = path.join(os.homedir(), '.gemini', 'antigravity-cli');
|
|
486
452
|
const ANTIGRAVITY_CONFIG = path.join(ANTIGRAVITY_DIR, 'settings.json');
|
|
@@ -493,6 +459,7 @@ async function main() {
|
|
|
493
459
|
if (!antigravityConfig.mcpServers) antigravityConfig.mcpServers = {};
|
|
494
460
|
const antigravityExisted = !!antigravityConfig.mcpServers.delimit;
|
|
495
461
|
antigravityConfig.mcpServers.delimit = {
|
|
462
|
+
type: 'stdio',
|
|
496
463
|
command: python,
|
|
497
464
|
args: [actualServer],
|
|
498
465
|
cwd: path.join(DELIMIT_HOME, 'server'),
|
|
@@ -519,7 +486,6 @@ async function main() {
|
|
|
519
486
|
log(` ${yellow('!')} Could not configure Antigravity CLI: ${e.message}`);
|
|
520
487
|
}
|
|
521
488
|
}
|
|
522
|
-
|
|
523
489
|
// Checkpoint: MCP is configured, now ask before modifying project files
|
|
524
490
|
log('');
|
|
525
491
|
log(` ${green('✓')} MCP server installed and configured`);
|
|
@@ -529,7 +495,6 @@ async function main() {
|
|
|
529
495
|
log(` • Update CLAUDE.md with Delimit instructions`);
|
|
530
496
|
log(` • Optional: governance wrapping + hooks`);
|
|
531
497
|
log('');
|
|
532
|
-
|
|
533
498
|
const inquirerMid = (() => { try { return require('inquirer'); } catch { return null; } })();
|
|
534
499
|
if (inquirerMid && process.stdin.isTTY) {
|
|
535
500
|
try {
|
|
@@ -546,12 +511,9 @@ async function main() {
|
|
|
546
511
|
}
|
|
547
512
|
} catch {}
|
|
548
513
|
}
|
|
549
|
-
|
|
550
514
|
// Step 4: Install default agents
|
|
551
515
|
step(4, 'Installing governance agents...');
|
|
552
|
-
|
|
553
516
|
fs.mkdirSync(AGENTS_DIR, { recursive: true });
|
|
554
|
-
|
|
555
517
|
const agents = {
|
|
556
518
|
'lint.md': `---
|
|
557
519
|
name: lint
|
|
@@ -567,9 +529,7 @@ tools:
|
|
|
567
529
|
- mcp__delimit__delimit_impact
|
|
568
530
|
- mcp__delimit__delimit_ledger
|
|
569
531
|
---
|
|
570
|
-
|
|
571
532
|
# Lint Agent
|
|
572
|
-
|
|
573
533
|
Run API governance checks. Use delimit_lint to compare specs, delimit_policy to check rules, delimit_impact for downstream analysis.
|
|
574
534
|
`,
|
|
575
535
|
'engineering.md': `---
|
|
@@ -587,9 +547,7 @@ tools:
|
|
|
587
547
|
- mcp__delimit__delimit_test_generate
|
|
588
548
|
- mcp__delimit__delimit_test_coverage
|
|
589
549
|
---
|
|
590
|
-
|
|
591
550
|
# Engineering Agent
|
|
592
|
-
|
|
593
551
|
Execute code directives. Use delimit_test_coverage to verify coverage targets. Use delimit_lint to check API compatibility after changes.
|
|
594
552
|
`,
|
|
595
553
|
'governance.md': `---
|
|
@@ -609,13 +567,10 @@ tools:
|
|
|
609
567
|
- mcp__delimit__delimit_repo_analyze
|
|
610
568
|
- mcp__delimit__delimit_repo_config_validate
|
|
611
569
|
---
|
|
612
|
-
|
|
613
570
|
# Governance Agent
|
|
614
|
-
|
|
615
571
|
Run full governance compliance checks. Verify security, policy compliance, evidence collection, and repo health.
|
|
616
572
|
`
|
|
617
573
|
};
|
|
618
|
-
|
|
619
574
|
let installed = 0;
|
|
620
575
|
for (const [filename, content] of Object.entries(agents)) {
|
|
621
576
|
const agentPath = path.join(AGENTS_DIR, filename);
|
|
@@ -625,7 +580,6 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
625
580
|
}
|
|
626
581
|
}
|
|
627
582
|
await logp(` ${green('✓')} ${installed} agents installed (${Object.keys(agents).length - installed} already existed)`);
|
|
628
|
-
|
|
629
583
|
// Step 4b: Install Git hooks if inside a git repository
|
|
630
584
|
const gitDir = findGitDir(process.cwd());
|
|
631
585
|
if (gitDir) {
|
|
@@ -660,10 +614,8 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
660
614
|
} else {
|
|
661
615
|
log(` ${dim(' Not inside a git repo — git hooks will be installed on next delimit setup inside a repo')}`);
|
|
662
616
|
}
|
|
663
|
-
|
|
664
617
|
// Step 5: Create/update CLAUDE.md and platform instruction files
|
|
665
618
|
step(5, 'Setting up AI instruction files...');
|
|
666
|
-
|
|
667
619
|
const claudeMd = path.join(os.homedir(), 'CLAUDE.md');
|
|
668
620
|
const claudeResult = upsertDelimitSection(claudeMd);
|
|
669
621
|
if (claudeResult.action === 'created') {
|
|
@@ -675,7 +627,6 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
675
627
|
} else {
|
|
676
628
|
log(` ${dim(' CLAUDE.md already up to date')}`);
|
|
677
629
|
}
|
|
678
|
-
|
|
679
630
|
// Codex instructions: AGENTS.md is the file Codex CLI auto-loads
|
|
680
631
|
// ("from CWD up to the root", per Codex binary spec). The previous
|
|
681
632
|
// ~/.codex/instructions.md write was dead code — Codex never read it.
|
|
@@ -699,7 +650,6 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
699
650
|
log(` ${dim(' AGENTS.md already up to date')}`);
|
|
700
651
|
}
|
|
701
652
|
}
|
|
702
|
-
|
|
703
653
|
// Gemini CLI: GEMINI.md is the auto-loaded instruction file
|
|
704
654
|
// (~/.gemini/GEMINI.md is the user-global tier per Gemini CLI bundle).
|
|
705
655
|
// LED-1399: install when Gemini CLI is present so governance triggers
|
|
@@ -724,7 +674,6 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
724
674
|
log(` ${dim(' GEMINI.md already up to date')}`);
|
|
725
675
|
}
|
|
726
676
|
}
|
|
727
|
-
|
|
728
677
|
// Antigravity CLI: ANTIGRAVITY.md is the auto-loaded instruction file.
|
|
729
678
|
// We install it when antigravity/agy is present, or when ~/.gemini/antigravity-cli exists.
|
|
730
679
|
const antigravityHome = path.join(os.homedir(), '.gemini', 'antigravity-cli');
|
|
@@ -746,7 +695,6 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
746
695
|
log(` ${dim(' ANTIGRAVITY.md already up to date')}`);
|
|
747
696
|
}
|
|
748
697
|
}
|
|
749
|
-
|
|
750
698
|
// Cursor rules (legacy .cursorrules + new .cursor/rules/ directory)
|
|
751
699
|
const cursorRules = path.join(os.homedir(), '.cursorrules');
|
|
752
700
|
if (fs.existsSync(path.join(os.homedir(), '.cursor'))) {
|
|
@@ -765,7 +713,6 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
765
713
|
log(` ${dim(' Could not install .cursor/rules: ' + e.message)}`);
|
|
766
714
|
}
|
|
767
715
|
}
|
|
768
|
-
|
|
769
716
|
// Silent: auto-detect API keys and configure models.json (no output)
|
|
770
717
|
const modelsPath = path.join(DELIMIT_HOME, 'models.json');
|
|
771
718
|
if (!fs.existsSync(modelsPath)) {
|
|
@@ -787,7 +734,6 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
787
734
|
fs.writeFileSync(modelsPath, JSON.stringify(models, null, 2));
|
|
788
735
|
}
|
|
789
736
|
}
|
|
790
|
-
|
|
791
737
|
// Step 6: Governance wrapping (shims)
|
|
792
738
|
step(6, 'Governance wrapping...');
|
|
793
739
|
log('');
|
|
@@ -807,18 +753,14 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
807
753
|
log(` ${dim('Adds ~/.delimit/shims to your shell PATH.')}`);
|
|
808
754
|
log(` ${dim('Disable anytime: delimit shims disable')}`);
|
|
809
755
|
log('');
|
|
810
|
-
|
|
811
756
|
// Check if shims already installed
|
|
812
757
|
const shimsDir = path.join(DELIMIT_HOME, 'shims');
|
|
813
758
|
const shimsInstalled = fs.existsSync(shimsDir) && fs.readdirSync(shimsDir).length > 0;
|
|
814
|
-
|
|
815
759
|
let enableShims = shimsInstalled; // Auto-yes if already installed (just update)
|
|
816
|
-
|
|
817
760
|
if (!shimsInstalled) {
|
|
818
761
|
// First install — prompt
|
|
819
762
|
const inquirer = (() => { try { return require('inquirer'); } catch { return null; } })();
|
|
820
763
|
enableShims = true;
|
|
821
|
-
|
|
822
764
|
if (inquirer && process.stdin.isTTY) {
|
|
823
765
|
try {
|
|
824
766
|
const answer = await inquirer.prompt([{
|
|
@@ -833,11 +775,9 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
833
775
|
}
|
|
834
776
|
}
|
|
835
777
|
}
|
|
836
|
-
|
|
837
778
|
if (enableShims) {
|
|
838
779
|
// Create/update shims with latest template
|
|
839
780
|
fs.mkdirSync(shimsDir, { recursive: true });
|
|
840
|
-
|
|
841
781
|
const shimTemplate = (toolName, displayName) => `#!/bin/sh
|
|
842
782
|
# Delimit Governance Shim for ${displayName}
|
|
843
783
|
PURPLE='\\033[35m'; MAGENTA='\\033[91m'; ORANGE='\\033[33m'; GREEN='\\033[32m'
|
|
@@ -896,7 +836,6 @@ printf " \${MAGENTA}\${BOLD}[Delimit]\${RESET} \${MAGENTA}═══════
|
|
|
896
836
|
sleep 0.08
|
|
897
837
|
printf " \${GREEN}\${BOLD}[Delimit]\${RESET} \${GREEN}✓ Allowed\${RESET}\\n"
|
|
898
838
|
echo ""
|
|
899
|
-
|
|
900
839
|
# --- Exit screen: session summary after AI exits ---
|
|
901
840
|
delimit_exit_screen() {
|
|
902
841
|
_EXIT_CODE=\$1
|
|
@@ -980,7 +919,6 @@ delimit_exit_screen() {
|
|
|
980
919
|
printf " \${MAGENTA}\${BOLD}[Delimit]\${RESET} \${MAGENTA}═══════════════════════════════════════════\${RESET}\\n"
|
|
981
920
|
echo ""
|
|
982
921
|
}
|
|
983
|
-
|
|
984
922
|
# Run real binary and show exit screen (replaces exec to allow post-exit code)
|
|
985
923
|
delimit_run_and_exit() {
|
|
986
924
|
"\$@"
|
|
@@ -988,7 +926,6 @@ delimit_run_and_exit() {
|
|
|
988
926
|
[ "$DELIMIT_QUIET" != "true" ] && delimit_exit_screen \$_RC
|
|
989
927
|
exit \$_RC
|
|
990
928
|
}
|
|
991
|
-
|
|
992
929
|
# Find real binary by stripping shim dir from PATH.
|
|
993
930
|
# We rely on PATH ordering ($HOME/.delimit/shims first) — no rename hack,
|
|
994
931
|
# no race with npm reinstalls. Previously we used mv tool→tool-real + cp shim,
|
|
@@ -1011,7 +948,6 @@ case "${toolName}" in
|
|
|
1011
948
|
esac
|
|
1012
949
|
exit 127
|
|
1013
950
|
`;
|
|
1014
|
-
|
|
1015
951
|
for (const [tool, display] of [
|
|
1016
952
|
['claude', 'Claude'],
|
|
1017
953
|
['codex', 'Codex'],
|
|
@@ -1023,7 +959,6 @@ exit 127
|
|
|
1023
959
|
fs.writeFileSync(shimPath, shimTemplate(tool, display));
|
|
1024
960
|
fs.chmodSync(shimPath, '755');
|
|
1025
961
|
}
|
|
1026
|
-
|
|
1027
962
|
// Governance is enforced via PATH ordering — $HOME/.delimit/shims
|
|
1028
963
|
// is prepended to PATH (see below), so `claude`/`codex`/`gemini`
|
|
1029
964
|
// resolve to our shim first, and the shim then PATH-strips itself
|
|
@@ -1035,7 +970,6 @@ exit 127
|
|
|
1035
970
|
// symlink), leaving users with "[Delimit] claude not found in PATH"
|
|
1036
971
|
// when the rename ran but the shim copy failed or the symlink got
|
|
1037
972
|
// re-created mid-operation. PATH ordering is the durable contract.
|
|
1038
|
-
|
|
1039
973
|
// Add to PATH in shell rc files (create if missing)
|
|
1040
974
|
const pathLine = `export PATH="${shimsDir}:$PATH" # Delimit governance wrapping`;
|
|
1041
975
|
let pathWritten = false;
|
|
@@ -1069,13 +1003,11 @@ exit 127
|
|
|
1069
1003
|
fs.writeFileSync(profileD, `# Delimit governance wrapping\n${pathLine}\n`);
|
|
1070
1004
|
}
|
|
1071
1005
|
} catch { /* not writable, skip */ }
|
|
1072
|
-
|
|
1073
1006
|
if (shimsInstalled) {
|
|
1074
1007
|
await logp(` ${green('✓')} Governance shims updated`);
|
|
1075
1008
|
} else {
|
|
1076
1009
|
log(` ${green('✓')} Governance wrapping enabled`);
|
|
1077
1010
|
}
|
|
1078
|
-
|
|
1079
1011
|
// Check if shims are already in current PATH
|
|
1080
1012
|
const currentPath = process.env.PATH || '';
|
|
1081
1013
|
if (!currentPath.includes('.delimit/shims')) {
|
|
@@ -1092,20 +1024,16 @@ exit 127
|
|
|
1092
1024
|
log(` ${dim(' Skipped. Enable later: delimit shims enable')}`);
|
|
1093
1025
|
}
|
|
1094
1026
|
log('');
|
|
1095
|
-
|
|
1096
1027
|
// Step 7: Install cross-model governance hooks (LED-202)
|
|
1097
1028
|
step(7, 'Installing AI assistant hooks...');
|
|
1098
|
-
|
|
1099
1029
|
try {
|
|
1100
1030
|
const crossModelHooks = require('../lib/cross-model-hooks');
|
|
1101
1031
|
const hookConfig = crossModelHooks.loadHookConfig();
|
|
1102
1032
|
const detected = crossModelHooks.detectAITools();
|
|
1103
|
-
|
|
1104
1033
|
if (detected.length === 0) {
|
|
1105
1034
|
log(` ${dim(' No AI assistants detected -- hooks will be installed when tools are found')}`);
|
|
1106
1035
|
} else {
|
|
1107
1036
|
log(` ${dim(' Detected: ' + detected.map(t => t.name).join(', '))}`);
|
|
1108
|
-
|
|
1109
1037
|
// Install hooks (auto-accept in non-interactive or prompt if TTY)
|
|
1110
1038
|
let installHooks = true;
|
|
1111
1039
|
const inq = (() => { try { return require('inquirer'); } catch { return null; } })();
|
|
@@ -1122,7 +1050,6 @@ exit 127
|
|
|
1122
1050
|
installHooks = true;
|
|
1123
1051
|
}
|
|
1124
1052
|
}
|
|
1125
|
-
|
|
1126
1053
|
if (installHooks) {
|
|
1127
1054
|
for (const tool of detected) {
|
|
1128
1055
|
const result = crossModelHooks.installHooksForTool(tool, hookConfig);
|
|
@@ -1140,10 +1067,8 @@ exit 127
|
|
|
1140
1067
|
log(` ${dim(' Hook installation skipped: ' + e.message)}`);
|
|
1141
1068
|
}
|
|
1142
1069
|
log('');
|
|
1143
|
-
|
|
1144
1070
|
// Step 8: Local dashboard API server
|
|
1145
1071
|
step(8, 'Local dashboard API...');
|
|
1146
|
-
|
|
1147
1072
|
const localServerPath = path.join(DELIMIT_HOME, 'server', 'ai', 'local_server.py');
|
|
1148
1073
|
if (fs.existsSync(localServerPath)) {
|
|
1149
1074
|
log(` ${green('✓')} Local API server available on port 7823`);
|
|
@@ -1153,10 +1078,8 @@ exit 127
|
|
|
1153
1078
|
log(` ${dim(' Local API server not found — dashboard will use cloud sync')}`);
|
|
1154
1079
|
}
|
|
1155
1080
|
log('');
|
|
1156
|
-
|
|
1157
1081
|
// Step 9: Post-install config validation (LED-098)
|
|
1158
1082
|
step(9, 'Validating config integrity...');
|
|
1159
|
-
|
|
1160
1083
|
let validationIssues = 0;
|
|
1161
1084
|
const configFiles = [
|
|
1162
1085
|
{ path: MCP_CONFIG, name: 'Claude Code', format: 'json' },
|
|
@@ -1164,7 +1087,6 @@ exit 127
|
|
|
1164
1087
|
{ path: CURSOR_CONFIG, name: 'Cursor', format: 'json' },
|
|
1165
1088
|
{ path: GEMINI_CONFIG, name: 'Gemini CLI', format: 'json' },
|
|
1166
1089
|
];
|
|
1167
|
-
|
|
1168
1090
|
for (const cfg of configFiles) {
|
|
1169
1091
|
if (!fs.existsSync(cfg.path)) continue;
|
|
1170
1092
|
try {
|
|
@@ -1178,7 +1100,6 @@ exit 127
|
|
|
1178
1100
|
const cmd = delimitEntry.command || '';
|
|
1179
1101
|
const args = delimitEntry.args || [];
|
|
1180
1102
|
const serverArg = args[0] || '';
|
|
1181
|
-
|
|
1182
1103
|
// Check command is python (not arbitrary binary)
|
|
1183
1104
|
if (!cmd.includes('python') && !cmd.includes('venv')) {
|
|
1184
1105
|
log(` ${yellow('⚠')} ${cfg.name}: delimit command is not python: ${cmd}`);
|
|
@@ -1212,7 +1133,6 @@ exit 127
|
|
|
1212
1133
|
validationIssues++;
|
|
1213
1134
|
}
|
|
1214
1135
|
}
|
|
1215
|
-
|
|
1216
1136
|
// Verify server file exists and is our code
|
|
1217
1137
|
if (fs.existsSync(actualServer)) {
|
|
1218
1138
|
const serverContent = fs.readFileSync(actualServer, 'utf-8').substring(0, 500);
|
|
@@ -1223,7 +1143,6 @@ exit 127
|
|
|
1223
1143
|
validationIssues++;
|
|
1224
1144
|
}
|
|
1225
1145
|
}
|
|
1226
|
-
|
|
1227
1146
|
// Check directory permissions
|
|
1228
1147
|
try {
|
|
1229
1148
|
const stat = fs.statSync(DELIMIT_HOME);
|
|
@@ -1235,24 +1154,20 @@ exit 127
|
|
|
1235
1154
|
log(` ${green('✓')} Directory permissions OK`);
|
|
1236
1155
|
}
|
|
1237
1156
|
} catch {}
|
|
1238
|
-
|
|
1239
1157
|
if (validationIssues === 0) {
|
|
1240
1158
|
log(` ${green('✓')} All config validations passed`);
|
|
1241
1159
|
} else {
|
|
1242
1160
|
log(` ${yellow(`⚠ ${validationIssues} issue(s) found — review above`)}`);
|
|
1243
1161
|
}
|
|
1244
1162
|
log('');
|
|
1245
|
-
|
|
1246
1163
|
// Step 10: Auto-detect OpenAPI specs
|
|
1247
1164
|
step(10, 'Scanning for API specs...');
|
|
1248
|
-
|
|
1249
1165
|
let detectedSpecs = [];
|
|
1250
1166
|
try {
|
|
1251
1167
|
const { minimatch } = (() => { try { return require('minimatch'); } catch { return { minimatch: null }; } })();
|
|
1252
1168
|
// Simple recursive glob for spec files
|
|
1253
1169
|
detectedSpecs = findSpecFiles(process.cwd());
|
|
1254
1170
|
} catch {}
|
|
1255
|
-
|
|
1256
1171
|
if (detectedSpecs.length > 0) {
|
|
1257
1172
|
log(` ${green('✓')} Found ${detectedSpecs.length} API spec(s):`);
|
|
1258
1173
|
detectedSpecs.forEach(s => log(` ${s}`));
|
|
@@ -1262,10 +1177,8 @@ exit 127
|
|
|
1262
1177
|
log(` ${dim(' No OpenAPI/Swagger specs found in current directory')}`);
|
|
1263
1178
|
}
|
|
1264
1179
|
log('');
|
|
1265
|
-
|
|
1266
1180
|
// Step 11: Social target scanning config
|
|
1267
1181
|
step(11, 'Configuring social target scanner...');
|
|
1268
|
-
|
|
1269
1182
|
const socialConfigPath = path.join(DELIMIT_HOME, 'social_target_config.json');
|
|
1270
1183
|
const socialDefaultConfig = {
|
|
1271
1184
|
platforms: {
|
|
@@ -1281,7 +1194,6 @@ exit 127
|
|
|
1281
1194
|
scan_limit: 10,
|
|
1282
1195
|
min_engagement: { score: 1, comments: 2 },
|
|
1283
1196
|
};
|
|
1284
|
-
|
|
1285
1197
|
if (!fs.existsSync(socialConfigPath)) {
|
|
1286
1198
|
fs.mkdirSync(path.dirname(socialConfigPath), { recursive: true });
|
|
1287
1199
|
fs.writeFileSync(socialConfigPath, JSON.stringify(socialDefaultConfig, null, 2) + '\n');
|
|
@@ -1289,7 +1201,6 @@ exit 127
|
|
|
1289
1201
|
} else {
|
|
1290
1202
|
log(` ${dim(' Config already exists:')} ${socialConfigPath}`);
|
|
1291
1203
|
}
|
|
1292
|
-
|
|
1293
1204
|
// Auto-detect available platforms
|
|
1294
1205
|
const detectedPlatforms = {};
|
|
1295
1206
|
// HN and Dev.to are always available (public APIs)
|
|
@@ -1310,13 +1221,11 @@ exit 127
|
|
|
1310
1221
|
} else {
|
|
1311
1222
|
detectedPlatforms['x'] = 'unavailable (no API key)';
|
|
1312
1223
|
}
|
|
1313
|
-
|
|
1314
1224
|
for (const [plat, status] of Object.entries(detectedPlatforms)) {
|
|
1315
1225
|
const icon = status.startsWith('available') ? green('\u2713') : yellow('\u2717');
|
|
1316
1226
|
log(` ${icon} ${plat}: ${dim(status)}`);
|
|
1317
1227
|
}
|
|
1318
1228
|
log('');
|
|
1319
|
-
|
|
1320
1229
|
// Step 12: Done
|
|
1321
1230
|
step(12, 'Done!');
|
|
1322
1231
|
log('');
|
|
@@ -1327,14 +1236,11 @@ exit 127
|
|
|
1327
1236
|
for (const t of tools) {
|
|
1328
1237
|
log(` ${green('✓')} ${t}`);
|
|
1329
1238
|
}
|
|
1330
|
-
|
|
1331
1239
|
log('');
|
|
1332
|
-
|
|
1333
1240
|
// Project scan — show what Delimit already knows (STR-046)
|
|
1334
1241
|
log(` ${bold('Your project:')}`);
|
|
1335
1242
|
const cwd = process.cwd();
|
|
1336
1243
|
let projectFindings = 0;
|
|
1337
|
-
|
|
1338
1244
|
// Detect framework
|
|
1339
1245
|
const frameworks = [
|
|
1340
1246
|
{ file: 'package.json', check: 'express', label: 'Express API' },
|
|
@@ -1356,7 +1262,6 @@ exit 127
|
|
|
1356
1262
|
}
|
|
1357
1263
|
} catch {}
|
|
1358
1264
|
}
|
|
1359
|
-
|
|
1360
1265
|
// Detect OpenAPI specs
|
|
1361
1266
|
const specPatterns = ['openapi.yaml', 'openapi.yml', 'openapi.json', 'swagger.yaml', 'swagger.json', 'api.yaml'];
|
|
1362
1267
|
let specFound = false;
|
|
@@ -1371,7 +1276,6 @@ exit 127
|
|
|
1371
1276
|
if (!specFound) {
|
|
1372
1277
|
await logp(` ${dim(' No API spec found — run')} ${blue('delimit-cli lint')} ${dim('to detect one')}`);
|
|
1373
1278
|
}
|
|
1374
|
-
|
|
1375
1279
|
// Count tests
|
|
1376
1280
|
const testDirs = ['tests', 'test', '__tests__', 'spec'];
|
|
1377
1281
|
for (const td of testDirs) {
|
|
@@ -1387,7 +1291,6 @@ exit 127
|
|
|
1387
1291
|
} catch {}
|
|
1388
1292
|
}
|
|
1389
1293
|
}
|
|
1390
|
-
|
|
1391
1294
|
// Check git status
|
|
1392
1295
|
try {
|
|
1393
1296
|
const gitStatus = execSync('git log --oneline -1 2>/dev/null', { encoding: 'utf-8', timeout: 3000 }).trim();
|
|
@@ -1396,7 +1299,6 @@ exit 127
|
|
|
1396
1299
|
projectFindings++;
|
|
1397
1300
|
}
|
|
1398
1301
|
} catch {}
|
|
1399
|
-
|
|
1400
1302
|
// Check for existing .delimit context
|
|
1401
1303
|
const ledgerDir = path.join(os.homedir(), '.delimit', 'ledger');
|
|
1402
1304
|
if (fs.existsSync(ledgerDir)) {
|
|
@@ -1415,7 +1317,6 @@ exit 127
|
|
|
1415
1317
|
}
|
|
1416
1318
|
} catch {}
|
|
1417
1319
|
}
|
|
1418
|
-
|
|
1419
1320
|
// Check for sessions
|
|
1420
1321
|
const sessionsDir = path.join(os.homedir(), '.delimit', 'sessions');
|
|
1421
1322
|
if (fs.existsSync(sessionsDir)) {
|
|
@@ -1427,13 +1328,10 @@ exit 127
|
|
|
1427
1328
|
}
|
|
1428
1329
|
} catch {}
|
|
1429
1330
|
}
|
|
1430
|
-
|
|
1431
1331
|
if (projectFindings === 0) {
|
|
1432
1332
|
log(` ${dim(' New project — run')} ${blue('delimit-cli demo')} ${dim('to see governance in action')}`);
|
|
1433
1333
|
}
|
|
1434
|
-
|
|
1435
1334
|
log('');
|
|
1436
|
-
|
|
1437
1335
|
// Suggested next action based on findings
|
|
1438
1336
|
log(` ${bold("What's next:")}`);
|
|
1439
1337
|
if (specFound) {
|
|
@@ -1452,7 +1350,6 @@ exit 127
|
|
|
1452
1350
|
log('');
|
|
1453
1351
|
log(` ${bold('Keep Building.')}`);
|
|
1454
1352
|
log('');
|
|
1455
|
-
|
|
1456
1353
|
// Show governance banner preview
|
|
1457
1354
|
const shimFile = path.join(DELIMIT_HOME, 'shims', 'claude');
|
|
1458
1355
|
if (fs.existsSync(shimFile)) {
|
|
@@ -1491,14 +1388,11 @@ exit 127
|
|
|
1491
1388
|
log('');
|
|
1492
1389
|
}
|
|
1493
1390
|
}
|
|
1494
|
-
|
|
1495
1391
|
// LED-213: Import canonical template from shared module
|
|
1496
1392
|
const { getDelimitSection } = require('../lib/delimit-template');
|
|
1497
|
-
|
|
1498
1393
|
function getClaudeMdContent() {
|
|
1499
1394
|
return getDelimitSection() + '\n';
|
|
1500
1395
|
}
|
|
1501
|
-
|
|
1502
1396
|
/**
|
|
1503
1397
|
* Upsert the Delimit section in a file using <!-- delimit:start --> / <!-- delimit:end --> markers.
|
|
1504
1398
|
*
|
|
@@ -1517,18 +1411,15 @@ function upsertDelimitSection(filePath) {
|
|
|
1517
1411
|
const newSection = getDelimitSection();
|
|
1518
1412
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
1519
1413
|
const version = pkg.version || '0.0.0';
|
|
1520
|
-
|
|
1521
1414
|
if (!fs.existsSync(filePath)) {
|
|
1522
1415
|
fs.writeFileSync(filePath, newSection + '\n');
|
|
1523
1416
|
return { action: 'created' };
|
|
1524
1417
|
}
|
|
1525
|
-
|
|
1526
1418
|
const rawExisting = fs.readFileSync(filePath, 'utf-8');
|
|
1527
1419
|
// Strip a UTF-8 BOM if present so the start-of-line anchor still matches
|
|
1528
1420
|
// the very first line of the file. We write back the stripped form to keep
|
|
1529
1421
|
// serialization deterministic.
|
|
1530
1422
|
const existing = rawExisting.replace(/^\uFEFF/, '');
|
|
1531
|
-
|
|
1532
1423
|
// Check if managed markers already exist.
|
|
1533
1424
|
// Markers MUST be on their own line — anchored with the multiline flag — so
|
|
1534
1425
|
// that documentation prose that quotes the markers (e.g. inside backticks,
|
|
@@ -1542,7 +1433,6 @@ function upsertDelimitSection(filePath) {
|
|
|
1542
1433
|
const endMatch = existing.match(endMarkerRe);
|
|
1543
1434
|
const hasStart = !!startMatch;
|
|
1544
1435
|
const hasEnd = !!endMatch;
|
|
1545
|
-
|
|
1546
1436
|
if (hasStart && hasEnd) {
|
|
1547
1437
|
// Extract current version from the marker (also anchored, allows indent)
|
|
1548
1438
|
const versionMatch = existing.match(/^[ \t]*<!-- delimit:start v([^ ]+) -->[ \t]*$/m);
|
|
@@ -1558,7 +1448,6 @@ function upsertDelimitSection(filePath) {
|
|
|
1558
1448
|
fs.writeFileSync(filePath, before + newSection + after);
|
|
1559
1449
|
return { action: 'updated' };
|
|
1560
1450
|
}
|
|
1561
|
-
|
|
1562
1451
|
// No markers present — append the managed section at the bottom.
|
|
1563
1452
|
// User content at the top is preserved verbatim. Markers get added so future
|
|
1564
1453
|
// upgrades can update just the managed region.
|
|
@@ -1566,7 +1455,6 @@ function upsertDelimitSection(filePath) {
|
|
|
1566
1455
|
fs.writeFileSync(filePath, existing + separator + newSection + '\n');
|
|
1567
1456
|
return { action: 'appended' };
|
|
1568
1457
|
}
|
|
1569
|
-
|
|
1570
1458
|
function copyDir(src, dest) {
|
|
1571
1459
|
fs.mkdirSync(dest, { recursive: true });
|
|
1572
1460
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -1580,7 +1468,6 @@ function copyDir(src, dest) {
|
|
|
1580
1468
|
}
|
|
1581
1469
|
}
|
|
1582
1470
|
}
|
|
1583
|
-
|
|
1584
1471
|
main().catch(err => {
|
|
1585
1472
|
console.error('Setup failed:', err.message);
|
|
1586
1473
|
process.exit(1);
|
package/gateway/ai/tui.py
CHANGED
|
@@ -538,6 +538,11 @@ def _channel_color(channel: str) -> str:
|
|
|
538
538
|
class ApprovalsPanel(Static):
|
|
539
539
|
"""Pending approvals view -- shows items from drafts.db."""
|
|
540
540
|
|
|
541
|
+
BINDINGS = [
|
|
542
|
+
Binding("y", "approve", "Approve", key_display="Y"),
|
|
543
|
+
Binding("n", "reject", "Reject", key_display="N"),
|
|
544
|
+
]
|
|
545
|
+
|
|
541
546
|
def compose(self) -> ComposeResult:
|
|
542
547
|
yield DataTable(id="approvals-table")
|
|
543
548
|
|
|
@@ -551,7 +556,8 @@ class ApprovalsPanel(Static):
|
|
|
551
556
|
def _refresh_data(self) -> None:
|
|
552
557
|
table = self.query_one("#approvals-table", DataTable)
|
|
553
558
|
table.clear()
|
|
554
|
-
|
|
559
|
+
self.items = _load_pending_approvals(25)
|
|
560
|
+
for item in self.items:
|
|
555
561
|
table.add_row(
|
|
556
562
|
item.get("draft_id", "")[:12],
|
|
557
563
|
item.get("draft_kind", ""),
|
|
@@ -560,6 +566,58 @@ class ApprovalsPanel(Static):
|
|
|
560
566
|
item.get("age_str", ""),
|
|
561
567
|
)
|
|
562
568
|
|
|
569
|
+
def action_approve(self) -> None:
|
|
570
|
+
self._handle_action("approve")
|
|
571
|
+
|
|
572
|
+
def action_reject(self) -> None:
|
|
573
|
+
self._handle_action("reject")
|
|
574
|
+
|
|
575
|
+
def _handle_action(self, action: str) -> None:
|
|
576
|
+
table = self.query_one("#approvals-table", DataTable)
|
|
577
|
+
cursor_row = table.cursor_row
|
|
578
|
+
if cursor_row is None or not hasattr(self, "items") or cursor_row >= len(self.items):
|
|
579
|
+
self.app.notify("No draft selected.", severity="warning")
|
|
580
|
+
return
|
|
581
|
+
|
|
582
|
+
item = self.items[cursor_row]
|
|
583
|
+
draft_id = item.get("draft_id")
|
|
584
|
+
current_status = item.get("status")
|
|
585
|
+
|
|
586
|
+
if not draft_id or not current_status:
|
|
587
|
+
self.app.notify("Invalid draft data selected.", severity="error")
|
|
588
|
+
return
|
|
589
|
+
|
|
590
|
+
from .inbox_drafts import transition
|
|
591
|
+
db_path = DELIMIT_HOME / "drafts.db"
|
|
592
|
+
new_status = "approved" if action == "approve" else "cancelled"
|
|
593
|
+
|
|
594
|
+
try:
|
|
595
|
+
success = transition(
|
|
596
|
+
draft_id,
|
|
597
|
+
expected=current_status,
|
|
598
|
+
new=new_status,
|
|
599
|
+
db_path=db_path
|
|
600
|
+
)
|
|
601
|
+
if success:
|
|
602
|
+
self.app.notify(
|
|
603
|
+
f"Draft {draft_id[:12]} {action}d successfully!",
|
|
604
|
+
title="Action Succeeded",
|
|
605
|
+
severity="information"
|
|
606
|
+
)
|
|
607
|
+
self._refresh_data()
|
|
608
|
+
else:
|
|
609
|
+
self.app.notify(
|
|
610
|
+
f"Failed to {action} draft {draft_id[:12]}: state mismatch.",
|
|
611
|
+
title="Action Failed",
|
|
612
|
+
severity="warning"
|
|
613
|
+
)
|
|
614
|
+
except Exception as e:
|
|
615
|
+
self.app.notify(
|
|
616
|
+
f"Error performing {action}: {e}",
|
|
617
|
+
title="System Error",
|
|
618
|
+
severity="error"
|
|
619
|
+
)
|
|
620
|
+
|
|
563
621
|
|
|
564
622
|
class FilesystemPanel(Static):
|
|
565
623
|
"""Filesystem browser -- navigate .delimit/ directory tree."""
|
|
@@ -884,6 +942,10 @@ class DelimitOS(App):
|
|
|
884
942
|
|
|
885
943
|
def action_focus_approvals(self) -> None:
|
|
886
944
|
self.query_one(TabbedContent).active = "tab-approvals"
|
|
945
|
+
try:
|
|
946
|
+
self.query_one("#approvals-table", DataTable).focus()
|
|
947
|
+
except Exception:
|
|
948
|
+
pass
|
|
887
949
|
|
|
888
950
|
def action_focus_ledger(self) -> None:
|
|
889
951
|
self.query_one(TabbedContent).active = "tab-ledger"
|
package/lib/chat-repl.js
CHANGED
|
@@ -140,7 +140,10 @@ class DelimitChatREPL {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
const args = [];
|
|
143
|
-
if (activeModel.id.
|
|
143
|
+
if (activeModel.id === 'antigravity' || activeModel.id === 'agy') {
|
|
144
|
+
args.push('--dangerously-skip-permissions');
|
|
145
|
+
}
|
|
146
|
+
if (activeModel.id.startsWith('gemini')) {
|
|
144
147
|
const p = this.modelsConfig[activeModel.id];
|
|
145
148
|
if (p && p.model) {
|
|
146
149
|
let modelName = p.model;
|
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.7.
|
|
4
|
+
"version": "4.7.5",
|
|
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": [
|