@deeplake/hivemind 0.7.60 → 0.7.62
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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/bundle/cli.js +34 -0
- package/codex/bundle/capture.js +34 -0
- package/codex/bundle/commands/auth-login.js +34 -0
- package/codex/bundle/graph-pull-worker.js +34 -0
- package/codex/bundle/pre-tool-use.js +34 -0
- package/codex/bundle/session-start-setup.js +34 -0
- package/codex/bundle/session-start.js +34 -0
- package/codex/bundle/shell/deeplake-shell.js +34 -0
- package/codex/bundle/stop.js +34 -0
- package/cursor/bundle/capture.js +34 -0
- package/cursor/bundle/commands/auth-login.js +34 -0
- package/cursor/bundle/graph-pull-worker.js +34 -0
- package/cursor/bundle/pre-tool-use.js +34 -0
- package/cursor/bundle/session-start.js +34 -0
- package/cursor/bundle/shell/deeplake-shell.js +34 -0
- package/hermes/bundle/capture.js +34 -0
- package/hermes/bundle/commands/auth-login.js +34 -0
- package/hermes/bundle/graph-pull-worker.js +34 -0
- package/hermes/bundle/pre-tool-use.js +34 -0
- package/hermes/bundle/session-start.js +34 -0
- package/hermes/bundle/shell/deeplake-shell.js +34 -0
- package/mcp/bundle/server.js +34 -0
- package/openclaw/dist/index.js +29 -1
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/package.json +2 -1
- package/scripts/audit-openclaw-bundle.mjs +187 -0
- package/scripts/ensure-tree-sitter.mjs +91 -0
- package/scripts/pack-check.mjs +29 -0
- package/scripts/sync-versions.d.mts +15 -0
- package/scripts/sync-versions.mjs +103 -0
- package/scripts/verify-install.sh +218 -0
package/mcp/bundle/server.js
CHANGED
|
@@ -23683,6 +23683,9 @@ function traceSql(msg) {
|
|
|
23683
23683
|
log3(msg);
|
|
23684
23684
|
}
|
|
23685
23685
|
var _signalledBalanceExhausted = false;
|
|
23686
|
+
var _signalledLowBalance = false;
|
|
23687
|
+
var LOW_BALANCE_THRESHOLD_CENTS = 200;
|
|
23688
|
+
var BALANCE_HEADER = "X-Activeloop-Balance-Cents";
|
|
23686
23689
|
function maybeSignalBalanceExhausted(status, bodyText) {
|
|
23687
23690
|
if (status !== 402)
|
|
23688
23691
|
return;
|
|
@@ -23703,6 +23706,35 @@ function maybeSignalBalanceExhausted(status, bodyText) {
|
|
|
23703
23706
|
log3(`enqueue balance-exhausted failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
23704
23707
|
});
|
|
23705
23708
|
}
|
|
23709
|
+
function signalLowBalanceFromHeader(resp) {
|
|
23710
|
+
if (_signalledLowBalance)
|
|
23711
|
+
return;
|
|
23712
|
+
const raw = resp.headers?.get?.(BALANCE_HEADER);
|
|
23713
|
+
if (!raw)
|
|
23714
|
+
return;
|
|
23715
|
+
if (!/^-?\d+$/.test(raw.trim()))
|
|
23716
|
+
return;
|
|
23717
|
+
const balance = Number(raw.trim());
|
|
23718
|
+
if (!Number.isFinite(balance))
|
|
23719
|
+
return;
|
|
23720
|
+
if (balance >= LOW_BALANCE_THRESHOLD_CENTS)
|
|
23721
|
+
return;
|
|
23722
|
+
if (balance <= 0)
|
|
23723
|
+
return;
|
|
23724
|
+
_signalledLowBalance = true;
|
|
23725
|
+
log3(`balance below threshold (${balance}\xA2) \u2014 enqueuing low-balance banner`);
|
|
23726
|
+
enqueueNotification({
|
|
23727
|
+
id: "low-balance-warning",
|
|
23728
|
+
severity: "warn",
|
|
23729
|
+
transient: true,
|
|
23730
|
+
title: "Your org's Hivemind balance is running low",
|
|
23731
|
+
body: `Only $${(balance / 100).toFixed(2)} of prepaid balance remains. Admins can top up at ${billingUrl()}; otherwise ask an org admin to top up before requests start failing.`,
|
|
23732
|
+
dedupKey: { reason: "low-balance" }
|
|
23733
|
+
}).catch((e) => {
|
|
23734
|
+
_signalledLowBalance = false;
|
|
23735
|
+
log3(`enqueue low-balance failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
23736
|
+
});
|
|
23737
|
+
}
|
|
23706
23738
|
function billingUrl() {
|
|
23707
23739
|
try {
|
|
23708
23740
|
const c = loadCredentials();
|
|
@@ -23828,6 +23860,7 @@ var DeeplakeApi = class {
|
|
|
23828
23860
|
}
|
|
23829
23861
|
throw lastError;
|
|
23830
23862
|
}
|
|
23863
|
+
signalLowBalanceFromHeader(resp);
|
|
23831
23864
|
if (resp.ok) {
|
|
23832
23865
|
const raw = await resp.json();
|
|
23833
23866
|
if (!raw?.rows || !raw?.columns)
|
|
@@ -23988,6 +24021,7 @@ var DeeplakeApi = class {
|
|
|
23988
24021
|
...deeplakeClientHeader()
|
|
23989
24022
|
}
|
|
23990
24023
|
});
|
|
24024
|
+
signalLowBalanceFromHeader(resp);
|
|
23991
24025
|
if (resp.ok) {
|
|
23992
24026
|
const data = await resp.json();
|
|
23993
24027
|
return {
|
package/openclaw/dist/index.js
CHANGED
|
@@ -494,6 +494,9 @@ function traceSql(msg) {
|
|
|
494
494
|
if (globalThis.__hivemind_tuning__.HIVEMIND_DEBUG === "1") log3(msg);
|
|
495
495
|
}
|
|
496
496
|
var _signalledBalanceExhausted = false;
|
|
497
|
+
var _signalledLowBalance = false;
|
|
498
|
+
var LOW_BALANCE_THRESHOLD_CENTS = 200;
|
|
499
|
+
var BALANCE_HEADER = "X-Activeloop-Balance-Cents";
|
|
497
500
|
function maybeSignalBalanceExhausted(status, bodyText) {
|
|
498
501
|
if (status !== 402) return;
|
|
499
502
|
if (!bodyText.includes("balance_cents")) return;
|
|
@@ -511,6 +514,29 @@ function maybeSignalBalanceExhausted(status, bodyText) {
|
|
|
511
514
|
log3(`enqueue balance-exhausted failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
512
515
|
});
|
|
513
516
|
}
|
|
517
|
+
function signalLowBalanceFromHeader(resp) {
|
|
518
|
+
if (_signalledLowBalance) return;
|
|
519
|
+
const raw = resp.headers?.get?.(BALANCE_HEADER);
|
|
520
|
+
if (!raw) return;
|
|
521
|
+
if (!/^-?\d+$/.test(raw.trim())) return;
|
|
522
|
+
const balance = Number(raw.trim());
|
|
523
|
+
if (!Number.isFinite(balance)) return;
|
|
524
|
+
if (balance >= LOW_BALANCE_THRESHOLD_CENTS) return;
|
|
525
|
+
if (balance <= 0) return;
|
|
526
|
+
_signalledLowBalance = true;
|
|
527
|
+
log3(`balance below threshold (${balance}\xA2) \u2014 enqueuing low-balance banner`);
|
|
528
|
+
enqueueNotification({
|
|
529
|
+
id: "low-balance-warning",
|
|
530
|
+
severity: "warn",
|
|
531
|
+
transient: true,
|
|
532
|
+
title: "Your org's Hivemind balance is running low",
|
|
533
|
+
body: `Only $${(balance / 100).toFixed(2)} of prepaid balance remains. Admins can top up at ${billingUrl()}; otherwise ask an org admin to top up before requests start failing.`,
|
|
534
|
+
dedupKey: { reason: "low-balance" }
|
|
535
|
+
}).catch((e) => {
|
|
536
|
+
_signalledLowBalance = false;
|
|
537
|
+
log3(`enqueue low-balance failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
514
540
|
function billingUrl() {
|
|
515
541
|
try {
|
|
516
542
|
const c = loadCredentials();
|
|
@@ -636,6 +662,7 @@ var DeeplakeApi = class {
|
|
|
636
662
|
}
|
|
637
663
|
throw lastError;
|
|
638
664
|
}
|
|
665
|
+
signalLowBalanceFromHeader(resp);
|
|
639
666
|
if (resp.ok) {
|
|
640
667
|
const raw = await resp.json();
|
|
641
668
|
if (!raw?.rows || !raw?.columns) return [];
|
|
@@ -797,6 +824,7 @@ var DeeplakeApi = class {
|
|
|
797
824
|
...deeplakeClientHeader()
|
|
798
825
|
}
|
|
799
826
|
});
|
|
827
|
+
signalLowBalanceFromHeader(resp);
|
|
800
828
|
if (resp.ok) {
|
|
801
829
|
const data = await resp.json();
|
|
802
830
|
return {
|
|
@@ -1795,7 +1823,7 @@ function extractLatestVersion(body) {
|
|
|
1795
1823
|
return typeof v === "string" && v.length > 0 ? v : null;
|
|
1796
1824
|
}
|
|
1797
1825
|
function getInstalledVersion() {
|
|
1798
|
-
return "0.7.
|
|
1826
|
+
return "0.7.62".length > 0 ? "0.7.62" : null;
|
|
1799
1827
|
}
|
|
1800
1828
|
function isNewer(latest, current) {
|
|
1801
1829
|
const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
|
package/openclaw/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeplake/hivemind",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.62",
|
|
4
4
|
"description": "Cloud-backed persistent shared memory for AI agents powered by Deeplake",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"openclaw/openclaw.plugin.json",
|
|
27
27
|
"openclaw/package.json",
|
|
28
28
|
".claude-plugin",
|
|
29
|
+
"scripts",
|
|
29
30
|
"README.md",
|
|
30
31
|
"LICENSE"
|
|
31
32
|
],
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Local replication of ClawHub's static-analysis scanner against the openclaw
|
|
4
|
+
* plugin bundle. Runs the same per-file regex rules ClawHub uses at publish
|
|
5
|
+
* time so we can see flags BEFORE shipping a release instead of after.
|
|
6
|
+
*
|
|
7
|
+
* Rules are replicated (not imported) from openclaw's skill-scanner — that
|
|
8
|
+
* code lives in our research-reference checkout under ~/al-projects/ext/ and
|
|
9
|
+
* is third-party we don't own. Re-sync these rules if upstream changes them.
|
|
10
|
+
*
|
|
11
|
+
* Reference: openclaw repo, src/security/skill-scanner.ts:147-206
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* node scripts/audit-openclaw-bundle.mjs # scan openclaw/dist
|
|
15
|
+
* node scripts/audit-openclaw-bundle.mjs <path> # scan a specific dir
|
|
16
|
+
*
|
|
17
|
+
* Exits non-zero if any "critical" or "warn" finding is reported.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync, statSync } from "node:fs";
|
|
21
|
+
import { readdir } from "node:fs/promises";
|
|
22
|
+
import { join, extname } from "node:path";
|
|
23
|
+
|
|
24
|
+
// Parse args: positional SCAN_DIR + optional --criticals-only flag.
|
|
25
|
+
//
|
|
26
|
+
// --criticals-only exits non-zero only on `critical` findings, treating
|
|
27
|
+
// `warn` as advisory. Used by the CI release gate so we block on
|
|
28
|
+
// definitively-malicious patterns but tolerate fundamental warns like
|
|
29
|
+
// the worker's "readFileSync + fetch in same file" — irreducible without
|
|
30
|
+
// splitting the worker into multiple shipped files. Local dev still
|
|
31
|
+
// runs with the default strict mode (exits on any finding) so we
|
|
32
|
+
// surface every drift before it ships.
|
|
33
|
+
const rawArgs = process.argv.slice(2);
|
|
34
|
+
const STRICT_MODE = !rawArgs.includes("--criticals-only");
|
|
35
|
+
const SCAN_DIR = rawArgs.find(a => !a.startsWith("--")) ?? "openclaw/dist";
|
|
36
|
+
const SCANNABLE_EXT = new Set([".js", ".mjs", ".cjs"]);
|
|
37
|
+
const MAX_FILE_BYTES = 1024 * 1024; // 1MB; matches upstream default
|
|
38
|
+
|
|
39
|
+
// ---- LINE_RULES (per-line; both pattern AND requiresContext must match) ----
|
|
40
|
+
const LINE_RULES = [
|
|
41
|
+
{
|
|
42
|
+
ruleId: "dangerous-exec",
|
|
43
|
+
severity: "critical",
|
|
44
|
+
message: "Shell command execution detected (child_process)",
|
|
45
|
+
pattern: /\b(exec|execSync|spawn|spawnSync|execFile|execFileSync)\s*\(/,
|
|
46
|
+
requiresContext: /child_process/,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
ruleId: "dynamic-code-execution",
|
|
50
|
+
severity: "critical",
|
|
51
|
+
message: "Dynamic code execution detected",
|
|
52
|
+
pattern: /\beval\s*\(|new\s+Function\s*\(/,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
ruleId: "crypto-mining",
|
|
56
|
+
severity: "critical",
|
|
57
|
+
message: "Possible crypto-mining reference detected",
|
|
58
|
+
pattern: /stratum\+tcp|stratum\+ssl|coinhive|cryptonight|xmrig/i,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
ruleId: "suspicious-network",
|
|
62
|
+
severity: "warn",
|
|
63
|
+
message: "WebSocket connection to non-standard port",
|
|
64
|
+
pattern: /new\s+WebSocket\s*\(\s*["']wss?:\/\/[^"']*:(\d+)/,
|
|
65
|
+
portCheck: true,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const STANDARD_PORTS = new Set([80, 443, 8080, 8443, 3000]);
|
|
70
|
+
const NETWORK_SEND = /\bfetch\s*\(|\bpost\s*\(|\.\s*post\s*\(|http\.request\s*\(/i;
|
|
71
|
+
|
|
72
|
+
// ---- SOURCE_RULES (whole file; both pattern AND requiresContext must match) ----
|
|
73
|
+
const SOURCE_RULES = [
|
|
74
|
+
{
|
|
75
|
+
ruleId: "potential-exfiltration",
|
|
76
|
+
severity: "warn",
|
|
77
|
+
message: "File read combined with network send (possible exfiltration)",
|
|
78
|
+
pattern: /readFileSync|readFile/,
|
|
79
|
+
requiresContext: NETWORK_SEND,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
ruleId: "obfuscated-code-hex",
|
|
83
|
+
severity: "warn",
|
|
84
|
+
message: "Hex-encoded string sequence detected (possible obfuscation)",
|
|
85
|
+
pattern: /(\\x[0-9a-fA-F]{2}){6,}/,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
ruleId: "obfuscated-code-base64",
|
|
89
|
+
severity: "warn",
|
|
90
|
+
message: "Large base64 payload with decode call detected (possible obfuscation)",
|
|
91
|
+
pattern: /(?:atob|Buffer\.from)\s*\(\s*["'][A-Za-z0-9+/=]{200,}["']/,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
ruleId: "env-harvesting",
|
|
95
|
+
severity: "critical",
|
|
96
|
+
message: "Environment variable access combined with network send (possible credential harvesting)",
|
|
97
|
+
pattern: /process\.env/,
|
|
98
|
+
requiresContext: NETWORK_SEND,
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
function truncate(s, n = 120) { return s.length <= n ? s : s.slice(0, n) + "…"; }
|
|
103
|
+
|
|
104
|
+
function scanFile(path) {
|
|
105
|
+
const stat = statSync(path);
|
|
106
|
+
if (stat.size > MAX_FILE_BYTES) {
|
|
107
|
+
return [{ ruleId: "file-too-large", severity: "info", file: path, line: 0, message: `Skipped (${stat.size} bytes > ${MAX_FILE_BYTES} byte limit)`, evidence: "" }];
|
|
108
|
+
}
|
|
109
|
+
const source = readFileSync(path, "utf-8");
|
|
110
|
+
const lines = source.split("\n");
|
|
111
|
+
const findings = [];
|
|
112
|
+
|
|
113
|
+
for (const rule of LINE_RULES) {
|
|
114
|
+
if (rule.requiresContext && !rule.requiresContext.test(source)) continue;
|
|
115
|
+
for (let i = 0; i < lines.length; i++) {
|
|
116
|
+
const line = lines[i];
|
|
117
|
+
const m = rule.pattern.exec(line);
|
|
118
|
+
if (!m) continue;
|
|
119
|
+
if (rule.portCheck) {
|
|
120
|
+
const port = Number.parseInt(m[1], 10);
|
|
121
|
+
if (STANDARD_PORTS.has(port)) continue;
|
|
122
|
+
}
|
|
123
|
+
findings.push({ ruleId: rule.ruleId, severity: rule.severity, file: path, line: i + 1, message: rule.message, evidence: truncate(line.trim()) });
|
|
124
|
+
break; // one finding per line-rule per file
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const rule of SOURCE_RULES) {
|
|
129
|
+
if (!rule.pattern.test(source)) continue;
|
|
130
|
+
if (rule.requiresContext && !rule.requiresContext.test(source)) continue;
|
|
131
|
+
let matchLine = 0, matchEvidence = "";
|
|
132
|
+
for (let i = 0; i < lines.length; i++) {
|
|
133
|
+
if (rule.pattern.test(lines[i])) { matchLine = i + 1; matchEvidence = lines[i].trim(); break; }
|
|
134
|
+
}
|
|
135
|
+
if (matchLine === 0) { matchLine = 1; matchEvidence = source.slice(0, 120); }
|
|
136
|
+
findings.push({ ruleId: rule.ruleId, severity: rule.severity, file: path, line: matchLine, message: rule.message, evidence: truncate(matchEvidence) });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return findings;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function* walk(dir) {
|
|
143
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
144
|
+
const p = join(dir, entry.name);
|
|
145
|
+
if (entry.isDirectory()) yield* walk(p);
|
|
146
|
+
else if (entry.isFile() && SCANNABLE_EXT.has(extname(entry.name).toLowerCase())) yield p;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const SEVERITY_RANK = { info: 0, warn: 1, critical: 2 };
|
|
151
|
+
const SEVERITY_ICON = { info: "·", warn: "!", critical: "✗" };
|
|
152
|
+
|
|
153
|
+
const allFindings = [];
|
|
154
|
+
let scannedFiles = 0;
|
|
155
|
+
for await (const file of walk(SCAN_DIR)) {
|
|
156
|
+
scannedFiles++;
|
|
157
|
+
for (const f of scanFile(file)) allFindings.push(f);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const counts = { info: 0, warn: 0, critical: 0 };
|
|
161
|
+
for (const f of allFindings) counts[f.severity] = (counts[f.severity] ?? 0) + 1;
|
|
162
|
+
|
|
163
|
+
console.log(`\nScanned ${scannedFiles} file(s) under ${SCAN_DIR}/\n`);
|
|
164
|
+
|
|
165
|
+
if (allFindings.length === 0) {
|
|
166
|
+
console.log("✓ No findings. Bundle is clean against ClawHub's static-analysis rules.\n");
|
|
167
|
+
process.exit(0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
allFindings.sort((a, b) => (SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity]) || a.file.localeCompare(b.file) || a.line - b.line);
|
|
171
|
+
for (const f of allFindings) {
|
|
172
|
+
console.log(`${SEVERITY_ICON[f.severity]} [${f.severity.toUpperCase()}] ${f.ruleId}`);
|
|
173
|
+
console.log(` ${f.file}:${f.line}`);
|
|
174
|
+
console.log(` ${f.message}`);
|
|
175
|
+
if (f.evidence) console.log(` > ${f.evidence}`);
|
|
176
|
+
console.log();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const summary = `${counts.critical} critical, ${counts.warn} warn, ${counts.info} info`;
|
|
180
|
+
console.log(`Summary: ${summary}\n`);
|
|
181
|
+
if (!STRICT_MODE && counts.critical === 0 && counts.warn > 0) {
|
|
182
|
+
console.log(`(--criticals-only: warns are advisory and do NOT block. Re-run without the flag for strict local checks.)\n`);
|
|
183
|
+
}
|
|
184
|
+
const shouldFail = STRICT_MODE
|
|
185
|
+
? (counts.critical > 0 || counts.warn > 0)
|
|
186
|
+
: counts.critical > 0;
|
|
187
|
+
process.exit(shouldFail ? 1 : 0);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Ensures the native tree-sitter bindings are loadable on this platform / Node ABI.
|
|
3
|
+
//
|
|
4
|
+
// Why this exists: tree-sitter@0.21.x ships no linux-arm64 prebuild, and
|
|
5
|
+
// tree-sitter-typescript@0.23.x ships a mislabeled (x86-64) one. On linux-arm64
|
|
6
|
+
// both must be compiled from source, and under Node >=22 that compile requires C++20
|
|
7
|
+
// (tree-sitter@0.21's binding.gyp does not request it). tree-sitter is declared as an
|
|
8
|
+
// optionalDependency so this expected arm64 build failure does not abort `npm install`;
|
|
9
|
+
// this script then heals it afterwards.
|
|
10
|
+
//
|
|
11
|
+
// On platforms where the shipped prebuilds work (x64 / darwin / CI) this is a fast
|
|
12
|
+
// no-op and never touches anything. It is intentionally non-fatal: if no toolchain is
|
|
13
|
+
// available it warns and exits 0 rather than breaking the install.
|
|
14
|
+
import { execSync } from 'node:child_process';
|
|
15
|
+
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
|
|
18
|
+
const ROOT = process.cwd();
|
|
19
|
+
const require = createRequire(`${ROOT}/`);
|
|
20
|
+
const PKGS = ['tree-sitter', 'tree-sitter-typescript'];
|
|
21
|
+
|
|
22
|
+
function bindingsLoad() {
|
|
23
|
+
try {
|
|
24
|
+
const Parser = require('tree-sitter');
|
|
25
|
+
const TS = require('tree-sitter-typescript').typescript;
|
|
26
|
+
const parser = new Parser();
|
|
27
|
+
parser.setLanguage(TS);
|
|
28
|
+
parser.parse('const x = 1;');
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (process.env.ENSURE_TS_RUNNING) process.exit(0); // recursion guard for the nested npm calls below
|
|
36
|
+
if (bindingsLoad()) process.exit(0); // healthy prebuild / prior build → nothing to do
|
|
37
|
+
|
|
38
|
+
console.error('[ensure-tree-sitter] native bindings not loadable on this platform — building from source...');
|
|
39
|
+
|
|
40
|
+
const pkg = JSON.parse(readFileSync(`${ROOT}/package.json`, 'utf8'));
|
|
41
|
+
const declared = { ...pkg.dependencies, ...pkg.optionalDependencies };
|
|
42
|
+
|
|
43
|
+
const env = { ...process.env, ENSURE_TS_RUNNING: '1' };
|
|
44
|
+
if (process.platform !== 'win32') {
|
|
45
|
+
// Node >=22 V8 headers require C++20; tree-sitter@0.21's binding.gyp doesn't request it.
|
|
46
|
+
env.CXXFLAGS = `${process.env.CXXFLAGS ?? ''} -std=c++20`.trim();
|
|
47
|
+
}
|
|
48
|
+
const run = (cmd) => execSync(cmd, { stdio: 'inherit', env, cwd: ROOT });
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// 1. Re-fetch any package npm dropped — an optional dependency whose build failed is
|
|
52
|
+
// removed from node_modules. --ignore-scripts: fetch only, so the compile below is
|
|
53
|
+
// the single source of truth and the project build isn't triggered prematurely.
|
|
54
|
+
const missing = PKGS.filter((n) => !existsSync(`${ROOT}/node_modules/${n}/package.json`));
|
|
55
|
+
if (missing.length) {
|
|
56
|
+
const specs = missing.map((n) => `${n}@${declared[n] ?? 'latest'}`);
|
|
57
|
+
run(`npm install ${specs.join(' ')} --no-save --ignore-scripts`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 2. Force a from-source compile. These packages install via node-gyp-build, which uses
|
|
61
|
+
// a local prebuild when present and otherwise compiles from source — no network. By
|
|
62
|
+
// removing the (absent or wrong-arch) prebuilds plus any stale build, the rebuild is
|
|
63
|
+
// guaranteed to compile locally, and node-gyp-build loads build/Release ahead of
|
|
64
|
+
// prebuilds, so the correct binary always wins.
|
|
65
|
+
for (const n of PKGS) {
|
|
66
|
+
rmSync(`${ROOT}/node_modules/${n}/prebuilds`, { recursive: true, force: true });
|
|
67
|
+
rmSync(`${ROOT}/node_modules/${n}/build`, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
run(`npm rebuild ${PKGS.join(' ')}`);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error('[ensure-tree-sitter] rebuild command failed:', err.message);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (bindingsLoad()) {
|
|
75
|
+
console.error('[ensure-tree-sitter] OK — bindings compiled from source and loadable.');
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Strict mode: turn the warning into a hard failure. Opt-in via
|
|
80
|
+
// HIVEMIND_STRICT_POSTINSTALL=1 — set by this repo's own CI workflows so a
|
|
81
|
+
// heal failure surfaces as a red check on the PR instead of getting swallowed
|
|
82
|
+
// and re-emerging downstream as `tsc: Cannot find module 'tree-sitter'`.
|
|
83
|
+
// Default stays non-fatal so end-user consumers of @deeplake/hivemind never
|
|
84
|
+
// get a hard install break — the runtime check at use-time is enough for them.
|
|
85
|
+
const strict = process.env.HIVEMIND_STRICT_POSTINSTALL === '1';
|
|
86
|
+
console.error(
|
|
87
|
+
'[ensure-tree-sitter] WARNING: tree-sitter bindings still unavailable. ' +
|
|
88
|
+
'Install a C/C++ toolchain and re-run `npm run rebuild:native`.' +
|
|
89
|
+
(strict ? ' (strict mode — failing this install)' : ' (non-fatal)'),
|
|
90
|
+
);
|
|
91
|
+
process.exit(strict ? 1 : 0);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Refuse a publish if `npm pack` would include filenames that should
|
|
3
|
+
// never ship to npm — credentials, CI workflows, git internals.
|
|
4
|
+
// Catches a future PR widening package.json's `files` array (or
|
|
5
|
+
// switching to a permissive .npmignore) before any token is touched.
|
|
6
|
+
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const FORBIDDEN = [
|
|
10
|
+
/(^|\/)\.npmrc$/,
|
|
11
|
+
/(^|\/)\.env($|\.)/,
|
|
12
|
+
/(^|\/)secrets?(\/|$)/,
|
|
13
|
+
/(^|\/)\.github(\/|$)/,
|
|
14
|
+
/(^|\/)\.git(\/|$)/,
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const raw = execFileSync('npm', ['pack', '--dry-run', '--json'], {
|
|
18
|
+
encoding: 'utf8',
|
|
19
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
20
|
+
});
|
|
21
|
+
const entries = JSON.parse(raw)[0].files.map((f) => f.path);
|
|
22
|
+
const hits = entries.filter((p) => FORBIDDEN.some((rx) => rx.test(p)));
|
|
23
|
+
|
|
24
|
+
if (hits.length) {
|
|
25
|
+
console.error('Refusing to publish — forbidden filenames in tarball:');
|
|
26
|
+
for (const h of hits) console.error(' ' + h);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
console.log(`pack-check OK — ${entries.length} files, no forbidden patterns`);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const SCALAR_TARGETS: readonly string[];
|
|
2
|
+
export declare const MARKETPLACE_PATH: string;
|
|
3
|
+
|
|
4
|
+
export interface SyncResult {
|
|
5
|
+
writes: number;
|
|
6
|
+
skips: number;
|
|
7
|
+
version: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SyncOptions {
|
|
11
|
+
root?: string;
|
|
12
|
+
log?: (msg: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export declare function syncVersions(opts?: SyncOptions): SyncResult;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Sync the version field across all manifests from the single source-of-truth
|
|
3
|
+
// in package.json. Runs as a `prebuild` hook so esbuild's version-define
|
|
4
|
+
// inlines the same value into bundles.
|
|
5
|
+
//
|
|
6
|
+
// Idempotent: skips writes when a target already matches.
|
|
7
|
+
// Exits non-zero if any target file is missing or if package.json has no version.
|
|
8
|
+
|
|
9
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const SOURCE = "package.json";
|
|
14
|
+
|
|
15
|
+
// Scalar targets: each has a single top-level `version` field tracking package.json.
|
|
16
|
+
export const SCALAR_TARGETS = [
|
|
17
|
+
".claude-plugin/plugin.json",
|
|
18
|
+
"claude-code/.claude-plugin/plugin.json",
|
|
19
|
+
"openclaw/openclaw.plugin.json",
|
|
20
|
+
"openclaw/package.json",
|
|
21
|
+
"codex/package.json",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// Marketplace target: has BOTH metadata.version AND every plugins[].version.
|
|
25
|
+
export const MARKETPLACE_PATH = ".claude-plugin/marketplace.json";
|
|
26
|
+
|
|
27
|
+
function readJsonAt(root, relPath) {
|
|
28
|
+
const full = resolve(root, relPath);
|
|
29
|
+
if (!existsSync(full)) {
|
|
30
|
+
throw new Error(`sync-versions: missing target ${relPath}`);
|
|
31
|
+
}
|
|
32
|
+
return JSON.parse(readFileSync(full, "utf-8"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function writeJsonAt(root, relPath, obj) {
|
|
36
|
+
writeFileSync(resolve(root, relPath), JSON.stringify(obj, null, 2) + "\n");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function syncVersions({ root = process.cwd(), log = (m) => console.error(m) } = {}) {
|
|
40
|
+
const source = readJsonAt(root, SOURCE);
|
|
41
|
+
const version = source.version;
|
|
42
|
+
if (!version || typeof version !== "string") {
|
|
43
|
+
throw new Error(`sync-versions: ${SOURCE} has no string \`version\` field`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let writes = 0;
|
|
47
|
+
let skips = 0;
|
|
48
|
+
|
|
49
|
+
for (const target of SCALAR_TARGETS) {
|
|
50
|
+
const data = readJsonAt(root, target);
|
|
51
|
+
if (data.version === version) {
|
|
52
|
+
log(`sync-versions: ${target} already at ${version}`);
|
|
53
|
+
skips++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const old = data.version;
|
|
57
|
+
data.version = version;
|
|
58
|
+
writeJsonAt(root, target, data);
|
|
59
|
+
log(`sync-versions: ${target}: ${old} -> ${version}`);
|
|
60
|
+
writes++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const marketplace = readJsonAt(root, MARKETPLACE_PATH);
|
|
64
|
+
let mpChanged = false;
|
|
65
|
+
if (marketplace.metadata?.version !== version) {
|
|
66
|
+
const old = marketplace.metadata?.version;
|
|
67
|
+
marketplace.metadata = marketplace.metadata || {};
|
|
68
|
+
marketplace.metadata.version = version;
|
|
69
|
+
log(`sync-versions: ${MARKETPLACE_PATH} metadata.version: ${old} -> ${version}`);
|
|
70
|
+
mpChanged = true;
|
|
71
|
+
}
|
|
72
|
+
if (Array.isArray(marketplace.plugins)) {
|
|
73
|
+
for (const plugin of marketplace.plugins) {
|
|
74
|
+
if (plugin.version !== version) {
|
|
75
|
+
const old = plugin.version;
|
|
76
|
+
plugin.version = version;
|
|
77
|
+
log(`sync-versions: ${MARKETPLACE_PATH} plugins[${plugin.name}].version: ${old} -> ${version}`);
|
|
78
|
+
mpChanged = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (mpChanged) {
|
|
83
|
+
writeJsonAt(root, MARKETPLACE_PATH, marketplace);
|
|
84
|
+
writes++;
|
|
85
|
+
} else {
|
|
86
|
+
log(`sync-versions: ${MARKETPLACE_PATH} already at ${version}`);
|
|
87
|
+
skips++;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
log(`sync-versions: ${writes} written, ${skips} unchanged`);
|
|
91
|
+
return { writes, skips, version };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Script mode — only runs when invoked directly, not when imported by tests.
|
|
95
|
+
const __entryUrl = process.argv[1] ? fileURLToPath(import.meta.url) === resolve(process.argv[1]) : false;
|
|
96
|
+
if (__entryUrl) {
|
|
97
|
+
try {
|
|
98
|
+
syncVersions();
|
|
99
|
+
} catch (e) {
|
|
100
|
+
console.error(e.message);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|