delimit-cli 4.5.13 → 4.6.1
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/CHANGELOG.md +48 -0
- package/README.md +9 -8
- package/bin/delimit-cli.js +179 -4
- package/bin/delimit-setup.js +46 -6
- package/gateway/ai/_compile_status.py +154 -0
- package/gateway/ai/agent_dispatch.py +41 -0
- package/gateway/ai/backends/git_health.py +175 -0
- package/gateway/ai/backends/tools_infra.py +163 -10
- package/gateway/ai/cli_contract.py +185 -0
- package/gateway/ai/daemon.py +10 -0
- package/gateway/ai/daily_digest.py +1 -2
- package/gateway/ai/delimit_daemon.py +67 -0
- package/gateway/ai/dispatch_gate.py +399 -0
- package/gateway/ai/governance.py +181 -0
- package/gateway/ai/heartbeat.py +290 -0
- package/gateway/ai/hot_reload.py +1 -2
- package/gateway/ai/led193_daemon/executor.py +9 -0
- package/gateway/ai/ledger_manager.py +90 -4
- package/gateway/ai/ledger_proof.py +127 -0
- package/gateway/ai/license.py +132 -47
- package/gateway/ai/license_core.cpython-310-x86_64-linux-gnu.so +0 -0
- package/gateway/ai/license_core.pyi +1 -1
- package/gateway/ai/notify.py +39 -0
- package/gateway/ai/outreach_loop_daemon.py +349 -0
- package/gateway/ai/outreach_substantive.py +1437 -0
- package/gateway/ai/pro_tools.yaml +167 -0
- package/gateway/ai/reaper.py +70 -0
- package/gateway/ai/reddit_scanner.py +17 -6
- package/gateway/ai/sensing/schema.py +1 -1
- package/gateway/ai/sensing/signal_store.py +0 -1
- package/gateway/ai/server.py +5490 -1602
- package/gateway/ai/social_capability/fit_floor.py +114 -12
- package/gateway/ai/social_queue.py +166 -10
- package/gateway/ai/tdqs_lint.py +611 -0
- package/gateway/ai/tenant_auth.py +329 -0
- package/gateway/ai/tenant_data.py +339 -0
- package/gateway/ai/tenant_paths.py +150 -0
- package/gateway/ai/usage_allowlist.py +198 -0
- package/gateway/ai/workers/base.py +2 -2
- package/gateway/ai/workers/executor.py +32 -3
- package/gateway/ai/workers/outreach_drafter.py +0 -1
- package/gateway/ai/workers/pr_drafter.py +0 -1
- package/gateway/ai/x_ranker.py +12 -2
- package/gateway/core/json_schema_diff.py +25 -1
- package/lib/auth-signin.js +136 -0
- package/lib/auth-signout.js +169 -0
- package/lib/delimit-template.js +11 -0
- package/lib/migration-2092-banner.js +213 -0
- package/package.json +5 -2
- package/server.json +4 -4
- package/scripts/build-license-core.sh +0 -85
- package/scripts/security-check.sh +0 -66
- package/scripts/test-license-core-so.sh +0 -107
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// lib/auth-signout.js
|
|
2
|
+
//
|
|
3
|
+
// LED-2106: convenience wrapper around `rm ~/.delimit/auth.json` that ONLY
|
|
4
|
+
// removes the OAuth-related keys written by `delimit signin` (LED-2100) and
|
|
5
|
+
// preserves the legacy bookkeeping keys (`configured`, `timestamp`, `tools`)
|
|
6
|
+
// written by `lib/auth-setup.js`. Wiping the whole file regresses any user
|
|
7
|
+
// who ran `delimit auth` for tool credential bookkeeping.
|
|
8
|
+
//
|
|
9
|
+
// Design notes
|
|
10
|
+
// - We MERGE-DELETE: read the file, drop the OAuth keys, write the
|
|
11
|
+
// remainder back. If the post-scrub object has zero keys, we delete
|
|
12
|
+
// the file entirely (cleanest state — equivalent to never having run
|
|
13
|
+
// `delimit signin` or `delimit auth`).
|
|
14
|
+
// - File mode is 0600 (owner-only readable). Write is atomic (tmp +
|
|
15
|
+
// rename) — same pattern as lib/auth-signin.js.
|
|
16
|
+
// - If auth.json is missing, malformed, or has no OAuth keys at all,
|
|
17
|
+
// this is a no-op (returns { changed: false }). The CLI prints
|
|
18
|
+
// "Not signed in." and exits 0 in that case.
|
|
19
|
+
//
|
|
20
|
+
// OAuth keys removed:
|
|
21
|
+
// - delimit_token
|
|
22
|
+
// - access_token
|
|
23
|
+
// - signed_in_at
|
|
24
|
+
// - email
|
|
25
|
+
//
|
|
26
|
+
// Returns: { path, changed, deleted, email } so callers can render a
|
|
27
|
+
// consistent success message.
|
|
28
|
+
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
const path = require('path');
|
|
31
|
+
const { delimitHome } = require('./delimit-home');
|
|
32
|
+
|
|
33
|
+
const AUTH_FILE_BASENAME = 'auth.json';
|
|
34
|
+
|
|
35
|
+
// The OAuth-related keys that `delimit signin` writes. Sign-out removes
|
|
36
|
+
// EXACTLY these and nothing else. Adding to this list is a behavior
|
|
37
|
+
// change that needs coordinated review (the gateway resolver in
|
|
38
|
+
// ai/deliberation.py::_read_oauth_token reads `delimit_token` /
|
|
39
|
+
// `access_token`).
|
|
40
|
+
const OAUTH_KEYS = Object.freeze([
|
|
41
|
+
'delimit_token',
|
|
42
|
+
'access_token',
|
|
43
|
+
'signed_in_at',
|
|
44
|
+
'email',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
function authFilePath() {
|
|
48
|
+
return path.join(delimitHome(), AUTH_FILE_BASENAME);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Read the existing auth.json if present, returning a plain object. Returns
|
|
53
|
+
* an empty object on any read/parse error (consistent with auth-signin.js).
|
|
54
|
+
*/
|
|
55
|
+
function readExistingAuth(filePath) {
|
|
56
|
+
if (!fs.existsSync(filePath)) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
61
|
+
const parsed = JSON.parse(raw);
|
|
62
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
65
|
+
return {};
|
|
66
|
+
} catch {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Write `data` to auth.json atomically with mode 0600. Mirrors the pattern
|
|
73
|
+
* used by lib/auth-signin.js::writeAuthToken.
|
|
74
|
+
*/
|
|
75
|
+
function writeAuthAtomic(filePath, data) {
|
|
76
|
+
const tmpPath = filePath + '.tmp';
|
|
77
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
78
|
+
try {
|
|
79
|
+
fs.chmodSync(tmpPath, 0o600);
|
|
80
|
+
} catch {
|
|
81
|
+
// Non-POSIX filesystems may reject chmod; the file is already gated
|
|
82
|
+
// by writeFileSync mode, so this is best-effort.
|
|
83
|
+
}
|
|
84
|
+
fs.renameSync(tmpPath, filePath);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Remove the OAuth keys from ~/.delimit/auth.json, preserving any other
|
|
89
|
+
* keys written by `delimit auth` / `lib/auth-setup.js`. If no OAuth keys
|
|
90
|
+
* are present, this is a no-op. If removing the OAuth keys leaves the
|
|
91
|
+
* file empty, the file is deleted.
|
|
92
|
+
*
|
|
93
|
+
* @param {object} [args]
|
|
94
|
+
* @param {string} [args.home] Override DELIMIT_HOME for tests
|
|
95
|
+
* @returns {{ path: string, changed: boolean, deleted: boolean, email: string }}
|
|
96
|
+
*/
|
|
97
|
+
function removeAuthToken(args) {
|
|
98
|
+
const opts = args || {};
|
|
99
|
+
const home = opts.home || delimitHome();
|
|
100
|
+
const filePath = path.join(home, AUTH_FILE_BASENAME);
|
|
101
|
+
|
|
102
|
+
if (!fs.existsSync(filePath)) {
|
|
103
|
+
return { path: filePath, changed: false, deleted: false, email: '' };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const existing = readExistingAuth(filePath);
|
|
107
|
+
if (Object.keys(existing).length === 0) {
|
|
108
|
+
// Malformed / empty / array. Nothing meaningful to scrub.
|
|
109
|
+
return { path: filePath, changed: false, deleted: false, email: '' };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Detect whether any OAuth keys are actually present. If not, we treat
|
|
113
|
+
// this as "not signed in" and return changed=false WITHOUT touching the
|
|
114
|
+
// file (preserves mtime / inode / mode).
|
|
115
|
+
const hasOAuthKey = OAUTH_KEYS.some((k) => Object.prototype.hasOwnProperty.call(existing, k));
|
|
116
|
+
if (!hasOAuthKey) {
|
|
117
|
+
return { path: filePath, changed: false, deleted: false, email: '' };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const previousEmail = (existing.email || '').toString().trim();
|
|
121
|
+
|
|
122
|
+
// Build the scrubbed object: keep everything that is NOT an OAuth key.
|
|
123
|
+
const scrubbed = {};
|
|
124
|
+
for (const [k, v] of Object.entries(existing)) {
|
|
125
|
+
if (!OAUTH_KEYS.includes(k)) {
|
|
126
|
+
scrubbed[k] = v;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (Object.keys(scrubbed).length === 0) {
|
|
131
|
+
// Nothing left worth keeping — delete the file entirely so the
|
|
132
|
+
// next session sees the cleanest possible state.
|
|
133
|
+
try {
|
|
134
|
+
fs.unlinkSync(filePath);
|
|
135
|
+
} catch {
|
|
136
|
+
// If unlink fails for some reason, fall back to writing an
|
|
137
|
+
// empty object (still valid JSON, still mode 0600).
|
|
138
|
+
writeAuthAtomic(filePath, {});
|
|
139
|
+
return {
|
|
140
|
+
path: filePath,
|
|
141
|
+
changed: true,
|
|
142
|
+
deleted: false,
|
|
143
|
+
email: previousEmail,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
path: filePath,
|
|
148
|
+
changed: true,
|
|
149
|
+
deleted: true,
|
|
150
|
+
email: previousEmail,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
writeAuthAtomic(filePath, scrubbed);
|
|
155
|
+
return {
|
|
156
|
+
path: filePath,
|
|
157
|
+
changed: true,
|
|
158
|
+
deleted: false,
|
|
159
|
+
email: previousEmail,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
authFilePath,
|
|
165
|
+
readExistingAuth,
|
|
166
|
+
removeAuthToken,
|
|
167
|
+
AUTH_FILE_BASENAME,
|
|
168
|
+
OAUTH_KEYS,
|
|
169
|
+
};
|
package/lib/delimit-template.js
CHANGED
|
@@ -51,6 +51,17 @@ Non-trivial engineering work routes through in-session subagents, not in-thread
|
|
|
51
51
|
- **Session end or idle**: call \`delimit_soul_capture\` + \`delimit_session_handoff\`
|
|
52
52
|
- If returning user, summarize what's pending. If new project, call \`delimit_scan\`.
|
|
53
53
|
|
|
54
|
+
### Memory Rules
|
|
55
|
+
Delimit captures routine code changes from git automatically. You do NOT need to call \`memory_store\` for normal work.
|
|
56
|
+
|
|
57
|
+
Use \`delimit_memory_store\` when capturing:
|
|
58
|
+
- **Failed approaches** — "tried X, it didn't work because Y" (git only shows what survived, not dead ends)
|
|
59
|
+
- **Architecture decisions** — "chose PostgreSQL over Redis for sessions because Z"
|
|
60
|
+
- **Key context** — customer requirements, API constraints, deployment gotchas
|
|
61
|
+
- **Things that will confuse the next session** — non-obvious setups, workarounds, tech debt
|
|
62
|
+
|
|
63
|
+
Do NOT store: routine code changes, file lists, commit messages, status updates — these are in git.
|
|
64
|
+
|
|
54
65
|
### Code Development (debounced per edit batch)
|
|
55
66
|
- After editing UI/CSS: call \`delimit_design_validate_responsive\`
|
|
56
67
|
- After editing API specs: call \`delimit_lint\` + \`delimit_drift_check\`
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// lib/migration-2092-banner.js
|
|
2
|
+
//
|
|
3
|
+
// LED-2095: one-time migration banner shown to existing Free-tier users
|
|
4
|
+
// who have BYOK deliberation configured. Surfaces the LED-2092 / LED-2093
|
|
5
|
+
// gateway behavior change: Free-tier BYOK deliberation is now operationally
|
|
6
|
+
// ephemeral (no persistent transcript, no signed attestation, no replay
|
|
7
|
+
// URL). Pro-tier behavior is unchanged.
|
|
8
|
+
//
|
|
9
|
+
// Trigger conditions (ALL must be true):
|
|
10
|
+
// 1. ~/.delimit/models.json exists with at least one BYOK entry
|
|
11
|
+
// (entry.enabled === true AND entry.api_key truthy)
|
|
12
|
+
// 2. User is on the Free tier (no Pro/Premium/Enterprise license active
|
|
13
|
+
// per the same check used by lib/wrap-engine.js)
|
|
14
|
+
// 3. The flag file ~/.delimit/migration_2092.shown does not exist yet
|
|
15
|
+
//
|
|
16
|
+
// On trigger: print the banner to stdout/stderr (caller's choice — we
|
|
17
|
+
// return the banner string and let the caller handle output) and write
|
|
18
|
+
// the flag file with `{ shown_at: ISO8601, version: <package version> }`
|
|
19
|
+
// so the banner never fires again on the same machine.
|
|
20
|
+
//
|
|
21
|
+
// Scope: trigger on the next deliberation-adjacent CLI call (deliberate,
|
|
22
|
+
// models). Do NOT trigger on every CLI call (would be noise). Do NOT
|
|
23
|
+
// trigger for Pro customers (no behavior change for them). Do NOT
|
|
24
|
+
// trigger for Free users without BYOK (they had no transcripts to lose).
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
const { delimitHome, homeSubpath } = require('./delimit-home');
|
|
29
|
+
|
|
30
|
+
const FLAG_FILE_BASENAME = 'migration_2092.shown';
|
|
31
|
+
const MODELS_CONFIG_BASENAME = 'models.json';
|
|
32
|
+
const LICENSE_FILE_BASENAME = 'license.json';
|
|
33
|
+
|
|
34
|
+
// Banner copy — locked. No em-dashes (per LED-2095 brief). No founder
|
|
35
|
+
// identity strings. Positive tone. The Pro upsell is a single line at
|
|
36
|
+
// the end with the pricing URL; do not expand without coordinated copy
|
|
37
|
+
// review.
|
|
38
|
+
const BANNER_BODY = [
|
|
39
|
+
'',
|
|
40
|
+
'Free deliberations now print to stdout only.',
|
|
41
|
+
'',
|
|
42
|
+
'Your existing transcripts under ~/.delimit/memory/deliberations/ are preserved.',
|
|
43
|
+
'New deliberations on Free tier are ephemeral by design.',
|
|
44
|
+
'',
|
|
45
|
+
'Pro turns every deliberation into a signed, replayable attestation',
|
|
46
|
+
'with 365-day retention: delimit.ai/pricing',
|
|
47
|
+
'',
|
|
48
|
+
].join('\n');
|
|
49
|
+
|
|
50
|
+
function flagFilePath() {
|
|
51
|
+
return homeSubpath(FLAG_FILE_BASENAME);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function modelsConfigPath() {
|
|
55
|
+
return homeSubpath(MODELS_CONFIG_BASENAME);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function licenseFilePath() {
|
|
59
|
+
return homeSubpath(LICENSE_FILE_BASENAME);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Detect whether the user has at least one BYOK model entry configured.
|
|
64
|
+
* Mirrors the truthiness rule used by bin/delimit-cli.js::getModelStatus:
|
|
65
|
+
* `entry.enabled === true AND entry.api_key truthy`.
|
|
66
|
+
*/
|
|
67
|
+
function hasByokConfigured() {
|
|
68
|
+
const p = modelsConfigPath();
|
|
69
|
+
if (!fs.existsSync(p)) return false;
|
|
70
|
+
let config;
|
|
71
|
+
try {
|
|
72
|
+
config = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
for (const entry of Object.values(config)) {
|
|
80
|
+
if (entry && typeof entry === 'object' && entry.enabled && entry.api_key) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Detect whether the user has an active Pro/Premium/Enterprise license.
|
|
89
|
+
* Mirrors the rule used by lib/wrap-engine.js::checkQuota — keep these in
|
|
90
|
+
* lockstep. A missing/malformed license.json means Free.
|
|
91
|
+
*/
|
|
92
|
+
function hasProLicense() {
|
|
93
|
+
const p = licenseFilePath();
|
|
94
|
+
if (!fs.existsSync(p)) return false;
|
|
95
|
+
try {
|
|
96
|
+
const lic = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
97
|
+
if (lic && lic.valid && ['pro', 'premium', 'enterprise'].includes(lic.tier)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check whether the migration flag file has already been written. Once
|
|
108
|
+
* written, the banner is suppressed forever on this machine (the file is
|
|
109
|
+
* the source of truth — even a corrupt JSON body counts as "shown" so a
|
|
110
|
+
* partially-written flag does not re-trigger the banner).
|
|
111
|
+
*/
|
|
112
|
+
function hasShownBanner() {
|
|
113
|
+
return fs.existsSync(flagFilePath());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Should the banner trigger right now? All three conditions must hold:
|
|
118
|
+
* BYOK configured, no Pro license, flag file absent.
|
|
119
|
+
*/
|
|
120
|
+
function shouldShowBanner() {
|
|
121
|
+
if (hasShownBanner()) return false;
|
|
122
|
+
if (hasProLicense()) return false;
|
|
123
|
+
if (!hasByokConfigured()) return false;
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Persist the flag file so the banner never fires again. Best-effort:
|
|
129
|
+
* directory is created at 0700 if missing, file is written at default
|
|
130
|
+
* mode (no secrets in this file — just a timestamp + version).
|
|
131
|
+
*
|
|
132
|
+
* @param {object} [args]
|
|
133
|
+
* @param {string} [args.now] ISO8601 override for tests
|
|
134
|
+
* @param {string} [args.version] CLI version string
|
|
135
|
+
*/
|
|
136
|
+
function writeShownFlag(args) {
|
|
137
|
+
const opts = args || {};
|
|
138
|
+
const home = delimitHome();
|
|
139
|
+
if (!fs.existsSync(home)) {
|
|
140
|
+
fs.mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
141
|
+
}
|
|
142
|
+
const payload = {
|
|
143
|
+
shown_at: opts.now || new Date().toISOString(),
|
|
144
|
+
version: opts.version || resolveCliVersion(),
|
|
145
|
+
led: 'LED-2095',
|
|
146
|
+
};
|
|
147
|
+
const tmpPath = flagFilePath() + '.tmp';
|
|
148
|
+
fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2));
|
|
149
|
+
fs.renameSync(tmpPath, flagFilePath());
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function resolveCliVersion() {
|
|
153
|
+
try {
|
|
154
|
+
// package.json sits two levels up from lib/migration-2092-banner.js
|
|
155
|
+
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
|
156
|
+
return pkg.version || 'unknown';
|
|
157
|
+
} catch {
|
|
158
|
+
return 'unknown';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The exact banner text shown to triggered users. Exposed so tests can
|
|
164
|
+
* snapshot it without re-running the trigger logic.
|
|
165
|
+
*/
|
|
166
|
+
function bannerText() {
|
|
167
|
+
return BANNER_BODY;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Composite entry point used by the CLI. If conditions are met, returns
|
|
172
|
+
* the banner string AND writes the flag file. If conditions are not met,
|
|
173
|
+
* returns "" and does not touch the filesystem.
|
|
174
|
+
*
|
|
175
|
+
* The CLI is responsible for printing the returned string. Returning the
|
|
176
|
+
* string (rather than printing here) keeps this module side-effect-light
|
|
177
|
+
* and lets tests assert on the output without capturing stdout.
|
|
178
|
+
*
|
|
179
|
+
* @param {object} [args]
|
|
180
|
+
* @param {string} [args.now] ISO8601 override for the flag-file
|
|
181
|
+
* timestamp (deterministic tests)
|
|
182
|
+
* @param {string} [args.version] Override the CLI version string
|
|
183
|
+
* @returns {{ shown: boolean, text: string }}
|
|
184
|
+
*/
|
|
185
|
+
function maybeShowMigrationBanner(args) {
|
|
186
|
+
if (!shouldShowBanner()) {
|
|
187
|
+
return { shown: false, text: '' };
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
writeShownFlag(args);
|
|
191
|
+
} catch {
|
|
192
|
+
// If we cannot persist the flag, do NOT show the banner —
|
|
193
|
+
// showing it without persisting would re-fire on every call.
|
|
194
|
+
return { shown: false, text: '' };
|
|
195
|
+
}
|
|
196
|
+
return { shown: true, text: bannerText() };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
FLAG_FILE_BASENAME,
|
|
201
|
+
MODELS_CONFIG_BASENAME,
|
|
202
|
+
LICENSE_FILE_BASENAME,
|
|
203
|
+
flagFilePath,
|
|
204
|
+
modelsConfigPath,
|
|
205
|
+
licenseFilePath,
|
|
206
|
+
hasByokConfigured,
|
|
207
|
+
hasProLicense,
|
|
208
|
+
hasShownBanner,
|
|
209
|
+
shouldShowBanner,
|
|
210
|
+
writeShownFlag,
|
|
211
|
+
bannerText,
|
|
212
|
+
maybeShowMigrationBanner,
|
|
213
|
+
};
|
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.
|
|
4
|
+
"version": "4.6.1",
|
|
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": [
|
|
@@ -42,6 +42,9 @@
|
|
|
42
42
|
"!scripts/demo-v420-clean.sh",
|
|
43
43
|
"!scripts/demo-v420-deliberation.sh",
|
|
44
44
|
"!scripts/sync-gateway.sh",
|
|
45
|
+
"!scripts/build-license-core.sh",
|
|
46
|
+
"!scripts/security-check.sh",
|
|
47
|
+
"!scripts/test-license-core-so.sh",
|
|
45
48
|
"!gateway/ai/continuity.py",
|
|
46
49
|
"server.json",
|
|
47
50
|
"README.md",
|
|
@@ -56,7 +59,7 @@
|
|
|
56
59
|
"postinstall": "node scripts/postinstall.js",
|
|
57
60
|
"sync-gateway": "bash scripts/sync-gateway.sh",
|
|
58
61
|
"prepublishOnly": "bash scripts/publish-ci-guard.sh && npm run sync-gateway && bash scripts/build-license-core.sh && bash scripts/security-check.sh",
|
|
59
|
-
"test": "node --test tests/setup-onboarding.test.js tests/setup-matrix.test.js tests/setup-no-clobber.test.js tests/config-export-import.test.js tests/cross-model-hooks.test.js tests/golden-path.test.js tests/v420-features.test.js tests/v43-wrap-engine.test.js tests/v43-trust-page-engine.test.js tests/v43-ai-sbom-engine.test.js tests/attest-mcp.test.js tests/delimit-home.test.js tests/postinstall-hardening.test.js"
|
|
62
|
+
"test": "node --test tests/setup-onboarding.test.js tests/setup-matrix.test.js tests/setup-no-clobber.test.js tests/config-export-import.test.js tests/cross-model-hooks.test.js tests/golden-path.test.js tests/v420-features.test.js tests/v43-wrap-engine.test.js tests/v43-trust-page-engine.test.js tests/v43-ai-sbom-engine.test.js tests/attest-mcp.test.js tests/delimit-home.test.js tests/postinstall-hardening.test.js tests/auth-signin.test.js tests/auth-signout.test.js tests/migration-2092-banner.test.js"
|
|
60
63
|
},
|
|
61
64
|
"keywords": [
|
|
62
65
|
"openapi",
|
package/server.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.delimit-ai/delimit-mcp-server",
|
|
4
|
-
"title": "Delimit —
|
|
5
|
-
"description": "
|
|
4
|
+
"title": "Delimit — The merge gate for AI-written code",
|
|
5
|
+
"description": "The merge gate for AI-written code. Cross-vendor adjudication of every AI-assisted merge with a signed, replayable attestation. Persistent ledger and multi-model deliberation across Claude, Codex, Cursor, and Gemini CLI.",
|
|
6
6
|
"repository": {
|
|
7
7
|
"url": "https://github.com/delimit-ai/delimit-mcp-server",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "4.
|
|
10
|
+
"version": "4.5.13",
|
|
11
11
|
"websiteUrl": "https://delimit.ai",
|
|
12
12
|
"packages": [
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "delimit-cli",
|
|
16
|
-
"version": "4.
|
|
16
|
+
"version": "4.5.13",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# LED-1259: Compile gateway/ai/license_core.py to a native .so via Nuitka,
|
|
3
|
-
# then strip the plaintext .py from the bundle so customers cannot grep
|
|
4
|
-
# the validation logic for bypass identifiers.
|
|
5
|
-
#
|
|
6
|
-
# Linux-only first ship. Mac/Windows expansion is filed as a follow-up
|
|
7
|
-
# ledger item — non-linux customers will hit the Python fallback in
|
|
8
|
-
# license.py (degraded Pro features) until we ship per-platform binaries.
|
|
9
|
-
#
|
|
10
|
-
# Idempotent: safe to re-run; will rebuild on every invocation.
|
|
11
|
-
|
|
12
|
-
set -euo pipefail
|
|
13
|
-
|
|
14
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
15
|
-
NPM_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
16
|
-
AI_DIR="$NPM_ROOT/gateway/ai"
|
|
17
|
-
SRC="$AI_DIR/license_core.py"
|
|
18
|
-
|
|
19
|
-
# ── Platform gate ────────────────────────────────────────────────────
|
|
20
|
-
UNAME_S="$(uname -s)"
|
|
21
|
-
UNAME_M="$(uname -m)"
|
|
22
|
-
if [ "$UNAME_S" != "Linux" ]; then
|
|
23
|
-
echo "⚠️ build-license-core: non-Linux host ($UNAME_S) — skipping compile."
|
|
24
|
-
echo " First ship is linux-only. The bundle will fall back to .py."
|
|
25
|
-
exit 0
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
if [ ! -f "$SRC" ]; then
|
|
29
|
-
echo "❌ Source not found: $SRC"
|
|
30
|
-
exit 1
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
# ── Toolchain check ──────────────────────────────────────────────────
|
|
34
|
-
PY="${PYTHON:-python3}"
|
|
35
|
-
if ! command -v "$PY" >/dev/null 2>&1; then
|
|
36
|
-
echo "❌ python3 not found"
|
|
37
|
-
exit 1
|
|
38
|
-
fi
|
|
39
|
-
|
|
40
|
-
PY_VER="$($PY -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
|
|
41
|
-
echo "🔧 build-license-core: python=$PY ($PY_VER), arch=$UNAME_M"
|
|
42
|
-
|
|
43
|
-
if ! "$PY" -m nuitka --version >/dev/null 2>&1; then
|
|
44
|
-
echo "📦 nuitka not installed — installing via pip..."
|
|
45
|
-
"$PY" -m pip install --quiet --user nuitka
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
NUITKA_VER="$($PY -m nuitka --version 2>&1 | head -1)"
|
|
49
|
-
echo " nuitka=$NUITKA_VER"
|
|
50
|
-
|
|
51
|
-
# ── Compile ──────────────────────────────────────────────────────────
|
|
52
|
-
echo "🔨 Compiling license_core.py → .so (this takes ~30s)..."
|
|
53
|
-
cd "$AI_DIR"
|
|
54
|
-
"$PY" -m nuitka --module --quiet --remove-output --output-dir=. license_core.py
|
|
55
|
-
|
|
56
|
-
# ── Verify output ────────────────────────────────────────────────────
|
|
57
|
-
SO_FILE="$(ls -1 license_core.cpython-*-*.so 2>/dev/null | head -1 || true)"
|
|
58
|
-
if [ -z "$SO_FILE" ] || [ ! -f "$SO_FILE" ]; then
|
|
59
|
-
echo "❌ Compile failed — no .so produced in $AI_DIR"
|
|
60
|
-
ls -la "$AI_DIR"/license_core* 2>&1 || true
|
|
61
|
-
exit 1
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
SO_SIZE="$(stat -c%s "$SO_FILE")"
|
|
65
|
-
echo " ✅ produced: $SO_FILE ($SO_SIZE bytes)"
|
|
66
|
-
|
|
67
|
-
# ── Bypass-identifier scan ───────────────────────────────────────────
|
|
68
|
-
# Customers must not be able to `strings | grep` the .so for known
|
|
69
|
-
# bypass class names. Fail the build if any leak through.
|
|
70
|
-
BYPASS_HITS="$(strings "$SO_FILE" | grep -iE 'DELIMIT_TEST_MODE|DELIMIT_INTERNAL_LICENSE_KEY|JAMSONS' || true)"
|
|
71
|
-
if [ -n "$BYPASS_HITS" ]; then
|
|
72
|
-
echo "❌ Bypass identifiers found in compiled .so:"
|
|
73
|
-
echo "$BYPASS_HITS"
|
|
74
|
-
exit 1
|
|
75
|
-
fi
|
|
76
|
-
echo " ✅ strings-grep clean (no bypass identifiers)"
|
|
77
|
-
|
|
78
|
-
# ── Drop the plaintext source from the bundle ────────────────────────
|
|
79
|
-
# .npmignore + package.json will also exclude it, but removing here is
|
|
80
|
-
# belt-and-suspenders so dev/test inspection of the bundle dir matches
|
|
81
|
-
# what gets packed.
|
|
82
|
-
rm -f "$AI_DIR/license_core.py"
|
|
83
|
-
echo " ✅ removed plaintext license_core.py from bundle"
|
|
84
|
-
|
|
85
|
-
echo "✅ build-license-core complete: $SO_FILE"
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Pre-publish security check — blocks npm publish if secrets are found
|
|
3
|
-
# Run: bash scripts/security-check.sh
|
|
4
|
-
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
echo "🔍 Delimit pre-publish security scan..."
|
|
8
|
-
|
|
9
|
-
FAIL=0
|
|
10
|
-
|
|
11
|
-
# Pack to temp and scan the actual tarball contents
|
|
12
|
-
TMPDIR=$(mktemp -d)
|
|
13
|
-
npm pack --pack-destination "$TMPDIR" --quiet 2>/dev/null
|
|
14
|
-
TARBALL=$(ls "$TMPDIR"/*.tgz)
|
|
15
|
-
tar -xzf "$TARBALL" -C "$TMPDIR"
|
|
16
|
-
|
|
17
|
-
# 1. Credential patterns
|
|
18
|
-
echo -n " Credentials... "
|
|
19
|
-
if grep -rEi '(password|passwd|secret|api_key|apikey)\s*[:=]\s*["\x27][^"\x27]{4,}' "$TMPDIR/package/" --include="*.py" --include="*.js" --include="*.json" 2>/dev/null | grep -v 'environ\|getenv\|process\.env\|os\.environ\|<configured\|example\|placeholder\|REDACTED\|\${credentials\|credentials\.\|security-scan-ignore'; then
|
|
20
|
-
echo "❌ FOUND CREDENTIALS"
|
|
21
|
-
FAIL=1
|
|
22
|
-
else
|
|
23
|
-
echo "✅ clean"
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
# 2. Blocklist terms
|
|
27
|
-
echo -n " Blocklist... "
|
|
28
|
-
BLOCKLIST="jamsonsholdings|Bladabah|Domainvested26|Delimit26|home/jamsons|infracore|crypttrx|\.wr_env"
|
|
29
|
-
if grep -rEi "$BLOCKLIST" "$TMPDIR/package/" --include="*.py" --include="*.js" --include="*.json" 2>/dev/null; then
|
|
30
|
-
echo "❌ BLOCKED TERMS FOUND"
|
|
31
|
-
FAIL=1
|
|
32
|
-
else
|
|
33
|
-
echo "✅ clean"
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
# 3. PII (email addresses that aren't examples)
|
|
37
|
-
echo -n " PII... "
|
|
38
|
-
if grep -rEi '[a-z0-9._%+-]+@(gmail|yahoo|hotmail|outlook|proton|jamsons|wire\.report|domainvested)' "$TMPDIR/package/" --include="*.py" --include="*.js" --include="*.json" 2>/dev/null | grep -v "example\|placeholder\|<configured\|noreply\|e\.g\.\|docstring\|Args:\|Credential resolution"; then
|
|
39
|
-
echo "❌ PII FOUND"
|
|
40
|
-
FAIL=1
|
|
41
|
-
else
|
|
42
|
-
echo "✅ clean"
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
# 4. Proprietary files that shouldn't ship
|
|
46
|
-
echo -n " Proprietary files... "
|
|
47
|
-
PROPRIETARY="social_target\.py|social\.py|founding_users\.py|inbox_daemon\.py|deliberation\.py"
|
|
48
|
-
if find "$TMPDIR/package/" -name "*.py" | grep -Ei "$PROPRIETARY" 2>/dev/null; then
|
|
49
|
-
echo "❌ PROPRIETARY FILES IN PACKAGE"
|
|
50
|
-
FAIL=1
|
|
51
|
-
else
|
|
52
|
-
echo "✅ clean"
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
# Cleanup
|
|
56
|
-
rm -rf "$TMPDIR"
|
|
57
|
-
|
|
58
|
-
if [ $FAIL -ne 0 ]; then
|
|
59
|
-
echo ""
|
|
60
|
-
echo "❌ SECURITY CHECK FAILED — do not publish"
|
|
61
|
-
exit 1
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
echo ""
|
|
65
|
-
echo "✅ All security checks passed"
|
|
66
|
-
exit 0
|