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.
Files changed (53) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +9 -8
  3. package/bin/delimit-cli.js +179 -4
  4. package/bin/delimit-setup.js +46 -6
  5. package/gateway/ai/_compile_status.py +154 -0
  6. package/gateway/ai/agent_dispatch.py +41 -0
  7. package/gateway/ai/backends/git_health.py +175 -0
  8. package/gateway/ai/backends/tools_infra.py +163 -10
  9. package/gateway/ai/cli_contract.py +185 -0
  10. package/gateway/ai/daemon.py +10 -0
  11. package/gateway/ai/daily_digest.py +1 -2
  12. package/gateway/ai/delimit_daemon.py +67 -0
  13. package/gateway/ai/dispatch_gate.py +399 -0
  14. package/gateway/ai/governance.py +181 -0
  15. package/gateway/ai/heartbeat.py +290 -0
  16. package/gateway/ai/hot_reload.py +1 -2
  17. package/gateway/ai/led193_daemon/executor.py +9 -0
  18. package/gateway/ai/ledger_manager.py +90 -4
  19. package/gateway/ai/ledger_proof.py +127 -0
  20. package/gateway/ai/license.py +132 -47
  21. package/gateway/ai/license_core.cpython-310-x86_64-linux-gnu.so +0 -0
  22. package/gateway/ai/license_core.pyi +1 -1
  23. package/gateway/ai/notify.py +39 -0
  24. package/gateway/ai/outreach_loop_daemon.py +349 -0
  25. package/gateway/ai/outreach_substantive.py +1437 -0
  26. package/gateway/ai/pro_tools.yaml +167 -0
  27. package/gateway/ai/reaper.py +70 -0
  28. package/gateway/ai/reddit_scanner.py +17 -6
  29. package/gateway/ai/sensing/schema.py +1 -1
  30. package/gateway/ai/sensing/signal_store.py +0 -1
  31. package/gateway/ai/server.py +5490 -1602
  32. package/gateway/ai/social_capability/fit_floor.py +114 -12
  33. package/gateway/ai/social_queue.py +166 -10
  34. package/gateway/ai/tdqs_lint.py +611 -0
  35. package/gateway/ai/tenant_auth.py +329 -0
  36. package/gateway/ai/tenant_data.py +339 -0
  37. package/gateway/ai/tenant_paths.py +150 -0
  38. package/gateway/ai/usage_allowlist.py +198 -0
  39. package/gateway/ai/workers/base.py +2 -2
  40. package/gateway/ai/workers/executor.py +32 -3
  41. package/gateway/ai/workers/outreach_drafter.py +0 -1
  42. package/gateway/ai/workers/pr_drafter.py +0 -1
  43. package/gateway/ai/x_ranker.py +12 -2
  44. package/gateway/core/json_schema_diff.py +25 -1
  45. package/lib/auth-signin.js +136 -0
  46. package/lib/auth-signout.js +169 -0
  47. package/lib/delimit-template.js +11 -0
  48. package/lib/migration-2092-banner.js +213 -0
  49. package/package.json +5 -2
  50. package/server.json +4 -4
  51. package/scripts/build-license-core.sh +0 -85
  52. package/scripts/security-check.sh +0 -66
  53. 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
+ };
@@ -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.5.13",
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 — API Governance for AI Coding Assistants",
5
- "description": "API governance for AI coding assistants. Breaking changes, policies, cross-model context.",
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.1.40",
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.1.40",
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