@deeplake/hivemind 0.7.41 → 0.7.45
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 +1258 -199
- package/codex/bundle/session-start.js +1 -0
- package/cursor/bundle/session-start.js +1 -0
- package/hermes/bundle/session-start.js +1 -0
- package/openclaw/dist/index.js +1 -1
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/package.json +1 -1
package/bundle/cli.js
CHANGED
|
@@ -144,23 +144,23 @@ function warn(msg) {
|
|
|
144
144
|
function confirm(message, defaultYes = true) {
|
|
145
145
|
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
146
146
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
147
|
-
return new Promise((
|
|
147
|
+
return new Promise((resolve3) => {
|
|
148
148
|
rl.question(`${message} ${hint} `, (answer) => {
|
|
149
149
|
rl.close();
|
|
150
150
|
const a = answer.trim().toLowerCase();
|
|
151
151
|
if (a === "")
|
|
152
|
-
|
|
152
|
+
resolve3(defaultYes);
|
|
153
153
|
else
|
|
154
|
-
|
|
154
|
+
resolve3(a === "y" || a === "yes");
|
|
155
155
|
});
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
158
|
function promptLine(message) {
|
|
159
159
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
160
|
-
return new Promise((
|
|
160
|
+
return new Promise((resolve3) => {
|
|
161
161
|
rl.question(message, (answer) => {
|
|
162
162
|
rl.close();
|
|
163
|
-
|
|
163
|
+
resolve3(answer.trim());
|
|
164
164
|
});
|
|
165
165
|
});
|
|
166
166
|
}
|
|
@@ -4745,7 +4745,7 @@ function getQueryTimeoutMs() {
|
|
|
4745
4745
|
return Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
|
|
4746
4746
|
}
|
|
4747
4747
|
function sleep2(ms) {
|
|
4748
|
-
return new Promise((
|
|
4748
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
4749
4749
|
}
|
|
4750
4750
|
function isTimeoutError(error) {
|
|
4751
4751
|
const name = error instanceof Error ? error.name.toLowerCase() : "";
|
|
@@ -4775,7 +4775,7 @@ var Semaphore = class {
|
|
|
4775
4775
|
this.active++;
|
|
4776
4776
|
return;
|
|
4777
4777
|
}
|
|
4778
|
-
await new Promise((
|
|
4778
|
+
await new Promise((resolve3) => this.waiting.push(resolve3));
|
|
4779
4779
|
}
|
|
4780
4780
|
release() {
|
|
4781
4781
|
this.active--;
|
|
@@ -5417,25 +5417,202 @@ if (process.argv[1] && process.argv[1].endsWith("auth-login.js")) {
|
|
|
5417
5417
|
});
|
|
5418
5418
|
}
|
|
5419
5419
|
|
|
5420
|
-
// dist/src/commands/
|
|
5421
|
-
import {
|
|
5422
|
-
import { homedir as
|
|
5423
|
-
import { dirname as
|
|
5420
|
+
// dist/src/commands/dashboard.js
|
|
5421
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync13 } from "node:fs";
|
|
5422
|
+
import { homedir as homedir14 } from "node:os";
|
|
5423
|
+
import { dirname as dirname6, join as join27, resolve as resolve2 } from "node:path";
|
|
5424
5424
|
|
|
5425
|
-
// dist/src/
|
|
5426
|
-
import { existsSync as
|
|
5427
|
-
import {
|
|
5425
|
+
// dist/src/dashboard/data.js
|
|
5426
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
|
|
5427
|
+
import { homedir as homedir13 } from "node:os";
|
|
5428
|
+
import { join as join25 } from "node:path";
|
|
5429
|
+
|
|
5430
|
+
// dist/src/notifications/sources/org-stats.js
|
|
5431
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "node:fs";
|
|
5432
|
+
import { homedir as homedir10 } from "node:os";
|
|
5433
|
+
import { dirname as dirname3, join as join20 } from "node:path";
|
|
5434
|
+
var log5 = (msg) => log2("notifications-org-stats", msg);
|
|
5435
|
+
var FETCH_TIMEOUT_MS = 1500;
|
|
5436
|
+
var DEFAULT_API_URL3 = "https://api.deeplake.ai";
|
|
5437
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
5438
|
+
function cacheFilePath() {
|
|
5439
|
+
return join20(homedir10(), ".deeplake", "hivemind-stats-cache.json");
|
|
5440
|
+
}
|
|
5441
|
+
function cacheScopeKey(creds) {
|
|
5442
|
+
return JSON.stringify({
|
|
5443
|
+
apiUrl: creds.apiUrl ?? DEFAULT_API_URL3,
|
|
5444
|
+
orgId: creds.orgId ?? "",
|
|
5445
|
+
userName: creds.userName ?? ""
|
|
5446
|
+
});
|
|
5447
|
+
}
|
|
5448
|
+
function scopeFromServer(s) {
|
|
5449
|
+
const n = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
5450
|
+
return {
|
|
5451
|
+
sessionsCount: n(s?.sessions_count),
|
|
5452
|
+
memoryRecallCount: n(s?.memory_recall_count),
|
|
5453
|
+
memorySearchBytes: n(s?.memory_search_bytes)
|
|
5454
|
+
};
|
|
5455
|
+
}
|
|
5456
|
+
function readCache(scopeKey) {
|
|
5457
|
+
if (!existsSync15(cacheFilePath()))
|
|
5458
|
+
return {};
|
|
5459
|
+
try {
|
|
5460
|
+
const parsed = JSON.parse(readFileSync15(cacheFilePath(), "utf-8"));
|
|
5461
|
+
if (!parsed || typeof parsed !== "object")
|
|
5462
|
+
return {};
|
|
5463
|
+
if (parsed.scopeKey !== scopeKey)
|
|
5464
|
+
return {};
|
|
5465
|
+
if (typeof parsed.fetchedAt !== "number")
|
|
5466
|
+
return {};
|
|
5467
|
+
const age = Date.now() - parsed.fetchedAt;
|
|
5468
|
+
const data = parsed.data;
|
|
5469
|
+
if (!data || typeof data !== "object" || !data.org || !data.user)
|
|
5470
|
+
return {};
|
|
5471
|
+
if (age >= 0 && age < CACHE_TTL_MS)
|
|
5472
|
+
return { fresh: data };
|
|
5473
|
+
return { stale: data };
|
|
5474
|
+
} catch (e) {
|
|
5475
|
+
log5(`cache read failed: ${e?.message ?? String(e)}`);
|
|
5476
|
+
return {};
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
function writeCache(scopeKey, data) {
|
|
5480
|
+
try {
|
|
5481
|
+
mkdirSync7(dirname3(cacheFilePath()), { recursive: true });
|
|
5482
|
+
const body = { fetchedAt: Date.now(), scopeKey, data };
|
|
5483
|
+
writeFileSync11(cacheFilePath(), JSON.stringify(body), "utf-8");
|
|
5484
|
+
} catch (e) {
|
|
5485
|
+
log5(`cache write failed: ${e?.message ?? String(e)}`);
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
async function fetchOrgStats(creds) {
|
|
5489
|
+
if (!creds?.token)
|
|
5490
|
+
return null;
|
|
5491
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL3;
|
|
5492
|
+
const scopeKey = cacheScopeKey(creds);
|
|
5493
|
+
const { fresh, stale } = readCache(scopeKey);
|
|
5494
|
+
if (fresh) {
|
|
5495
|
+
log5("cache hit \u2014 returning fresh org stats");
|
|
5496
|
+
return fresh;
|
|
5497
|
+
}
|
|
5498
|
+
const url = `${apiUrl}/me/hivemind-stats`;
|
|
5499
|
+
const ctrl = new AbortController();
|
|
5500
|
+
const timeoutHandle = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
5501
|
+
try {
|
|
5502
|
+
const resp = await fetch(url, {
|
|
5503
|
+
headers: {
|
|
5504
|
+
Authorization: `Bearer ${creds.token}`,
|
|
5505
|
+
...creds.orgId ? { "X-Activeloop-Org-Id": creds.orgId } : {}
|
|
5506
|
+
},
|
|
5507
|
+
signal: ctrl.signal
|
|
5508
|
+
});
|
|
5509
|
+
if (!resp.ok) {
|
|
5510
|
+
log5(`fetch ${url} returned ${resp.status}`);
|
|
5511
|
+
return stale ?? null;
|
|
5512
|
+
}
|
|
5513
|
+
const body = await resp.json();
|
|
5514
|
+
if (!body || typeof body !== "object") {
|
|
5515
|
+
log5(`fetch ${url} returned malformed body`);
|
|
5516
|
+
return stale ?? null;
|
|
5517
|
+
}
|
|
5518
|
+
const data = {
|
|
5519
|
+
org: scopeFromServer(body.org),
|
|
5520
|
+
user: scopeFromServer(body.user)
|
|
5521
|
+
};
|
|
5522
|
+
writeCache(scopeKey, data);
|
|
5523
|
+
log5(`fetched org stats from ${apiUrl}`);
|
|
5524
|
+
return data;
|
|
5525
|
+
} catch (e) {
|
|
5526
|
+
log5(`fetch ${url} failed: ${e?.message ?? String(e)}`);
|
|
5527
|
+
return stale ?? null;
|
|
5528
|
+
} finally {
|
|
5529
|
+
clearTimeout(timeoutHandle);
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
|
|
5533
|
+
// dist/src/notifications/usage-tracker.js
|
|
5534
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync16, readdirSync as readdirSync2 } from "node:fs";
|
|
5535
|
+
import { dirname as dirname4, join as join21 } from "node:path";
|
|
5536
|
+
import { homedir as homedir11 } from "node:os";
|
|
5537
|
+
var log6 = (msg) => log2("usage-tracker", msg);
|
|
5538
|
+
function statsFilePath() {
|
|
5539
|
+
return join21(homedir11(), ".deeplake", "usage-stats.jsonl");
|
|
5540
|
+
}
|
|
5541
|
+
function readUsageRecords() {
|
|
5542
|
+
try {
|
|
5543
|
+
if (!existsSync16(statsFilePath()))
|
|
5544
|
+
return [];
|
|
5545
|
+
const raw = readFileSync16(statsFilePath(), "utf-8");
|
|
5546
|
+
const out = [];
|
|
5547
|
+
for (const line of raw.split("\n")) {
|
|
5548
|
+
const trimmed = line.trim();
|
|
5549
|
+
if (!trimmed)
|
|
5550
|
+
continue;
|
|
5551
|
+
try {
|
|
5552
|
+
const rec = JSON.parse(trimmed);
|
|
5553
|
+
if (typeof rec.endedAt === "string" && typeof rec.sessionId === "string") {
|
|
5554
|
+
out.push({
|
|
5555
|
+
endedAt: rec.endedAt,
|
|
5556
|
+
sessionId: rec.sessionId,
|
|
5557
|
+
memorySearchBytes: typeof rec.memorySearchBytes === "number" ? rec.memorySearchBytes : 0,
|
|
5558
|
+
memorySearchCount: typeof rec.memorySearchCount === "number" ? rec.memorySearchCount : 0
|
|
5559
|
+
});
|
|
5560
|
+
}
|
|
5561
|
+
} catch {
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
return out;
|
|
5565
|
+
} catch (e) {
|
|
5566
|
+
log6(`readUsageRecords failed: ${e?.message ?? String(e)}`);
|
|
5567
|
+
return [];
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
function sumMetric(records, key) {
|
|
5571
|
+
let total = 0;
|
|
5572
|
+
for (const r of records) {
|
|
5573
|
+
const v = r[key];
|
|
5574
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
5575
|
+
total += v;
|
|
5576
|
+
}
|
|
5577
|
+
return total;
|
|
5578
|
+
}
|
|
5579
|
+
function countUserGeneratedSkills(userName) {
|
|
5580
|
+
if (!userName)
|
|
5581
|
+
return 0;
|
|
5582
|
+
const dir = join21(homedir11(), ".claude", "skills");
|
|
5583
|
+
if (!existsSync16(dir))
|
|
5584
|
+
return 0;
|
|
5585
|
+
const suffix = `--${userName}`;
|
|
5586
|
+
try {
|
|
5587
|
+
let count = 0;
|
|
5588
|
+
for (const name of readdirSync2(dir)) {
|
|
5589
|
+
const idx = name.lastIndexOf(suffix);
|
|
5590
|
+
if (idx > 0 && idx + suffix.length === name.length)
|
|
5591
|
+
count += 1;
|
|
5592
|
+
}
|
|
5593
|
+
return count;
|
|
5594
|
+
} catch (e) {
|
|
5595
|
+
log6(`countUserGeneratedSkills readdir failed: ${e?.message ?? String(e)}`);
|
|
5596
|
+
return 0;
|
|
5597
|
+
}
|
|
5598
|
+
}
|
|
5599
|
+
|
|
5600
|
+
// dist/src/skillify/state.js
|
|
5601
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync12, writeSync, mkdirSync as mkdirSync9, renameSync as renameSync5, rmdirSync, existsSync as existsSync18, lstatSync as lstatSync3, unlinkSync as unlinkSync8, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
|
|
5602
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
5603
|
+
import { createHash } from "node:crypto";
|
|
5604
|
+
import { join as join24, basename } from "node:path";
|
|
5428
5605
|
|
|
5429
5606
|
// dist/src/skillify/legacy-migration.js
|
|
5430
|
-
import { existsSync as
|
|
5431
|
-
import { dirname as
|
|
5607
|
+
import { existsSync as existsSync17, renameSync as renameSync4 } from "node:fs";
|
|
5608
|
+
import { dirname as dirname5, join as join23 } from "node:path";
|
|
5432
5609
|
|
|
5433
5610
|
// dist/src/skillify/state-dir.js
|
|
5434
|
-
import { homedir as
|
|
5435
|
-
import { join as
|
|
5611
|
+
import { homedir as homedir12 } from "node:os";
|
|
5612
|
+
import { join as join22 } from "node:path";
|
|
5436
5613
|
function getStateDir() {
|
|
5437
5614
|
const override = process.env.HIVEMIND_STATE_DIR?.trim();
|
|
5438
|
-
return override && override.length > 0 ? override :
|
|
5615
|
+
return override && override.length > 0 ? override : join22(homedir12(), ".deeplake", "state", "skillify");
|
|
5439
5616
|
}
|
|
5440
5617
|
|
|
5441
5618
|
// dist/src/skillify/legacy-migration.js
|
|
@@ -5448,10 +5625,10 @@ function migrateLegacyStateDir() {
|
|
|
5448
5625
|
return;
|
|
5449
5626
|
attempted = true;
|
|
5450
5627
|
const current = getStateDir();
|
|
5451
|
-
const legacy =
|
|
5452
|
-
if (!
|
|
5628
|
+
const legacy = join23(dirname5(current), "skilify");
|
|
5629
|
+
if (!existsSync17(legacy))
|
|
5453
5630
|
return;
|
|
5454
|
-
if (
|
|
5631
|
+
if (existsSync17(current))
|
|
5455
5632
|
return;
|
|
5456
5633
|
try {
|
|
5457
5634
|
renameSync4(legacy, current);
|
|
@@ -5466,18 +5643,859 @@ function migrateLegacyStateDir() {
|
|
|
5466
5643
|
}
|
|
5467
5644
|
}
|
|
5468
5645
|
|
|
5646
|
+
// dist/src/skillify/state.js
|
|
5647
|
+
var YIELD_BUF = new Int32Array(new SharedArrayBuffer(4));
|
|
5648
|
+
var TRIGGER_THRESHOLD = (() => {
|
|
5649
|
+
const n = Number(process.env.HIVEMIND_SKILLIFY_EVERY_N_TURNS ?? "");
|
|
5650
|
+
return Number.isInteger(n) && n > 0 ? n : 20;
|
|
5651
|
+
})();
|
|
5652
|
+
var DEFAULT_PORTS = {
|
|
5653
|
+
http: "80",
|
|
5654
|
+
https: "443",
|
|
5655
|
+
ssh: "22",
|
|
5656
|
+
git: "9418"
|
|
5657
|
+
};
|
|
5658
|
+
function normalizeGitRemoteUrl(url) {
|
|
5659
|
+
let s = url.trim();
|
|
5660
|
+
const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
|
|
5661
|
+
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
|
|
5662
|
+
if (schemeMatch)
|
|
5663
|
+
s = s.slice(schemeMatch[0].length);
|
|
5664
|
+
if (!scheme) {
|
|
5665
|
+
const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
|
|
5666
|
+
if (scp)
|
|
5667
|
+
s = `${scp[1]}/${scp[2]}`;
|
|
5668
|
+
}
|
|
5669
|
+
s = s.replace(/^[^@/]+@/, "");
|
|
5670
|
+
if (scheme && DEFAULT_PORTS[scheme]) {
|
|
5671
|
+
s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
|
|
5672
|
+
}
|
|
5673
|
+
s = s.replace(/\.git\/?$/i, "");
|
|
5674
|
+
s = s.replace(/\/+$/, "");
|
|
5675
|
+
return s.toLowerCase();
|
|
5676
|
+
}
|
|
5677
|
+
function deriveProjectKey(cwd) {
|
|
5678
|
+
const project = basename(cwd) || "unknown";
|
|
5679
|
+
let signature = null;
|
|
5680
|
+
try {
|
|
5681
|
+
const raw = execSync2("git config --get remote.origin.url", {
|
|
5682
|
+
cwd,
|
|
5683
|
+
encoding: "utf-8",
|
|
5684
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5685
|
+
}).trim();
|
|
5686
|
+
signature = raw ? normalizeGitRemoteUrl(raw) : null;
|
|
5687
|
+
} catch {
|
|
5688
|
+
}
|
|
5689
|
+
const input = signature ?? cwd;
|
|
5690
|
+
const key = createHash("sha1").update(input).digest("hex").slice(0, 16);
|
|
5691
|
+
return { key, project };
|
|
5692
|
+
}
|
|
5693
|
+
|
|
5694
|
+
// dist/src/dashboard/data.js
|
|
5695
|
+
var log7 = (msg) => log2("dashboard-data", msg);
|
|
5696
|
+
var BYTES_PER_TOKEN = 4;
|
|
5697
|
+
var SAVINGS_MULTIPLIER = 1.7;
|
|
5698
|
+
function graphsRoot() {
|
|
5699
|
+
return process.env.HIVEMIND_GRAPHS_HOME ?? join25(homedir13(), ".hivemind", "graphs");
|
|
5700
|
+
}
|
|
5701
|
+
function bytesToSavedTokens(bytes) {
|
|
5702
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
5703
|
+
return 0;
|
|
5704
|
+
const delivered = bytes / BYTES_PER_TOKEN;
|
|
5705
|
+
return (SAVINGS_MULTIPLIER - 1) * delivered;
|
|
5706
|
+
}
|
|
5707
|
+
function resolveSnapshot(repoDir) {
|
|
5708
|
+
const snapshotsDir = join25(repoDir, "snapshots");
|
|
5709
|
+
if (!existsSync19(snapshotsDir))
|
|
5710
|
+
return null;
|
|
5711
|
+
let snapshotPath = null;
|
|
5712
|
+
const pointer = join25(repoDir, "latest-commit.txt");
|
|
5713
|
+
if (existsSync19(pointer)) {
|
|
5714
|
+
try {
|
|
5715
|
+
const sha = readFileSync18(pointer, "utf-8").trim();
|
|
5716
|
+
if (sha) {
|
|
5717
|
+
const candidate = join25(snapshotsDir, `${sha}.json`);
|
|
5718
|
+
if (existsSync19(candidate))
|
|
5719
|
+
snapshotPath = candidate;
|
|
5720
|
+
else
|
|
5721
|
+
log7(`latest-commit.txt points at missing ${sha}.json \u2014 scanning snapshots/`);
|
|
5722
|
+
}
|
|
5723
|
+
} catch (e) {
|
|
5724
|
+
log7(`latest-commit.txt read failed: ${e?.message ?? String(e)}`);
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
if (!snapshotPath) {
|
|
5728
|
+
try {
|
|
5729
|
+
const candidates = readdirSync3(snapshotsDir).filter((name) => name.endsWith(".json")).map((name) => {
|
|
5730
|
+
const full = join25(snapshotsDir, name);
|
|
5731
|
+
return { full, mtime: statSync3(full).mtimeMs };
|
|
5732
|
+
}).sort((a, b) => b.mtime - a.mtime);
|
|
5733
|
+
if (candidates.length > 0)
|
|
5734
|
+
snapshotPath = candidates[0].full;
|
|
5735
|
+
} catch (e) {
|
|
5736
|
+
log7(`snapshots/ scan failed: ${e?.message ?? String(e)}`);
|
|
5737
|
+
}
|
|
5738
|
+
}
|
|
5739
|
+
if (!snapshotPath)
|
|
5740
|
+
return null;
|
|
5741
|
+
let raw;
|
|
5742
|
+
try {
|
|
5743
|
+
raw = readFileSync18(snapshotPath, "utf-8");
|
|
5744
|
+
} catch (e) {
|
|
5745
|
+
log7(`snapshot read failed: ${e?.message ?? String(e)}`);
|
|
5746
|
+
return null;
|
|
5747
|
+
}
|
|
5748
|
+
let parsed;
|
|
5749
|
+
try {
|
|
5750
|
+
parsed = JSON.parse(raw);
|
|
5751
|
+
} catch (e) {
|
|
5752
|
+
log7(`snapshot parse failed: ${e?.message ?? String(e)}`);
|
|
5753
|
+
return null;
|
|
5754
|
+
}
|
|
5755
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.nodes) || !Array.isArray(parsed.links)) {
|
|
5756
|
+
log7("snapshot shape invalid (missing nodes/links arrays)");
|
|
5757
|
+
return null;
|
|
5758
|
+
}
|
|
5759
|
+
return {
|
|
5760
|
+
commitSha: parsed.graph?.commit_sha ?? null,
|
|
5761
|
+
snapshotPath,
|
|
5762
|
+
nodeCount: parsed.nodes.length,
|
|
5763
|
+
edgeCount: parsed.links.length,
|
|
5764
|
+
snapshot: parsed
|
|
5765
|
+
};
|
|
5766
|
+
}
|
|
5767
|
+
async function loadKpis(creds) {
|
|
5768
|
+
const userName = creds?.userName;
|
|
5769
|
+
const skillsCreated = countUserGeneratedSkills(userName);
|
|
5770
|
+
const records = readUsageRecords();
|
|
5771
|
+
const localBytes = sumMetric(records, "memorySearchBytes");
|
|
5772
|
+
const localCount = sumMetric(records, "memorySearchCount");
|
|
5773
|
+
let orgStats = null;
|
|
5774
|
+
if (creds?.token) {
|
|
5775
|
+
try {
|
|
5776
|
+
orgStats = await fetchOrgStats(creds);
|
|
5777
|
+
} catch (e) {
|
|
5778
|
+
log7(`fetchOrgStats threw: ${e?.message ?? String(e)}`);
|
|
5779
|
+
}
|
|
5780
|
+
}
|
|
5781
|
+
if (orgStats) {
|
|
5782
|
+
return {
|
|
5783
|
+
tokensSaved: bytesToSavedTokens(orgStats.org.memorySearchBytes),
|
|
5784
|
+
tokensSource: "org",
|
|
5785
|
+
skillsCreated,
|
|
5786
|
+
memorySearches: orgStats.org.memoryRecallCount,
|
|
5787
|
+
sessionsCount: orgStats.org.sessionsCount,
|
|
5788
|
+
userTokensSaved: bytesToSavedTokens(orgStats.user.memorySearchBytes)
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5791
|
+
if (records.length > 0) {
|
|
5792
|
+
return {
|
|
5793
|
+
tokensSaved: bytesToSavedTokens(localBytes),
|
|
5794
|
+
tokensSource: "local",
|
|
5795
|
+
skillsCreated,
|
|
5796
|
+
memorySearches: localCount,
|
|
5797
|
+
sessionsCount: records.length,
|
|
5798
|
+
userTokensSaved: bytesToSavedTokens(localBytes)
|
|
5799
|
+
};
|
|
5800
|
+
}
|
|
5801
|
+
return {
|
|
5802
|
+
tokensSaved: null,
|
|
5803
|
+
tokensSource: "none",
|
|
5804
|
+
skillsCreated,
|
|
5805
|
+
memorySearches: 0,
|
|
5806
|
+
sessionsCount: null,
|
|
5807
|
+
userTokensSaved: null
|
|
5808
|
+
};
|
|
5809
|
+
}
|
|
5810
|
+
async function loadDashboardData(opts = {}) {
|
|
5811
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
5812
|
+
const { key: repoKey, project: repoProject } = deriveProjectKey(cwd);
|
|
5813
|
+
const repoDir = join25(opts.graphsHome ?? graphsRoot(), repoKey);
|
|
5814
|
+
const graph = resolveSnapshot(repoDir);
|
|
5815
|
+
const creds = opts.creds === void 0 ? loadCredentials() : opts.creds;
|
|
5816
|
+
const kpis = await loadKpis(creds);
|
|
5817
|
+
return {
|
|
5818
|
+
repoKey,
|
|
5819
|
+
repoProject,
|
|
5820
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5821
|
+
kpis,
|
|
5822
|
+
graph
|
|
5823
|
+
};
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5826
|
+
// dist/src/dashboard/open.js
|
|
5827
|
+
import { spawn } from "node:child_process";
|
|
5828
|
+
import { accessSync, constants as fsConstants, statSync as statSync4 } from "node:fs";
|
|
5829
|
+
import { platform as nodePlatform } from "node:os";
|
|
5830
|
+
import { delimiter, join as join26 } from "node:path";
|
|
5831
|
+
function resolveOpenPlatform() {
|
|
5832
|
+
const p = nodePlatform();
|
|
5833
|
+
if (p === "linux" || p === "darwin" || p === "win32")
|
|
5834
|
+
return p;
|
|
5835
|
+
return null;
|
|
5836
|
+
}
|
|
5837
|
+
function openCommandFor(p, path) {
|
|
5838
|
+
switch (p) {
|
|
5839
|
+
case "linux":
|
|
5840
|
+
return { command: "xdg-open", args: [path] };
|
|
5841
|
+
case "darwin":
|
|
5842
|
+
return { command: "open", args: [path] };
|
|
5843
|
+
case "win32":
|
|
5844
|
+
return { command: "cmd", args: ["/c", "start", "", path] };
|
|
5845
|
+
}
|
|
5846
|
+
}
|
|
5847
|
+
function findBinaryOnPath(name) {
|
|
5848
|
+
const PATH = process.env.PATH ?? "";
|
|
5849
|
+
if (!PATH)
|
|
5850
|
+
return null;
|
|
5851
|
+
const isWin = nodePlatform() === "win32";
|
|
5852
|
+
const exts = isWin ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean) : [""];
|
|
5853
|
+
for (const dir of PATH.split(delimiter)) {
|
|
5854
|
+
if (!dir)
|
|
5855
|
+
continue;
|
|
5856
|
+
for (const ext of exts) {
|
|
5857
|
+
const candidate = join26(dir, name + ext);
|
|
5858
|
+
try {
|
|
5859
|
+
const st = statSync4(candidate);
|
|
5860
|
+
if (!st.isFile())
|
|
5861
|
+
continue;
|
|
5862
|
+
if (isWin)
|
|
5863
|
+
return candidate;
|
|
5864
|
+
try {
|
|
5865
|
+
accessSync(candidate, fsConstants.X_OK);
|
|
5866
|
+
return candidate;
|
|
5867
|
+
} catch {
|
|
5868
|
+
}
|
|
5869
|
+
} catch {
|
|
5870
|
+
}
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
return null;
|
|
5874
|
+
}
|
|
5875
|
+
function openInBrowser(path, opts = {}) {
|
|
5876
|
+
const p = opts.platformOverride === void 0 ? resolveOpenPlatform() : opts.platformOverride;
|
|
5877
|
+
if (!p)
|
|
5878
|
+
return { attempted: false };
|
|
5879
|
+
const { command, args } = openCommandFor(p, path);
|
|
5880
|
+
const exists = opts.binaryExists ?? ((cmd) => findBinaryOnPath(cmd) !== null);
|
|
5881
|
+
if (!exists(command))
|
|
5882
|
+
return { attempted: false };
|
|
5883
|
+
const useSpawn = opts.spawner ?? spawn;
|
|
5884
|
+
try {
|
|
5885
|
+
const child = useSpawn(command, args, { stdio: "ignore", detached: true });
|
|
5886
|
+
child.on("error", () => {
|
|
5887
|
+
});
|
|
5888
|
+
if (typeof child.unref === "function") {
|
|
5889
|
+
child.unref();
|
|
5890
|
+
}
|
|
5891
|
+
return { attempted: true, command };
|
|
5892
|
+
} catch {
|
|
5893
|
+
return { attempted: false };
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
|
|
5897
|
+
// dist/src/dashboard/render.js
|
|
5898
|
+
var VIS_NETWORK_CDN = "https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js";
|
|
5899
|
+
var KIND_COLORS = {
|
|
5900
|
+
function: "#7aa2f7",
|
|
5901
|
+
// soft blue
|
|
5902
|
+
class: "#bb9af7",
|
|
5903
|
+
// purple
|
|
5904
|
+
method: "#9ece6a",
|
|
5905
|
+
// green
|
|
5906
|
+
interface: "#e0af68",
|
|
5907
|
+
// amber
|
|
5908
|
+
type_alias: "#7dcfff",
|
|
5909
|
+
// cyan
|
|
5910
|
+
enum: "#f7768e",
|
|
5911
|
+
// pink
|
|
5912
|
+
const: "#9d7cd8",
|
|
5913
|
+
// muted purple
|
|
5914
|
+
module: "#565f89"
|
|
5915
|
+
// slate
|
|
5916
|
+
};
|
|
5917
|
+
var DEFAULT_NODE_COLOR = "#565f89";
|
|
5918
|
+
function isObject2(v) {
|
|
5919
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
5920
|
+
}
|
|
5921
|
+
function asString(v) {
|
|
5922
|
+
return typeof v === "string" ? v : null;
|
|
5923
|
+
}
|
|
5924
|
+
function transformSnapshotToVis(snapshot) {
|
|
5925
|
+
if (!isObject2(snapshot))
|
|
5926
|
+
return { nodes: [], edges: [] };
|
|
5927
|
+
const raw = snapshot;
|
|
5928
|
+
const visNodes = [];
|
|
5929
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5930
|
+
if (Array.isArray(raw.nodes)) {
|
|
5931
|
+
for (const n of raw.nodes) {
|
|
5932
|
+
if (!isObject2(n))
|
|
5933
|
+
continue;
|
|
5934
|
+
const node = n;
|
|
5935
|
+
const id = asString(node.id);
|
|
5936
|
+
if (!id)
|
|
5937
|
+
continue;
|
|
5938
|
+
if (ids.has(id))
|
|
5939
|
+
continue;
|
|
5940
|
+
ids.add(id);
|
|
5941
|
+
const label = asString(node.label) ?? id;
|
|
5942
|
+
const kind = asString(node.kind);
|
|
5943
|
+
const sourceFile = asString(node.source_file);
|
|
5944
|
+
const sourceLoc = asString(node.source_location);
|
|
5945
|
+
const titleParts = [];
|
|
5946
|
+
if (kind)
|
|
5947
|
+
titleParts.push(kind);
|
|
5948
|
+
if (sourceFile) {
|
|
5949
|
+
const loc = sourceLoc ? `${sourceFile}:${sourceLoc}` : sourceFile;
|
|
5950
|
+
titleParts.push(loc);
|
|
5951
|
+
}
|
|
5952
|
+
const color = kind && KIND_COLORS[kind] ? KIND_COLORS[kind] : DEFAULT_NODE_COLOR;
|
|
5953
|
+
visNodes.push({
|
|
5954
|
+
id,
|
|
5955
|
+
label,
|
|
5956
|
+
title: titleParts.length > 0 ? titleParts.map(escHtml).join(" \xB7 ") : escHtml(id),
|
|
5957
|
+
group: kind ?? void 0,
|
|
5958
|
+
color: { background: color, border: color }
|
|
5959
|
+
});
|
|
5960
|
+
}
|
|
5961
|
+
}
|
|
5962
|
+
const visEdges = [];
|
|
5963
|
+
if (Array.isArray(raw.links)) {
|
|
5964
|
+
for (const l of raw.links) {
|
|
5965
|
+
if (!isObject2(l))
|
|
5966
|
+
continue;
|
|
5967
|
+
const edge = l;
|
|
5968
|
+
const from = asString(edge.source);
|
|
5969
|
+
const to = asString(edge.target);
|
|
5970
|
+
if (!from || !to)
|
|
5971
|
+
continue;
|
|
5972
|
+
const relation = asString(edge.relation);
|
|
5973
|
+
const confidence = asString(edge.confidence);
|
|
5974
|
+
const titleParts = [];
|
|
5975
|
+
if (relation)
|
|
5976
|
+
titleParts.push(relation);
|
|
5977
|
+
if (confidence)
|
|
5978
|
+
titleParts.push(`[${confidence}]`);
|
|
5979
|
+
visEdges.push({
|
|
5980
|
+
from,
|
|
5981
|
+
to,
|
|
5982
|
+
title: titleParts.length > 0 ? titleParts.map(escHtml).join(" ") : `${escHtml(from)} \u2192 ${escHtml(to)}`
|
|
5983
|
+
});
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5986
|
+
return { nodes: visNodes, edges: visEdges };
|
|
5987
|
+
}
|
|
5988
|
+
function escHtml(s) {
|
|
5989
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
5990
|
+
}
|
|
5991
|
+
function safeJsonForScript(value) {
|
|
5992
|
+
return JSON.stringify(value).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\u0021--").replace(/-->/g, "--\\u003e");
|
|
5993
|
+
}
|
|
5994
|
+
function formatTokensCompact(n) {
|
|
5995
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
5996
|
+
return "0";
|
|
5997
|
+
if (n < 1e3)
|
|
5998
|
+
return `${Math.round(n)}`;
|
|
5999
|
+
if (n < 1e5)
|
|
6000
|
+
return `${(n / 1e3).toFixed(1)}k`;
|
|
6001
|
+
if (n < 1e6)
|
|
6002
|
+
return `${Math.round(n / 1e3)}k`;
|
|
6003
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
6004
|
+
}
|
|
6005
|
+
function formatInt(n) {
|
|
6006
|
+
if (!Number.isFinite(n))
|
|
6007
|
+
return "0";
|
|
6008
|
+
return Math.round(n).toLocaleString("en-US");
|
|
6009
|
+
}
|
|
6010
|
+
function renderKpiCards(kpis) {
|
|
6011
|
+
const tokensValue = kpis.tokensSaved == null ? "\u2014" : `~${formatTokensCompact(kpis.tokensSaved)}`;
|
|
6012
|
+
const tokensSub = (() => {
|
|
6013
|
+
if (kpis.tokensSource === "org") {
|
|
6014
|
+
return kpis.userTokensSaved != null ? `Org-wide \xB7 you ~${formatTokensCompact(kpis.userTokensSaved)}` : "Org-wide";
|
|
6015
|
+
}
|
|
6016
|
+
if (kpis.tokensSource === "local")
|
|
6017
|
+
return "Local (this machine)";
|
|
6018
|
+
return "Run a session to start tracking";
|
|
6019
|
+
})();
|
|
6020
|
+
const memoryValue = kpis.memorySearches > 0 ? formatInt(kpis.memorySearches) : kpis.tokensSource === "none" ? "\u2014" : "0";
|
|
6021
|
+
const sessionsValue = kpis.sessionsCount == null ? "\u2014" : formatInt(kpis.sessionsCount);
|
|
6022
|
+
const cards = [
|
|
6023
|
+
{
|
|
6024
|
+
label: "Tokens saved",
|
|
6025
|
+
value: tokensValue,
|
|
6026
|
+
sub: tokensSub
|
|
6027
|
+
},
|
|
6028
|
+
{
|
|
6029
|
+
label: "Skills created",
|
|
6030
|
+
value: formatInt(kpis.skillsCreated),
|
|
6031
|
+
sub: "~/.claude/skills/"
|
|
6032
|
+
},
|
|
6033
|
+
{
|
|
6034
|
+
label: "Memory recalls",
|
|
6035
|
+
value: memoryValue,
|
|
6036
|
+
sub: kpis.tokensSource === "org" ? "Org-wide" : kpis.tokensSource === "local" ? "Local" : ""
|
|
6037
|
+
},
|
|
6038
|
+
{
|
|
6039
|
+
label: "Sessions",
|
|
6040
|
+
value: sessionsValue,
|
|
6041
|
+
sub: kpis.tokensSource === "org" ? "Org-wide" : kpis.tokensSource === "local" ? "Local" : ""
|
|
6042
|
+
}
|
|
6043
|
+
];
|
|
6044
|
+
return cards.map((c) => `
|
|
6045
|
+
<div class="kpi">
|
|
6046
|
+
<div class="kpi-label">${escHtml(c.label)}</div>
|
|
6047
|
+
<div class="kpi-value">${escHtml(c.value)}</div>
|
|
6048
|
+
<div class="kpi-sub">${escHtml(c.sub)}</div>
|
|
6049
|
+
</div>`).join("");
|
|
6050
|
+
}
|
|
6051
|
+
function renderGraphSection(data) {
|
|
6052
|
+
if (data.graph == null) {
|
|
6053
|
+
return `
|
|
6054
|
+
<div class="graph-card">
|
|
6055
|
+
<h2>Codebase graph</h2>
|
|
6056
|
+
<div class="empty">
|
|
6057
|
+
No graph snapshot yet for this repo.<br>
|
|
6058
|
+
Run <code>hivemind graph build</code> to generate one.
|
|
6059
|
+
</div>
|
|
6060
|
+
</div>`;
|
|
6061
|
+
}
|
|
6062
|
+
const visPayload = transformSnapshotToVis(data.graph.snapshot);
|
|
6063
|
+
const commitLabel = data.graph.commitSha ? `commit ${data.graph.commitSha.slice(0, 12)}` : "no commit (loose dir)";
|
|
6064
|
+
const meta = `${formatInt(data.graph.nodeCount)} nodes \xB7 ${formatInt(data.graph.edgeCount)} edges \xB7 ${commitLabel}`;
|
|
6065
|
+
return `
|
|
6066
|
+
<div class="graph-card">
|
|
6067
|
+
<h2>Codebase graph</h2>
|
|
6068
|
+
<div class="graph-meta">${escHtml(meta)}</div>
|
|
6069
|
+
<div id="graph"></div>
|
|
6070
|
+
</div>
|
|
6071
|
+
<script type="application/json" id="hm-graph-data">${safeJsonForScript(visPayload)}</script>
|
|
6072
|
+
<script src="${VIS_NETWORK_CDN}"></script>
|
|
6073
|
+
<script>
|
|
6074
|
+
(function () {
|
|
6075
|
+
var holder = document.getElementById('hm-graph-data');
|
|
6076
|
+
var container = document.getElementById('graph');
|
|
6077
|
+
if (!holder || !container || typeof vis === 'undefined') return;
|
|
6078
|
+
var payload;
|
|
6079
|
+
try { payload = JSON.parse(holder.textContent); }
|
|
6080
|
+
catch (e) { container.textContent = 'graph payload parse failed'; return; }
|
|
6081
|
+
if (!payload || !Array.isArray(payload.nodes) || payload.nodes.length === 0) {
|
|
6082
|
+
container.textContent = 'snapshot has no nodes';
|
|
6083
|
+
return;
|
|
6084
|
+
}
|
|
6085
|
+
new vis.Network(container, payload, {
|
|
6086
|
+
nodes: {
|
|
6087
|
+
shape: 'dot',
|
|
6088
|
+
size: 9,
|
|
6089
|
+
font: { color: '#e8eaed', size: 11, face: 'system-ui, sans-serif' },
|
|
6090
|
+
borderWidth: 1,
|
|
6091
|
+
},
|
|
6092
|
+
edges: {
|
|
6093
|
+
color: { color: 'rgba(120, 130, 150, 0.45)', highlight: '#f5b80a', hover: '#e8eaed' },
|
|
6094
|
+
arrows: { to: { enabled: true, scaleFactor: 0.45 } },
|
|
6095
|
+
smooth: { enabled: true, type: 'continuous', roundness: 0.2 },
|
|
6096
|
+
width: 1,
|
|
6097
|
+
},
|
|
6098
|
+
physics: {
|
|
6099
|
+
stabilization: { iterations: 120 },
|
|
6100
|
+
barnesHut: { gravitationalConstant: -2200, springLength: 80, springConstant: 0.04 },
|
|
6101
|
+
},
|
|
6102
|
+
interaction: { hover: true, dragNodes: true, tooltipDelay: 120 },
|
|
6103
|
+
});
|
|
6104
|
+
}());
|
|
6105
|
+
</script>`;
|
|
6106
|
+
}
|
|
6107
|
+
var STYLES = `
|
|
6108
|
+
:root {
|
|
6109
|
+
color-scheme: dark;
|
|
6110
|
+
--bg: #0b0d10;
|
|
6111
|
+
--fg: #e8eaed;
|
|
6112
|
+
--muted: #8b9099;
|
|
6113
|
+
--accent: #f5b80a;
|
|
6114
|
+
--card: #15181d;
|
|
6115
|
+
--border: #22272e;
|
|
6116
|
+
}
|
|
6117
|
+
* { box-sizing: border-box; }
|
|
6118
|
+
html, body { margin: 0; padding: 0; }
|
|
6119
|
+
body {
|
|
6120
|
+
font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
6121
|
+
background: var(--bg);
|
|
6122
|
+
color: var(--fg);
|
|
6123
|
+
padding: 24px;
|
|
6124
|
+
}
|
|
6125
|
+
.header { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 24px; gap: 16px; flex-wrap: wrap; }
|
|
6126
|
+
.brand { font-weight: 600; font-size: 18px; }
|
|
6127
|
+
.brand .bee { color: var(--accent); margin-right: 4px; }
|
|
6128
|
+
.brand .repo { color: var(--muted); font-weight: 400; margin-left: 8px; }
|
|
6129
|
+
.header .ts { color: var(--muted); font-size: 12px; }
|
|
6130
|
+
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 32px; }
|
|
6131
|
+
.kpi { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px 20px; }
|
|
6132
|
+
.kpi-label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; }
|
|
6133
|
+
.kpi-value { font-size: 28px; font-weight: 600; margin-top: 6px; line-height: 1.1; }
|
|
6134
|
+
.kpi-sub { color: var(--muted); font-size: 12px; margin-top: 4px; }
|
|
6135
|
+
.graph-card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }
|
|
6136
|
+
.graph-card h2 { margin: 0 0 8px; font-size: 15px; font-weight: 500; }
|
|
6137
|
+
.graph-meta { color: var(--muted); font-size: 12px; margin-bottom: 12px; }
|
|
6138
|
+
#graph { height: 70vh; border: 1px solid var(--border); border-radius: 4px; background: #0e1116; }
|
|
6139
|
+
.empty { padding: 48px 16px; text-align: center; color: var(--muted); }
|
|
6140
|
+
.empty code { background: #1c2128; padding: 2px 6px; border-radius: 3px; color: var(--fg); font-family: ui-monospace, "SFMono-Regular", monospace; }
|
|
6141
|
+
.footer { color: var(--muted); font-size: 11px; margin-top: 24px; text-align: right; }
|
|
6142
|
+
`;
|
|
6143
|
+
function renderDashboardHtml(data) {
|
|
6144
|
+
const title = `Hivemind Dashboard \xB7 ${data.repoProject}`;
|
|
6145
|
+
return `<!DOCTYPE html>
|
|
6146
|
+
<html lang="en">
|
|
6147
|
+
<head>
|
|
6148
|
+
<meta charset="utf-8">
|
|
6149
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6150
|
+
<title>${escHtml(title)}</title>
|
|
6151
|
+
<style>${STYLES}</style>
|
|
6152
|
+
</head>
|
|
6153
|
+
<body>
|
|
6154
|
+
<div class="header">
|
|
6155
|
+
<div class="brand">
|
|
6156
|
+
<span class="bee">\u{1F41D}</span>hivemind dashboard
|
|
6157
|
+
<span class="repo">/ ${escHtml(data.repoProject)}</span>
|
|
6158
|
+
</div>
|
|
6159
|
+
<div class="ts">${escHtml(data.generatedAt)}</div>
|
|
6160
|
+
</div>
|
|
6161
|
+
<div class="kpi-grid">${renderKpiCards(data.kpis)}
|
|
6162
|
+
</div>
|
|
6163
|
+
${renderGraphSection(data)}
|
|
6164
|
+
<div class="footer">repo_key ${escHtml(data.repoKey)}</div>
|
|
6165
|
+
</body>
|
|
6166
|
+
</html>
|
|
6167
|
+
`;
|
|
6168
|
+
}
|
|
6169
|
+
|
|
6170
|
+
// dist/src/dashboard/serve.js
|
|
6171
|
+
import { createServer } from "node:http";
|
|
6172
|
+
var DEFAULT_PORT = 8123;
|
|
6173
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
6174
|
+
function handleRequest(html) {
|
|
6175
|
+
return (req, res) => {
|
|
6176
|
+
const url = req.url ?? "/";
|
|
6177
|
+
const path = url.split("?")[0];
|
|
6178
|
+
if (req.method === "GET" && (path === "/" || path === "/index.html")) {
|
|
6179
|
+
res.statusCode = 200;
|
|
6180
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
6181
|
+
res.setHeader("Cache-Control", "no-store");
|
|
6182
|
+
res.end(html);
|
|
6183
|
+
return;
|
|
6184
|
+
}
|
|
6185
|
+
if (req.method === "GET" && path === "/health") {
|
|
6186
|
+
res.statusCode = 204;
|
|
6187
|
+
res.end();
|
|
6188
|
+
return;
|
|
6189
|
+
}
|
|
6190
|
+
res.statusCode = 404;
|
|
6191
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
6192
|
+
res.end("Not found. The dashboard lives at /.\n");
|
|
6193
|
+
};
|
|
6194
|
+
}
|
|
6195
|
+
function tryListen(server, host, port) {
|
|
6196
|
+
return new Promise((resolve3, reject) => {
|
|
6197
|
+
const onError = (err) => {
|
|
6198
|
+
server.off("listening", onListening);
|
|
6199
|
+
reject(err);
|
|
6200
|
+
};
|
|
6201
|
+
const onListening = () => {
|
|
6202
|
+
server.off("error", onError);
|
|
6203
|
+
const addr = server.address();
|
|
6204
|
+
if (!addr || typeof addr === "string") {
|
|
6205
|
+
reject(new Error("server bound to a non-IP address"));
|
|
6206
|
+
return;
|
|
6207
|
+
}
|
|
6208
|
+
resolve3(addr.port);
|
|
6209
|
+
};
|
|
6210
|
+
server.once("error", onError);
|
|
6211
|
+
server.once("listening", onListening);
|
|
6212
|
+
server.listen(port, host);
|
|
6213
|
+
});
|
|
6214
|
+
}
|
|
6215
|
+
async function serveDashboardHtml(opts) {
|
|
6216
|
+
const host = opts.host ?? DEFAULT_HOST;
|
|
6217
|
+
const requested = opts.port === void 0 || !Number.isFinite(opts.port) || opts.port < 0 ? DEFAULT_PORT : opts.port;
|
|
6218
|
+
const server = createServer(handleRequest(opts.html));
|
|
6219
|
+
let bound;
|
|
6220
|
+
try {
|
|
6221
|
+
bound = await tryListen(server, host, requested);
|
|
6222
|
+
} catch (e) {
|
|
6223
|
+
if (e?.code !== "EADDRINUSE")
|
|
6224
|
+
throw e;
|
|
6225
|
+
const fallback = createServer(handleRequest(opts.html));
|
|
6226
|
+
bound = await tryListen(fallback, host, 0);
|
|
6227
|
+
server.removeAllListeners();
|
|
6228
|
+
return makeHandle(fallback, host, bound);
|
|
6229
|
+
}
|
|
6230
|
+
return makeHandle(server, host, bound);
|
|
6231
|
+
}
|
|
6232
|
+
function makeHandle(server, host, port) {
|
|
6233
|
+
let resolveStopped;
|
|
6234
|
+
const stopped = new Promise((resolve3) => {
|
|
6235
|
+
resolveStopped = resolve3;
|
|
6236
|
+
});
|
|
6237
|
+
server.on("close", () => resolveStopped());
|
|
6238
|
+
return {
|
|
6239
|
+
host,
|
|
6240
|
+
port,
|
|
6241
|
+
stopped,
|
|
6242
|
+
close: () => new Promise((resolve3, reject) => {
|
|
6243
|
+
server.close((err) => err ? reject(err) : resolve3());
|
|
6244
|
+
})
|
|
6245
|
+
};
|
|
6246
|
+
}
|
|
6247
|
+
|
|
6248
|
+
// dist/src/commands/dashboard.js
|
|
6249
|
+
var USAGE = `hivemind dashboard \u2014 codebase graph + KPI dashboard (HTML)
|
|
6250
|
+
|
|
6251
|
+
Usage:
|
|
6252
|
+
hivemind dashboard [--cwd <path>] [--out <path>] [--no-open]
|
|
6253
|
+
[--serve] [--port <n>]
|
|
6254
|
+
Build a self-contained HTML dashboard for this repo, write it
|
|
6255
|
+
to disk, and either open it in the default browser or serve
|
|
6256
|
+
it over loopback HTTP for headless / SSH workflows.
|
|
6257
|
+
|
|
6258
|
+
--cwd <path> Use a different project root (defaults to cwd).
|
|
6259
|
+
--out <path> Write to a custom path (defaults to
|
|
6260
|
+
~/.hivemind/dashboards/<repo-key>/index.html).
|
|
6261
|
+
--no-open Don't open the browser. Combine with --serve
|
|
6262
|
+
to start the server without auto-launching.
|
|
6263
|
+
--serve Start a loopback HTTP server (127.0.0.1) so the
|
|
6264
|
+
dashboard is reachable at a URL. Stays alive
|
|
6265
|
+
until Ctrl+C. Ideal for VS Code / Cursor
|
|
6266
|
+
Remote-SSH (auto-forwards the port \u2192 click to
|
|
6267
|
+
open in the integrated browser tab).
|
|
6268
|
+
--port <n> Port for --serve (default 8123). Falls back to
|
|
6269
|
+
a kernel-assigned port if <n> is in use.
|
|
6270
|
+
|
|
6271
|
+
hivemind dashboard --help
|
|
6272
|
+
Show this message.
|
|
6273
|
+
|
|
6274
|
+
Data sources (all read-only):
|
|
6275
|
+
- Graph snapshot at ~/.hivemind/graphs/<repo-key>/ (produced by
|
|
6276
|
+
\`hivemind graph build\`; the dashboard works without it and shows
|
|
6277
|
+
an empty-state until the producer has run)
|
|
6278
|
+
- KPIs via the org stats endpoint (cached) with a local fallback
|
|
6279
|
+
to ~/.deeplake/usage-stats.jsonl
|
|
6280
|
+
- Skills created from ~/.claude/skills/<name>--<author>/ directories
|
|
6281
|
+
`;
|
|
6282
|
+
function parsePort(raw) {
|
|
6283
|
+
if (raw === void 0 || raw === "")
|
|
6284
|
+
return { error: "--port requires a value" };
|
|
6285
|
+
const n = Number(raw);
|
|
6286
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535) {
|
|
6287
|
+
return { error: `--port must be an integer in [0, 65535], got '${raw}'` };
|
|
6288
|
+
}
|
|
6289
|
+
return n;
|
|
6290
|
+
}
|
|
6291
|
+
function parseDashboardArgs(args) {
|
|
6292
|
+
let cwd;
|
|
6293
|
+
let outPath = "";
|
|
6294
|
+
let open = true;
|
|
6295
|
+
let serve = false;
|
|
6296
|
+
let port;
|
|
6297
|
+
for (let i = 0; i < args.length; i++) {
|
|
6298
|
+
const a = args[i];
|
|
6299
|
+
if (a === "--help" || a === "-h")
|
|
6300
|
+
return { help: true };
|
|
6301
|
+
if (a === "--no-open") {
|
|
6302
|
+
open = false;
|
|
6303
|
+
continue;
|
|
6304
|
+
}
|
|
6305
|
+
if (a === "--serve") {
|
|
6306
|
+
serve = true;
|
|
6307
|
+
continue;
|
|
6308
|
+
}
|
|
6309
|
+
if (a === "--cwd") {
|
|
6310
|
+
const v = args[++i];
|
|
6311
|
+
if (v === void 0 || v.startsWith("-")) {
|
|
6312
|
+
return { error: "--cwd requires a value" };
|
|
6313
|
+
}
|
|
6314
|
+
cwd = v;
|
|
6315
|
+
continue;
|
|
6316
|
+
}
|
|
6317
|
+
if (a.startsWith("--cwd=")) {
|
|
6318
|
+
cwd = a.slice("--cwd=".length);
|
|
6319
|
+
continue;
|
|
6320
|
+
}
|
|
6321
|
+
if (a === "--out") {
|
|
6322
|
+
const v = args[++i];
|
|
6323
|
+
if (v === void 0 || v.startsWith("-")) {
|
|
6324
|
+
return { error: "--out requires a value" };
|
|
6325
|
+
}
|
|
6326
|
+
outPath = v;
|
|
6327
|
+
continue;
|
|
6328
|
+
}
|
|
6329
|
+
if (a.startsWith("--out=")) {
|
|
6330
|
+
outPath = a.slice("--out=".length);
|
|
6331
|
+
continue;
|
|
6332
|
+
}
|
|
6333
|
+
if (a === "--port") {
|
|
6334
|
+
const v = args[++i];
|
|
6335
|
+
if (v === void 0 || v.startsWith("-")) {
|
|
6336
|
+
return { error: "--port requires a value" };
|
|
6337
|
+
}
|
|
6338
|
+
const parsed = parsePort(v);
|
|
6339
|
+
if (typeof parsed === "object")
|
|
6340
|
+
return { error: parsed.error };
|
|
6341
|
+
port = parsed;
|
|
6342
|
+
continue;
|
|
6343
|
+
}
|
|
6344
|
+
if (a.startsWith("--port=")) {
|
|
6345
|
+
const parsed = parsePort(a.slice("--port=".length));
|
|
6346
|
+
if (typeof parsed === "object")
|
|
6347
|
+
return { error: parsed.error };
|
|
6348
|
+
port = parsed;
|
|
6349
|
+
continue;
|
|
6350
|
+
}
|
|
6351
|
+
return { error: `unknown arg '${a}'` };
|
|
6352
|
+
}
|
|
6353
|
+
if (port !== void 0 && !serve) {
|
|
6354
|
+
return { error: "--port requires --serve" };
|
|
6355
|
+
}
|
|
6356
|
+
return {
|
|
6357
|
+
args: {
|
|
6358
|
+
cwd: cwd ?? process.cwd(),
|
|
6359
|
+
outPath,
|
|
6360
|
+
open,
|
|
6361
|
+
serve,
|
|
6362
|
+
port
|
|
6363
|
+
}
|
|
6364
|
+
};
|
|
6365
|
+
}
|
|
6366
|
+
function defaultDashboardOutPath(repoKey) {
|
|
6367
|
+
return join27(homedir14(), ".hivemind", "dashboards", repoKey, "index.html");
|
|
6368
|
+
}
|
|
6369
|
+
async function runDashboardCommand(rawArgs, runOpts = {}) {
|
|
6370
|
+
const out = runOpts.out ?? ((s) => {
|
|
6371
|
+
process.stdout.write(s);
|
|
6372
|
+
});
|
|
6373
|
+
const err = runOpts.err ?? ((s) => {
|
|
6374
|
+
process.stderr.write(s);
|
|
6375
|
+
});
|
|
6376
|
+
const opener = runOpts.opener ?? openInBrowser;
|
|
6377
|
+
const parsed = parseDashboardArgs(rawArgs);
|
|
6378
|
+
if (parsed.help) {
|
|
6379
|
+
out(USAGE);
|
|
6380
|
+
return 0;
|
|
6381
|
+
}
|
|
6382
|
+
if (parsed.error || !parsed.args) {
|
|
6383
|
+
err(`hivemind dashboard: ${parsed.error ?? "invalid arguments"}
|
|
6384
|
+
`);
|
|
6385
|
+
err(USAGE);
|
|
6386
|
+
return 2;
|
|
6387
|
+
}
|
|
6388
|
+
const { cwd, outPath, open } = parsed.args;
|
|
6389
|
+
let data;
|
|
6390
|
+
try {
|
|
6391
|
+
data = await loadDashboardData({ cwd });
|
|
6392
|
+
} catch (e) {
|
|
6393
|
+
err(`hivemind dashboard: failed to load data: ${e?.message ?? String(e)}
|
|
6394
|
+
`);
|
|
6395
|
+
return 1;
|
|
6396
|
+
}
|
|
6397
|
+
const html = renderDashboardHtml(data);
|
|
6398
|
+
const finalOut = outPath || defaultDashboardOutPath(data.repoKey);
|
|
6399
|
+
const absOut = resolve2(finalOut);
|
|
6400
|
+
try {
|
|
6401
|
+
mkdirSync10(dirname6(absOut), { recursive: true });
|
|
6402
|
+
writeFileSync13(absOut, html, "utf-8");
|
|
6403
|
+
} catch (e) {
|
|
6404
|
+
err(`hivemind dashboard: failed to write ${absOut}: ${e?.message ?? String(e)}
|
|
6405
|
+
`);
|
|
6406
|
+
return 1;
|
|
6407
|
+
}
|
|
6408
|
+
out(`Wrote ${absOut}
|
|
6409
|
+
`);
|
|
6410
|
+
if (data.graph == null) {
|
|
6411
|
+
out(`(no codebase graph yet \u2014 run 'hivemind graph build' to populate)
|
|
6412
|
+
`);
|
|
6413
|
+
}
|
|
6414
|
+
if (parsed.args.serve) {
|
|
6415
|
+
return await runServeLoop(html, parsed.args, runOpts, out, err);
|
|
6416
|
+
}
|
|
6417
|
+
if (open) {
|
|
6418
|
+
const result = opener(absOut);
|
|
6419
|
+
if (result.attempted) {
|
|
6420
|
+
out(`Opening via ${result.command}
|
|
6421
|
+
`);
|
|
6422
|
+
} else {
|
|
6423
|
+
out(`(no opener for this platform; open the file above manually)
|
|
6424
|
+
`);
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
return 0;
|
|
6428
|
+
}
|
|
6429
|
+
async function runServeLoop(html, args, runOpts, out, err) {
|
|
6430
|
+
const server = runOpts.server ?? serveDashboardHtml;
|
|
6431
|
+
const opener = runOpts.opener ?? openInBrowser;
|
|
6432
|
+
const onSignal = runOpts.onSignal ?? defaultOnSignal;
|
|
6433
|
+
let handle;
|
|
6434
|
+
try {
|
|
6435
|
+
handle = await server({ html, port: args.port });
|
|
6436
|
+
} catch (e) {
|
|
6437
|
+
err(`hivemind dashboard: failed to start server: ${e?.message ?? String(e)}
|
|
6438
|
+
`);
|
|
6439
|
+
return 1;
|
|
6440
|
+
}
|
|
6441
|
+
const url = `http://${handle.host}:${handle.port}/`;
|
|
6442
|
+
out(`Serving dashboard at ${url} (Ctrl+C to stop)
|
|
6443
|
+
`);
|
|
6444
|
+
if (args.open) {
|
|
6445
|
+
const result = opener(url);
|
|
6446
|
+
if (result.attempted) {
|
|
6447
|
+
out(`Opening via ${result.command}
|
|
6448
|
+
`);
|
|
6449
|
+
} else {
|
|
6450
|
+
out(`(no opener for this platform; click the URL above or open it manually)
|
|
6451
|
+
`);
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
let resolveDone;
|
|
6455
|
+
const done = new Promise((r) => {
|
|
6456
|
+
resolveDone = r;
|
|
6457
|
+
});
|
|
6458
|
+
const shutdown = async () => {
|
|
6459
|
+
try {
|
|
6460
|
+
await handle.close();
|
|
6461
|
+
} catch {
|
|
6462
|
+
}
|
|
6463
|
+
resolveDone(0);
|
|
6464
|
+
};
|
|
6465
|
+
const offInt = onSignal("SIGINT", shutdown);
|
|
6466
|
+
const offTerm = onSignal("SIGTERM", shutdown);
|
|
6467
|
+
handle.stopped.then(() => resolveDone(0));
|
|
6468
|
+
try {
|
|
6469
|
+
return await done;
|
|
6470
|
+
} finally {
|
|
6471
|
+
offInt();
|
|
6472
|
+
offTerm();
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
function defaultOnSignal(signal, handler) {
|
|
6476
|
+
process.on(signal, handler);
|
|
6477
|
+
return () => process.off(signal, handler);
|
|
6478
|
+
}
|
|
6479
|
+
|
|
6480
|
+
// dist/src/commands/skillify.js
|
|
6481
|
+
import { readdirSync as readdirSync7, existsSync as existsSync30, readFileSync as readFileSync26, mkdirSync as mkdirSync17, renameSync as renameSync8 } from "node:fs";
|
|
6482
|
+
import { homedir as homedir23 } from "node:os";
|
|
6483
|
+
import { dirname as dirname11, join as join38 } from "node:path";
|
|
6484
|
+
|
|
5469
6485
|
// dist/src/skillify/scope-config.js
|
|
6486
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync19, writeFileSync as writeFileSync14 } from "node:fs";
|
|
6487
|
+
import { join as join28 } from "node:path";
|
|
5470
6488
|
function configPath() {
|
|
5471
|
-
return
|
|
6489
|
+
return join28(getStateDir(), "config.json");
|
|
5472
6490
|
}
|
|
5473
6491
|
var DEFAULT = { scope: "me", team: [], install: "project" };
|
|
5474
6492
|
function loadScopeConfig() {
|
|
5475
6493
|
migrateLegacyStateDir();
|
|
5476
6494
|
const CONFIG_PATH2 = configPath();
|
|
5477
|
-
if (!
|
|
6495
|
+
if (!existsSync20(CONFIG_PATH2))
|
|
5478
6496
|
return DEFAULT;
|
|
5479
6497
|
try {
|
|
5480
|
-
const raw = JSON.parse(
|
|
6498
|
+
const raw = JSON.parse(readFileSync19(CONFIG_PATH2, "utf-8"));
|
|
5481
6499
|
const scope = raw.scope === "team" ? "team" : raw.scope === "org" ? "team" : "me";
|
|
5482
6500
|
const team = Array.isArray(raw.team) ? raw.team.filter((s) => typeof s === "string") : [];
|
|
5483
6501
|
const install = raw.install === "global" ? "global" : "project";
|
|
@@ -5488,19 +6506,19 @@ function loadScopeConfig() {
|
|
|
5488
6506
|
}
|
|
5489
6507
|
function saveScopeConfig(cfg) {
|
|
5490
6508
|
migrateLegacyStateDir();
|
|
5491
|
-
|
|
5492
|
-
|
|
6509
|
+
mkdirSync11(getStateDir(), { recursive: true });
|
|
6510
|
+
writeFileSync14(configPath(), JSON.stringify(cfg, null, 2));
|
|
5493
6511
|
}
|
|
5494
6512
|
|
|
5495
6513
|
// dist/src/skillify/pull.js
|
|
5496
|
-
import { existsSync as
|
|
5497
|
-
import { homedir as
|
|
5498
|
-
import { dirname as
|
|
6514
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync17, mkdirSync as mkdirSync14, renameSync as renameSync7, lstatSync as lstatSync5, readlinkSync as readlinkSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync10 } from "node:fs";
|
|
6515
|
+
import { homedir as homedir17 } from "node:os";
|
|
6516
|
+
import { dirname as dirname8, join as join32 } from "node:path";
|
|
5499
6517
|
|
|
5500
6518
|
// dist/src/skillify/skill-writer.js
|
|
5501
|
-
import { existsSync as
|
|
5502
|
-
import { homedir as
|
|
5503
|
-
import { join as
|
|
6519
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync20, readdirSync as readdirSync4, statSync as statSync5, writeFileSync as writeFileSync15 } from "node:fs";
|
|
6520
|
+
import { homedir as homedir15 } from "node:os";
|
|
6521
|
+
import { join as join29 } from "node:path";
|
|
5504
6522
|
function assertValidSkillName(name) {
|
|
5505
6523
|
if (typeof name !== "string" || name.length === 0) {
|
|
5506
6524
|
throw new Error(`invalid skill name: empty or non-string`);
|
|
@@ -5516,10 +6534,10 @@ function assertValidSkillName(name) {
|
|
|
5516
6534
|
}
|
|
5517
6535
|
}
|
|
5518
6536
|
function skillDir(skillsRoot, name) {
|
|
5519
|
-
return
|
|
6537
|
+
return join29(skillsRoot, name);
|
|
5520
6538
|
}
|
|
5521
6539
|
function skillPath(skillsRoot, name) {
|
|
5522
|
-
return
|
|
6540
|
+
return join29(skillDir(skillsRoot, name), "SKILL.md");
|
|
5523
6541
|
}
|
|
5524
6542
|
function renderFrontmatter(fm) {
|
|
5525
6543
|
const lines = ["---"];
|
|
@@ -5597,10 +6615,10 @@ function writeNewSkill(args) {
|
|
|
5597
6615
|
assertValidSkillName(args.name);
|
|
5598
6616
|
const dir = skillDir(args.skillsRoot, args.name);
|
|
5599
6617
|
const path = skillPath(args.skillsRoot, args.name);
|
|
5600
|
-
if (
|
|
6618
|
+
if (existsSync21(path)) {
|
|
5601
6619
|
throw new Error(`skill already exists at ${path}; use mergeSkill`);
|
|
5602
6620
|
}
|
|
5603
|
-
|
|
6621
|
+
mkdirSync12(dir, { recursive: true });
|
|
5604
6622
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5605
6623
|
const author = args.author && args.author.length > 0 ? args.author : void 0;
|
|
5606
6624
|
const contributors = author ? [author] : [];
|
|
@@ -5620,7 +6638,7 @@ function writeNewSkill(args) {
|
|
|
5620
6638
|
|
|
5621
6639
|
${args.body.trim()}
|
|
5622
6640
|
`;
|
|
5623
|
-
|
|
6641
|
+
writeFileSync15(path, text);
|
|
5624
6642
|
return {
|
|
5625
6643
|
path,
|
|
5626
6644
|
action: "created",
|
|
@@ -5632,40 +6650,40 @@ ${args.body.trim()}
|
|
|
5632
6650
|
};
|
|
5633
6651
|
}
|
|
5634
6652
|
function listSkills(skillsRoot) {
|
|
5635
|
-
if (!
|
|
6653
|
+
if (!existsSync21(skillsRoot))
|
|
5636
6654
|
return [];
|
|
5637
6655
|
const out = [];
|
|
5638
|
-
for (const name of
|
|
5639
|
-
const skillFile =
|
|
5640
|
-
if (
|
|
5641
|
-
out.push({ name, body:
|
|
6656
|
+
for (const name of readdirSync4(skillsRoot)) {
|
|
6657
|
+
const skillFile = join29(skillsRoot, name, "SKILL.md");
|
|
6658
|
+
if (existsSync21(skillFile) && statSync5(skillFile).isFile()) {
|
|
6659
|
+
out.push({ name, body: readFileSync20(skillFile, "utf-8") });
|
|
5642
6660
|
}
|
|
5643
6661
|
}
|
|
5644
6662
|
return out;
|
|
5645
6663
|
}
|
|
5646
6664
|
function resolveSkillsRoot(install, cwd) {
|
|
5647
6665
|
if (install === "global") {
|
|
5648
|
-
return
|
|
6666
|
+
return join29(homedir15(), ".claude", "skills");
|
|
5649
6667
|
}
|
|
5650
|
-
return
|
|
6668
|
+
return join29(cwd, ".claude", "skills");
|
|
5651
6669
|
}
|
|
5652
6670
|
|
|
5653
6671
|
// dist/src/skillify/manifest.js
|
|
5654
|
-
import { existsSync as
|
|
5655
|
-
import { dirname as
|
|
6672
|
+
import { existsSync as existsSync22, lstatSync as lstatSync4, mkdirSync as mkdirSync13, readFileSync as readFileSync21, renameSync as renameSync6, unlinkSync as unlinkSync9, writeFileSync as writeFileSync16 } from "node:fs";
|
|
6673
|
+
import { dirname as dirname7, join as join30 } from "node:path";
|
|
5656
6674
|
function emptyManifest() {
|
|
5657
6675
|
return { version: 1, entries: [] };
|
|
5658
6676
|
}
|
|
5659
6677
|
function manifestPath() {
|
|
5660
|
-
return
|
|
6678
|
+
return join30(getStateDir(), "pulled.json");
|
|
5661
6679
|
}
|
|
5662
6680
|
function loadManifest(path = manifestPath()) {
|
|
5663
6681
|
migrateLegacyStateDir();
|
|
5664
|
-
if (!
|
|
6682
|
+
if (!existsSync22(path))
|
|
5665
6683
|
return emptyManifest();
|
|
5666
6684
|
let raw;
|
|
5667
6685
|
try {
|
|
5668
|
-
raw =
|
|
6686
|
+
raw = readFileSync21(path, "utf-8");
|
|
5669
6687
|
} catch {
|
|
5670
6688
|
return emptyManifest();
|
|
5671
6689
|
}
|
|
@@ -5712,10 +6730,10 @@ function loadManifest(path = manifestPath()) {
|
|
|
5712
6730
|
}
|
|
5713
6731
|
function saveManifest(m, path = manifestPath()) {
|
|
5714
6732
|
migrateLegacyStateDir();
|
|
5715
|
-
|
|
6733
|
+
mkdirSync13(dirname7(path), { recursive: true });
|
|
5716
6734
|
const tmp = `${path}.tmp`;
|
|
5717
|
-
|
|
5718
|
-
|
|
6735
|
+
writeFileSync16(tmp, JSON.stringify(m, null, 2) + "\n", { mode: 384 });
|
|
6736
|
+
renameSync6(tmp, path);
|
|
5719
6737
|
}
|
|
5720
6738
|
function recordPull(entry, path = manifestPath()) {
|
|
5721
6739
|
const m = loadManifest(path);
|
|
@@ -5740,14 +6758,14 @@ function unlinkSymlinks(paths) {
|
|
|
5740
6758
|
for (const path of paths) {
|
|
5741
6759
|
let st;
|
|
5742
6760
|
try {
|
|
5743
|
-
st =
|
|
6761
|
+
st = lstatSync4(path);
|
|
5744
6762
|
} catch {
|
|
5745
6763
|
continue;
|
|
5746
6764
|
}
|
|
5747
6765
|
if (!st.isSymbolicLink())
|
|
5748
6766
|
continue;
|
|
5749
6767
|
try {
|
|
5750
|
-
|
|
6768
|
+
unlinkSync9(path);
|
|
5751
6769
|
} catch {
|
|
5752
6770
|
}
|
|
5753
6771
|
}
|
|
@@ -5757,7 +6775,7 @@ function pruneOrphanedEntries(path = manifestPath()) {
|
|
|
5757
6775
|
const live = [];
|
|
5758
6776
|
let pruned = 0;
|
|
5759
6777
|
for (const e of m.entries) {
|
|
5760
|
-
if (
|
|
6778
|
+
if (existsSync22(join30(e.installRoot, e.dirName))) {
|
|
5761
6779
|
live.push(e);
|
|
5762
6780
|
continue;
|
|
5763
6781
|
}
|
|
@@ -5770,26 +6788,26 @@ function pruneOrphanedEntries(path = manifestPath()) {
|
|
|
5770
6788
|
}
|
|
5771
6789
|
|
|
5772
6790
|
// dist/src/skillify/agent-roots.js
|
|
5773
|
-
import { existsSync as
|
|
5774
|
-
import { homedir as
|
|
5775
|
-
import { join as
|
|
6791
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
6792
|
+
import { homedir as homedir16 } from "node:os";
|
|
6793
|
+
import { join as join31 } from "node:path";
|
|
5776
6794
|
function resolveDetected(home) {
|
|
5777
6795
|
const out = [];
|
|
5778
|
-
const codexInstalled =
|
|
5779
|
-
const piInstalled =
|
|
5780
|
-
const hermesInstalled =
|
|
6796
|
+
const codexInstalled = existsSync23(join31(home, ".codex"));
|
|
6797
|
+
const piInstalled = existsSync23(join31(home, ".pi", "agent"));
|
|
6798
|
+
const hermesInstalled = existsSync23(join31(home, ".hermes"));
|
|
5781
6799
|
if (codexInstalled || piInstalled) {
|
|
5782
|
-
out.push(
|
|
6800
|
+
out.push(join31(home, ".agents", "skills"));
|
|
5783
6801
|
}
|
|
5784
6802
|
if (hermesInstalled) {
|
|
5785
|
-
out.push(
|
|
6803
|
+
out.push(join31(home, ".hermes", "skills"));
|
|
5786
6804
|
}
|
|
5787
6805
|
if (piInstalled) {
|
|
5788
|
-
out.push(
|
|
6806
|
+
out.push(join31(home, ".pi", "agent", "skills"));
|
|
5789
6807
|
}
|
|
5790
6808
|
return out;
|
|
5791
6809
|
}
|
|
5792
|
-
function detectAgentSkillsRoots(canonicalRoot, home =
|
|
6810
|
+
function detectAgentSkillsRoots(canonicalRoot, home = homedir16()) {
|
|
5793
6811
|
return resolveDetected(home).filter((p) => p !== canonicalRoot);
|
|
5794
6812
|
}
|
|
5795
6813
|
|
|
@@ -5833,18 +6851,18 @@ function isMissingTableError(message) {
|
|
|
5833
6851
|
}
|
|
5834
6852
|
function resolvePullDestination(install, cwd) {
|
|
5835
6853
|
if (install === "global")
|
|
5836
|
-
return
|
|
6854
|
+
return join32(homedir17(), ".claude", "skills");
|
|
5837
6855
|
if (!cwd)
|
|
5838
6856
|
throw new Error("install=project requires a cwd");
|
|
5839
|
-
return
|
|
6857
|
+
return join32(cwd, ".claude", "skills");
|
|
5840
6858
|
}
|
|
5841
6859
|
function fanOutSymlinks(canonicalDir, dirName, agentRoots) {
|
|
5842
6860
|
const out = [];
|
|
5843
6861
|
for (const root of agentRoots) {
|
|
5844
|
-
const link =
|
|
6862
|
+
const link = join32(root, dirName);
|
|
5845
6863
|
let existing;
|
|
5846
6864
|
try {
|
|
5847
|
-
existing =
|
|
6865
|
+
existing = lstatSync5(link);
|
|
5848
6866
|
} catch {
|
|
5849
6867
|
existing = null;
|
|
5850
6868
|
}
|
|
@@ -5863,13 +6881,13 @@ function fanOutSymlinks(canonicalDir, dirName, agentRoots) {
|
|
|
5863
6881
|
continue;
|
|
5864
6882
|
}
|
|
5865
6883
|
try {
|
|
5866
|
-
|
|
6884
|
+
unlinkSync10(link);
|
|
5867
6885
|
} catch {
|
|
5868
6886
|
continue;
|
|
5869
6887
|
}
|
|
5870
6888
|
}
|
|
5871
6889
|
try {
|
|
5872
|
-
|
|
6890
|
+
mkdirSync14(dirname8(link), { recursive: true });
|
|
5873
6891
|
symlinkSync2(canonicalDir, link, "dir");
|
|
5874
6892
|
out.push(link);
|
|
5875
6893
|
} catch {
|
|
@@ -5884,8 +6902,8 @@ function backfillSymlinks(installRoot) {
|
|
|
5884
6902
|
return;
|
|
5885
6903
|
const detected = detectAgentSkillsRoots(installRoot);
|
|
5886
6904
|
for (const entry of entries) {
|
|
5887
|
-
const canonical =
|
|
5888
|
-
if (!
|
|
6905
|
+
const canonical = join32(entry.installRoot, entry.dirName);
|
|
6906
|
+
if (!existsSync24(canonical))
|
|
5889
6907
|
continue;
|
|
5890
6908
|
const fresh = fanOutSymlinks(canonical, entry.dirName, detected);
|
|
5891
6909
|
if (sameSorted(fresh, entry.symlinks))
|
|
@@ -5995,10 +7013,10 @@ function renderFrontmatter2(fm) {
|
|
|
5995
7013
|
return lines.join("\n");
|
|
5996
7014
|
}
|
|
5997
7015
|
function readLocalVersion(path) {
|
|
5998
|
-
if (!
|
|
7016
|
+
if (!existsSync24(path))
|
|
5999
7017
|
return null;
|
|
6000
7018
|
try {
|
|
6001
|
-
const text =
|
|
7019
|
+
const text = readFileSync22(path, "utf-8");
|
|
6002
7020
|
const parsed = parseFrontmatter(text);
|
|
6003
7021
|
if (!parsed)
|
|
6004
7022
|
return null;
|
|
@@ -6093,8 +7111,8 @@ async function runPull(opts) {
|
|
|
6093
7111
|
summary.skipped++;
|
|
6094
7112
|
continue;
|
|
6095
7113
|
}
|
|
6096
|
-
const skillDir2 =
|
|
6097
|
-
const skillFile =
|
|
7114
|
+
const skillDir2 = join32(root, dirName);
|
|
7115
|
+
const skillFile = join32(skillDir2, "SKILL.md");
|
|
6098
7116
|
const remoteVersion = Number(row.version ?? 1);
|
|
6099
7117
|
const localVersion = readLocalVersion(skillFile);
|
|
6100
7118
|
const action = decideAction({
|
|
@@ -6105,14 +7123,14 @@ async function runPull(opts) {
|
|
|
6105
7123
|
});
|
|
6106
7124
|
let manifestError;
|
|
6107
7125
|
if (action === "wrote") {
|
|
6108
|
-
|
|
6109
|
-
if (
|
|
7126
|
+
mkdirSync14(skillDir2, { recursive: true });
|
|
7127
|
+
if (existsSync24(skillFile)) {
|
|
6110
7128
|
try {
|
|
6111
|
-
|
|
7129
|
+
renameSync7(skillFile, `${skillFile}.bak`);
|
|
6112
7130
|
} catch {
|
|
6113
7131
|
}
|
|
6114
7132
|
}
|
|
6115
|
-
|
|
7133
|
+
writeFileSync17(skillFile, renderSkillFile(row));
|
|
6116
7134
|
const symlinks = opts.install === "global" ? fanOutSymlinks(skillDir2, dirName, detectAgentSkillsRoots(root)) : [];
|
|
6117
7135
|
try {
|
|
6118
7136
|
recordPull({
|
|
@@ -6154,15 +7172,15 @@ async function runPull(opts) {
|
|
|
6154
7172
|
}
|
|
6155
7173
|
|
|
6156
7174
|
// dist/src/skillify/unpull.js
|
|
6157
|
-
import { existsSync as
|
|
6158
|
-
import { homedir as
|
|
6159
|
-
import { join as
|
|
7175
|
+
import { existsSync as existsSync25, readdirSync as readdirSync5, rmSync as rmSync5, statSync as statSync6 } from "node:fs";
|
|
7176
|
+
import { homedir as homedir18 } from "node:os";
|
|
7177
|
+
import { join as join33 } from "node:path";
|
|
6160
7178
|
function resolveUnpullRoot(install, cwd) {
|
|
6161
7179
|
if (install === "global")
|
|
6162
|
-
return
|
|
7180
|
+
return join33(homedir18(), ".claude", "skills");
|
|
6163
7181
|
if (!cwd)
|
|
6164
7182
|
throw new Error("cwd required when install === 'project'");
|
|
6165
|
-
return
|
|
7183
|
+
return join33(cwd, ".claude", "skills");
|
|
6166
7184
|
}
|
|
6167
7185
|
function runUnpull(opts) {
|
|
6168
7186
|
const root = resolveUnpullRoot(opts.install, opts.cwd);
|
|
@@ -6185,8 +7203,8 @@ function runUnpull(opts) {
|
|
|
6185
7203
|
const entries = entriesForRoot(manifest, opts.install, root);
|
|
6186
7204
|
for (const entry of entries) {
|
|
6187
7205
|
summary.scanned++;
|
|
6188
|
-
const path =
|
|
6189
|
-
if (!
|
|
7206
|
+
const path = join33(root, entry.dirName);
|
|
7207
|
+
if (!existsSync25(path)) {
|
|
6190
7208
|
if (!opts.dryRun) {
|
|
6191
7209
|
unlinkSymlinks(entry.symlinks);
|
|
6192
7210
|
removePullEntry(opts.install, entry.installRoot, entry.dirName);
|
|
@@ -6239,15 +7257,15 @@ function runUnpull(opts) {
|
|
|
6239
7257
|
}
|
|
6240
7258
|
summary.entries.push(result);
|
|
6241
7259
|
}
|
|
6242
|
-
if (
|
|
7260
|
+
if (existsSync25(root) && (opts.all || opts.legacyCleanup)) {
|
|
6243
7261
|
const manifestDirNames = new Set(entries.map((e) => e.dirName));
|
|
6244
|
-
for (const dirName of
|
|
7262
|
+
for (const dirName of readdirSync5(root)) {
|
|
6245
7263
|
if (manifestDirNames.has(dirName))
|
|
6246
7264
|
continue;
|
|
6247
|
-
const path =
|
|
7265
|
+
const path = join33(root, dirName);
|
|
6248
7266
|
let st;
|
|
6249
7267
|
try {
|
|
6250
|
-
st =
|
|
7268
|
+
st = statSync6(path);
|
|
6251
7269
|
} catch {
|
|
6252
7270
|
continue;
|
|
6253
7271
|
}
|
|
@@ -6322,31 +7340,31 @@ function decideTargetForManifestEntry(entry, opts, userFilter, haveUserFilter) {
|
|
|
6322
7340
|
}
|
|
6323
7341
|
|
|
6324
7342
|
// dist/src/commands/mine-local.js
|
|
6325
|
-
import { spawn } from "node:child_process";
|
|
6326
|
-
import { existsSync as
|
|
6327
|
-
import { homedir as
|
|
6328
|
-
import { basename, dirname as
|
|
7343
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
7344
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "node:fs";
|
|
7345
|
+
import { homedir as homedir22 } from "node:os";
|
|
7346
|
+
import { basename as basename2, dirname as dirname10, join as join37 } from "node:path";
|
|
6329
7347
|
|
|
6330
7348
|
// dist/src/skillify/local-source.js
|
|
6331
|
-
import { readdirSync as
|
|
6332
|
-
import { homedir as
|
|
6333
|
-
import { join as
|
|
6334
|
-
var HOME2 =
|
|
7349
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync23, existsSync as existsSync26, statSync as statSync7 } from "node:fs";
|
|
7350
|
+
import { homedir as homedir19 } from "node:os";
|
|
7351
|
+
import { join as join34 } from "node:path";
|
|
7352
|
+
var HOME2 = homedir19();
|
|
6335
7353
|
function encodeCwdClaudeCode(cwd) {
|
|
6336
7354
|
return cwd.replace(/[/_]/g, "-");
|
|
6337
7355
|
}
|
|
6338
7356
|
function detectInstalledAgents() {
|
|
6339
7357
|
const installs = [];
|
|
6340
|
-
const claudeRoot =
|
|
6341
|
-
if (
|
|
7358
|
+
const claudeRoot = join34(HOME2, ".claude", "projects");
|
|
7359
|
+
if (existsSync26(claudeRoot)) {
|
|
6342
7360
|
installs.push({
|
|
6343
7361
|
agent: "claude_code",
|
|
6344
7362
|
sessionRoot: claudeRoot,
|
|
6345
7363
|
encodeCwd: encodeCwdClaudeCode
|
|
6346
7364
|
});
|
|
6347
7365
|
}
|
|
6348
|
-
const codexRoot =
|
|
6349
|
-
if (
|
|
7366
|
+
const codexRoot = join34(HOME2, ".codex", "sessions");
|
|
7367
|
+
if (existsSync26(codexRoot)) {
|
|
6350
7368
|
installs.push({
|
|
6351
7369
|
agent: "codex",
|
|
6352
7370
|
sessionRoot: codexRoot,
|
|
@@ -6368,14 +7386,14 @@ function listLocalSessions(installs, cwd) {
|
|
|
6368
7386
|
const cwdEncoded = install.encodeCwd(cwd);
|
|
6369
7387
|
let subdirs = [];
|
|
6370
7388
|
try {
|
|
6371
|
-
subdirs =
|
|
7389
|
+
subdirs = readdirSync6(install.sessionRoot);
|
|
6372
7390
|
} catch {
|
|
6373
7391
|
continue;
|
|
6374
7392
|
}
|
|
6375
7393
|
for (const sub of subdirs) {
|
|
6376
|
-
const subdirPath =
|
|
7394
|
+
const subdirPath = join34(install.sessionRoot, sub);
|
|
6377
7395
|
try {
|
|
6378
|
-
if (!
|
|
7396
|
+
if (!statSync7(subdirPath).isDirectory())
|
|
6379
7397
|
continue;
|
|
6380
7398
|
} catch {
|
|
6381
7399
|
continue;
|
|
@@ -6383,17 +7401,17 @@ function listLocalSessions(installs, cwd) {
|
|
|
6383
7401
|
const inCwd = sub === cwdEncoded;
|
|
6384
7402
|
let files = [];
|
|
6385
7403
|
try {
|
|
6386
|
-
files =
|
|
7404
|
+
files = readdirSync6(subdirPath);
|
|
6387
7405
|
} catch {
|
|
6388
7406
|
continue;
|
|
6389
7407
|
}
|
|
6390
7408
|
for (const f of files) {
|
|
6391
7409
|
if (!f.endsWith(".jsonl"))
|
|
6392
7410
|
continue;
|
|
6393
|
-
const fullPath =
|
|
7411
|
+
const fullPath = join34(subdirPath, f);
|
|
6394
7412
|
let stats;
|
|
6395
7413
|
try {
|
|
6396
|
-
stats =
|
|
7414
|
+
stats = statSync7(fullPath);
|
|
6397
7415
|
} catch {
|
|
6398
7416
|
continue;
|
|
6399
7417
|
}
|
|
@@ -6451,7 +7469,7 @@ function pickSessions(candidates, opts) {
|
|
|
6451
7469
|
function nativeJsonlToRows(filePath, sessionId, agent) {
|
|
6452
7470
|
let raw;
|
|
6453
7471
|
try {
|
|
6454
|
-
raw =
|
|
7472
|
+
raw = readFileSync23(filePath, "utf-8");
|
|
6455
7473
|
} catch {
|
|
6456
7474
|
return [];
|
|
6457
7475
|
}
|
|
@@ -6541,22 +7559,22 @@ function extractPairs(rows) {
|
|
|
6541
7559
|
}
|
|
6542
7560
|
|
|
6543
7561
|
// dist/src/skillify/gate-runner.js
|
|
6544
|
-
import { existsSync as
|
|
7562
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
6545
7563
|
import { createRequire } from "node:module";
|
|
6546
|
-
import { homedir as
|
|
6547
|
-
import { join as
|
|
7564
|
+
import { homedir as homedir20 } from "node:os";
|
|
7565
|
+
import { join as join35 } from "node:path";
|
|
6548
7566
|
var requireForCp = createRequire(import.meta.url);
|
|
6549
7567
|
var { execFileSync: runChildProcess } = requireForCp("node:child_process");
|
|
6550
7568
|
var inheritedEnv = process;
|
|
6551
7569
|
function firstExistingPath(candidates) {
|
|
6552
7570
|
for (const c of candidates) {
|
|
6553
|
-
if (
|
|
7571
|
+
if (existsSync27(c))
|
|
6554
7572
|
return c;
|
|
6555
7573
|
}
|
|
6556
7574
|
return null;
|
|
6557
7575
|
}
|
|
6558
7576
|
function findAgentBin(agent) {
|
|
6559
|
-
const home =
|
|
7577
|
+
const home = homedir20();
|
|
6560
7578
|
switch (agent) {
|
|
6561
7579
|
// /usr/bin/<name> is included in every candidate list — that's the
|
|
6562
7580
|
// common Linux package-manager install path (apt, dnf, pacman). Old
|
|
@@ -6565,45 +7583,45 @@ function findAgentBin(agent) {
|
|
|
6565
7583
|
// #170 caught the gap.
|
|
6566
7584
|
case "claude_code":
|
|
6567
7585
|
return firstExistingPath([
|
|
6568
|
-
|
|
7586
|
+
join35(home, ".claude", "local", "claude"),
|
|
6569
7587
|
"/usr/local/bin/claude",
|
|
6570
7588
|
"/usr/bin/claude",
|
|
6571
|
-
|
|
6572
|
-
|
|
7589
|
+
join35(home, ".npm-global", "bin", "claude"),
|
|
7590
|
+
join35(home, ".local", "bin", "claude"),
|
|
6573
7591
|
"/opt/homebrew/bin/claude"
|
|
6574
|
-
]) ??
|
|
7592
|
+
]) ?? join35(home, ".claude", "local", "claude");
|
|
6575
7593
|
case "codex":
|
|
6576
7594
|
return firstExistingPath([
|
|
6577
7595
|
"/usr/local/bin/codex",
|
|
6578
7596
|
"/usr/bin/codex",
|
|
6579
|
-
|
|
6580
|
-
|
|
7597
|
+
join35(home, ".npm-global", "bin", "codex"),
|
|
7598
|
+
join35(home, ".local", "bin", "codex"),
|
|
6581
7599
|
"/opt/homebrew/bin/codex"
|
|
6582
7600
|
]) ?? "/usr/local/bin/codex";
|
|
6583
7601
|
case "cursor":
|
|
6584
7602
|
return firstExistingPath([
|
|
6585
7603
|
"/usr/local/bin/cursor-agent",
|
|
6586
7604
|
"/usr/bin/cursor-agent",
|
|
6587
|
-
|
|
6588
|
-
|
|
7605
|
+
join35(home, ".npm-global", "bin", "cursor-agent"),
|
|
7606
|
+
join35(home, ".local", "bin", "cursor-agent"),
|
|
6589
7607
|
"/opt/homebrew/bin/cursor-agent"
|
|
6590
7608
|
]) ?? "/usr/local/bin/cursor-agent";
|
|
6591
7609
|
case "hermes":
|
|
6592
7610
|
return firstExistingPath([
|
|
6593
|
-
|
|
7611
|
+
join35(home, ".local", "bin", "hermes"),
|
|
6594
7612
|
"/usr/local/bin/hermes",
|
|
6595
7613
|
"/usr/bin/hermes",
|
|
6596
|
-
|
|
7614
|
+
join35(home, ".npm-global", "bin", "hermes"),
|
|
6597
7615
|
"/opt/homebrew/bin/hermes"
|
|
6598
|
-
]) ??
|
|
7616
|
+
]) ?? join35(home, ".local", "bin", "hermes");
|
|
6599
7617
|
case "pi":
|
|
6600
7618
|
return firstExistingPath([
|
|
6601
|
-
|
|
7619
|
+
join35(home, ".local", "bin", "pi"),
|
|
6602
7620
|
"/usr/local/bin/pi",
|
|
6603
7621
|
"/usr/bin/pi",
|
|
6604
|
-
|
|
7622
|
+
join35(home, ".npm-global", "bin", "pi"),
|
|
6605
7623
|
"/opt/homebrew/bin/pi"
|
|
6606
|
-
]) ??
|
|
7624
|
+
]) ?? join35(home, ".local", "bin", "pi");
|
|
6607
7625
|
}
|
|
6608
7626
|
}
|
|
6609
7627
|
|
|
@@ -6633,27 +7651,28 @@ function extractJsonBlock(s) {
|
|
|
6633
7651
|
}
|
|
6634
7652
|
|
|
6635
7653
|
// dist/src/skillify/local-manifest.js
|
|
6636
|
-
import { existsSync as
|
|
6637
|
-
import { homedir as
|
|
6638
|
-
import { dirname as
|
|
6639
|
-
var LOCAL_MANIFEST_PATH =
|
|
6640
|
-
var LOCAL_MINE_LOCK_PATH =
|
|
7654
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync15, readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "node:fs";
|
|
7655
|
+
import { homedir as homedir21 } from "node:os";
|
|
7656
|
+
import { dirname as dirname9, join as join36 } from "node:path";
|
|
7657
|
+
var LOCAL_MANIFEST_PATH = join36(homedir21(), ".claude", "hivemind", "local-mined.json");
|
|
7658
|
+
var LOCAL_MINE_LOCK_PATH = join36(homedir21(), ".claude", "hivemind", "local-mined.lock");
|
|
6641
7659
|
function readLocalManifest(path = LOCAL_MANIFEST_PATH) {
|
|
6642
|
-
if (!
|
|
7660
|
+
if (!existsSync28(path))
|
|
6643
7661
|
return null;
|
|
6644
7662
|
try {
|
|
6645
|
-
return JSON.parse(
|
|
7663
|
+
return JSON.parse(readFileSync24(path, "utf-8"));
|
|
6646
7664
|
} catch {
|
|
6647
7665
|
return null;
|
|
6648
7666
|
}
|
|
6649
7667
|
}
|
|
6650
7668
|
function writeLocalManifest(m, path = LOCAL_MANIFEST_PATH) {
|
|
6651
|
-
|
|
6652
|
-
|
|
7669
|
+
mkdirSync15(dirname9(path), { recursive: true });
|
|
7670
|
+
writeFileSync18(path, JSON.stringify(m, null, 2));
|
|
6653
7671
|
}
|
|
7672
|
+
var LATEST_RUN_WINDOW_MS = 5 * 60 * 1e3;
|
|
6654
7673
|
|
|
6655
7674
|
// dist/src/commands/mine-local.js
|
|
6656
|
-
import { unlinkSync as
|
|
7675
|
+
import { unlinkSync as unlinkSync11 } from "node:fs";
|
|
6657
7676
|
var EPSILON = 0.3;
|
|
6658
7677
|
var DEFAULT_N = 8;
|
|
6659
7678
|
var PAIR_CHAR_CAP = 4e3;
|
|
@@ -6664,9 +7683,9 @@ var IN_FLIGHT_MAX_AGE_MS = 6e4;
|
|
|
6664
7683
|
var GATE_TIMEOUT_MS = 24e4;
|
|
6665
7684
|
var MANIFEST_PATH = LOCAL_MANIFEST_PATH;
|
|
6666
7685
|
function runGateViaStdin(opts) {
|
|
6667
|
-
return new Promise((
|
|
7686
|
+
return new Promise((resolve3) => {
|
|
6668
7687
|
if (opts.agent !== "claude_code") {
|
|
6669
|
-
|
|
7688
|
+
resolve3({
|
|
6670
7689
|
stdout: "",
|
|
6671
7690
|
stderr: "",
|
|
6672
7691
|
errored: true,
|
|
@@ -6674,8 +7693,8 @@ function runGateViaStdin(opts) {
|
|
|
6674
7693
|
});
|
|
6675
7694
|
return;
|
|
6676
7695
|
}
|
|
6677
|
-
if (!
|
|
6678
|
-
|
|
7696
|
+
if (!existsSync29(opts.bin)) {
|
|
7697
|
+
resolve3({
|
|
6679
7698
|
stdout: "",
|
|
6680
7699
|
stderr: "",
|
|
6681
7700
|
errored: true,
|
|
@@ -6691,7 +7710,7 @@ function runGateViaStdin(opts) {
|
|
|
6691
7710
|
"--permission-mode",
|
|
6692
7711
|
"bypassPermissions"
|
|
6693
7712
|
];
|
|
6694
|
-
const child =
|
|
7713
|
+
const child = spawn2(opts.bin, args, {
|
|
6695
7714
|
stdio: ["pipe", "pipe", "pipe"],
|
|
6696
7715
|
env: { ...process.env, HIVEMIND_WIKI_WORKER: "1", HIVEMIND_CAPTURE: "false" }
|
|
6697
7716
|
});
|
|
@@ -6702,7 +7721,7 @@ function runGateViaStdin(opts) {
|
|
|
6702
7721
|
if (settled)
|
|
6703
7722
|
return;
|
|
6704
7723
|
settled = true;
|
|
6705
|
-
|
|
7724
|
+
resolve3(r);
|
|
6706
7725
|
};
|
|
6707
7726
|
const timer = setTimeout(() => {
|
|
6708
7727
|
try {
|
|
@@ -6782,11 +7801,26 @@ function buildSessionPrompt(pairs2, session, verdictPath) {
|
|
|
6782
7801
|
`- A skill qualifies if it captures a concrete, repeatable workflow OR a non-obvious`,
|
|
6783
7802
|
` constraint/gotcha a future engineer would benefit from knowing. Intra-session is fine \u2014`,
|
|
6784
7803
|
` one deep dive yielding a generalizable takeaway counts.`,
|
|
7804
|
+
`- Especially valuable: REPEATABLE-MISTAKE patterns. Cases where the assistant declared`,
|
|
7805
|
+
` work "done"/"fixed"/"verified" and the user came back to the same problem later; where`,
|
|
7806
|
+
` the same class of mistake recurs (forgot to run tests, mishandled async state,`,
|
|
7807
|
+
` hallucinated function/file existence, re-asked for confirmation on already-authorized`,
|
|
7808
|
+
` work, jumped to plans without checking with the user, etc.); where the user manually`,
|
|
7809
|
+
` corrected the same kind of error >1 time. These are the highest-value catches.`,
|
|
6785
7810
|
`- Skip patterns that are obvious from reading the codebase or already in CLAUDE.md.`,
|
|
6786
7811
|
`- Each body uses short sections (When to use, Workflow, Anti-patterns), concrete commands`,
|
|
6787
7812
|
` / paths / snippets drawn from the exchanges below, no marketing, no emojis.`,
|
|
6788
7813
|
`- Each body under ~3000 characters.`,
|
|
6789
7814
|
`- Skill names are kebab-case slugs (lowercase letters/digits/hyphens only).`,
|
|
7815
|
+
`- For each skill, also emit a one-line "insight": a concrete, quantified, second-person`,
|
|
7816
|
+
` sentence describing what hivemind found that prompted the skill. Examples:`,
|
|
7817
|
+
` "You revisited 4 merged PRs in the last month because the assistant declared 'done'`,
|
|
7818
|
+
` before checking test output."`,
|
|
7819
|
+
` "You corrected the same env-mismatch (beta vs prod) twice in the same week before`,
|
|
7820
|
+
` deciding to switch deployment targets."`,
|
|
7821
|
+
` The insight is what users will see at next SessionStart, so it must be honest \u2014 only`,
|
|
7822
|
+
` assert counts and patterns you can ground in THIS session's exchanges. Omit the field`,
|
|
7823
|
+
` if you cannot write a concrete, quantified line.`,
|
|
6790
7824
|
``,
|
|
6791
7825
|
`=== EXCHANGES (user prompts + assistant final answers, tool calls stripped) ===`,
|
|
6792
7826
|
renderPairsBlock(pairs2),
|
|
@@ -6805,7 +7839,8 @@ function buildSessionPrompt(pairs2, session, verdictPath) {
|
|
|
6805
7839
|
` "name": "<kebab-case>",`,
|
|
6806
7840
|
` "description": "<one-line>",`,
|
|
6807
7841
|
` "trigger": "<short trigger>",`,
|
|
6808
|
-
` "body": "<full SKILL.md body without frontmatter>"
|
|
7842
|
+
` "body": "<full SKILL.md body without frontmatter>",`,
|
|
7843
|
+
` "insight": "<one-line, concrete + quantified + second person; OPTIONAL>"`,
|
|
6809
7844
|
` },`,
|
|
6810
7845
|
` ... up to 3 entries, or [] if nothing qualifies`,
|
|
6811
7846
|
` ]`,
|
|
@@ -6837,9 +7872,12 @@ function parseMultiVerdict(raw) {
|
|
|
6837
7872
|
const description = typeof s.description === "string" ? s.description.trim() : "";
|
|
6838
7873
|
const body = typeof s.body === "string" ? s.body.trim() : "";
|
|
6839
7874
|
const trigger = typeof s.trigger === "string" ? s.trigger.trim() : void 0;
|
|
7875
|
+
const rawInsight = typeof s.insight === "string" ? s.insight : "";
|
|
7876
|
+
const normalizedInsight = rawInsight.replace(/\s+/g, " ").trim();
|
|
7877
|
+
const insight = normalizedInsight.length > 0 ? normalizedInsight.slice(0, 280) : void 0;
|
|
6840
7878
|
if (!name || !body)
|
|
6841
7879
|
continue;
|
|
6842
|
-
out.push({ name, description, body, trigger });
|
|
7880
|
+
out.push({ name, description, body, trigger, insight });
|
|
6843
7881
|
}
|
|
6844
7882
|
return { reason: typeof parsed.reason === "string" ? parsed.reason : void 0, skills: out };
|
|
6845
7883
|
}
|
|
@@ -6962,7 +8000,7 @@ async function runMineLocal(args) {
|
|
|
6962
8000
|
return;
|
|
6963
8001
|
lockReleased = true;
|
|
6964
8002
|
try {
|
|
6965
|
-
|
|
8003
|
+
unlinkSync11(LOCAL_MINE_LOCK_PATH);
|
|
6966
8004
|
} catch {
|
|
6967
8005
|
}
|
|
6968
8006
|
};
|
|
@@ -7019,8 +8057,8 @@ async function runMineLocalImpl(args) {
|
|
|
7019
8057
|
console.log(`Dry-run: would invoke ${gateAgent} gate on ${picked.length} session(s) in parallel (concurrency=${GATE_CONCURRENCY}).`);
|
|
7020
8058
|
return;
|
|
7021
8059
|
}
|
|
7022
|
-
const tmpDir =
|
|
7023
|
-
|
|
8060
|
+
const tmpDir = join37(homedir22(), ".claude", "hivemind", `mine-local-${Date.now()}`);
|
|
8061
|
+
mkdirSync16(tmpDir, { recursive: true });
|
|
7024
8062
|
console.log(`Running ${picked.length} gate call(s) in parallel (concurrency=${GATE_CONCURRENCY}, timeout=${GATE_TIMEOUT_MS / 1e3}s each)...`);
|
|
7025
8063
|
const results = await parallelMap(picked, GATE_CONCURRENCY, async (s) => {
|
|
7026
8064
|
const shortId = s.sessionId.slice(0, 8);
|
|
@@ -7031,23 +8069,23 @@ async function runMineLocalImpl(args) {
|
|
|
7031
8069
|
return { session: s, skills: [], reason: "no pairs", error: null };
|
|
7032
8070
|
}
|
|
7033
8071
|
const tail = pairs2.slice(-PER_SESSION_PAIR_CAP);
|
|
7034
|
-
const sessionTmp =
|
|
7035
|
-
|
|
7036
|
-
const verdictPath =
|
|
8072
|
+
const sessionTmp = join37(tmpDir, `s-${shortId}`);
|
|
8073
|
+
mkdirSync16(sessionTmp, { recursive: true });
|
|
8074
|
+
const verdictPath = join37(sessionTmp, "verdict.json");
|
|
7037
8075
|
const prompt = buildSessionPrompt(tail, s, verdictPath);
|
|
7038
|
-
|
|
8076
|
+
writeFileSync19(join37(sessionTmp, "prompt.txt"), prompt);
|
|
7039
8077
|
const gate = await runGateViaStdin({ agent: gateAgent, bin: gateBin, prompt, timeoutMs: GATE_TIMEOUT_MS });
|
|
7040
8078
|
try {
|
|
7041
|
-
|
|
8079
|
+
writeFileSync19(join37(sessionTmp, "gate-stdout.txt"), gate.stdout);
|
|
7042
8080
|
if (gate.stderr)
|
|
7043
|
-
|
|
8081
|
+
writeFileSync19(join37(sessionTmp, "gate-stderr.txt"), gate.stderr);
|
|
7044
8082
|
} catch {
|
|
7045
8083
|
}
|
|
7046
8084
|
if (gate.errored) {
|
|
7047
8085
|
console.log(` [${shortId}] gate failed: ${gate.errorMessage}`);
|
|
7048
8086
|
return { session: s, skills: [], reason: null, error: gate.errorMessage ?? "gate failed" };
|
|
7049
8087
|
}
|
|
7050
|
-
const verdictText =
|
|
8088
|
+
const verdictText = existsSync29(verdictPath) ? readFileSync25(verdictPath, "utf-8") : gate.stdout;
|
|
7051
8089
|
const mv = parseMultiVerdict(verdictText);
|
|
7052
8090
|
if (!mv) {
|
|
7053
8091
|
console.log(` [${shortId}] unparseable verdict (kept at ${sessionTmp})`);
|
|
@@ -7099,8 +8137,8 @@ async function runMineLocalImpl(args) {
|
|
|
7099
8137
|
sourceSessions: [session.sessionId],
|
|
7100
8138
|
agent: gateAgent
|
|
7101
8139
|
});
|
|
7102
|
-
const canonicalDir =
|
|
7103
|
-
const symlinks = fanOutRoots.length > 0 ? fanOutSymlinks(canonicalDir,
|
|
8140
|
+
const canonicalDir = dirname10(result.path);
|
|
8141
|
+
const symlinks = fanOutRoots.length > 0 ? fanOutSymlinks(canonicalDir, basename2(canonicalDir), fanOutRoots) : [];
|
|
7104
8142
|
const symlinkSuffix = symlinks.length > 0 ? `, fan-out \u2192 ${symlinks.length} root(s)` : "";
|
|
7105
8143
|
console.log(` wrote ${skill.name} \u2190 session ${session.sessionId.slice(0, 8)} (${session.agent}${symlinkSuffix})`);
|
|
7106
8144
|
written.push({ skill, session, result, symlinks });
|
|
@@ -7124,7 +8162,12 @@ async function runMineLocalImpl(args) {
|
|
|
7124
8162
|
source_agent: session.agent,
|
|
7125
8163
|
gate_agent: gateAgent,
|
|
7126
8164
|
created_at: result.createdAt,
|
|
7127
|
-
uploaded: false
|
|
8165
|
+
uploaded: false,
|
|
8166
|
+
// Persist the one-line insight when the gate produced one. Omitted
|
|
8167
|
+
// (undefined → absent in JSON) when the gate couldn't ground a
|
|
8168
|
+
// concrete line, so the SessionStart banner falls back to the
|
|
8169
|
+
// count-only surface for entries written before this field landed.
|
|
8170
|
+
...skill.insight ? { insight: skill.insight } : {}
|
|
7128
8171
|
}));
|
|
7129
8172
|
saveManifest2({
|
|
7130
8173
|
created_at: existing?.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7271,11 +8314,11 @@ function showStatus() {
|
|
|
7271
8314
|
console.log(`team: ${cfg.team.length === 0 ? "(empty)" : cfg.team.join(", ")}`);
|
|
7272
8315
|
console.log(`install: ${cfg.install} (${cfg.install === "global" ? "~/.claude/skills/" : "<project>/.claude/skills/"})`);
|
|
7273
8316
|
const dir = stateDir();
|
|
7274
|
-
if (!
|
|
8317
|
+
if (!existsSync30(dir)) {
|
|
7275
8318
|
console.log(`state: (no projects tracked yet)`);
|
|
7276
8319
|
return;
|
|
7277
8320
|
}
|
|
7278
|
-
const files =
|
|
8321
|
+
const files = readdirSync7(dir).filter((f) => f.endsWith(".json") && f !== "config.json" && f !== "pulled.json" && f !== "autopull-last-run.json");
|
|
7279
8322
|
if (files.length === 0) {
|
|
7280
8323
|
console.log(`state: (no projects tracked yet)`);
|
|
7281
8324
|
return;
|
|
@@ -7283,7 +8326,7 @@ function showStatus() {
|
|
|
7283
8326
|
console.log(`state: ${files.length} project(s) tracked`);
|
|
7284
8327
|
for (const f of files) {
|
|
7285
8328
|
try {
|
|
7286
|
-
const s = JSON.parse(
|
|
8329
|
+
const s = JSON.parse(readFileSync26(join38(dir, f), "utf-8"));
|
|
7287
8330
|
const last = typeof s.updatedAt === "number" ? new Date(s.updatedAt).toISOString() : s.lastDate ?? "never";
|
|
7288
8331
|
const skills = Array.isArray(s.skillsGenerated) && s.skillsGenerated.length > 0 ? s.skillsGenerated.join(", ") : "none";
|
|
7289
8332
|
console.log(` - ${s.project} (counter=${s.counter}, last=${last}, skills=${skills})`);
|
|
@@ -7310,7 +8353,7 @@ function setInstall(loc) {
|
|
|
7310
8353
|
}
|
|
7311
8354
|
const cfg = loadScopeConfig();
|
|
7312
8355
|
saveScopeConfig({ ...cfg, install: loc });
|
|
7313
|
-
const path = loc === "global" ?
|
|
8356
|
+
const path = loc === "global" ? join38(homedir23(), ".claude", "skills") : "<cwd>/.claude/skills";
|
|
7314
8357
|
console.log(`Install location set to '${loc}'. New skills will be written to ${path}/<name>/SKILL.md.`);
|
|
7315
8358
|
}
|
|
7316
8359
|
function promoteSkill(name, cwd) {
|
|
@@ -7318,18 +8361,18 @@ function promoteSkill(name, cwd) {
|
|
|
7318
8361
|
console.error("Usage: hivemind skillify promote <skill-name>");
|
|
7319
8362
|
process.exit(1);
|
|
7320
8363
|
}
|
|
7321
|
-
const projectPath =
|
|
7322
|
-
const globalPath =
|
|
7323
|
-
if (!
|
|
8364
|
+
const projectPath = join38(cwd, ".claude", "skills", name);
|
|
8365
|
+
const globalPath = join38(homedir23(), ".claude", "skills", name);
|
|
8366
|
+
if (!existsSync30(join38(projectPath, "SKILL.md"))) {
|
|
7324
8367
|
console.error(`Skill '${name}' not found at ${projectPath}/SKILL.md`);
|
|
7325
8368
|
process.exit(1);
|
|
7326
8369
|
}
|
|
7327
|
-
if (
|
|
8370
|
+
if (existsSync30(join38(globalPath, "SKILL.md"))) {
|
|
7328
8371
|
console.error(`Skill '${name}' already exists at ${globalPath}/SKILL.md \u2014 refusing to overwrite. Remove it first or rename the project skill.`);
|
|
7329
8372
|
process.exit(1);
|
|
7330
8373
|
}
|
|
7331
|
-
|
|
7332
|
-
|
|
8374
|
+
mkdirSync17(dirname11(globalPath), { recursive: true });
|
|
8375
|
+
renameSync8(projectPath, globalPath);
|
|
7333
8376
|
console.log(`Promoted '${name}' from ${projectPath} \u2192 ${globalPath}.`);
|
|
7334
8377
|
}
|
|
7335
8378
|
function teamAdd(name) {
|
|
@@ -7435,7 +8478,7 @@ async function pullSkills(args) {
|
|
|
7435
8478
|
console.error(`pull failed: ${e?.message ?? e}`);
|
|
7436
8479
|
process.exit(1);
|
|
7437
8480
|
}
|
|
7438
|
-
const dest = toRaw === "global" ?
|
|
8481
|
+
const dest = toRaw === "global" ? join38(homedir23(), ".claude", "skills") : `${process.cwd()}/.claude/skills`;
|
|
7439
8482
|
const filterDesc = users.length === 0 ? "all users" : users.join(", ");
|
|
7440
8483
|
console.log(`Destination: ${dest}`);
|
|
7441
8484
|
console.log(`Filter: ${filterDesc}${skillName ? ` \xB7 skill='${skillName}'` : ""}${dryRun ? " \xB7 dry-run" : ""}${force ? " \xB7 force" : ""}`);
|
|
@@ -7485,7 +8528,7 @@ async function unpullSkills(args) {
|
|
|
7485
8528
|
all,
|
|
7486
8529
|
legacyCleanup
|
|
7487
8530
|
});
|
|
7488
|
-
const dest = toRaw === "global" ?
|
|
8531
|
+
const dest = toRaw === "global" ? join38(homedir23(), ".claude", "skills") : `${process.cwd()}/.claude/skills`;
|
|
7489
8532
|
const filterParts = [];
|
|
7490
8533
|
if (users.length > 0)
|
|
7491
8534
|
filterParts.push(`users=${users.join(",")}`);
|
|
@@ -7581,14 +8624,14 @@ if (process.argv[1] && process.argv[1].endsWith("skillify.js")) {
|
|
|
7581
8624
|
|
|
7582
8625
|
// dist/src/cli/update.js
|
|
7583
8626
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
7584
|
-
import { closeSync as
|
|
7585
|
-
import { homedir as
|
|
7586
|
-
import { dirname as
|
|
8627
|
+
import { closeSync as closeSync3, existsSync as existsSync31, mkdirSync as mkdirSync18, openSync as openSync3, readFileSync as readFileSync28, realpathSync, unlinkSync as unlinkSync12, writeSync as writeSync2 } from "node:fs";
|
|
8628
|
+
import { homedir as homedir24 } from "node:os";
|
|
8629
|
+
import { dirname as dirname13, join as join40, sep } from "node:path";
|
|
7587
8630
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7588
8631
|
|
|
7589
8632
|
// dist/src/utils/version-check.js
|
|
7590
|
-
import { readFileSync as
|
|
7591
|
-
import { dirname as
|
|
8633
|
+
import { readFileSync as readFileSync27 } from "node:fs";
|
|
8634
|
+
import { dirname as dirname12, join as join39 } from "node:path";
|
|
7592
8635
|
function isNewer(latest, current) {
|
|
7593
8636
|
const parse = (v) => v.split(".").map(Number);
|
|
7594
8637
|
const [la, lb, lc] = parse(latest);
|
|
@@ -7600,7 +8643,7 @@ function isNewer(latest, current) {
|
|
|
7600
8643
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@deeplake/hivemind/latest";
|
|
7601
8644
|
var PKG_NAME = "@deeplake/hivemind";
|
|
7602
8645
|
function defaultLockPath() {
|
|
7603
|
-
return
|
|
8646
|
+
return join40(homedir24(), ".deeplake", "hivemind-update.lock");
|
|
7604
8647
|
}
|
|
7605
8648
|
function detectInstallKind(argv1) {
|
|
7606
8649
|
const realArgv1 = (() => {
|
|
@@ -7610,24 +8653,24 @@ function detectInstallKind(argv1) {
|
|
|
7610
8653
|
return argv1 ?? process.argv[1] ?? fileURLToPath2(import.meta.url);
|
|
7611
8654
|
}
|
|
7612
8655
|
})();
|
|
7613
|
-
let dir =
|
|
8656
|
+
let dir = dirname13(realArgv1);
|
|
7614
8657
|
let installDir = null;
|
|
7615
8658
|
for (let i = 0; i < 10; i++) {
|
|
7616
8659
|
const pkgPath = `${dir}${sep}package.json`;
|
|
7617
8660
|
try {
|
|
7618
|
-
const pkg = JSON.parse(
|
|
8661
|
+
const pkg = JSON.parse(readFileSync28(pkgPath, "utf-8"));
|
|
7619
8662
|
if (pkg.name === PKG_NAME || pkg.name === "hivemind") {
|
|
7620
8663
|
installDir = dir;
|
|
7621
8664
|
break;
|
|
7622
8665
|
}
|
|
7623
8666
|
} catch {
|
|
7624
8667
|
}
|
|
7625
|
-
const parent =
|
|
8668
|
+
const parent = dirname13(dir);
|
|
7626
8669
|
if (parent === dir)
|
|
7627
8670
|
break;
|
|
7628
8671
|
dir = parent;
|
|
7629
8672
|
}
|
|
7630
|
-
installDir ??=
|
|
8673
|
+
installDir ??= dirname13(realArgv1);
|
|
7631
8674
|
if (realArgv1.includes(`${sep}_npx${sep}`) || realArgv1.includes(`${sep}.npx${sep}`)) {
|
|
7632
8675
|
return { kind: "npx", installDir };
|
|
7633
8676
|
}
|
|
@@ -7636,10 +8679,10 @@ function detectInstallKind(argv1) {
|
|
|
7636
8679
|
}
|
|
7637
8680
|
let gitDir = installDir;
|
|
7638
8681
|
for (let i = 0; i < 6; i++) {
|
|
7639
|
-
if (
|
|
8682
|
+
if (existsSync31(`${gitDir}${sep}.git`)) {
|
|
7640
8683
|
return { kind: "local-dev", installDir };
|
|
7641
8684
|
}
|
|
7642
|
-
const parent =
|
|
8685
|
+
const parent = dirname13(gitDir);
|
|
7643
8686
|
if (parent === gitDir)
|
|
7644
8687
|
break;
|
|
7645
8688
|
gitDir = parent;
|
|
@@ -7661,10 +8704,10 @@ var defaultSpawn = (cmd, args) => {
|
|
|
7661
8704
|
execFileSync4(cmd, args, { stdio: "inherit" });
|
|
7662
8705
|
};
|
|
7663
8706
|
function tryAcquireLock(path) {
|
|
7664
|
-
|
|
8707
|
+
mkdirSync18(dirname13(path), { recursive: true, mode: 448 });
|
|
7665
8708
|
const claim = () => {
|
|
7666
|
-
const fd =
|
|
7667
|
-
|
|
8709
|
+
const fd = openSync3(path, "wx", 384);
|
|
8710
|
+
writeSync2(fd, String(process.pid));
|
|
7668
8711
|
return fd;
|
|
7669
8712
|
};
|
|
7670
8713
|
try {
|
|
@@ -7675,7 +8718,7 @@ function tryAcquireLock(path) {
|
|
|
7675
8718
|
}
|
|
7676
8719
|
let holderPid = 0;
|
|
7677
8720
|
try {
|
|
7678
|
-
holderPid = Number(
|
|
8721
|
+
holderPid = Number(readFileSync28(path, "utf-8").trim()) || 0;
|
|
7679
8722
|
} catch {
|
|
7680
8723
|
try {
|
|
7681
8724
|
return claim();
|
|
@@ -7692,7 +8735,7 @@ function tryAcquireLock(path) {
|
|
|
7692
8735
|
}
|
|
7693
8736
|
}
|
|
7694
8737
|
try {
|
|
7695
|
-
|
|
8738
|
+
unlinkSync12(path);
|
|
7696
8739
|
} catch {
|
|
7697
8740
|
}
|
|
7698
8741
|
try {
|
|
@@ -7704,11 +8747,11 @@ function tryAcquireLock(path) {
|
|
|
7704
8747
|
}
|
|
7705
8748
|
function releaseLock(fd, path) {
|
|
7706
8749
|
try {
|
|
7707
|
-
|
|
8750
|
+
closeSync3(fd);
|
|
7708
8751
|
} catch {
|
|
7709
8752
|
}
|
|
7710
8753
|
try {
|
|
7711
|
-
|
|
8754
|
+
unlinkSync12(path);
|
|
7712
8755
|
} catch {
|
|
7713
8756
|
}
|
|
7714
8757
|
}
|
|
@@ -7726,7 +8769,7 @@ async function runUpdate(opts = {}) {
|
|
|
7726
8769
|
}
|
|
7727
8770
|
log(`Update available: ${current} \u2192 ${latest}`);
|
|
7728
8771
|
const detected = opts.installKindOverride ?? detectInstallKind();
|
|
7729
|
-
const
|
|
8772
|
+
const spawn3 = opts.spawn ?? defaultSpawn;
|
|
7730
8773
|
switch (detected.kind) {
|
|
7731
8774
|
case "npm-global": {
|
|
7732
8775
|
if (opts.dryRun) {
|
|
@@ -7741,7 +8784,7 @@ async function runUpdate(opts = {}) {
|
|
|
7741
8784
|
try {
|
|
7742
8785
|
log(`Upgrading via npm\u2026`);
|
|
7743
8786
|
try {
|
|
7744
|
-
|
|
8787
|
+
spawn3("npm", ["install", "-g", `${PKG_NAME}@latest`]);
|
|
7745
8788
|
} catch (e) {
|
|
7746
8789
|
warn(`npm install failed: ${e.message}`);
|
|
7747
8790
|
warn(`Try running it manually: npm install -g ${PKG_NAME}@latest`);
|
|
@@ -7750,7 +8793,7 @@ async function runUpdate(opts = {}) {
|
|
|
7750
8793
|
log(``);
|
|
7751
8794
|
log(`Refreshing agent bundles\u2026`);
|
|
7752
8795
|
try {
|
|
7753
|
-
|
|
8796
|
+
spawn3("hivemind", ["install", "--skip-auth"]);
|
|
7754
8797
|
} catch (e) {
|
|
7755
8798
|
warn(`Agent refresh failed: ${e.message}`);
|
|
7756
8799
|
warn(`Run manually: hivemind install`);
|
|
@@ -7814,7 +8857,7 @@ var AUTH_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
7814
8857
|
"autoupdate",
|
|
7815
8858
|
"sessions"
|
|
7816
8859
|
]);
|
|
7817
|
-
var
|
|
8860
|
+
var USAGE2 = `
|
|
7818
8861
|
hivemind \u2014 one brain for every agent on your team
|
|
7819
8862
|
|
|
7820
8863
|
Usage:
|
|
@@ -7844,6 +8887,18 @@ Usage:
|
|
|
7844
8887
|
Check npm for a newer @deeplake/hivemind, upgrade the CLI, and refresh
|
|
7845
8888
|
every detected agent bundle. Single command for all agents.
|
|
7846
8889
|
|
|
8890
|
+
hivemind dashboard [--cwd <path>] [--out <path>] [--no-open]
|
|
8891
|
+
[--serve] [--port <n>]
|
|
8892
|
+
Build a self-contained HTML dashboard for this repo. Combines
|
|
8893
|
+
KPI cards (tokens saved, skills created, memory recalls,
|
|
8894
|
+
sessions) with the codebase-graph visualization. Writes to
|
|
8895
|
+
~/.hivemind/dashboards/<repo-key>/index.html by default.
|
|
8896
|
+
--no-open skips the browser launch (headless / CI scenarios).
|
|
8897
|
+
--serve starts a loopback HTTP server at http://127.0.0.1:<port>
|
|
8898
|
+
(default 8123) so the dashboard is reachable via a URL \u2014 useful
|
|
8899
|
+
over SSH; VS Code / Cursor Remote-SSH auto-forwards the port
|
|
8900
|
+
and opens it in the integrated Simple Browser tab on click.
|
|
8901
|
+
|
|
7847
8902
|
Semantic search (embeddings):
|
|
7848
8903
|
hivemind embeddings install Download @huggingface/transformers
|
|
7849
8904
|
once (~600 MB) into a shared dir,
|
|
@@ -8062,7 +9117,7 @@ async function main() {
|
|
|
8062
9117
|
const args = process.argv.slice(2);
|
|
8063
9118
|
const cmd = args[0];
|
|
8064
9119
|
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
8065
|
-
log(
|
|
9120
|
+
log(USAGE2);
|
|
8066
9121
|
return;
|
|
8067
9122
|
}
|
|
8068
9123
|
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
@@ -8096,6 +9151,10 @@ async function main() {
|
|
|
8096
9151
|
runSkillifyCommand(args.slice(1));
|
|
8097
9152
|
return;
|
|
8098
9153
|
}
|
|
9154
|
+
if (cmd === "dashboard") {
|
|
9155
|
+
const code = await runDashboardCommand(args.slice(1));
|
|
9156
|
+
process.exit(code);
|
|
9157
|
+
}
|
|
8099
9158
|
if (cmd === "embeddings") {
|
|
8100
9159
|
const sub = args[1];
|
|
8101
9160
|
if (sub === "install") {
|
|
@@ -8143,7 +9202,7 @@ async function main() {
|
|
|
8143
9202
|
return;
|
|
8144
9203
|
}
|
|
8145
9204
|
warn(`Unknown command: ${cmd}`);
|
|
8146
|
-
log(
|
|
9205
|
+
log(USAGE2);
|
|
8147
9206
|
process.exit(1);
|
|
8148
9207
|
}
|
|
8149
9208
|
main().catch((err) => {
|