@deeplake/hivemind 0.7.60 → 0.7.61
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/openclaw/dist/index.js +1 -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
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Cloud-backed persistent shared memory for AI agents powered by Deeplake",
|
|
9
|
-
"version": "0.7.
|
|
9
|
+
"version": "0.7.61"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "hivemind",
|
|
14
14
|
"description": "Persistent shared memory powered by Deeplake — captures all session activity and provides cross-session, cross-agent memory search",
|
|
15
|
-
"version": "0.7.
|
|
15
|
+
"version": "0.7.61",
|
|
16
16
|
"source": {
|
|
17
17
|
"source": "git-subdir",
|
|
18
18
|
"url": "https://github.com/activeloopai/hivemind.git",
|
|
19
19
|
"path": "claude-code",
|
|
20
|
-
"sha": "
|
|
20
|
+
"sha": "edf4a6520595067cd8c0f6064034014e3fc2790e"
|
|
21
21
|
},
|
|
22
22
|
"homepage": "https://github.com/activeloopai/hivemind"
|
|
23
23
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hivemind",
|
|
3
3
|
"description": "Cloud-backed persistent memory powered by Deeplake — read, write, and share memory across Claude Code sessions and agents",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.61",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Activeloop",
|
|
7
7
|
"url": "https://deeplake.ai"
|
package/openclaw/dist/index.js
CHANGED
|
@@ -1795,7 +1795,7 @@ function extractLatestVersion(body) {
|
|
|
1795
1795
|
return typeof v === "string" && v.length > 0 ? v : null;
|
|
1796
1796
|
}
|
|
1797
1797
|
function getInstalledVersion() {
|
|
1798
|
-
return "0.7.
|
|
1798
|
+
return "0.7.61".length > 0 ? "0.7.61" : null;
|
|
1799
1799
|
}
|
|
1800
1800
|
function isNewer(latest, current) {
|
|
1801
1801
|
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.61",
|
|
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
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Hivemind install verifier — sanity-checks each of the 9 agent integrations.
|
|
3
|
+
#
|
|
4
|
+
# Run this after `npx -y @deeplake/hivemind install`. It checks file
|
|
5
|
+
# placement, config schema, dry-invokes hook scripts, and exercises the
|
|
6
|
+
# MCP server against its initialize handshake. Doesn't require any agent
|
|
7
|
+
# to actually be running — just verifies our installer's footprint.
|
|
8
|
+
#
|
|
9
|
+
# Exits 0 if everything we installed is healthy; 1 if any agent fails.
|
|
10
|
+
# Skips agents whose marker dir doesn't exist (you don't have them).
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# bash scripts/verify-install.sh
|
|
14
|
+
# curl -sSL https://raw.githubusercontent.com/activeloopai/hivemind/main/scripts/verify-install.sh | bash
|
|
15
|
+
|
|
16
|
+
set -u
|
|
17
|
+
|
|
18
|
+
PASS=0
|
|
19
|
+
FAIL=0
|
|
20
|
+
SKIP=0
|
|
21
|
+
|
|
22
|
+
green() { printf "\033[0;32m%s\033[0m" "$1"; }
|
|
23
|
+
red() { printf "\033[0;31m%s\033[0m" "$1"; }
|
|
24
|
+
gray() { printf "\033[0;90m%s\033[0m" "$1"; }
|
|
25
|
+
yellow(){ printf "\033[0;33m%s\033[0m" "$1"; }
|
|
26
|
+
|
|
27
|
+
ok() { echo " $(green PASS) $1"; PASS=$((PASS+1)); }
|
|
28
|
+
bad() { echo " $(red FAIL) $1${2:+ — $2}"; FAIL=$((FAIL+1)); }
|
|
29
|
+
skip() { echo " $(gray SKIP) $1${2:+ — $2}"; SKIP=$((SKIP+1)); }
|
|
30
|
+
|
|
31
|
+
section() { echo; echo "$(yellow "▎ $1")"; }
|
|
32
|
+
|
|
33
|
+
require_jq() {
|
|
34
|
+
command -v jq >/dev/null 2>&1 || {
|
|
35
|
+
echo "$(red "fatal:") jq not found on PATH (install jq for this script to run)" >&2
|
|
36
|
+
exit 2
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
require_jq
|
|
40
|
+
|
|
41
|
+
# ───────────────────────────────────────────────────────────────────────
|
|
42
|
+
# Claude Code — marketplace plugin
|
|
43
|
+
section "Claude Code"
|
|
44
|
+
if [ -d "$HOME/.claude" ]; then
|
|
45
|
+
if command -v claude >/dev/null 2>&1; then
|
|
46
|
+
if claude plugin list 2>/dev/null | grep -q "hivemind@hivemind"; then
|
|
47
|
+
ok "claude plugin list shows hivemind@hivemind"
|
|
48
|
+
if claude plugin list 2>/dev/null | grep -q "✔ enabled"; then
|
|
49
|
+
ok "plugin enabled"
|
|
50
|
+
else
|
|
51
|
+
bad "plugin not enabled" "run: claude plugin enable hivemind@hivemind"
|
|
52
|
+
fi
|
|
53
|
+
else
|
|
54
|
+
bad "claude plugin list does not show hivemind@hivemind" "rerun: hivemind claude install"
|
|
55
|
+
fi
|
|
56
|
+
else
|
|
57
|
+
bad "claude CLI not found on PATH" "Claude Code is not installed correctly"
|
|
58
|
+
fi
|
|
59
|
+
else
|
|
60
|
+
skip "Claude Code" "no ~/.claude"
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# ───────────────────────────────────────────────────────────────────────
|
|
64
|
+
# Codex — ~/.codex/hooks.json + bundle
|
|
65
|
+
section "Codex"
|
|
66
|
+
if [ -d "$HOME/.codex" ]; then
|
|
67
|
+
HOOKS="$HOME/.codex/hooks.json"
|
|
68
|
+
if [ -f "$HOOKS" ]; then
|
|
69
|
+
if jq -e '.hooks.SessionStart' "$HOOKS" >/dev/null 2>&1; then
|
|
70
|
+
ok "$HOOKS has SessionStart"
|
|
71
|
+
else
|
|
72
|
+
bad "$HOOKS missing SessionStart"
|
|
73
|
+
fi
|
|
74
|
+
else
|
|
75
|
+
bad "$HOOKS not found" "rerun: hivemind codex install"
|
|
76
|
+
fi
|
|
77
|
+
if [ -x "$HOME/.codex/hivemind/bundle/session-start.js" ]; then
|
|
78
|
+
ok "session-start.js executable"
|
|
79
|
+
else
|
|
80
|
+
bad "session-start.js missing or not executable"
|
|
81
|
+
fi
|
|
82
|
+
else
|
|
83
|
+
skip "Codex" "no ~/.codex"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# ───────────────────────────────────────────────────────────────────────
|
|
87
|
+
# OpenClaw — ~/.openclaw/extensions/hivemind/
|
|
88
|
+
section "OpenClaw"
|
|
89
|
+
if [ -d "$HOME/.openclaw" ]; then
|
|
90
|
+
EXT="$HOME/.openclaw/extensions/hivemind"
|
|
91
|
+
if [ -f "$EXT/openclaw.plugin.json" ]; then
|
|
92
|
+
ok "openclaw.plugin.json present"
|
|
93
|
+
if jq -e '.contracts.tools' "$EXT/openclaw.plugin.json" >/dev/null 2>&1; then
|
|
94
|
+
ok "manifest declares contracts.tools"
|
|
95
|
+
else
|
|
96
|
+
bad "manifest missing contracts.tools (stale install)"
|
|
97
|
+
fi
|
|
98
|
+
else
|
|
99
|
+
bad "$EXT/openclaw.plugin.json not found" "rerun: hivemind claw install"
|
|
100
|
+
fi
|
|
101
|
+
[ -f "$EXT/dist/index.js" ] && ok "dist/index.js present" || bad "dist/index.js not found"
|
|
102
|
+
else
|
|
103
|
+
skip "OpenClaw" "no ~/.openclaw"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# ───────────────────────────────────────────────────────────────────────
|
|
107
|
+
# Cursor — ~/.cursor/hooks.json (1.7+ schema)
|
|
108
|
+
section "Cursor"
|
|
109
|
+
if [ -d "$HOME/.cursor" ]; then
|
|
110
|
+
HOOKS="$HOME/.cursor/hooks.json"
|
|
111
|
+
if [ -f "$HOOKS" ]; then
|
|
112
|
+
for ev in sessionStart beforeSubmitPrompt postToolUse afterAgentResponse stop sessionEnd; do
|
|
113
|
+
if jq -e ".hooks.$ev[0].command" "$HOOKS" >/dev/null 2>&1; then
|
|
114
|
+
ok "hooks.$ev wired"
|
|
115
|
+
else
|
|
116
|
+
bad "hooks.$ev missing" "rerun: hivemind cursor install"
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
# Dry-invoke session-start.js
|
|
120
|
+
payload='{"hook_event_name":"sessionStart","session_id":"verify","conversation_id":"verify","workspace_roots":["/tmp"]}'
|
|
121
|
+
if echo "$payload" | timeout 5 node "$HOME/.cursor/hivemind/bundle/session-start.js" 2>/dev/null | jq -e '.additional_context' >/dev/null 2>&1; then
|
|
122
|
+
ok "session-start.js produces valid additional_context JSON"
|
|
123
|
+
else
|
|
124
|
+
bad "session-start.js dry-invoke did not return additional_context"
|
|
125
|
+
fi
|
|
126
|
+
else
|
|
127
|
+
bad "$HOOKS not found" "rerun: hivemind cursor install"
|
|
128
|
+
fi
|
|
129
|
+
else
|
|
130
|
+
skip "Cursor" "no ~/.cursor"
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# ───────────────────────────────────────────────────────────────────────
|
|
134
|
+
# Hermes Agent — skill drop
|
|
135
|
+
section "Hermes Agent"
|
|
136
|
+
if [ -d "$HOME/.hermes" ]; then
|
|
137
|
+
SKILL="$HOME/.hermes/skills/hivemind-memory/SKILL.md"
|
|
138
|
+
if [ -f "$SKILL" ]; then
|
|
139
|
+
ok "SKILL.md present at $SKILL"
|
|
140
|
+
if grep -q "Hivemind Memory" "$SKILL"; then
|
|
141
|
+
ok "skill content looks right"
|
|
142
|
+
else
|
|
143
|
+
bad "skill content missing 'Hivemind Memory' header"
|
|
144
|
+
fi
|
|
145
|
+
else
|
|
146
|
+
bad "$SKILL not found" "rerun: hivemind hermes install"
|
|
147
|
+
fi
|
|
148
|
+
else
|
|
149
|
+
skip "Hermes Agent" "no ~/.hermes"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# ───────────────────────────────────────────────────────────────────────
|
|
153
|
+
# pi — AGENTS.md + extension
|
|
154
|
+
# No per-agent SKILL.md: pi reads skills from both ~/.pi/agent/skills/ AND
|
|
155
|
+
# ~/.agents/skills/ (the agentskills.io shared dir), so dropping a local
|
|
156
|
+
# skill would collide with the codex installer's shared symlink. AGENTS.md
|
|
157
|
+
# (auto-loaded every turn) plus the registered hivemind tools cover the
|
|
158
|
+
# action surface; see install-pi.ts for the rationale.
|
|
159
|
+
section "pi"
|
|
160
|
+
if [ -d "$HOME/.pi" ]; then
|
|
161
|
+
AGENTS="$HOME/.pi/agent/AGENTS.md"
|
|
162
|
+
if [ -f "$AGENTS" ] && grep -q "BEGIN hivemind-memory" "$AGENTS"; then
|
|
163
|
+
ok "AGENTS.md has BEGIN hivemind-memory marker"
|
|
164
|
+
else
|
|
165
|
+
bad "AGENTS.md missing hivemind block" "rerun: hivemind pi install"
|
|
166
|
+
fi
|
|
167
|
+
EXT="$HOME/.pi/agent/extensions/hivemind.ts"
|
|
168
|
+
if [ -f "$EXT" ]; then
|
|
169
|
+
ok "extension installed at $EXT"
|
|
170
|
+
# Sanity-check that key surfaces are wired in the installed extension.
|
|
171
|
+
if grep -q "registerTool" "$EXT" && grep -q "hivemind_search" "$EXT" && \
|
|
172
|
+
grep -q "hivemind_read" "$EXT" && grep -q "hivemind_index" "$EXT"; then
|
|
173
|
+
ok "extension registers hivemind_search / hivemind_read / hivemind_index"
|
|
174
|
+
else
|
|
175
|
+
bad "extension missing one or more hivemind_* tool registrations"
|
|
176
|
+
fi
|
|
177
|
+
if grep -q 'pi.on("tool_result"' "$EXT" && grep -q 'pi.on("input"' "$EXT"; then
|
|
178
|
+
ok "extension subscribes to input + tool_result for autocapture"
|
|
179
|
+
else
|
|
180
|
+
bad "extension missing input/tool_result subscriptions"
|
|
181
|
+
fi
|
|
182
|
+
else
|
|
183
|
+
bad "$EXT not found" "rerun: hivemind pi install"
|
|
184
|
+
fi
|
|
185
|
+
else
|
|
186
|
+
skip "pi" "no ~/.pi"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# ───────────────────────────────────────────────────────────────────────
|
|
190
|
+
# MCP server (used by Hermes; reused by any future MCP-aware client).
|
|
191
|
+
section "Hivemind MCP server"
|
|
192
|
+
SERVER="$HOME/.hivemind/mcp/server.js"
|
|
193
|
+
if [ -f "$SERVER" ]; then
|
|
194
|
+
ok "server.js installed at $SERVER"
|
|
195
|
+
# Initialize + tools/list handshake. Expect "hivemind_search" in response.
|
|
196
|
+
init='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"verify","version":"1.0"}}}'
|
|
197
|
+
inited='{"jsonrpc":"2.0","method":"notifications/initialized"}'
|
|
198
|
+
list='{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
|
|
199
|
+
resp=$( ( printf '%s\n%s\n%s\n' "$init" "$inited" "$list"; sleep 1 ) | timeout 8 node "$SERVER" 2>/dev/null )
|
|
200
|
+
if echo "$resp" | grep -q '"hivemind_search"'; then
|
|
201
|
+
ok "tools/list returns hivemind_search / hivemind_read / hivemind_index"
|
|
202
|
+
else
|
|
203
|
+
bad "MCP server did not list expected tools" "check ~/.deeplake/credentials.json"
|
|
204
|
+
fi
|
|
205
|
+
else
|
|
206
|
+
skip "MCP server" "no ~/.hivemind/mcp/server.js (no MCP-aware agent installed yet)"
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
# ───────────────────────────────────────────────────────────────────────
|
|
210
|
+
echo
|
|
211
|
+
echo "$(yellow "▎ Summary")"
|
|
212
|
+
echo " $(green PASS): $PASS"
|
|
213
|
+
echo " $(red FAIL): $FAIL"
|
|
214
|
+
echo " $(gray SKIP): $SKIP (agent not present on this machine)"
|
|
215
|
+
echo
|
|
216
|
+
|
|
217
|
+
if [ "$FAIL" -gt 0 ]; then exit 1; fi
|
|
218
|
+
exit 0
|