delimit-cli 3.11.8 → 3.11.10
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 +23 -9
- package/adapters/codex-security.js +64 -0
- package/adapters/codex-skill.js +78 -0
- package/adapters/cursor-rules.js +97 -0
- package/bin/delimit-cli.js +189 -38
- package/bin/delimit-setup.js +103 -4
- package/package.json +6 -5
- package/server.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `</>` Delimit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/delimit-cli)
|
|
6
6
|
[](https://github.com/marketplace/actions/delimit-api-governance)
|
|
@@ -68,26 +68,40 @@ npx delimit-cli setup
|
|
|
68
68
|
|
|
69
69
|
No API keys. No account. Installs in 10 seconds.
|
|
70
70
|
|
|
71
|
-
### CLI commands
|
|
71
|
+
### CLI commands (all free)
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
|
-
npx delimit-cli
|
|
74
|
+
npx delimit-cli setup # Install into all AI assistants
|
|
75
|
+
npx delimit-cli setup --dry-run # Preview changes first
|
|
76
|
+
npx delimit-cli lint api/openapi.yaml # Check for breaking changes
|
|
75
77
|
npx delimit-cli diff old.yaml new.yaml # Compare two specs
|
|
76
78
|
npx delimit-cli explain old.yaml new.yaml # Generate migration guide
|
|
77
79
|
npx delimit-cli init --preset strict # Initialize policies
|
|
78
80
|
npx delimit-cli doctor # Check setup health
|
|
81
|
+
npx delimit-cli uninstall --dry-run # Preview removal
|
|
79
82
|
```
|
|
80
83
|
|
|
81
84
|
### What the MCP toolkit adds
|
|
82
85
|
|
|
83
|
-
When installed into your AI coding assistant, Delimit provides:
|
|
86
|
+
When installed into your AI coding assistant, Delimit provides tools across two tiers:
|
|
87
|
+
|
|
88
|
+
#### Free (no account needed)
|
|
84
89
|
|
|
85
90
|
- **API governance** -- lint, diff, policy enforcement, semver classification
|
|
86
|
-
- **
|
|
87
|
-
- **Security audit** -- scans dependencies, detects secrets and anti-patterns
|
|
88
|
-
- **Persistent ledger** -- tracks tasks across sessions, auto-creates items from governance
|
|
89
|
-
- **Multi-model consensus** -- Grok, Gemini, and Codex debate until they agree
|
|
91
|
+
- **Persistent ledger** -- track tasks across sessions, shared between all AI assistants
|
|
90
92
|
- **Zero-spec extraction** -- generate OpenAPI specs from FastAPI, Express, or NestJS source
|
|
93
|
+
- **Project scan** -- auto-detect specs, frameworks, security issues, and tests
|
|
94
|
+
- **Quickstart** -- guided first-run that proves value in 60 seconds
|
|
95
|
+
|
|
96
|
+
#### Pro
|
|
97
|
+
|
|
98
|
+
- **Multi-model deliberation** -- Grok, Gemini, and Codex debate until they agree
|
|
99
|
+
- **Security audit** -- dependency scanning, secret detection, SAST analysis
|
|
100
|
+
- **Test verification** -- confirms tests ran, measures coverage, generates new tests
|
|
101
|
+
- **Memory & vault** -- persistent context and encrypted secrets across sessions
|
|
102
|
+
- **Evidence collection** -- governance audit trail for compliance
|
|
103
|
+
- **Deploy pipeline** -- governed build, publish, and rollback
|
|
104
|
+
- **OS layer** -- agent identity, execution plans, approval gates
|
|
91
105
|
|
|
92
106
|
---
|
|
93
107
|
|
|
@@ -149,7 +163,7 @@ rules:
|
|
|
149
163
|
- [delimit.ai](https://delimit.ai) -- homepage
|
|
150
164
|
- [Docs](https://delimit.ai/docs) -- full documentation
|
|
151
165
|
- [GitHub Action](https://github.com/marketplace/actions/delimit-api-governance) -- Marketplace listing
|
|
152
|
-
- [Quickstart](https://github.com/delimit-ai/delimit-
|
|
166
|
+
- [Quickstart](https://github.com/delimit-ai/delimit-mcp-server) -- try it in 2 minutes
|
|
153
167
|
- [npm](https://www.npmjs.com/package/delimit-cli) -- CLI package
|
|
154
168
|
- [Pricing](https://delimit.ai/pricing) -- free tier + Pro
|
|
155
169
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Delimit Security Skill for Codex CLI
|
|
4
|
+
*
|
|
5
|
+
* Validates that Codex-generated code doesn't introduce security anti-patterns.
|
|
6
|
+
* Runs as a security validation skill.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const SECURITY_PATTERNS = [
|
|
13
|
+
{ pattern: /eval\s*\(/g, severity: 'high', message: 'eval() usage detected — potential code injection' },
|
|
14
|
+
{ pattern: /exec\s*\(/g, severity: 'medium', message: 'exec() usage — verify input sanitization' },
|
|
15
|
+
{ pattern: /shell\s*=\s*True/g, severity: 'high', message: 'subprocess with shell=True — command injection risk' },
|
|
16
|
+
{ pattern: /dangerouslySetInnerHTML/g, severity: 'medium', message: 'dangerouslySetInnerHTML — XSS risk' },
|
|
17
|
+
{ pattern: /password\s*=\s*["'][^"']+["']/gi, severity: 'high', message: 'Hardcoded password detected' },
|
|
18
|
+
{ pattern: /api[_-]?key\s*=\s*["'][A-Za-z0-9]{10,}["']/gi, severity: 'high', message: 'Hardcoded API key detected' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function checkSecurity(context) {
|
|
22
|
+
const code = context.code || context.content || '';
|
|
23
|
+
if (!code) return { status: 'clean', findings: [] };
|
|
24
|
+
|
|
25
|
+
const findings = [];
|
|
26
|
+
for (const { pattern, severity, message } of SECURITY_PATTERNS) {
|
|
27
|
+
const matches = code.match(pattern);
|
|
28
|
+
if (matches) {
|
|
29
|
+
findings.push({ severity, message, count: matches.length });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Audit
|
|
34
|
+
try {
|
|
35
|
+
const auditDir = path.join(process.env.HOME || '', '.delimit', 'audit');
|
|
36
|
+
fs.mkdirSync(auditDir, { recursive: true });
|
|
37
|
+
const record = {
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
source: 'codex-security',
|
|
40
|
+
findings_count: findings.length,
|
|
41
|
+
high_count: findings.filter(f => f.severity === 'high').length,
|
|
42
|
+
};
|
|
43
|
+
const auditFile = path.join(auditDir, `${new Date().toISOString().split('T')[0]}.jsonl`);
|
|
44
|
+
fs.appendFileSync(auditFile, JSON.stringify(record) + '\n');
|
|
45
|
+
} catch {}
|
|
46
|
+
|
|
47
|
+
const hasHigh = findings.some(f => f.severity === 'high');
|
|
48
|
+
return {
|
|
49
|
+
status: hasHigh ? 'flagged' : findings.length > 0 ? 'warnings' : 'clean',
|
|
50
|
+
findings,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const context = process.argv[2] ? JSON.parse(process.argv[2]) : {};
|
|
55
|
+
const result = checkSecurity(context);
|
|
56
|
+
|
|
57
|
+
if (result.status === 'flagged') {
|
|
58
|
+
for (const f of result.findings) {
|
|
59
|
+
console.error(`[Delimit Security] ${f.severity.toUpperCase()}: ${f.message}`);
|
|
60
|
+
}
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
process.exit(0);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Delimit Governance Skill for Codex CLI
|
|
4
|
+
*
|
|
5
|
+
* Runs as a validation skill triggered on pre-code-generation and pre-suggestion.
|
|
6
|
+
* Checks governance state and policy compliance before Codex executes actions.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const DELIMIT_HOME = path.join(process.env.HOME || '', '.delimit');
|
|
13
|
+
const MODE_FILE = path.join(DELIMIT_HOME, 'enforcement_mode');
|
|
14
|
+
|
|
15
|
+
function getMode() {
|
|
16
|
+
try {
|
|
17
|
+
return fs.readFileSync(MODE_FILE, 'utf-8').trim();
|
|
18
|
+
} catch {
|
|
19
|
+
return 'guarded'; // Default
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function checkGovernance(context) {
|
|
24
|
+
const mode = getMode();
|
|
25
|
+
const warnings = [];
|
|
26
|
+
|
|
27
|
+
// Check if governance is initialized
|
|
28
|
+
const policiesFile = path.join(process.cwd(), '.delimit', 'policies.yml');
|
|
29
|
+
if (!fs.existsSync(policiesFile)) {
|
|
30
|
+
warnings.push('No .delimit/policies.yml — run: delimit init');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check for sensitive file access
|
|
34
|
+
const sensitivePatterns = ['.env', 'credentials', '.ssh', 'secrets'];
|
|
35
|
+
const target = context.target || context.file || '';
|
|
36
|
+
for (const pattern of sensitivePatterns) {
|
|
37
|
+
if (target.includes(pattern)) {
|
|
38
|
+
if (mode === 'enforce') {
|
|
39
|
+
return { status: 'blocked', reason: `Access to sensitive path: ${target}` };
|
|
40
|
+
}
|
|
41
|
+
warnings.push(`Accessing sensitive path: ${target}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Audit log
|
|
46
|
+
try {
|
|
47
|
+
const auditDir = path.join(DELIMIT_HOME, 'audit');
|
|
48
|
+
fs.mkdirSync(auditDir, { recursive: true });
|
|
49
|
+
const record = {
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
source: 'codex-skill',
|
|
52
|
+
mode,
|
|
53
|
+
context: typeof context === 'object' ? JSON.stringify(context).slice(0, 200) : String(context).slice(0, 200),
|
|
54
|
+
warnings,
|
|
55
|
+
};
|
|
56
|
+
const auditFile = path.join(auditDir, `${new Date().toISOString().split('T')[0]}.jsonl`);
|
|
57
|
+
fs.appendFileSync(auditFile, JSON.stringify(record) + '\n');
|
|
58
|
+
} catch {}
|
|
59
|
+
|
|
60
|
+
return { status: 'allowed', mode, warnings };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Entry point — read context from stdin or args
|
|
64
|
+
const context = process.argv[2] ? JSON.parse(process.argv[2]) : {};
|
|
65
|
+
const result = checkGovernance(context);
|
|
66
|
+
|
|
67
|
+
if (result.status === 'blocked') {
|
|
68
|
+
console.error(`[Delimit] BLOCKED: ${result.reason}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (result.warnings.length > 0) {
|
|
73
|
+
for (const w of result.warnings) {
|
|
74
|
+
console.error(`[Delimit] Warning: ${w}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
process.exit(0);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Delimit Governance Rules for Cursor
|
|
4
|
+
*
|
|
5
|
+
* Cursor doesn't have a hook system like Claude Code or Codex,
|
|
6
|
+
* so governance enforcement happens server-side via MCP tool calls.
|
|
7
|
+
* This adapter manages the .cursorrules and .cursor/rules/ files
|
|
8
|
+
* that guide Cursor's behavior.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const HOME = process.env.HOME || '';
|
|
15
|
+
const CURSOR_DIR = path.join(HOME, '.cursor');
|
|
16
|
+
const CURSOR_RULES_DIR = path.join(CURSOR_DIR, 'rules');
|
|
17
|
+
const CURSORRULES_FILE = path.join(HOME, '.cursorrules');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Install Delimit governance rules into Cursor.
|
|
21
|
+
* Creates both .cursorrules (legacy) and .cursor/rules/delimit.md (new).
|
|
22
|
+
*/
|
|
23
|
+
function installRules(version) {
|
|
24
|
+
const rules = getDelimitRules(version);
|
|
25
|
+
|
|
26
|
+
// Install to .cursor/rules/delimit.md (new location, Cursor 0.45+)
|
|
27
|
+
if (fs.existsSync(CURSOR_DIR)) {
|
|
28
|
+
fs.mkdirSync(CURSOR_RULES_DIR, { recursive: true });
|
|
29
|
+
const rulesFile = path.join(CURSOR_RULES_DIR, 'delimit.md');
|
|
30
|
+
fs.writeFileSync(rulesFile, rules);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { installed: true, paths: [CURSORRULES_FILE, path.join(CURSOR_RULES_DIR, 'delimit.md')] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Remove Delimit rules from Cursor.
|
|
38
|
+
*/
|
|
39
|
+
function uninstallRules() {
|
|
40
|
+
const removed = [];
|
|
41
|
+
|
|
42
|
+
// Remove from .cursor/rules/
|
|
43
|
+
const rulesFile = path.join(CURSOR_RULES_DIR, 'delimit.md');
|
|
44
|
+
if (fs.existsSync(rulesFile)) {
|
|
45
|
+
fs.unlinkSync(rulesFile);
|
|
46
|
+
removed.push(rulesFile);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { removed };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getDelimitRules(version) {
|
|
53
|
+
return `# Delimit Governance (v${version})
|
|
54
|
+
|
|
55
|
+
## On every session start:
|
|
56
|
+
1. Call \`delimit_ledger_context\` to check for open tasks
|
|
57
|
+
2. Call \`delimit_gov_health\` to check governance status
|
|
58
|
+
|
|
59
|
+
## After editing code:
|
|
60
|
+
- After editing UI/CSS: call \`delimit_design_validate_responsive\`
|
|
61
|
+
- After editing API specs: call \`delimit_lint\`
|
|
62
|
+
- After editing tests: call \`delimit_test_smoke\`
|
|
63
|
+
|
|
64
|
+
## Before deploying:
|
|
65
|
+
1. Call \`delimit_security_audit\`
|
|
66
|
+
2. Call \`delimit_test_smoke\`
|
|
67
|
+
3. Call \`delimit_deploy_plan\`
|
|
68
|
+
|
|
69
|
+
## Before committing:
|
|
70
|
+
- Call \`delimit_repo_diagnose\` to check for common issues
|
|
71
|
+
|
|
72
|
+
## Governance enforcement:
|
|
73
|
+
- All tool calls route through governance automatically (server-side)
|
|
74
|
+
- Critical actions are blocked until approved
|
|
75
|
+
- High-risk actions require approval in enforce mode
|
|
76
|
+
- Check mode: advisory (warn only), guarded (block critical), enforce (block critical + high-risk)
|
|
77
|
+
|
|
78
|
+
## Links
|
|
79
|
+
- Docs: https://delimit.ai/docs
|
|
80
|
+
- GitHub: https://github.com/delimit-ai/delimit-mcp-server
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { installRules, uninstallRules, getDelimitRules };
|
|
85
|
+
|
|
86
|
+
// CLI entry point
|
|
87
|
+
if (require.main === module) {
|
|
88
|
+
const action = process.argv[2] || 'install';
|
|
89
|
+
const version = process.argv[3] || '3.11.9';
|
|
90
|
+
if (action === 'install') {
|
|
91
|
+
const result = installRules(version);
|
|
92
|
+
console.log(`Installed Delimit rules to Cursor: ${result.paths.join(', ')}`);
|
|
93
|
+
} else if (action === 'uninstall') {
|
|
94
|
+
const result = uninstallRules();
|
|
95
|
+
console.log(`Removed: ${result.removed.join(', ') || 'nothing to remove'}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
package/bin/delimit-cli.js
CHANGED
|
@@ -443,77 +443,181 @@ program
|
|
|
443
443
|
// Uninstall command
|
|
444
444
|
program
|
|
445
445
|
.command('uninstall')
|
|
446
|
-
.description('Remove Delimit governance')
|
|
446
|
+
.description('Remove Delimit governance from all AI assistants')
|
|
447
|
+
.option('--dry-run', 'Preview what would be removed without making changes')
|
|
448
|
+
.action(async (options) => {
|
|
449
|
+
const dryRun = options.dryRun;
|
|
450
|
+
const HOME = process.env.HOME;
|
|
451
|
+
const backupDir = path.join(HOME, '.delimit', 'backups', `uninstall-${Date.now()}`);
|
|
452
|
+
const changes = [];
|
|
447
453
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}]);
|
|
455
|
-
|
|
456
|
-
if (!confirm) return;
|
|
457
|
-
|
|
458
|
-
// Remove Git hooks
|
|
454
|
+
if (dryRun) {
|
|
455
|
+
console.log(chalk.yellow.bold('\nDRY RUN — No changes will be made\n'));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Collect all changes first
|
|
459
|
+
// 1. Git hooks
|
|
459
460
|
try {
|
|
460
|
-
execSync('git config --global --
|
|
461
|
-
|
|
461
|
+
const hooksPath = execSync('git config --global --get core.hooksPath 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
462
|
+
if (hooksPath && hooksPath.includes('delimit')) {
|
|
463
|
+
changes.push({ target: 'Git global hooks', action: 'unset core.hooksPath', current: hooksPath });
|
|
464
|
+
}
|
|
462
465
|
} catch (e) {}
|
|
463
|
-
|
|
464
|
-
//
|
|
466
|
+
|
|
467
|
+
// 2. Shell profiles
|
|
465
468
|
const profiles = ['.bashrc', '.zshrc', '.profile'];
|
|
466
469
|
profiles.forEach(profile => {
|
|
467
|
-
const profilePath = path.join(
|
|
470
|
+
const profilePath = path.join(HOME, profile);
|
|
468
471
|
if (fs.existsSync(profilePath)) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
+
const content = fs.readFileSync(profilePath, 'utf8');
|
|
473
|
+
if (content.includes('# Delimit Governance Layer')) {
|
|
474
|
+
changes.push({ target: `~/${profile}`, action: 'Remove Delimit PATH block' });
|
|
475
|
+
}
|
|
472
476
|
}
|
|
473
477
|
});
|
|
474
|
-
console.log(chalk.green('✓ Removed PATH modifications'));
|
|
475
478
|
|
|
476
|
-
//
|
|
477
|
-
const mcpPath = path.join(
|
|
479
|
+
// 3. Claude Code MCP
|
|
480
|
+
const mcpPath = path.join(HOME, '.mcp.json');
|
|
478
481
|
if (fs.existsSync(mcpPath)) {
|
|
479
482
|
try {
|
|
480
483
|
const mcp = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
481
484
|
if (mcp.mcpServers && mcp.mcpServers.delimit) {
|
|
482
|
-
|
|
483
|
-
fs.writeFileSync(mcpPath, JSON.stringify(mcp, null, 2));
|
|
484
|
-
console.log(chalk.green('✓ Removed from Claude Code MCP config'));
|
|
485
|
+
changes.push({ target: '~/.mcp.json', action: 'Remove delimit MCP entry' });
|
|
485
486
|
}
|
|
486
487
|
} catch (e) {}
|
|
487
488
|
}
|
|
488
489
|
|
|
489
|
-
//
|
|
490
|
-
const codexConfig = path.join(
|
|
490
|
+
// 4. Codex config
|
|
491
|
+
const codexConfig = path.join(HOME, '.codex', 'config.json');
|
|
492
|
+
const codexToml = path.join(HOME, '.codex', 'config.toml');
|
|
491
493
|
if (fs.existsSync(codexConfig)) {
|
|
492
494
|
try {
|
|
493
495
|
const cfg = JSON.parse(fs.readFileSync(codexConfig, 'utf8'));
|
|
494
496
|
if (cfg.mcpServers && cfg.mcpServers.delimit) {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
497
|
+
changes.push({ target: '~/.codex/config.json', action: 'Remove delimit MCP entry' });
|
|
498
|
+
}
|
|
499
|
+
} catch (e) {}
|
|
500
|
+
}
|
|
501
|
+
if (fs.existsSync(codexToml)) {
|
|
502
|
+
try {
|
|
503
|
+
const toml = fs.readFileSync(codexToml, 'utf8');
|
|
504
|
+
if (toml.includes('[mcp_servers.delimit]')) {
|
|
505
|
+
changes.push({ target: '~/.codex/config.toml', action: 'Remove [mcp_servers.delimit] block' });
|
|
498
506
|
}
|
|
499
507
|
} catch (e) {}
|
|
500
508
|
}
|
|
501
509
|
|
|
502
|
-
//
|
|
503
|
-
const
|
|
510
|
+
// 5. Cursor config
|
|
511
|
+
const cursorConfig = path.join(HOME, '.cursor', 'mcp.json');
|
|
512
|
+
if (fs.existsSync(cursorConfig)) {
|
|
513
|
+
try {
|
|
514
|
+
const cfg = JSON.parse(fs.readFileSync(cursorConfig, 'utf8'));
|
|
515
|
+
if (cfg.mcpServers && cfg.mcpServers.delimit) {
|
|
516
|
+
changes.push({ target: '~/.cursor/mcp.json', action: 'Remove delimit MCP entry' });
|
|
517
|
+
}
|
|
518
|
+
} catch (e) {}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// 6. Gemini CLI config
|
|
522
|
+
const geminiConfig = path.join(HOME, '.gemini', 'settings.json');
|
|
504
523
|
if (fs.existsSync(geminiConfig)) {
|
|
505
524
|
try {
|
|
506
525
|
const cfg = JSON.parse(fs.readFileSync(geminiConfig, 'utf8'));
|
|
507
526
|
if (cfg.mcpServers && cfg.mcpServers.delimit) {
|
|
527
|
+
changes.push({ target: '~/.gemini/settings.json', action: 'Remove delimit MCP entry' });
|
|
528
|
+
}
|
|
529
|
+
} catch (e) {}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// 7. Shims
|
|
533
|
+
const shimsDir = path.join(HOME, '.delimit', 'shims');
|
|
534
|
+
if (fs.existsSync(shimsDir)) {
|
|
535
|
+
changes.push({ target: '~/.delimit/shims/', action: 'Remove CLI shims directory' });
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (changes.length === 0) {
|
|
539
|
+
console.log(chalk.green('\nNo Delimit integrations found. Nothing to remove.\n'));
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Show what will be changed
|
|
544
|
+
console.log(chalk.bold('\nThe following changes will be made:\n'));
|
|
545
|
+
changes.forEach((c, i) => {
|
|
546
|
+
console.log(` ${i + 1}. ${chalk.cyan(c.target)} — ${c.action}`);
|
|
547
|
+
});
|
|
548
|
+
console.log('');
|
|
549
|
+
|
|
550
|
+
if (dryRun) {
|
|
551
|
+
console.log(chalk.yellow('Run without --dry-run to apply these changes.\n'));
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const { confirm } = await inquirer.prompt([{
|
|
556
|
+
type: 'confirm',
|
|
557
|
+
name: 'confirm',
|
|
558
|
+
message: `Apply ${changes.length} changes? Backups will be saved to ~/.delimit/backups/`,
|
|
559
|
+
default: false
|
|
560
|
+
}]);
|
|
561
|
+
|
|
562
|
+
if (!confirm) return;
|
|
563
|
+
|
|
564
|
+
// Create backup directory
|
|
565
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
566
|
+
|
|
567
|
+
// Execute changes with backups
|
|
568
|
+
// Git hooks
|
|
569
|
+
try {
|
|
570
|
+
execSync('git config --global --unset core.hooksPath 2>/dev/null');
|
|
571
|
+
console.log(chalk.green('✓ Removed Git hooks'));
|
|
572
|
+
} catch (e) {}
|
|
573
|
+
|
|
574
|
+
// Shell profiles
|
|
575
|
+
profiles.forEach(profile => {
|
|
576
|
+
const profilePath = path.join(HOME, profile);
|
|
577
|
+
if (fs.existsSync(profilePath)) {
|
|
578
|
+
let content = fs.readFileSync(profilePath, 'utf8');
|
|
579
|
+
if (content.includes('# Delimit Governance Layer')) {
|
|
580
|
+
fs.copyFileSync(profilePath, path.join(backupDir, profile));
|
|
581
|
+
content = content.replace(/# Delimit Governance Layer[\s\S]*?fi\n/g, '');
|
|
582
|
+
fs.writeFileSync(profilePath, content);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
console.log(chalk.green('✓ Removed PATH modifications'));
|
|
587
|
+
|
|
588
|
+
// Helper to remove delimit from JSON config
|
|
589
|
+
function removeFromJsonConfig(configPath, label) {
|
|
590
|
+
if (!fs.existsSync(configPath)) return;
|
|
591
|
+
try {
|
|
592
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
593
|
+
if (cfg.mcpServers && cfg.mcpServers.delimit) {
|
|
594
|
+
fs.copyFileSync(configPath, path.join(backupDir, path.basename(configPath) + '.' + label));
|
|
508
595
|
delete cfg.mcpServers.delimit;
|
|
509
|
-
fs.writeFileSync(
|
|
510
|
-
console.log(chalk.green(
|
|
596
|
+
fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
|
|
597
|
+
console.log(chalk.green(`✓ Removed from ${label}`));
|
|
598
|
+
}
|
|
599
|
+
} catch (e) {}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
removeFromJsonConfig(mcpPath, 'claude-code');
|
|
603
|
+
removeFromJsonConfig(codexConfig, 'codex');
|
|
604
|
+
removeFromJsonConfig(cursorConfig, 'cursor');
|
|
605
|
+
removeFromJsonConfig(geminiConfig, 'gemini');
|
|
606
|
+
|
|
607
|
+
// Handle Codex TOML config
|
|
608
|
+
if (fs.existsSync(codexToml)) {
|
|
609
|
+
try {
|
|
610
|
+
let toml = fs.readFileSync(codexToml, 'utf8');
|
|
611
|
+
if (toml.includes('[mcp_servers.delimit]')) {
|
|
612
|
+
fs.copyFileSync(codexToml, path.join(backupDir, 'config.toml.codex'));
|
|
613
|
+
toml = toml.replace(/\n\[mcp_servers\.delimit\][\s\S]*?(?=\n\[|$)/, '');
|
|
614
|
+
fs.writeFileSync(codexToml, toml);
|
|
615
|
+
console.log(chalk.green('✓ Removed from Codex TOML config'));
|
|
511
616
|
}
|
|
512
617
|
} catch (e) {}
|
|
513
618
|
}
|
|
514
619
|
|
|
515
620
|
// Remove shims
|
|
516
|
-
const shimsDir = path.join(process.env.HOME, '.delimit', 'shims');
|
|
517
621
|
if (fs.existsSync(shimsDir)) {
|
|
518
622
|
try {
|
|
519
623
|
fs.rmSync(shimsDir, { recursive: true });
|
|
@@ -522,6 +626,7 @@ program
|
|
|
522
626
|
}
|
|
523
627
|
|
|
524
628
|
console.log(chalk.green('\n Delimit has been completely removed.'));
|
|
629
|
+
console.log(chalk.gray(` Backups saved to: ${backupDir}`));
|
|
525
630
|
console.log(chalk.gray(' Your data in ~/.delimit/ has been preserved.'));
|
|
526
631
|
console.log(chalk.gray(' Delete it manually if you want: rm -rf ~/.delimit\n'));
|
|
527
632
|
});
|
|
@@ -1239,8 +1344,54 @@ program
|
|
|
1239
1344
|
// Setup command — install MCP governance tools into Claude Code
|
|
1240
1345
|
program
|
|
1241
1346
|
.command('setup')
|
|
1242
|
-
.description('Install Delimit MCP governance tools into
|
|
1243
|
-
.
|
|
1347
|
+
.description('Install Delimit MCP governance tools into all AI assistants')
|
|
1348
|
+
.option('--dry-run', 'Preview config changes without writing anything')
|
|
1349
|
+
.action((options) => {
|
|
1350
|
+
if (options.dryRun) {
|
|
1351
|
+
const os = require('os');
|
|
1352
|
+
const HOME = os.homedir();
|
|
1353
|
+
console.log(chalk.yellow.bold('\nDRY RUN — Previewing setup changes\n'));
|
|
1354
|
+
|
|
1355
|
+
const configs = [
|
|
1356
|
+
{ name: 'Claude Code', path: path.join(HOME, '.mcp.json'), key: 'mcpServers.delimit' },
|
|
1357
|
+
{ name: 'Codex', path: path.join(HOME, '.codex', 'config.toml'), key: '[mcp_servers.delimit]' },
|
|
1358
|
+
{ name: 'Cursor', path: path.join(HOME, '.cursor', 'mcp.json'), key: 'mcpServers.delimit' },
|
|
1359
|
+
{ name: 'Gemini CLI', path: path.join(HOME, '.gemini', 'settings.json'), key: 'mcpServers.delimit' },
|
|
1360
|
+
];
|
|
1361
|
+
|
|
1362
|
+
console.log(chalk.bold('Files that will be created or modified:\n'));
|
|
1363
|
+
console.log(` ${chalk.cyan('~/.delimit/')} — Delimit home directory (server, ledger, config)`);
|
|
1364
|
+
|
|
1365
|
+
configs.forEach(cfg => {
|
|
1366
|
+
const exists = fs.existsSync(cfg.path);
|
|
1367
|
+
let hasDelimit = false;
|
|
1368
|
+
if (exists) {
|
|
1369
|
+
try {
|
|
1370
|
+
const content = fs.readFileSync(cfg.path, 'utf8');
|
|
1371
|
+
hasDelimit = content.includes('delimit');
|
|
1372
|
+
} catch {}
|
|
1373
|
+
}
|
|
1374
|
+
const relPath = cfg.path.replace(HOME, '~');
|
|
1375
|
+
if (hasDelimit) {
|
|
1376
|
+
console.log(` ${chalk.green('✓')} ${relPath} — ${cfg.name} already configured`);
|
|
1377
|
+
} else if (exists) {
|
|
1378
|
+
console.log(` ${chalk.yellow('+')} ${relPath} — Will add delimit entry to ${cfg.name}`);
|
|
1379
|
+
} else {
|
|
1380
|
+
const dirExists = fs.existsSync(path.dirname(cfg.path));
|
|
1381
|
+
if (dirExists || cfg.name === 'Claude Code') {
|
|
1382
|
+
console.log(` ${chalk.yellow('+')} ${relPath} — Will create for ${cfg.name}`);
|
|
1383
|
+
} else {
|
|
1384
|
+
console.log(` ${chalk.dim('—')} ${relPath} — ${cfg.name} not installed, skipping`);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
console.log(`\n ${chalk.cyan('~/.delimit/venv/')} — Isolated Python virtual environment`);
|
|
1390
|
+
console.log(` ${chalk.cyan('~/.delimit/ledger/')} — Persistent task ledger`);
|
|
1391
|
+
|
|
1392
|
+
console.log(chalk.yellow('\nRun without --dry-run to apply these changes.\n'));
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1244
1395
|
require('./delimit-setup.js');
|
|
1245
1396
|
});
|
|
1246
1397
|
|
package/bin/delimit-setup.js
CHANGED
|
@@ -415,13 +415,23 @@ Run full governance compliance checks. Verify security, policy compliance, evide
|
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
-
// Cursor rules
|
|
418
|
+
// Cursor rules (legacy .cursorrules + new .cursor/rules/ directory)
|
|
419
419
|
const cursorRules = path.join(os.homedir(), '.cursorrules');
|
|
420
420
|
if (fs.existsSync(path.join(os.homedir(), '.cursor'))) {
|
|
421
421
|
const cursorResult = upsertDelimitSection(cursorRules);
|
|
422
422
|
if (cursorResult.action !== 'unchanged') {
|
|
423
423
|
log(` ${green('✓')} ${cursorResult.action === 'created' ? 'Created' : 'Updated'} ${cursorRules}`);
|
|
424
424
|
}
|
|
425
|
+
// Also install to .cursor/rules/delimit.md (Cursor 0.45+)
|
|
426
|
+
try {
|
|
427
|
+
const cursorRulesDir = path.join(os.homedir(), '.cursor', 'rules');
|
|
428
|
+
fs.mkdirSync(cursorRulesDir, { recursive: true });
|
|
429
|
+
const cursorAdapter = require('../adapters/cursor-rules');
|
|
430
|
+
cursorAdapter.installRules(pkg.version);
|
|
431
|
+
log(` ${green('✓')} Installed governance rules to .cursor/rules/delimit.md`);
|
|
432
|
+
} catch (e) {
|
|
433
|
+
log(` ${dim(' Could not install .cursor/rules: ' + e.message)}`);
|
|
434
|
+
}
|
|
425
435
|
}
|
|
426
436
|
|
|
427
437
|
// Silent: auto-detect API keys and configure models.json (no output)
|
|
@@ -570,8 +580,97 @@ echo "[Delimit] ${toolName} not found" >&2; exit 127
|
|
|
570
580
|
}
|
|
571
581
|
log('');
|
|
572
582
|
|
|
573
|
-
// Step 8:
|
|
574
|
-
step(8, '
|
|
583
|
+
// Step 8: Post-install config validation (LED-098)
|
|
584
|
+
step(8, 'Validating config integrity...');
|
|
585
|
+
|
|
586
|
+
let validationIssues = 0;
|
|
587
|
+
const configFiles = [
|
|
588
|
+
{ path: MCP_CONFIG, name: 'Claude Code', format: 'json' },
|
|
589
|
+
{ path: CODEX_CONFIG, name: 'Codex', format: 'toml' },
|
|
590
|
+
{ path: CURSOR_CONFIG, name: 'Cursor', format: 'json' },
|
|
591
|
+
{ path: GEMINI_CONFIG, name: 'Gemini CLI', format: 'json' },
|
|
592
|
+
];
|
|
593
|
+
|
|
594
|
+
for (const cfg of configFiles) {
|
|
595
|
+
if (!fs.existsSync(cfg.path)) continue;
|
|
596
|
+
try {
|
|
597
|
+
const content = fs.readFileSync(cfg.path, 'utf-8');
|
|
598
|
+
if (cfg.format === 'json') {
|
|
599
|
+
const parsed = JSON.parse(content);
|
|
600
|
+
const servers = parsed.mcpServers || {};
|
|
601
|
+
const delimitEntry = servers.delimit;
|
|
602
|
+
if (delimitEntry) {
|
|
603
|
+
// Validate the delimit entry points to our server
|
|
604
|
+
const cmd = delimitEntry.command || '';
|
|
605
|
+
const args = delimitEntry.args || [];
|
|
606
|
+
const serverArg = args[0] || '';
|
|
607
|
+
|
|
608
|
+
// Check command is python (not arbitrary binary)
|
|
609
|
+
if (!cmd.includes('python') && !cmd.includes('venv')) {
|
|
610
|
+
log(` ${yellow('⚠')} ${cfg.name}: delimit command is not python: ${cmd}`);
|
|
611
|
+
validationIssues++;
|
|
612
|
+
}
|
|
613
|
+
// Check server arg points to our server file
|
|
614
|
+
if (serverArg && !serverArg.includes('delimit') && !serverArg.includes('server.py')) {
|
|
615
|
+
log(` ${yellow('⚠')} ${cfg.name}: server path looks unexpected: ${serverArg}`);
|
|
616
|
+
validationIssues++;
|
|
617
|
+
}
|
|
618
|
+
// Check no unexpected MCP servers were added
|
|
619
|
+
const knownServers = new Set(['delimit', 'codex', 'gemini', 'gemini-vertexai', 'filesystem', 'brave-search', 'fetch', 'memory', 'puppeteer', 'github', 'slack', 'datadog']);
|
|
620
|
+
for (const serverName of Object.keys(servers)) {
|
|
621
|
+
if (!knownServers.has(serverName) && !serverName.includes('delimit')) {
|
|
622
|
+
// Not necessarily bad, just note it
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
} else if (cfg.format === 'toml') {
|
|
627
|
+
// Basic TOML check — ensure delimit entry has correct structure
|
|
628
|
+
if (content.includes('[mcp_servers.delimit]')) {
|
|
629
|
+
if (!content.match(/command\s*=\s*"[^"]*python[^"]*"/)) {
|
|
630
|
+
log(` ${yellow('⚠')} ${cfg.name}: delimit command may not be python`);
|
|
631
|
+
validationIssues++;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
log(` ${green('✓')} ${cfg.name} config valid`);
|
|
636
|
+
} catch (e) {
|
|
637
|
+
log(` ${yellow('⚠')} ${cfg.name}: could not validate — ${e.message}`);
|
|
638
|
+
validationIssues++;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Verify server file exists and is our code
|
|
643
|
+
if (fs.existsSync(actualServer)) {
|
|
644
|
+
const serverContent = fs.readFileSync(actualServer, 'utf-8').substring(0, 500);
|
|
645
|
+
if (serverContent.includes('delimit') || serverContent.includes('Delimit')) {
|
|
646
|
+
log(` ${green('✓')} Server file verified`);
|
|
647
|
+
} else {
|
|
648
|
+
log(` ${yellow('⚠')} Server file at ${actualServer} does not appear to be Delimit`);
|
|
649
|
+
validationIssues++;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Check directory permissions
|
|
654
|
+
try {
|
|
655
|
+
const stat = fs.statSync(DELIMIT_HOME);
|
|
656
|
+
const mode = (stat.mode & 0o777).toString(8);
|
|
657
|
+
if (mode.endsWith('7') || mode.endsWith('6')) {
|
|
658
|
+
log(` ${yellow('⚠')} ~/.delimit/ is world-readable/writable (${mode}) — consider: chmod 700 ~/.delimit`);
|
|
659
|
+
validationIssues++;
|
|
660
|
+
} else {
|
|
661
|
+
log(` ${green('✓')} Directory permissions OK`);
|
|
662
|
+
}
|
|
663
|
+
} catch {}
|
|
664
|
+
|
|
665
|
+
if (validationIssues === 0) {
|
|
666
|
+
log(` ${green('✓')} All config validations passed`);
|
|
667
|
+
} else {
|
|
668
|
+
log(` ${yellow(`⚠ ${validationIssues} issue(s) found — review above`)}`);
|
|
669
|
+
}
|
|
670
|
+
log('');
|
|
671
|
+
|
|
672
|
+
// Step 9: Done
|
|
673
|
+
step(9, 'Done!');
|
|
575
674
|
log('');
|
|
576
675
|
log(` ${green('Delimit is installed.')} Your AI now has persistent memory and governance.`);
|
|
577
676
|
log('');
|
|
@@ -598,7 +697,7 @@ echo "[Delimit] ${toolName} not found" >&2; exit 127
|
|
|
598
697
|
log(` ${dim('Agents:')} ${AGENTS_DIR}`);
|
|
599
698
|
log('');
|
|
600
699
|
log(` ${dim('Docs:')} https://delimit.ai/docs`);
|
|
601
|
-
log(` ${dim('GitHub:')} https://github.com/delimit-ai/delimit`);
|
|
700
|
+
log(` ${dim('GitHub:')} https://github.com/delimit-ai/delimit-mcp-server`);
|
|
602
701
|
log('');
|
|
603
702
|
}
|
|
604
703
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "delimit-cli",
|
|
3
|
-
"mcpName": "io.github.delimit-ai/delimit",
|
|
4
|
-
"version": "3.11.
|
|
5
|
-
"description": "
|
|
3
|
+
"mcpName": "io.github.delimit-ai/delimit-mcp-server",
|
|
4
|
+
"version": "3.11.10",
|
|
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": [
|
|
8
8
|
"bin/",
|
|
9
9
|
"lib/",
|
|
10
|
+
"adapters/",
|
|
10
11
|
"gateway/",
|
|
11
12
|
"server.json",
|
|
12
13
|
"README.md",
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
},
|
|
20
21
|
"scripts": {
|
|
21
22
|
"postinstall": "echo '\\nRun: npx delimit-cli setup\\n'",
|
|
22
|
-
"test": "node --test tests/setup-onboarding.test.js"
|
|
23
|
+
"test": "node --test tests/setup-onboarding.test.js tests/setup-matrix.test.js"
|
|
23
24
|
},
|
|
24
25
|
"keywords": [
|
|
25
26
|
"openapi",
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
"homepage": "https://delimit.ai",
|
|
58
59
|
"repository": {
|
|
59
60
|
"type": "git",
|
|
60
|
-
"url": "https://github.com/delimit-ai/delimit.git"
|
|
61
|
+
"url": "https://github.com/delimit-ai/delimit-mcp-server.git"
|
|
61
62
|
},
|
|
62
63
|
"dependencies": {
|
|
63
64
|
"axios": "^1.0.0",
|
package/server.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
-
"name": "io.github.delimit-ai/delimit",
|
|
4
|
-
"title": "Delimit",
|
|
5
|
-
"description": "
|
|
3
|
+
"name": "io.github.delimit-ai/delimit-mcp-server",
|
|
4
|
+
"title": "Delimit MCP Server",
|
|
5
|
+
"description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
|
|
6
6
|
"repository": {
|
|
7
|
-
"url": "https://github.com/delimit-ai/delimit",
|
|
7
|
+
"url": "https://github.com/delimit-ai/delimit-mcp-server",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
10
|
"version": "3.11.8",
|