@deeplake/hivemind 0.7.25 → 0.7.27
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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/bundle/cli.js +1102 -151
- package/codex/bundle/session-start.js +223 -131
- package/codex/skills/deeplake-memory/SKILL.md +33 -0
- package/cursor/bundle/session-start.js +228 -82
- package/hermes/bundle/session-start.js +229 -82
- 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/pi/extension-source/hivemind.ts +157 -19
package/bundle/cli.js
CHANGED
|
@@ -181,74 +181,74 @@ function pluginAlreadyInstalled() {
|
|
|
181
181
|
return r.stdout.includes(PLUGIN_KEY);
|
|
182
182
|
}
|
|
183
183
|
var PLUGIN_SCOPES = ["user", "project", "local", "managed"];
|
|
184
|
-
function resolvePluginRoot() {
|
|
185
|
-
return join2(homedir2(), ".claude", "plugins", "hivemind");
|
|
186
|
-
}
|
|
187
|
-
function marketplaceHooksJsonPath() {
|
|
188
|
-
return join2(homedir2(), ".claude", "plugins", "marketplaces", "hivemind", "claude-code", "hooks", "hooks.json");
|
|
189
|
-
}
|
|
190
184
|
function settingsJsonPath() {
|
|
191
185
|
return join2(homedir2(), ".claude", "settings.json");
|
|
192
186
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
187
|
+
var LEGACY_PATH_FRAGMENT = ".claude/plugins/hivemind/bundle/";
|
|
188
|
+
function isBrokenHivemindHookEntry(h) {
|
|
189
|
+
if (typeof h.command !== "string")
|
|
190
|
+
return false;
|
|
191
|
+
const normalized = h.command.replace(/\\/g, "/");
|
|
192
|
+
if (!normalized.includes(LEGACY_PATH_FRAGMENT))
|
|
193
|
+
return false;
|
|
194
|
+
const match = normalized.match(/"([^"]+\.claude\/plugins\/hivemind\/bundle\/[^"]+)"/);
|
|
195
|
+
const filePath = match ? match[1] : null;
|
|
196
|
+
if (!filePath)
|
|
197
|
+
return false;
|
|
198
|
+
return !existsSync2(filePath);
|
|
203
199
|
}
|
|
204
|
-
function
|
|
205
|
-
const hooksPath = marketplaceHooksJsonPath();
|
|
200
|
+
function cleanupBrokenSettingsHooks() {
|
|
206
201
|
const settingsPath = settingsJsonPath();
|
|
207
|
-
if (!existsSync2(
|
|
208
|
-
return {
|
|
209
|
-
let
|
|
202
|
+
if (!existsSync2(settingsPath))
|
|
203
|
+
return { removed: 0, events: [] };
|
|
204
|
+
let parsed;
|
|
210
205
|
try {
|
|
211
|
-
|
|
206
|
+
parsed = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
212
207
|
} catch {
|
|
213
|
-
return {
|
|
214
|
-
}
|
|
215
|
-
if (!
|
|
216
|
-
return {
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
208
|
+
return { removed: 0, events: [] };
|
|
209
|
+
}
|
|
210
|
+
if (!parsed || typeof parsed !== "object")
|
|
211
|
+
return { removed: 0, events: [] };
|
|
212
|
+
const settings = parsed;
|
|
213
|
+
if (!settings.hooks || typeof settings.hooks !== "object")
|
|
214
|
+
return { removed: 0, events: [] };
|
|
215
|
+
let removed = 0;
|
|
216
|
+
const touchedEvents = [];
|
|
217
|
+
for (const [event, matchers] of Object.entries(settings.hooks)) {
|
|
218
|
+
if (!Array.isArray(matchers))
|
|
219
|
+
continue;
|
|
220
|
+
const cleanedMatchers = [];
|
|
221
|
+
let eventTouched = false;
|
|
222
|
+
for (const m of matchers) {
|
|
223
|
+
if (!m || !Array.isArray(m.hooks)) {
|
|
224
|
+
cleanedMatchers.push(m);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const keptHooks = m.hooks.filter((h) => {
|
|
228
|
+
const broken = isBrokenHivemindHookEntry(h);
|
|
229
|
+
if (broken) {
|
|
230
|
+
removed += 1;
|
|
231
|
+
eventTouched = true;
|
|
232
|
+
}
|
|
233
|
+
return !broken;
|
|
234
|
+
});
|
|
235
|
+
if (keptHooks.length > 0) {
|
|
236
|
+
cleanedMatchers.push({ ...m, hooks: keptHooks });
|
|
237
|
+
} else if (m.hooks.length > 0) {
|
|
238
|
+
eventTouched = true;
|
|
239
|
+
} else {
|
|
240
|
+
cleanedMatchers.push(m);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (eventTouched) {
|
|
244
|
+
settings.hooks[event] = cleanedMatchers;
|
|
245
|
+
touchedEvents.push(event);
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
-
if (
|
|
248
|
+
if (removed > 0) {
|
|
249
249
|
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
250
250
|
}
|
|
251
|
-
return {
|
|
251
|
+
return { removed, events: touchedEvents };
|
|
252
252
|
}
|
|
253
253
|
function installClaude() {
|
|
254
254
|
requireClaudeCli();
|
|
@@ -273,12 +273,12 @@ function installClaude() {
|
|
|
273
273
|
}
|
|
274
274
|
runClaude(["plugin", "enable", PLUGIN_KEY]);
|
|
275
275
|
try {
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
278
|
-
log(` Claude Code settings.json
|
|
276
|
+
const cleanup = cleanupBrokenSettingsHooks();
|
|
277
|
+
if (cleanup.removed > 0) {
|
|
278
|
+
log(` Claude Code settings.json cleaned: removed ${cleanup.removed} stale hook entr${cleanup.removed === 1 ? "y" : "ies"} (events: ${cleanup.events.join(", ")})`);
|
|
279
279
|
}
|
|
280
280
|
} catch (e) {
|
|
281
|
-
log(` Claude Code settings.json
|
|
281
|
+
log(` Claude Code settings.json cleanup skipped: ${e?.message ?? String(e)}`);
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
function uninstallClaude() {
|
|
@@ -4801,9 +4801,9 @@ if (process.argv[1] && process.argv[1].endsWith("auth-login.js")) {
|
|
|
4801
4801
|
}
|
|
4802
4802
|
|
|
4803
4803
|
// dist/src/commands/skillify.js
|
|
4804
|
-
import { readdirSync as
|
|
4805
|
-
import { homedir as
|
|
4806
|
-
import { dirname as
|
|
4804
|
+
import { readdirSync as readdirSync5, existsSync as existsSync24, readFileSync as readFileSync17, mkdirSync as mkdirSync10, renameSync as renameSync4 } from "node:fs";
|
|
4805
|
+
import { homedir as homedir17 } from "node:os";
|
|
4806
|
+
import { dirname as dirname6, join as join27 } from "node:path";
|
|
4807
4807
|
|
|
4808
4808
|
// dist/src/skillify/scope-config.js
|
|
4809
4809
|
import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
|
|
@@ -4887,6 +4887,35 @@ function assertValidSkillName(name) {
|
|
|
4887
4887
|
throw new Error(`invalid skill name: must be kebab-case (lowercase a-z, 0-9, hyphen): ${name}`);
|
|
4888
4888
|
}
|
|
4889
4889
|
}
|
|
4890
|
+
function skillDir(skillsRoot, name) {
|
|
4891
|
+
return join18(skillsRoot, name);
|
|
4892
|
+
}
|
|
4893
|
+
function skillPath(skillsRoot, name) {
|
|
4894
|
+
return join18(skillDir(skillsRoot, name), "SKILL.md");
|
|
4895
|
+
}
|
|
4896
|
+
function renderFrontmatter(fm) {
|
|
4897
|
+
const lines = ["---"];
|
|
4898
|
+
lines.push(`name: ${fm.name}`);
|
|
4899
|
+
lines.push(`description: ${JSON.stringify(fm.description)}`);
|
|
4900
|
+
if (fm.trigger)
|
|
4901
|
+
lines.push(`trigger: ${JSON.stringify(fm.trigger)}`);
|
|
4902
|
+
if (fm.author)
|
|
4903
|
+
lines.push(`author: ${fm.author}`);
|
|
4904
|
+
lines.push(`source_sessions:`);
|
|
4905
|
+
for (const s of fm.source_sessions)
|
|
4906
|
+
lines.push(` - ${s}`);
|
|
4907
|
+
if (fm.contributors && fm.contributors.length > 0) {
|
|
4908
|
+
lines.push(`contributors:`);
|
|
4909
|
+
for (const c of fm.contributors)
|
|
4910
|
+
lines.push(` - ${c}`);
|
|
4911
|
+
}
|
|
4912
|
+
lines.push(`version: ${fm.version}`);
|
|
4913
|
+
lines.push(`created_by_agent: ${fm.created_by_agent}`);
|
|
4914
|
+
lines.push(`created_at: ${fm.created_at}`);
|
|
4915
|
+
lines.push(`updated_at: ${fm.updated_at}`);
|
|
4916
|
+
lines.push("---");
|
|
4917
|
+
return lines.join("\n");
|
|
4918
|
+
}
|
|
4890
4919
|
function parseFrontmatter(text) {
|
|
4891
4920
|
if (!text.startsWith("---\n") && !text.startsWith("---\r\n"))
|
|
4892
4921
|
return null;
|
|
@@ -4936,6 +4965,62 @@ function parseFrontmatter(text) {
|
|
|
4936
4965
|
}
|
|
4937
4966
|
return { fm, body };
|
|
4938
4967
|
}
|
|
4968
|
+
function writeNewSkill(args) {
|
|
4969
|
+
assertValidSkillName(args.name);
|
|
4970
|
+
const dir = skillDir(args.skillsRoot, args.name);
|
|
4971
|
+
const path = skillPath(args.skillsRoot, args.name);
|
|
4972
|
+
if (existsSync15(path)) {
|
|
4973
|
+
throw new Error(`skill already exists at ${path}; use mergeSkill`);
|
|
4974
|
+
}
|
|
4975
|
+
mkdirSync5(dir, { recursive: true });
|
|
4976
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4977
|
+
const author = args.author && args.author.length > 0 ? args.author : void 0;
|
|
4978
|
+
const contributors = author ? [author] : [];
|
|
4979
|
+
const fm = {
|
|
4980
|
+
name: args.name,
|
|
4981
|
+
description: args.description,
|
|
4982
|
+
trigger: args.trigger,
|
|
4983
|
+
author,
|
|
4984
|
+
source_sessions: args.sourceSessions,
|
|
4985
|
+
contributors,
|
|
4986
|
+
version: 1,
|
|
4987
|
+
created_by_agent: args.agent,
|
|
4988
|
+
created_at: now,
|
|
4989
|
+
updated_at: now
|
|
4990
|
+
};
|
|
4991
|
+
const text = `${renderFrontmatter(fm)}
|
|
4992
|
+
|
|
4993
|
+
${args.body.trim()}
|
|
4994
|
+
`;
|
|
4995
|
+
writeFileSync8(path, text);
|
|
4996
|
+
return {
|
|
4997
|
+
path,
|
|
4998
|
+
action: "created",
|
|
4999
|
+
version: 1,
|
|
5000
|
+
createdAt: now,
|
|
5001
|
+
updatedAt: now,
|
|
5002
|
+
author,
|
|
5003
|
+
contributors
|
|
5004
|
+
};
|
|
5005
|
+
}
|
|
5006
|
+
function listSkills(skillsRoot) {
|
|
5007
|
+
if (!existsSync15(skillsRoot))
|
|
5008
|
+
return [];
|
|
5009
|
+
const out = [];
|
|
5010
|
+
for (const name of readdirSync2(skillsRoot)) {
|
|
5011
|
+
const skillFile = join18(skillsRoot, name, "SKILL.md");
|
|
5012
|
+
if (existsSync15(skillFile) && statSync2(skillFile).isFile()) {
|
|
5013
|
+
out.push({ name, body: readFileSync11(skillFile, "utf-8") });
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
return out;
|
|
5017
|
+
}
|
|
5018
|
+
function resolveSkillsRoot(install, cwd) {
|
|
5019
|
+
if (install === "global") {
|
|
5020
|
+
return join18(homedir8(), ".claude", "skills");
|
|
5021
|
+
}
|
|
5022
|
+
return join18(cwd, ".claude", "skills");
|
|
5023
|
+
}
|
|
4939
5024
|
|
|
4940
5025
|
// dist/src/skillify/manifest.js
|
|
4941
5026
|
import { existsSync as existsSync16, lstatSync as lstatSync3, mkdirSync as mkdirSync6, readFileSync as readFileSync12, renameSync as renameSync2, unlinkSync as unlinkSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
@@ -5228,7 +5313,7 @@ function renderSkillFile(row) {
|
|
|
5228
5313
|
updated_at: String(row.updated_at ?? (/* @__PURE__ */ new Date()).toISOString())
|
|
5229
5314
|
};
|
|
5230
5315
|
const body = String(row.body ?? "").trim();
|
|
5231
|
-
return `${
|
|
5316
|
+
return `${renderFrontmatter2(fm)}
|
|
5232
5317
|
|
|
5233
5318
|
${body}
|
|
5234
5319
|
`;
|
|
@@ -5259,7 +5344,7 @@ function parseContributors(v) {
|
|
|
5259
5344
|
}
|
|
5260
5345
|
return [];
|
|
5261
5346
|
}
|
|
5262
|
-
function
|
|
5347
|
+
function renderFrontmatter2(fm) {
|
|
5263
5348
|
const lines = ["---"];
|
|
5264
5349
|
lines.push(`name: ${fm.name}`);
|
|
5265
5350
|
lines.push(`description: ${JSON.stringify(fm.description)}`);
|
|
@@ -5381,8 +5466,8 @@ async function runPull(opts) {
|
|
|
5381
5466
|
summary.skipped++;
|
|
5382
5467
|
continue;
|
|
5383
5468
|
}
|
|
5384
|
-
const
|
|
5385
|
-
const skillFile = join21(
|
|
5469
|
+
const skillDir2 = join21(root, dirName);
|
|
5470
|
+
const skillFile = join21(skillDir2, "SKILL.md");
|
|
5386
5471
|
const remoteVersion = Number(row.version ?? 1);
|
|
5387
5472
|
const localVersion = readLocalVersion(skillFile);
|
|
5388
5473
|
const action = decideAction({
|
|
@@ -5393,7 +5478,7 @@ async function runPull(opts) {
|
|
|
5393
5478
|
});
|
|
5394
5479
|
let manifestError;
|
|
5395
5480
|
if (action === "wrote") {
|
|
5396
|
-
mkdirSync7(
|
|
5481
|
+
mkdirSync7(skillDir2, { recursive: true });
|
|
5397
5482
|
if (existsSync18(skillFile)) {
|
|
5398
5483
|
try {
|
|
5399
5484
|
renameSync3(skillFile, `${skillFile}.bak`);
|
|
@@ -5401,7 +5486,7 @@ async function runPull(opts) {
|
|
|
5401
5486
|
}
|
|
5402
5487
|
}
|
|
5403
5488
|
writeFileSync10(skillFile, renderSkillFile(row));
|
|
5404
|
-
const symlinks = opts.install === "global" ? fanOutSymlinks(
|
|
5489
|
+
const symlinks = opts.install === "global" ? fanOutSymlinks(skillDir2, dirName, detectAgentSkillsRoots(root)) : [];
|
|
5405
5490
|
try {
|
|
5406
5491
|
recordPull({
|
|
5407
5492
|
dirName,
|
|
@@ -5609,9 +5694,913 @@ function decideTargetForManifestEntry(entry, opts, userFilter, haveUserFilter) {
|
|
|
5609
5694
|
return { shouldRemove: true };
|
|
5610
5695
|
}
|
|
5611
5696
|
|
|
5697
|
+
// dist/src/commands/mine-local.js
|
|
5698
|
+
import { spawn } from "node:child_process";
|
|
5699
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync9, readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "node:fs";
|
|
5700
|
+
import { homedir as homedir16 } from "node:os";
|
|
5701
|
+
import { basename, dirname as dirname5, join as join26 } from "node:path";
|
|
5702
|
+
|
|
5703
|
+
// dist/src/skillify/local-source.js
|
|
5704
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync14, existsSync as existsSync20, statSync as statSync4 } from "node:fs";
|
|
5705
|
+
import { homedir as homedir13 } from "node:os";
|
|
5706
|
+
import { join as join23 } from "node:path";
|
|
5707
|
+
var HOME2 = homedir13();
|
|
5708
|
+
function encodeCwdClaudeCode(cwd) {
|
|
5709
|
+
return cwd.replace(/[/_]/g, "-");
|
|
5710
|
+
}
|
|
5711
|
+
function detectInstalledAgents() {
|
|
5712
|
+
const installs = [];
|
|
5713
|
+
const claudeRoot = join23(HOME2, ".claude", "projects");
|
|
5714
|
+
if (existsSync20(claudeRoot)) {
|
|
5715
|
+
installs.push({
|
|
5716
|
+
agent: "claude_code",
|
|
5717
|
+
sessionRoot: claudeRoot,
|
|
5718
|
+
encodeCwd: encodeCwdClaudeCode
|
|
5719
|
+
});
|
|
5720
|
+
}
|
|
5721
|
+
const codexRoot = join23(HOME2, ".codex", "sessions");
|
|
5722
|
+
if (existsSync20(codexRoot)) {
|
|
5723
|
+
installs.push({
|
|
5724
|
+
agent: "codex",
|
|
5725
|
+
sessionRoot: codexRoot,
|
|
5726
|
+
encodeCwd: () => "__cwd_unknown__"
|
|
5727
|
+
});
|
|
5728
|
+
}
|
|
5729
|
+
return installs;
|
|
5730
|
+
}
|
|
5731
|
+
function detectHostAgent() {
|
|
5732
|
+
if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE_ENTRYPOINT)
|
|
5733
|
+
return "claude_code";
|
|
5734
|
+
if (process.env.CODEX_HOME || process.env.CODEX_SESSION_ID)
|
|
5735
|
+
return "codex";
|
|
5736
|
+
return null;
|
|
5737
|
+
}
|
|
5738
|
+
function listLocalSessions(installs, cwd) {
|
|
5739
|
+
const out = [];
|
|
5740
|
+
for (const install of installs) {
|
|
5741
|
+
const cwdEncoded = install.encodeCwd(cwd);
|
|
5742
|
+
let subdirs = [];
|
|
5743
|
+
try {
|
|
5744
|
+
subdirs = readdirSync4(install.sessionRoot);
|
|
5745
|
+
} catch {
|
|
5746
|
+
continue;
|
|
5747
|
+
}
|
|
5748
|
+
for (const sub of subdirs) {
|
|
5749
|
+
const subdirPath = join23(install.sessionRoot, sub);
|
|
5750
|
+
try {
|
|
5751
|
+
if (!statSync4(subdirPath).isDirectory())
|
|
5752
|
+
continue;
|
|
5753
|
+
} catch {
|
|
5754
|
+
continue;
|
|
5755
|
+
}
|
|
5756
|
+
const inCwd = sub === cwdEncoded;
|
|
5757
|
+
let files = [];
|
|
5758
|
+
try {
|
|
5759
|
+
files = readdirSync4(subdirPath);
|
|
5760
|
+
} catch {
|
|
5761
|
+
continue;
|
|
5762
|
+
}
|
|
5763
|
+
for (const f of files) {
|
|
5764
|
+
if (!f.endsWith(".jsonl"))
|
|
5765
|
+
continue;
|
|
5766
|
+
const fullPath = join23(subdirPath, f);
|
|
5767
|
+
let stats;
|
|
5768
|
+
try {
|
|
5769
|
+
stats = statSync4(fullPath);
|
|
5770
|
+
} catch {
|
|
5771
|
+
continue;
|
|
5772
|
+
}
|
|
5773
|
+
if (!stats.isFile())
|
|
5774
|
+
continue;
|
|
5775
|
+
const sessionId = f.replace(/\.jsonl$/, "");
|
|
5776
|
+
out.push({
|
|
5777
|
+
agent: install.agent,
|
|
5778
|
+
path: fullPath,
|
|
5779
|
+
mtime: stats.mtimeMs,
|
|
5780
|
+
inCwd,
|
|
5781
|
+
sessionId
|
|
5782
|
+
});
|
|
5783
|
+
}
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
return out;
|
|
5787
|
+
}
|
|
5788
|
+
function pickSessions(candidates, opts) {
|
|
5789
|
+
const { n, epsilon } = opts;
|
|
5790
|
+
if (n <= 0 || candidates.length === 0)
|
|
5791
|
+
return [];
|
|
5792
|
+
const sorted = [...candidates].sort((a, b) => b.mtime - a.mtime);
|
|
5793
|
+
const cwdQuota = Math.ceil((1 - epsilon) * n);
|
|
5794
|
+
const globalQuota = Math.floor(epsilon * n);
|
|
5795
|
+
const picked = [];
|
|
5796
|
+
const taken = /* @__PURE__ */ new Set();
|
|
5797
|
+
for (const s of sorted) {
|
|
5798
|
+
if (picked.length >= cwdQuota)
|
|
5799
|
+
break;
|
|
5800
|
+
if (s.inCwd && !taken.has(s.path)) {
|
|
5801
|
+
picked.push(s);
|
|
5802
|
+
taken.add(s.path);
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
const cap2 = picked.length + globalQuota;
|
|
5806
|
+
for (const s of sorted) {
|
|
5807
|
+
if (picked.length >= cap2)
|
|
5808
|
+
break;
|
|
5809
|
+
if (!taken.has(s.path)) {
|
|
5810
|
+
picked.push(s);
|
|
5811
|
+
taken.add(s.path);
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5814
|
+
for (const s of sorted) {
|
|
5815
|
+
if (picked.length >= n)
|
|
5816
|
+
break;
|
|
5817
|
+
if (!taken.has(s.path)) {
|
|
5818
|
+
picked.push(s);
|
|
5819
|
+
taken.add(s.path);
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5822
|
+
return picked;
|
|
5823
|
+
}
|
|
5824
|
+
function nativeJsonlToRows(filePath, sessionId, agent) {
|
|
5825
|
+
let raw;
|
|
5826
|
+
try {
|
|
5827
|
+
raw = readFileSync14(filePath, "utf-8");
|
|
5828
|
+
} catch {
|
|
5829
|
+
return [];
|
|
5830
|
+
}
|
|
5831
|
+
const rows = [];
|
|
5832
|
+
let pendingAsstText;
|
|
5833
|
+
let pendingAsstTs;
|
|
5834
|
+
const flushAssistant = () => {
|
|
5835
|
+
if (pendingAsstText && pendingAsstText.trim().length > 0) {
|
|
5836
|
+
rows.push({
|
|
5837
|
+
type: "assistant_message",
|
|
5838
|
+
content: pendingAsstText,
|
|
5839
|
+
creation_date: pendingAsstTs,
|
|
5840
|
+
session_id: sessionId,
|
|
5841
|
+
agent
|
|
5842
|
+
});
|
|
5843
|
+
}
|
|
5844
|
+
pendingAsstText = void 0;
|
|
5845
|
+
pendingAsstTs = void 0;
|
|
5846
|
+
};
|
|
5847
|
+
for (const line of raw.split(/\n/)) {
|
|
5848
|
+
if (!line)
|
|
5849
|
+
continue;
|
|
5850
|
+
let obj;
|
|
5851
|
+
try {
|
|
5852
|
+
obj = JSON.parse(line);
|
|
5853
|
+
} catch {
|
|
5854
|
+
continue;
|
|
5855
|
+
}
|
|
5856
|
+
const t = obj?.type;
|
|
5857
|
+
const ts = obj?.timestamp ?? obj?.created_at;
|
|
5858
|
+
if (t === "user") {
|
|
5859
|
+
const c = obj?.message?.content;
|
|
5860
|
+
if (typeof c === "string" && c.trim().length > 0) {
|
|
5861
|
+
flushAssistant();
|
|
5862
|
+
rows.push({
|
|
5863
|
+
type: "user_message",
|
|
5864
|
+
content: c,
|
|
5865
|
+
creation_date: ts,
|
|
5866
|
+
session_id: sessionId,
|
|
5867
|
+
agent
|
|
5868
|
+
});
|
|
5869
|
+
}
|
|
5870
|
+
} else if (t === "assistant") {
|
|
5871
|
+
const c = obj?.message?.content;
|
|
5872
|
+
if (Array.isArray(c)) {
|
|
5873
|
+
const text = c.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n\n");
|
|
5874
|
+
if (text.trim().length > 0) {
|
|
5875
|
+
pendingAsstText = text;
|
|
5876
|
+
pendingAsstTs = ts;
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
flushAssistant();
|
|
5882
|
+
return rows;
|
|
5883
|
+
}
|
|
5884
|
+
|
|
5885
|
+
// dist/src/skillify/extractors/index.js
|
|
5886
|
+
function extractPairs(rows) {
|
|
5887
|
+
const pairs2 = [];
|
|
5888
|
+
let pendingPrompt = null;
|
|
5889
|
+
let pendingAnswer = [];
|
|
5890
|
+
function flush() {
|
|
5891
|
+
if (pendingPrompt && pendingAnswer.length > 0) {
|
|
5892
|
+
pairs2.push({
|
|
5893
|
+
sessionId: pendingPrompt.row.session_id ?? "",
|
|
5894
|
+
agent: pendingPrompt.row.agent ?? null,
|
|
5895
|
+
date: pendingPrompt.row.creation_date ?? null,
|
|
5896
|
+
prompt: pendingPrompt.content,
|
|
5897
|
+
answer: pendingAnswer.join("\n\n")
|
|
5898
|
+
});
|
|
5899
|
+
}
|
|
5900
|
+
pendingPrompt = null;
|
|
5901
|
+
pendingAnswer = [];
|
|
5902
|
+
}
|
|
5903
|
+
for (const r of rows) {
|
|
5904
|
+
if (r.type === "user_message" && typeof r.content === "string") {
|
|
5905
|
+
flush();
|
|
5906
|
+
pendingPrompt = { content: r.content, row: r };
|
|
5907
|
+
} else if (r.type === "assistant_message" && typeof r.content === "string" && pendingPrompt) {
|
|
5908
|
+
if (r.content.trim().length > 0)
|
|
5909
|
+
pendingAnswer.push(r.content);
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
flush();
|
|
5913
|
+
return pairs2;
|
|
5914
|
+
}
|
|
5915
|
+
|
|
5916
|
+
// dist/src/skillify/gate-runner.js
|
|
5917
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
5918
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
5919
|
+
import { homedir as homedir14 } from "node:os";
|
|
5920
|
+
import { join as join24 } from "node:path";
|
|
5921
|
+
function findAgentBin(agent) {
|
|
5922
|
+
const which = (name) => {
|
|
5923
|
+
try {
|
|
5924
|
+
const out = execFileSync4("which", [name], {
|
|
5925
|
+
encoding: "utf-8",
|
|
5926
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5927
|
+
});
|
|
5928
|
+
return out.trim() || null;
|
|
5929
|
+
} catch {
|
|
5930
|
+
return null;
|
|
5931
|
+
}
|
|
5932
|
+
};
|
|
5933
|
+
switch (agent) {
|
|
5934
|
+
case "claude_code":
|
|
5935
|
+
return which("claude") ?? join24(homedir14(), ".claude", "local", "claude");
|
|
5936
|
+
case "codex":
|
|
5937
|
+
return which("codex") ?? "/usr/local/bin/codex";
|
|
5938
|
+
case "cursor":
|
|
5939
|
+
return which("cursor-agent") ?? "/usr/local/bin/cursor-agent";
|
|
5940
|
+
case "hermes":
|
|
5941
|
+
return which("hermes") ?? join24(homedir14(), ".local", "bin", "hermes");
|
|
5942
|
+
case "pi":
|
|
5943
|
+
return which("pi") ?? join24(homedir14(), ".local", "bin", "pi");
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
|
|
5947
|
+
// dist/src/skillify/gate-parser.js
|
|
5948
|
+
function extractJsonBlock(s) {
|
|
5949
|
+
const trimmed = s.trim();
|
|
5950
|
+
if (!trimmed)
|
|
5951
|
+
return null;
|
|
5952
|
+
const fenced = trimmed.match(/```(?:json)?\s*\n([\s\S]*?)\n```/);
|
|
5953
|
+
if (fenced)
|
|
5954
|
+
return fenced[1].trim();
|
|
5955
|
+
const start = trimmed.indexOf("{");
|
|
5956
|
+
if (start < 0)
|
|
5957
|
+
return null;
|
|
5958
|
+
let depth = 0;
|
|
5959
|
+
for (let i = start; i < trimmed.length; i++) {
|
|
5960
|
+
const c = trimmed[i];
|
|
5961
|
+
if (c === "{")
|
|
5962
|
+
depth++;
|
|
5963
|
+
else if (c === "}") {
|
|
5964
|
+
depth--;
|
|
5965
|
+
if (depth === 0)
|
|
5966
|
+
return trimmed.slice(start, i + 1);
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
return null;
|
|
5970
|
+
}
|
|
5971
|
+
|
|
5972
|
+
// dist/src/skillify/local-manifest.js
|
|
5973
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "node:fs";
|
|
5974
|
+
import { homedir as homedir15 } from "node:os";
|
|
5975
|
+
import { dirname as dirname4, join as join25 } from "node:path";
|
|
5976
|
+
var LOCAL_MANIFEST_PATH = join25(homedir15(), ".claude", "hivemind", "local-mined.json");
|
|
5977
|
+
var LOCAL_MINE_LOCK_PATH = join25(homedir15(), ".claude", "hivemind", "local-mined.lock");
|
|
5978
|
+
function readLocalManifest(path = LOCAL_MANIFEST_PATH) {
|
|
5979
|
+
if (!existsSync22(path))
|
|
5980
|
+
return null;
|
|
5981
|
+
try {
|
|
5982
|
+
return JSON.parse(readFileSync15(path, "utf-8"));
|
|
5983
|
+
} catch {
|
|
5984
|
+
return null;
|
|
5985
|
+
}
|
|
5986
|
+
}
|
|
5987
|
+
function writeLocalManifest(m, path = LOCAL_MANIFEST_PATH) {
|
|
5988
|
+
mkdirSync8(dirname4(path), { recursive: true });
|
|
5989
|
+
writeFileSync11(path, JSON.stringify(m, null, 2));
|
|
5990
|
+
}
|
|
5991
|
+
|
|
5992
|
+
// dist/src/commands/mine-local.js
|
|
5993
|
+
import { unlinkSync as unlinkSync9 } from "node:fs";
|
|
5994
|
+
var EPSILON = 0.3;
|
|
5995
|
+
var DEFAULT_N = 8;
|
|
5996
|
+
var PAIR_CHAR_CAP = 4e3;
|
|
5997
|
+
var PER_SESSION_PAIR_CAP = 30;
|
|
5998
|
+
var PER_SESSION_PROMPT_CAP = 12e4;
|
|
5999
|
+
var GATE_CONCURRENCY = 4;
|
|
6000
|
+
var IN_FLIGHT_MAX_AGE_MS = 6e4;
|
|
6001
|
+
var GATE_TIMEOUT_MS = 24e4;
|
|
6002
|
+
var MANIFEST_PATH = LOCAL_MANIFEST_PATH;
|
|
6003
|
+
function runGateViaStdin(opts) {
|
|
6004
|
+
return new Promise((resolve) => {
|
|
6005
|
+
if (opts.agent !== "claude_code") {
|
|
6006
|
+
resolve({
|
|
6007
|
+
stdout: "",
|
|
6008
|
+
stderr: "",
|
|
6009
|
+
errored: true,
|
|
6010
|
+
errorMessage: `stdin gate runner only supports claude_code (got ${opts.agent}); for other agents the prompt must fit in argv`
|
|
6011
|
+
});
|
|
6012
|
+
return;
|
|
6013
|
+
}
|
|
6014
|
+
if (!existsSync23(opts.bin)) {
|
|
6015
|
+
resolve({
|
|
6016
|
+
stdout: "",
|
|
6017
|
+
stderr: "",
|
|
6018
|
+
errored: true,
|
|
6019
|
+
errorMessage: `agent binary not found at ${opts.bin}`
|
|
6020
|
+
});
|
|
6021
|
+
return;
|
|
6022
|
+
}
|
|
6023
|
+
const args = [
|
|
6024
|
+
"-p",
|
|
6025
|
+
"--no-session-persistence",
|
|
6026
|
+
"--model",
|
|
6027
|
+
"haiku",
|
|
6028
|
+
"--permission-mode",
|
|
6029
|
+
"bypassPermissions"
|
|
6030
|
+
];
|
|
6031
|
+
const child = spawn(opts.bin, args, {
|
|
6032
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
6033
|
+
env: { ...process.env, HIVEMIND_WIKI_WORKER: "1", HIVEMIND_CAPTURE: "false" }
|
|
6034
|
+
});
|
|
6035
|
+
let stdout = "";
|
|
6036
|
+
let stderr = "";
|
|
6037
|
+
let settled = false;
|
|
6038
|
+
const finish = (r) => {
|
|
6039
|
+
if (settled)
|
|
6040
|
+
return;
|
|
6041
|
+
settled = true;
|
|
6042
|
+
resolve(r);
|
|
6043
|
+
};
|
|
6044
|
+
const timer = setTimeout(() => {
|
|
6045
|
+
try {
|
|
6046
|
+
child.kill("SIGKILL");
|
|
6047
|
+
} catch {
|
|
6048
|
+
}
|
|
6049
|
+
finish({
|
|
6050
|
+
stdout,
|
|
6051
|
+
stderr,
|
|
6052
|
+
errored: true,
|
|
6053
|
+
errorMessage: `gate timed out after ${opts.timeoutMs}ms`
|
|
6054
|
+
});
|
|
6055
|
+
}, opts.timeoutMs);
|
|
6056
|
+
child.stdout.on("data", (b) => {
|
|
6057
|
+
stdout += b.toString("utf-8");
|
|
6058
|
+
});
|
|
6059
|
+
child.stderr.on("data", (b) => {
|
|
6060
|
+
stderr += b.toString("utf-8");
|
|
6061
|
+
});
|
|
6062
|
+
child.on("error", (e) => {
|
|
6063
|
+
clearTimeout(timer);
|
|
6064
|
+
finish({ stdout, stderr, errored: true, errorMessage: e.message });
|
|
6065
|
+
});
|
|
6066
|
+
child.on("close", (code) => {
|
|
6067
|
+
clearTimeout(timer);
|
|
6068
|
+
finish({
|
|
6069
|
+
stdout,
|
|
6070
|
+
stderr,
|
|
6071
|
+
errored: code !== 0,
|
|
6072
|
+
errorMessage: code !== 0 ? `claude_code CLI exited with code ${code}` : void 0
|
|
6073
|
+
});
|
|
6074
|
+
});
|
|
6075
|
+
child.stdin.on("error", (e) => {
|
|
6076
|
+
clearTimeout(timer);
|
|
6077
|
+
finish({ stdout, stderr, errored: true, errorMessage: `stdin write failed: ${e.message}` });
|
|
6078
|
+
});
|
|
6079
|
+
child.stdin.end(opts.prompt);
|
|
6080
|
+
});
|
|
6081
|
+
}
|
|
6082
|
+
var loadManifest2 = readLocalManifest;
|
|
6083
|
+
var saveManifest2 = writeLocalManifest;
|
|
6084
|
+
function truncate(s, max) {
|
|
6085
|
+
if (s.length <= max)
|
|
6086
|
+
return s;
|
|
6087
|
+
return s.slice(0, max) + `
|
|
6088
|
+
[\u2026truncated ${s.length - max} chars]`;
|
|
6089
|
+
}
|
|
6090
|
+
function renderPairsBlock(pairs2) {
|
|
6091
|
+
let total = 0;
|
|
6092
|
+
const out = [];
|
|
6093
|
+
for (const [i, p] of pairs2.entries()) {
|
|
6094
|
+
const block = `--- exchange ${i + 1} ---
|
|
6095
|
+
USER:
|
|
6096
|
+
${truncate(p.prompt, PAIR_CHAR_CAP)}
|
|
6097
|
+
|
|
6098
|
+
ASSISTANT:
|
|
6099
|
+
${truncate(p.answer, PAIR_CHAR_CAP)}
|
|
6100
|
+
`;
|
|
6101
|
+
if (total + block.length > PER_SESSION_PROMPT_CAP) {
|
|
6102
|
+
out.push(`[\u2026${pairs2.length - i} more exchanges omitted to stay under budget]`);
|
|
6103
|
+
break;
|
|
6104
|
+
}
|
|
6105
|
+
out.push(block);
|
|
6106
|
+
total += block.length;
|
|
6107
|
+
}
|
|
6108
|
+
return out.join("\n");
|
|
6109
|
+
}
|
|
6110
|
+
function buildSessionPrompt(pairs2, session, verdictPath) {
|
|
6111
|
+
return [
|
|
6112
|
+
`You are a skill curator examining ONE session of recent agent activity.`,
|
|
6113
|
+
`Your job: identify up to 3 distinct, non-overlapping reusable skills hiding in this session.`,
|
|
6114
|
+
`Distinct = different problem domains. Empty list is fine if nothing qualifies.`,
|
|
6115
|
+
``,
|
|
6116
|
+
`Session: ${session.sessionId} (agent: ${session.agent})`,
|
|
6117
|
+
``,
|
|
6118
|
+
`RULES:`,
|
|
6119
|
+
`- A skill qualifies if it captures a concrete, repeatable workflow OR a non-obvious`,
|
|
6120
|
+
` constraint/gotcha a future engineer would benefit from knowing. Intra-session is fine \u2014`,
|
|
6121
|
+
` one deep dive yielding a generalizable takeaway counts.`,
|
|
6122
|
+
`- Skip patterns that are obvious from reading the codebase or already in CLAUDE.md.`,
|
|
6123
|
+
`- Each body uses short sections (When to use, Workflow, Anti-patterns), concrete commands`,
|
|
6124
|
+
` / paths / snippets drawn from the exchanges below, no marketing, no emojis.`,
|
|
6125
|
+
`- Each body under ~3000 characters.`,
|
|
6126
|
+
`- Skill names are kebab-case slugs (lowercase letters/digits/hyphens only).`,
|
|
6127
|
+
``,
|
|
6128
|
+
`=== EXCHANGES (user prompts + assistant final answers, tool calls stripped) ===`,
|
|
6129
|
+
renderPairsBlock(pairs2),
|
|
6130
|
+
``,
|
|
6131
|
+
`=== YOUR TASK ===`,
|
|
6132
|
+
`Output a single JSON object. You may either:`,
|
|
6133
|
+
` (a) Write the JSON to this exact path using the Write tool: ${verdictPath}`,
|
|
6134
|
+
` (b) Print the JSON object to stdout as your final message, nothing else.`,
|
|
6135
|
+
`Pick whichever you prefer. Do not do both.`,
|
|
6136
|
+
``,
|
|
6137
|
+
`Required shape:`,
|
|
6138
|
+
`{`,
|
|
6139
|
+
` "reason": "<one-line justification>",`,
|
|
6140
|
+
` "skills": [`,
|
|
6141
|
+
` {`,
|
|
6142
|
+
` "name": "<kebab-case>",`,
|
|
6143
|
+
` "description": "<one-line>",`,
|
|
6144
|
+
` "trigger": "<short trigger>",`,
|
|
6145
|
+
` "body": "<full SKILL.md body without frontmatter>"`,
|
|
6146
|
+
` },`,
|
|
6147
|
+
` ... up to 3 entries, or [] if nothing qualifies`,
|
|
6148
|
+
` ]`,
|
|
6149
|
+
`}`,
|
|
6150
|
+
``,
|
|
6151
|
+
`If you print to stdout, do not include any prose before or after the JSON.`
|
|
6152
|
+
].join("\n");
|
|
6153
|
+
}
|
|
6154
|
+
function parseMultiVerdict(raw) {
|
|
6155
|
+
const block = extractJsonBlock(raw);
|
|
6156
|
+
if (!block)
|
|
6157
|
+
return null;
|
|
6158
|
+
let parsed;
|
|
6159
|
+
try {
|
|
6160
|
+
parsed = JSON.parse(block);
|
|
6161
|
+
} catch {
|
|
6162
|
+
return null;
|
|
6163
|
+
}
|
|
6164
|
+
if (!parsed || typeof parsed !== "object")
|
|
6165
|
+
return null;
|
|
6166
|
+
const skills = parsed.skills;
|
|
6167
|
+
if (!Array.isArray(skills))
|
|
6168
|
+
return null;
|
|
6169
|
+
const out = [];
|
|
6170
|
+
for (const s of skills) {
|
|
6171
|
+
if (!s || typeof s !== "object")
|
|
6172
|
+
continue;
|
|
6173
|
+
const name = typeof s.name === "string" ? s.name.trim() : "";
|
|
6174
|
+
const description = typeof s.description === "string" ? s.description.trim() : "";
|
|
6175
|
+
const body = typeof s.body === "string" ? s.body.trim() : "";
|
|
6176
|
+
const trigger = typeof s.trigger === "string" ? s.trigger.trim() : void 0;
|
|
6177
|
+
if (!name || !body)
|
|
6178
|
+
continue;
|
|
6179
|
+
out.push({ name, description, body, trigger });
|
|
6180
|
+
}
|
|
6181
|
+
return { reason: typeof parsed.reason === "string" ? parsed.reason : void 0, skills: out };
|
|
6182
|
+
}
|
|
6183
|
+
function gateAgentFor(host, fallback, installs) {
|
|
6184
|
+
const installed = new Set(installs.map((i) => i.agent));
|
|
6185
|
+
if (installed.has("claude_code"))
|
|
6186
|
+
return "claude_code";
|
|
6187
|
+
return host ?? fallback;
|
|
6188
|
+
}
|
|
6189
|
+
async function parallelMap(items, concurrency, fn) {
|
|
6190
|
+
const results = new Array(items.length);
|
|
6191
|
+
let cursor = 0;
|
|
6192
|
+
const workers = [];
|
|
6193
|
+
for (let w = 0; w < Math.min(concurrency, items.length); w++) {
|
|
6194
|
+
workers.push((async () => {
|
|
6195
|
+
while (true) {
|
|
6196
|
+
const i = cursor++;
|
|
6197
|
+
if (i >= items.length)
|
|
6198
|
+
return;
|
|
6199
|
+
results[i] = await fn(items[i], i);
|
|
6200
|
+
}
|
|
6201
|
+
})());
|
|
6202
|
+
}
|
|
6203
|
+
await Promise.all(workers);
|
|
6204
|
+
return results;
|
|
6205
|
+
}
|
|
6206
|
+
var SUMMARY_STOPWORDS = /* @__PURE__ */ new Set([
|
|
6207
|
+
"the",
|
|
6208
|
+
"and",
|
|
6209
|
+
"for",
|
|
6210
|
+
"with",
|
|
6211
|
+
"from",
|
|
6212
|
+
"into",
|
|
6213
|
+
"via",
|
|
6214
|
+
"this",
|
|
6215
|
+
"that",
|
|
6216
|
+
"your",
|
|
6217
|
+
"you",
|
|
6218
|
+
"are",
|
|
6219
|
+
"was",
|
|
6220
|
+
"were",
|
|
6221
|
+
"use",
|
|
6222
|
+
"using",
|
|
6223
|
+
"uses",
|
|
6224
|
+
"used",
|
|
6225
|
+
"skill",
|
|
6226
|
+
"when",
|
|
6227
|
+
"what",
|
|
6228
|
+
"where",
|
|
6229
|
+
"which",
|
|
6230
|
+
"while",
|
|
6231
|
+
"how",
|
|
6232
|
+
"non",
|
|
6233
|
+
"any",
|
|
6234
|
+
"all",
|
|
6235
|
+
"code",
|
|
6236
|
+
"file",
|
|
6237
|
+
"files",
|
|
6238
|
+
"way",
|
|
6239
|
+
"ways",
|
|
6240
|
+
"via"
|
|
6241
|
+
]);
|
|
6242
|
+
function summaryTokens(s) {
|
|
6243
|
+
return new Set(s.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 3 && !SUMMARY_STOPWORDS.has(t)));
|
|
6244
|
+
}
|
|
6245
|
+
function jaccard(a, b) {
|
|
6246
|
+
if (a.size === 0 || b.size === 0)
|
|
6247
|
+
return 0;
|
|
6248
|
+
let intersection = 0;
|
|
6249
|
+
for (const t of a)
|
|
6250
|
+
if (b.has(t))
|
|
6251
|
+
intersection++;
|
|
6252
|
+
return intersection / (a.size + b.size - intersection);
|
|
6253
|
+
}
|
|
6254
|
+
var OVERLAP_THRESHOLD = 0.4;
|
|
6255
|
+
function findOverlap(candidateDesc, others) {
|
|
6256
|
+
const ct = summaryTokens(candidateDesc);
|
|
6257
|
+
let best = null;
|
|
6258
|
+
for (const e of others) {
|
|
6259
|
+
const score = jaccard(ct, summaryTokens(e.desc));
|
|
6260
|
+
if (score >= OVERLAP_THRESHOLD && (!best || score > best.score)) {
|
|
6261
|
+
best = { name: e.name, score };
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
return best;
|
|
6265
|
+
}
|
|
6266
|
+
function loadExistingSummaries(skillsRoot) {
|
|
6267
|
+
const out = [];
|
|
6268
|
+
for (const s of listSkills(skillsRoot)) {
|
|
6269
|
+
const parsed = parseFrontmatter(s.body);
|
|
6270
|
+
const desc = parsed?.fm.description ?? "";
|
|
6271
|
+
if (desc)
|
|
6272
|
+
out.push({ name: s.name, desc });
|
|
6273
|
+
}
|
|
6274
|
+
return out;
|
|
6275
|
+
}
|
|
6276
|
+
function takeFlagValue(args, flag) {
|
|
6277
|
+
const idx = args.indexOf(flag);
|
|
6278
|
+
if (idx < 0)
|
|
6279
|
+
return null;
|
|
6280
|
+
const v = args[idx + 1];
|
|
6281
|
+
if (v === void 0 || v.startsWith("--")) {
|
|
6282
|
+
console.error(`${flag} requires a value`);
|
|
6283
|
+
process.exit(1);
|
|
6284
|
+
}
|
|
6285
|
+
args.splice(idx, 2);
|
|
6286
|
+
return v;
|
|
6287
|
+
}
|
|
6288
|
+
function takeBoolFlag(args, flag) {
|
|
6289
|
+
const idx = args.indexOf(flag);
|
|
6290
|
+
if (idx < 0)
|
|
6291
|
+
return false;
|
|
6292
|
+
args.splice(idx, 1);
|
|
6293
|
+
return true;
|
|
6294
|
+
}
|
|
6295
|
+
async function runMineLocal(args) {
|
|
6296
|
+
let lockReleased = false;
|
|
6297
|
+
const releaseLock = () => {
|
|
6298
|
+
if (lockReleased)
|
|
6299
|
+
return;
|
|
6300
|
+
lockReleased = true;
|
|
6301
|
+
try {
|
|
6302
|
+
unlinkSync9(LOCAL_MINE_LOCK_PATH);
|
|
6303
|
+
} catch {
|
|
6304
|
+
}
|
|
6305
|
+
};
|
|
6306
|
+
process.on("exit", releaseLock);
|
|
6307
|
+
try {
|
|
6308
|
+
return await runMineLocalImpl(args);
|
|
6309
|
+
} finally {
|
|
6310
|
+
releaseLock();
|
|
6311
|
+
}
|
|
6312
|
+
}
|
|
6313
|
+
async function runMineLocalImpl(args) {
|
|
6314
|
+
const work = [...args];
|
|
6315
|
+
const force = takeBoolFlag(work, "--force");
|
|
6316
|
+
const dryRun = takeBoolFlag(work, "--dry-run");
|
|
6317
|
+
const nRaw = takeFlagValue(work, "--n");
|
|
6318
|
+
if (loadManifest2() && !force) {
|
|
6319
|
+
console.error(`Local skills have already been mined on this machine.`);
|
|
6320
|
+
console.error(`Manifest: ${MANIFEST_PATH}`);
|
|
6321
|
+
console.error(`Pass --force to re-mine.`);
|
|
6322
|
+
process.exit(1);
|
|
6323
|
+
}
|
|
6324
|
+
const installs = detectInstalledAgents();
|
|
6325
|
+
if (installs.length === 0) {
|
|
6326
|
+
console.error(`No agent session directories detected. Run a session first.`);
|
|
6327
|
+
process.exit(1);
|
|
6328
|
+
}
|
|
6329
|
+
console.log(`Detected installed agents: ${installs.map((i) => i.agent).join(", ")}`);
|
|
6330
|
+
const host = detectHostAgent();
|
|
6331
|
+
const fallback = installs[0].agent;
|
|
6332
|
+
const gateAgent = gateAgentFor(host, fallback, installs);
|
|
6333
|
+
if (gateAgent !== "claude_code") {
|
|
6334
|
+
console.error(`mine-local v1 requires the Claude Code CLI as its LLM gate.`);
|
|
6335
|
+
console.error(`Detected gate agent: ${gateAgent} (no claude_code session dir found at ~/.claude/projects/).`);
|
|
6336
|
+
console.error(`Install Claude Code, or run a Claude Code session once, then re-run.`);
|
|
6337
|
+
process.exit(1);
|
|
6338
|
+
}
|
|
6339
|
+
const gateBin = findAgentBin(gateAgent);
|
|
6340
|
+
console.log(`Gate CLI: ${gateAgent} (${gateBin})${host ? " \u2014 host-agent detected" : ""}`);
|
|
6341
|
+
const cwd = process.cwd();
|
|
6342
|
+
const rawSessions = listLocalSessions(installs, cwd);
|
|
6343
|
+
const now = Date.now();
|
|
6344
|
+
const allSessions = rawSessions.filter((s) => now - s.mtime >= IN_FLIGHT_MAX_AGE_MS);
|
|
6345
|
+
const dropped = rawSessions.length - allSessions.length;
|
|
6346
|
+
const cwdCount = allSessions.filter((s) => s.inCwd).length;
|
|
6347
|
+
console.log(`Found ${allSessions.length} local session(s) (${cwdCount} in cwd${dropped > 0 ? `, ${dropped} in-flight skipped` : ""})`);
|
|
6348
|
+
if (allSessions.length === 0) {
|
|
6349
|
+
console.error(`No mineable session files (all were modified within the last ${IN_FLIGHT_MAX_AGE_MS / 1e3}s).`);
|
|
6350
|
+
process.exit(1);
|
|
6351
|
+
}
|
|
6352
|
+
const n = nRaw === "all" ? allSessions.length : nRaw ? Math.max(1, parseInt(nRaw, 10) || DEFAULT_N) : DEFAULT_N;
|
|
6353
|
+
const picked = pickSessions(allSessions, { n, epsilon: EPSILON });
|
|
6354
|
+
console.log(`Picking ${picked.length} session(s) (\u03B5=${EPSILON}, N=${n}): ${picked.map((s) => s.sessionId.slice(0, 8)).join(", ")}`);
|
|
6355
|
+
if (dryRun) {
|
|
6356
|
+
console.log(`Dry-run: would invoke ${gateAgent} gate on ${picked.length} session(s) in parallel (concurrency=${GATE_CONCURRENCY}).`);
|
|
6357
|
+
return;
|
|
6358
|
+
}
|
|
6359
|
+
const tmpDir = join26(homedir16(), ".claude", "hivemind", `mine-local-${Date.now()}`);
|
|
6360
|
+
mkdirSync9(tmpDir, { recursive: true });
|
|
6361
|
+
console.log(`Running ${picked.length} gate call(s) in parallel (concurrency=${GATE_CONCURRENCY}, timeout=${GATE_TIMEOUT_MS / 1e3}s each)...`);
|
|
6362
|
+
const results = await parallelMap(picked, GATE_CONCURRENCY, async (s) => {
|
|
6363
|
+
const shortId = s.sessionId.slice(0, 8);
|
|
6364
|
+
const rows = nativeJsonlToRows(s.path, s.sessionId, s.agent);
|
|
6365
|
+
const pairs2 = extractPairs(rows);
|
|
6366
|
+
if (pairs2.length === 0) {
|
|
6367
|
+
console.log(` [${shortId}] no usable pairs \u2014 skipped`);
|
|
6368
|
+
return { session: s, skills: [], reason: "no pairs", error: null };
|
|
6369
|
+
}
|
|
6370
|
+
const tail = pairs2.slice(-PER_SESSION_PAIR_CAP);
|
|
6371
|
+
const sessionTmp = join26(tmpDir, `s-${shortId}`);
|
|
6372
|
+
mkdirSync9(sessionTmp, { recursive: true });
|
|
6373
|
+
const verdictPath = join26(sessionTmp, "verdict.json");
|
|
6374
|
+
const prompt = buildSessionPrompt(tail, s, verdictPath);
|
|
6375
|
+
writeFileSync12(join26(sessionTmp, "prompt.txt"), prompt);
|
|
6376
|
+
const gate = await runGateViaStdin({ agent: gateAgent, bin: gateBin, prompt, timeoutMs: GATE_TIMEOUT_MS });
|
|
6377
|
+
try {
|
|
6378
|
+
writeFileSync12(join26(sessionTmp, "gate-stdout.txt"), gate.stdout);
|
|
6379
|
+
if (gate.stderr)
|
|
6380
|
+
writeFileSync12(join26(sessionTmp, "gate-stderr.txt"), gate.stderr);
|
|
6381
|
+
} catch {
|
|
6382
|
+
}
|
|
6383
|
+
if (gate.errored) {
|
|
6384
|
+
console.log(` [${shortId}] gate failed: ${gate.errorMessage}`);
|
|
6385
|
+
return { session: s, skills: [], reason: null, error: gate.errorMessage ?? "gate failed" };
|
|
6386
|
+
}
|
|
6387
|
+
const verdictText = existsSync23(verdictPath) ? readFileSync16(verdictPath, "utf-8") : gate.stdout;
|
|
6388
|
+
const mv = parseMultiVerdict(verdictText);
|
|
6389
|
+
if (!mv) {
|
|
6390
|
+
console.log(` [${shortId}] unparseable verdict (kept at ${sessionTmp})`);
|
|
6391
|
+
return { session: s, skills: [], reason: null, error: "unparseable verdict" };
|
|
6392
|
+
}
|
|
6393
|
+
console.log(` [${shortId}] ${mv.skills.length} skill candidate(s) \u2014 ${mv.reason ?? "no reason given"}`);
|
|
6394
|
+
return { session: s, skills: mv.skills, reason: mv.reason ?? null, error: null };
|
|
6395
|
+
});
|
|
6396
|
+
const skillsRoot = resolveSkillsRoot("global", cwd);
|
|
6397
|
+
const totalCandidates = results.reduce((sum, r) => sum + r.skills.length, 0);
|
|
6398
|
+
const existingSummaries = loadExistingSummaries(skillsRoot);
|
|
6399
|
+
console.log("");
|
|
6400
|
+
console.log(`Got ${totalCandidates} candidate(s) across ${picked.length} session(s). Checking overlap against ${existingSummaries.length} installed skill(s) + each new write.`);
|
|
6401
|
+
if (totalCandidates === 0) {
|
|
6402
|
+
const existing = loadManifest2();
|
|
6403
|
+
saveManifest2({
|
|
6404
|
+
created_at: existing?.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6405
|
+
entries: existing?.entries ?? []
|
|
6406
|
+
});
|
|
6407
|
+
console.log(`No skills to write.`);
|
|
6408
|
+
console.log(`tmp dir kept for inspection: ${tmpDir}`);
|
|
6409
|
+
return;
|
|
6410
|
+
}
|
|
6411
|
+
const flat = [];
|
|
6412
|
+
for (const r of results) {
|
|
6413
|
+
for (const sk of r.skills)
|
|
6414
|
+
flat.push({ skill: sk, session: r.session });
|
|
6415
|
+
}
|
|
6416
|
+
flat.sort((a, b) => b.session.mtime - a.session.mtime);
|
|
6417
|
+
const fanOutRoots = detectAgentSkillsRoots(skillsRoot);
|
|
6418
|
+
if (fanOutRoots.length > 0) {
|
|
6419
|
+
console.log(`Fan-out targets: ${fanOutRoots.join(", ")}`);
|
|
6420
|
+
}
|
|
6421
|
+
const written = [];
|
|
6422
|
+
const knownSummaries = [...existingSummaries];
|
|
6423
|
+
for (const { skill, session } of flat) {
|
|
6424
|
+
const overlap = findOverlap(skill.description, knownSummaries);
|
|
6425
|
+
if (overlap) {
|
|
6426
|
+
console.log(` skipped ${skill.name} \u2190 session ${session.sessionId.slice(0, 8)} (description overlaps "${overlap.name}", Jaccard=${overlap.score.toFixed(2)})`);
|
|
6427
|
+
continue;
|
|
6428
|
+
}
|
|
6429
|
+
try {
|
|
6430
|
+
const result = writeNewSkill({
|
|
6431
|
+
skillsRoot,
|
|
6432
|
+
name: skill.name,
|
|
6433
|
+
description: skill.description,
|
|
6434
|
+
trigger: skill.trigger,
|
|
6435
|
+
body: skill.body,
|
|
6436
|
+
sourceSessions: [session.sessionId],
|
|
6437
|
+
agent: gateAgent
|
|
6438
|
+
});
|
|
6439
|
+
const canonicalDir = dirname5(result.path);
|
|
6440
|
+
const symlinks = fanOutRoots.length > 0 ? fanOutSymlinks(canonicalDir, basename(canonicalDir), fanOutRoots) : [];
|
|
6441
|
+
const symlinkSuffix = symlinks.length > 0 ? `, fan-out \u2192 ${symlinks.length} root(s)` : "";
|
|
6442
|
+
console.log(` wrote ${skill.name} \u2190 session ${session.sessionId.slice(0, 8)} (${session.agent}${symlinkSuffix})`);
|
|
6443
|
+
written.push({ skill, session, result, symlinks });
|
|
6444
|
+
knownSummaries.push({ name: skill.name, desc: skill.description });
|
|
6445
|
+
} catch (e) {
|
|
6446
|
+
if (/already exists/i.test(e.message ?? "")) {
|
|
6447
|
+
console.log(` skipped ${skill.name} (file already exists at ${skillsRoot})`);
|
|
6448
|
+
} else {
|
|
6449
|
+
console.log(` failed ${skill.name}: ${e.message}`);
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
if (written.length > 0) {
|
|
6454
|
+
const existing = loadManifest2();
|
|
6455
|
+
const newEntries = written.map(({ skill, session, result, symlinks }) => ({
|
|
6456
|
+
skill_name: skill.name,
|
|
6457
|
+
canonical_path: result.path,
|
|
6458
|
+
symlinks,
|
|
6459
|
+
source_session_ids: [session.sessionId],
|
|
6460
|
+
source_session_paths: [session.path],
|
|
6461
|
+
source_agent: session.agent,
|
|
6462
|
+
gate_agent: gateAgent,
|
|
6463
|
+
created_at: result.createdAt,
|
|
6464
|
+
uploaded: false
|
|
6465
|
+
}));
|
|
6466
|
+
saveManifest2({
|
|
6467
|
+
created_at: existing?.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6468
|
+
entries: [...existing?.entries ?? [], ...newEntries]
|
|
6469
|
+
});
|
|
6470
|
+
}
|
|
6471
|
+
console.log("");
|
|
6472
|
+
console.log(`Mined ${written.length} skill(s) from ${picked.length} session(s) (${results.filter((r) => r.skills.length > 0).length} session(s) contributed candidate(s)).`);
|
|
6473
|
+
console.log(`Installed to ${skillsRoot}/ \u2014 local-only, not shared.`);
|
|
6474
|
+
console.log(`Sign in with 'hivemind login' to share with your team later.`);
|
|
6475
|
+
}
|
|
6476
|
+
|
|
6477
|
+
// dist/src/cli/skillify-spec.js
|
|
6478
|
+
var SKILLIFY_SPEC = [
|
|
6479
|
+
{
|
|
6480
|
+
cmd: "hivemind skillify",
|
|
6481
|
+
desc: "show scope, team, install, per-project state"
|
|
6482
|
+
},
|
|
6483
|
+
{
|
|
6484
|
+
cmd: "hivemind skillify pull",
|
|
6485
|
+
desc: "sync project skills from the org table to local FS",
|
|
6486
|
+
options: [
|
|
6487
|
+
{ flag: "--user <email>", desc: "only skills authored by that user" },
|
|
6488
|
+
{ flag: "--users <a,b,c>", desc: "only skills from those authors" },
|
|
6489
|
+
{ flag: "--all-users", desc: 'explicit "no author filter" (default)' },
|
|
6490
|
+
{ flag: "--to <project|global>", desc: "install location (project=cwd/.claude/skills, global=~/.claude/skills)" },
|
|
6491
|
+
{ flag: "--dry-run", desc: "preview without touching disk" },
|
|
6492
|
+
{ flag: "--force", desc: "overwrite local files even if up-to-date (creates .bak)" },
|
|
6493
|
+
{ flag: "<skill-name>", desc: "pull only that one skill (combines with --user)" }
|
|
6494
|
+
],
|
|
6495
|
+
note: "every agent's SessionStart hook auto-runs 'pull --all-users --to global' on every session. File writes are idempotent (skipped when local is at-or-newer than remote). Disable via HIVEMIND_AUTOPULL_DISABLED=1."
|
|
6496
|
+
},
|
|
6497
|
+
{
|
|
6498
|
+
cmd: "hivemind skillify unpull",
|
|
6499
|
+
desc: "remove every skill previously installed by pull",
|
|
6500
|
+
options: [
|
|
6501
|
+
{ flag: "--user <email>", desc: "remove only that author's pulls" },
|
|
6502
|
+
{ flag: "--not-mine", desc: "remove all pulls except your own" },
|
|
6503
|
+
{ flag: "--dry-run", desc: "preview without touching disk" }
|
|
6504
|
+
]
|
|
6505
|
+
},
|
|
6506
|
+
{
|
|
6507
|
+
cmd: "hivemind skillify scope",
|
|
6508
|
+
args: "<me|team|org>",
|
|
6509
|
+
desc: "sharing scope for newly mined skills"
|
|
6510
|
+
},
|
|
6511
|
+
{
|
|
6512
|
+
cmd: "hivemind skillify install",
|
|
6513
|
+
args: "<project|global>",
|
|
6514
|
+
desc: "default install location for new skills"
|
|
6515
|
+
},
|
|
6516
|
+
{
|
|
6517
|
+
cmd: "hivemind skillify promote",
|
|
6518
|
+
args: "<skill-name>",
|
|
6519
|
+
desc: "move a project skill to the global location"
|
|
6520
|
+
},
|
|
6521
|
+
{
|
|
6522
|
+
cmd: "hivemind skillify team add|remove|list",
|
|
6523
|
+
args: "<name>",
|
|
6524
|
+
desc: "manage team member list"
|
|
6525
|
+
},
|
|
6526
|
+
{
|
|
6527
|
+
cmd: "hivemind skillify mine-local",
|
|
6528
|
+
desc: "one-shot: mine skills from local sessions (no auth needed)",
|
|
6529
|
+
options: [
|
|
6530
|
+
{ flag: "--n <num|all>", desc: "how many sessions to mine (default: 8)" },
|
|
6531
|
+
{ flag: "--force", desc: "re-run even if the manifest sentinel exists" },
|
|
6532
|
+
{ flag: "--dry-run", desc: "stop before calling the LLM gate" }
|
|
6533
|
+
]
|
|
6534
|
+
}
|
|
6535
|
+
];
|
|
6536
|
+
function renderCliHelpBlock() {
|
|
6537
|
+
const INDENT = " ";
|
|
6538
|
+
const CMD_COL_WIDTH = 42;
|
|
6539
|
+
const lines = [];
|
|
6540
|
+
for (const sub of SKILLIFY_SPEC) {
|
|
6541
|
+
const left = sub.args ? `${sub.cmd} ${sub.args}` : sub.cmd;
|
|
6542
|
+
const padded = left.length >= CMD_COL_WIDTH ? `${left} ` : left.padEnd(CMD_COL_WIDTH);
|
|
6543
|
+
lines.push(`${INDENT}${padded}${capitalize(sub.desc)}.`);
|
|
6544
|
+
if (sub.options && sub.options.length > 0) {
|
|
6545
|
+
const optsList = sub.options.map((o) => o.flag).join(", ");
|
|
6546
|
+
lines.push(`${INDENT}${" ".repeat(CMD_COL_WIDTH)}Options: ${optsList}.`);
|
|
6547
|
+
}
|
|
6548
|
+
if (sub.note) {
|
|
6549
|
+
const noteWrapped = wrapAt(`Note: ${sub.note}`, 72);
|
|
6550
|
+
for (const noteLine of noteWrapped) {
|
|
6551
|
+
lines.push(`${INDENT}${" ".repeat(CMD_COL_WIDTH)}${noteLine}`);
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
return lines.join("\n");
|
|
6556
|
+
}
|
|
6557
|
+
function renderSubcommandUsageBlock() {
|
|
6558
|
+
const INDENT = " ";
|
|
6559
|
+
const SUB_INDENT = " ";
|
|
6560
|
+
const FLAG_INDENT = " ";
|
|
6561
|
+
const CMD_COL_WIDTH = 44;
|
|
6562
|
+
const FLAG_COL_WIDTH = 26;
|
|
6563
|
+
const lines = [];
|
|
6564
|
+
for (const sub of SKILLIFY_SPEC) {
|
|
6565
|
+
const left = sub.args ? `${sub.cmd} ${sub.args}` : sub.cmd;
|
|
6566
|
+
const padded = left.length >= CMD_COL_WIDTH ? `${left} ` : left.padEnd(CMD_COL_WIDTH);
|
|
6567
|
+
lines.push(`${INDENT}${padded}${sub.desc}`);
|
|
6568
|
+
if (sub.options && sub.options.length > 0) {
|
|
6569
|
+
const tail = sub.cmd.split(" ").slice(-1)[0];
|
|
6570
|
+
lines.push(`${SUB_INDENT}Options for ${tail}:`);
|
|
6571
|
+
for (const opt of sub.options) {
|
|
6572
|
+
const flagPadded = opt.flag.length >= FLAG_COL_WIDTH ? `${opt.flag} ` : opt.flag.padEnd(FLAG_COL_WIDTH);
|
|
6573
|
+
lines.push(`${FLAG_INDENT}${flagPadded}${opt.desc}`);
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6576
|
+
}
|
|
6577
|
+
return lines.join("\n");
|
|
6578
|
+
}
|
|
6579
|
+
function capitalize(s) {
|
|
6580
|
+
return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
|
|
6581
|
+
}
|
|
6582
|
+
function wrapAt(s, max) {
|
|
6583
|
+
const words = s.split(/\s+/);
|
|
6584
|
+
const out = [];
|
|
6585
|
+
let cur = "";
|
|
6586
|
+
for (const w of words) {
|
|
6587
|
+
if (cur.length === 0) {
|
|
6588
|
+
cur = w;
|
|
6589
|
+
} else if (cur.length + 1 + w.length > max) {
|
|
6590
|
+
out.push(cur);
|
|
6591
|
+
cur = w;
|
|
6592
|
+
} else {
|
|
6593
|
+
cur += " " + w;
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
if (cur)
|
|
6597
|
+
out.push(cur);
|
|
6598
|
+
return out;
|
|
6599
|
+
}
|
|
6600
|
+
|
|
5612
6601
|
// dist/src/commands/skillify.js
|
|
5613
6602
|
function stateDir() {
|
|
5614
|
-
return
|
|
6603
|
+
return join27(homedir17(), ".deeplake", "state", "skillify");
|
|
5615
6604
|
}
|
|
5616
6605
|
function showStatus() {
|
|
5617
6606
|
const cfg = loadScopeConfig();
|
|
@@ -5619,11 +6608,11 @@ function showStatus() {
|
|
|
5619
6608
|
console.log(`team: ${cfg.team.length === 0 ? "(empty)" : cfg.team.join(", ")}`);
|
|
5620
6609
|
console.log(`install: ${cfg.install} (${cfg.install === "global" ? "~/.claude/skills/" : "<project>/.claude/skills/"})`);
|
|
5621
6610
|
const dir = stateDir();
|
|
5622
|
-
if (!
|
|
6611
|
+
if (!existsSync24(dir)) {
|
|
5623
6612
|
console.log(`state: (no projects tracked yet)`);
|
|
5624
6613
|
return;
|
|
5625
6614
|
}
|
|
5626
|
-
const files =
|
|
6615
|
+
const files = readdirSync5(dir).filter((f) => f.endsWith(".json") && f !== "config.json" && f !== "pulled.json" && f !== "autopull-last-run.json");
|
|
5627
6616
|
if (files.length === 0) {
|
|
5628
6617
|
console.log(`state: (no projects tracked yet)`);
|
|
5629
6618
|
return;
|
|
@@ -5631,7 +6620,7 @@ function showStatus() {
|
|
|
5631
6620
|
console.log(`state: ${files.length} project(s) tracked`);
|
|
5632
6621
|
for (const f of files) {
|
|
5633
6622
|
try {
|
|
5634
|
-
const s = JSON.parse(
|
|
6623
|
+
const s = JSON.parse(readFileSync17(join27(dir, f), "utf-8"));
|
|
5635
6624
|
const last = typeof s.updatedAt === "number" ? new Date(s.updatedAt).toISOString() : s.lastDate ?? "never";
|
|
5636
6625
|
const skills = Array.isArray(s.skillsGenerated) && s.skillsGenerated.length > 0 ? s.skillsGenerated.join(", ") : "none";
|
|
5637
6626
|
console.log(` - ${s.project} (counter=${s.counter}, last=${last}, skills=${skills})`);
|
|
@@ -5658,7 +6647,7 @@ function setInstall(loc) {
|
|
|
5658
6647
|
}
|
|
5659
6648
|
const cfg = loadScopeConfig();
|
|
5660
6649
|
saveScopeConfig({ ...cfg, install: loc });
|
|
5661
|
-
const path = loc === "global" ?
|
|
6650
|
+
const path = loc === "global" ? join27(homedir17(), ".claude", "skills") : "<cwd>/.claude/skills";
|
|
5662
6651
|
console.log(`Install location set to '${loc}'. New skills will be written to ${path}/<name>/SKILL.md.`);
|
|
5663
6652
|
}
|
|
5664
6653
|
function promoteSkill(name, cwd) {
|
|
@@ -5666,17 +6655,17 @@ function promoteSkill(name, cwd) {
|
|
|
5666
6655
|
console.error("Usage: hivemind skillify promote <skill-name>");
|
|
5667
6656
|
process.exit(1);
|
|
5668
6657
|
}
|
|
5669
|
-
const projectPath =
|
|
5670
|
-
const globalPath =
|
|
5671
|
-
if (!
|
|
6658
|
+
const projectPath = join27(cwd, ".claude", "skills", name);
|
|
6659
|
+
const globalPath = join27(homedir17(), ".claude", "skills", name);
|
|
6660
|
+
if (!existsSync24(join27(projectPath, "SKILL.md"))) {
|
|
5672
6661
|
console.error(`Skill '${name}' not found at ${projectPath}/SKILL.md`);
|
|
5673
6662
|
process.exit(1);
|
|
5674
6663
|
}
|
|
5675
|
-
if (
|
|
6664
|
+
if (existsSync24(join27(globalPath, "SKILL.md"))) {
|
|
5676
6665
|
console.error(`Skill '${name}' already exists at ${globalPath}/SKILL.md \u2014 refusing to overwrite. Remove it first or rename the project skill.`);
|
|
5677
6666
|
process.exit(1);
|
|
5678
6667
|
}
|
|
5679
|
-
|
|
6668
|
+
mkdirSync10(dirname6(globalPath), { recursive: true });
|
|
5680
6669
|
renameSync4(projectPath, globalPath);
|
|
5681
6670
|
console.log(`Promoted '${name}' from ${projectPath} \u2192 ${globalPath}.`);
|
|
5682
6671
|
}
|
|
@@ -5719,33 +6708,9 @@ function teamList() {
|
|
|
5719
6708
|
}
|
|
5720
6709
|
function usage() {
|
|
5721
6710
|
console.log("Usage:");
|
|
5722
|
-
console.log(
|
|
5723
|
-
console.log(" hivemind skillify scope <me|team> set the mining scope");
|
|
5724
|
-
console.log(" hivemind skillify install <project|global> set where new skills are written");
|
|
5725
|
-
console.log(" hivemind skillify promote <skill-name> move a project skill to the global location");
|
|
5726
|
-
console.log(" hivemind skillify team add <username> add a username to the team list");
|
|
5727
|
-
console.log(" hivemind skillify team remove <username> remove a username from the team list");
|
|
5728
|
-
console.log(" hivemind skillify team list list current team members");
|
|
5729
|
-
console.log(" hivemind skillify pull [skill-name] [opts] fetch skills from Deeplake to local FS");
|
|
5730
|
-
console.log(" Options for pull:");
|
|
5731
|
-
console.log(" --to <project|global> destination (default: global)");
|
|
5732
|
-
console.log(" --user <name> only skills authored by this user");
|
|
5733
|
-
console.log(" --users <a,b,c> only skills authored by these users");
|
|
5734
|
-
console.log(" --all-users all authors (default \u2014 equivalent to no filter)");
|
|
5735
|
-
console.log(" --dry-run show what would be written, don't touch disk");
|
|
5736
|
-
console.log(" --force overwrite even when local version >= remote");
|
|
5737
|
-
console.log(" hivemind skillify unpull [opts] remove skills previously installed by pull");
|
|
5738
|
-
console.log(" Options for unpull:");
|
|
5739
|
-
console.log(" --to <project|global> where to scan (default: global)");
|
|
5740
|
-
console.log(" --user <name> only entries authored by this user");
|
|
5741
|
-
console.log(" --users <a,b,c> only entries authored by these users");
|
|
5742
|
-
console.log(" --not-mine remove all pulled entries except your own");
|
|
5743
|
-
console.log(" --dry-run show what would be removed");
|
|
5744
|
-
console.log(" --all also remove flat-layout (locally-mined) entries");
|
|
5745
|
-
console.log(" --legacy-cleanup also remove pre-`--author`-layout legacy `<projectKey>/` dirs");
|
|
5746
|
-
console.log(" hivemind skillify status show per-project state");
|
|
6711
|
+
console.log(renderSubcommandUsageBlock());
|
|
5747
6712
|
}
|
|
5748
|
-
function
|
|
6713
|
+
function takeFlagValue2(args, flag) {
|
|
5749
6714
|
const idx = args.indexOf(flag);
|
|
5750
6715
|
if (idx < 0)
|
|
5751
6716
|
return null;
|
|
@@ -5766,9 +6731,9 @@ function takeBooleanFlag(args, flag) {
|
|
|
5766
6731
|
}
|
|
5767
6732
|
async function pullSkills(args) {
|
|
5768
6733
|
const work = [...args];
|
|
5769
|
-
const toRaw =
|
|
5770
|
-
const userOne =
|
|
5771
|
-
const usersMany =
|
|
6734
|
+
const toRaw = takeFlagValue2(work, "--to") ?? "global";
|
|
6735
|
+
const userOne = takeFlagValue2(work, "--user");
|
|
6736
|
+
const usersMany = takeFlagValue2(work, "--users");
|
|
5772
6737
|
const allUsers = takeBooleanFlag(work, "--all-users");
|
|
5773
6738
|
const dryRun = takeBooleanFlag(work, "--dry-run");
|
|
5774
6739
|
const force = takeBooleanFlag(work, "--force");
|
|
@@ -5807,7 +6772,7 @@ async function pullSkills(args) {
|
|
|
5807
6772
|
console.error(`pull failed: ${e?.message ?? e}`);
|
|
5808
6773
|
process.exit(1);
|
|
5809
6774
|
}
|
|
5810
|
-
const dest = toRaw === "global" ?
|
|
6775
|
+
const dest = toRaw === "global" ? join27(homedir17(), ".claude", "skills") : `${process.cwd()}/.claude/skills`;
|
|
5811
6776
|
const filterDesc = users.length === 0 ? "all users" : users.join(", ");
|
|
5812
6777
|
console.log(`Destination: ${dest}`);
|
|
5813
6778
|
console.log(`Filter: ${filterDesc}${skillName ? ` \xB7 skill='${skillName}'` : ""}${dryRun ? " \xB7 dry-run" : ""}${force ? " \xB7 force" : ""}`);
|
|
@@ -5824,9 +6789,9 @@ async function pullSkills(args) {
|
|
|
5824
6789
|
}
|
|
5825
6790
|
async function unpullSkills(args) {
|
|
5826
6791
|
const work = [...args];
|
|
5827
|
-
const toRaw =
|
|
5828
|
-
const userOne =
|
|
5829
|
-
const usersMany =
|
|
6792
|
+
const toRaw = takeFlagValue2(work, "--to") ?? "global";
|
|
6793
|
+
const userOne = takeFlagValue2(work, "--user");
|
|
6794
|
+
const usersMany = takeFlagValue2(work, "--users");
|
|
5830
6795
|
const notMine = takeBooleanFlag(work, "--not-mine");
|
|
5831
6796
|
const dryRun = takeBooleanFlag(work, "--dry-run");
|
|
5832
6797
|
const all = takeBooleanFlag(work, "--all");
|
|
@@ -5857,7 +6822,7 @@ async function unpullSkills(args) {
|
|
|
5857
6822
|
all,
|
|
5858
6823
|
legacyCleanup
|
|
5859
6824
|
});
|
|
5860
|
-
const dest = toRaw === "global" ?
|
|
6825
|
+
const dest = toRaw === "global" ? join27(homedir17(), ".claude", "skills") : `${process.cwd()}/.claude/skills`;
|
|
5861
6826
|
const filterParts = [];
|
|
5862
6827
|
if (users.length > 0)
|
|
5863
6828
|
filterParts.push(`users=${users.join(",")}`);
|
|
@@ -5932,6 +6897,13 @@ function runSkillifyCommand(args) {
|
|
|
5932
6897
|
console.error("Usage: hivemind skillify team <add|remove|list> [name]");
|
|
5933
6898
|
process.exit(1);
|
|
5934
6899
|
}
|
|
6900
|
+
if (sub === "mine-local") {
|
|
6901
|
+
runMineLocal(args.slice(1)).catch((e) => {
|
|
6902
|
+
console.error(`mine-local error: ${e?.message ?? e}`);
|
|
6903
|
+
process.exit(1);
|
|
6904
|
+
});
|
|
6905
|
+
return;
|
|
6906
|
+
}
|
|
5935
6907
|
if (sub === "--help" || sub === "-h" || sub === "help") {
|
|
5936
6908
|
usage();
|
|
5937
6909
|
return;
|
|
@@ -5945,14 +6917,14 @@ if (process.argv[1] && process.argv[1].endsWith("skillify.js")) {
|
|
|
5945
6917
|
}
|
|
5946
6918
|
|
|
5947
6919
|
// dist/src/cli/update.js
|
|
5948
|
-
import { execFileSync as
|
|
5949
|
-
import { existsSync as
|
|
5950
|
-
import { dirname as
|
|
6920
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
6921
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, realpathSync } from "node:fs";
|
|
6922
|
+
import { dirname as dirname8, sep } from "node:path";
|
|
5951
6923
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5952
6924
|
|
|
5953
6925
|
// dist/src/utils/version-check.js
|
|
5954
|
-
import { readFileSync as
|
|
5955
|
-
import { dirname as
|
|
6926
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
6927
|
+
import { dirname as dirname7, join as join28 } from "node:path";
|
|
5956
6928
|
function isNewer(latest, current) {
|
|
5957
6929
|
const parse = (v) => v.split(".").map(Number);
|
|
5958
6930
|
const [la, lb, lc] = parse(latest);
|
|
@@ -5971,24 +6943,24 @@ function detectInstallKind(argv1) {
|
|
|
5971
6943
|
return argv1 ?? process.argv[1] ?? fileURLToPath2(import.meta.url);
|
|
5972
6944
|
}
|
|
5973
6945
|
})();
|
|
5974
|
-
let dir =
|
|
6946
|
+
let dir = dirname8(realArgv1);
|
|
5975
6947
|
let installDir = null;
|
|
5976
6948
|
for (let i = 0; i < 10; i++) {
|
|
5977
6949
|
const pkgPath = `${dir}${sep}package.json`;
|
|
5978
6950
|
try {
|
|
5979
|
-
const pkg = JSON.parse(
|
|
6951
|
+
const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
5980
6952
|
if (pkg.name === PKG_NAME || pkg.name === "hivemind") {
|
|
5981
6953
|
installDir = dir;
|
|
5982
6954
|
break;
|
|
5983
6955
|
}
|
|
5984
6956
|
} catch {
|
|
5985
6957
|
}
|
|
5986
|
-
const parent =
|
|
6958
|
+
const parent = dirname8(dir);
|
|
5987
6959
|
if (parent === dir)
|
|
5988
6960
|
break;
|
|
5989
6961
|
dir = parent;
|
|
5990
6962
|
}
|
|
5991
|
-
installDir ??=
|
|
6963
|
+
installDir ??= dirname8(realArgv1);
|
|
5992
6964
|
if (realArgv1.includes(`${sep}_npx${sep}`) || realArgv1.includes(`${sep}.npx${sep}`)) {
|
|
5993
6965
|
return { kind: "npx", installDir };
|
|
5994
6966
|
}
|
|
@@ -5997,10 +6969,10 @@ function detectInstallKind(argv1) {
|
|
|
5997
6969
|
}
|
|
5998
6970
|
let gitDir = installDir;
|
|
5999
6971
|
for (let i = 0; i < 6; i++) {
|
|
6000
|
-
if (
|
|
6972
|
+
if (existsSync25(`${gitDir}${sep}.git`)) {
|
|
6001
6973
|
return { kind: "local-dev", installDir };
|
|
6002
6974
|
}
|
|
6003
|
-
const parent =
|
|
6975
|
+
const parent = dirname8(gitDir);
|
|
6004
6976
|
if (parent === gitDir)
|
|
6005
6977
|
break;
|
|
6006
6978
|
gitDir = parent;
|
|
@@ -6019,7 +6991,7 @@ async function getLatestNpmVersion(timeoutMs = 5e3) {
|
|
|
6019
6991
|
}
|
|
6020
6992
|
}
|
|
6021
6993
|
var defaultSpawn = (cmd, args) => {
|
|
6022
|
-
|
|
6994
|
+
execFileSync5(cmd, args, { stdio: "inherit" });
|
|
6023
6995
|
};
|
|
6024
6996
|
async function runUpdate(opts = {}) {
|
|
6025
6997
|
const current = opts.currentVersionOverride ?? getVersion();
|
|
@@ -6035,7 +7007,7 @@ async function runUpdate(opts = {}) {
|
|
|
6035
7007
|
}
|
|
6036
7008
|
log(`Update available: ${current} \u2192 ${latest}`);
|
|
6037
7009
|
const detected = opts.installKindOverride ?? detectInstallKind();
|
|
6038
|
-
const
|
|
7010
|
+
const spawn2 = opts.spawn ?? defaultSpawn;
|
|
6039
7011
|
switch (detected.kind) {
|
|
6040
7012
|
case "npm-global": {
|
|
6041
7013
|
if (opts.dryRun) {
|
|
@@ -6045,7 +7017,7 @@ async function runUpdate(opts = {}) {
|
|
|
6045
7017
|
}
|
|
6046
7018
|
log(`Upgrading via npm\u2026`);
|
|
6047
7019
|
try {
|
|
6048
|
-
|
|
7020
|
+
spawn2("npm", ["install", "-g", `${PKG_NAME}@latest`]);
|
|
6049
7021
|
} catch (e) {
|
|
6050
7022
|
warn(`npm install failed: ${e.message}`);
|
|
6051
7023
|
warn(`Try running it manually: npm install -g ${PKG_NAME}@latest`);
|
|
@@ -6054,7 +7026,7 @@ async function runUpdate(opts = {}) {
|
|
|
6054
7026
|
log(``);
|
|
6055
7027
|
log(`Refreshing agent bundles\u2026`);
|
|
6056
7028
|
try {
|
|
6057
|
-
|
|
7029
|
+
spawn2("hivemind", ["install", "--skip-auth"]);
|
|
6058
7030
|
} catch (e) {
|
|
6059
7031
|
warn(`Agent refresh failed: ${e.message}`);
|
|
6060
7032
|
warn(`Run manually: hivemind install`);
|
|
@@ -6154,28 +7126,7 @@ Semantic search (embeddings):
|
|
|
6154
7126
|
to run "embeddings install" automatically after installing the agent(s).
|
|
6155
7127
|
|
|
6156
7128
|
Skill management (mine + share reusable Claude skills across the org):
|
|
6157
|
-
|
|
6158
|
-
hivemind skillify pull [skill-name] Sync skills from the org table to local FS.
|
|
6159
|
-
Options: --user <email>, --users a,b,c,
|
|
6160
|
-
--all-users, --to <project|global>,
|
|
6161
|
-
--dry-run, --force.
|
|
6162
|
-
Note: every agent's SessionStart hook
|
|
6163
|
-
auto-runs 'pull --all-users --to global'
|
|
6164
|
-
on every session. File writes are
|
|
6165
|
-
idempotent (skipped when local is
|
|
6166
|
-
at-or-newer than remote). Disable via
|
|
6167
|
-
HIVEMIND_AUTOPULL_DISABLED=1.
|
|
6168
|
-
hivemind skillify unpull Remove skills previously installed by pull.
|
|
6169
|
-
Options: --user, --users, --not-mine,
|
|
6170
|
-
--to <project|global>, --dry-run,
|
|
6171
|
-
--all (also locally-mined),
|
|
6172
|
-
--legacy-cleanup (pre-suffix-author dirs).
|
|
6173
|
-
hivemind skillify scope <me|team> Set the sharing scope for newly mined skills.
|
|
6174
|
-
hivemind skillify install <project|global> Set where new skills are written.
|
|
6175
|
-
hivemind skillify promote <name> Move a project skill to the global location.
|
|
6176
|
-
hivemind skillify team add <username> Add a username to the team list.
|
|
6177
|
-
hivemind skillify team remove <username> Remove a username from the team list.
|
|
6178
|
-
hivemind skillify team list List current team members.
|
|
7129
|
+
${renderCliHelpBlock()}
|
|
6179
7130
|
|
|
6180
7131
|
Account / org / workspace:
|
|
6181
7132
|
hivemind whoami Show current user, org, workspace.
|