@deeplake/hivemind 0.7.13 → 0.7.15

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.
@@ -6,13 +6,13 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Cloud-backed persistent shared memory for AI agents powered by Deeplake",
9
- "version": "0.7.13"
9
+ "version": "0.7.15"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "hivemind",
14
14
  "description": "Persistent shared memory powered by Deeplake — captures all session activity and provides cross-session, cross-agent memory search",
15
- "version": "0.7.13",
15
+ "version": "0.7.15",
16
16
  "source": "./claude-code",
17
17
  "homepage": "https://github.com/activeloopai/hivemind"
18
18
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hivemind",
3
3
  "description": "Cloud-backed persistent memory powered by Deeplake — read, write, and share memory across Claude Code sessions and agents",
4
- "version": "0.7.13",
4
+ "version": "0.7.15",
5
5
  "author": {
6
6
  "name": "Activeloop",
7
7
  "url": "https://deeplake.ai"
package/README.md CHANGED
@@ -456,6 +456,59 @@ filters by your own username, `scope=team` filters by `author IN
456
456
  Config persists at `~/.deeplake/state/skilify/config.json` (one global
457
457
  file shared across projects).
458
458
 
459
+ ### `pull` / `unpull` — sharing skills across the org
460
+
461
+ Once a teammate's skills are mined into the Deeplake `skills` table, you
462
+ can install them locally with `pull`. Layout written to disk:
463
+
464
+ ```text
465
+ <root>/<name>--<author>/SKILL.md ← pulled skills (e.g. deploy--alice/)
466
+ <root>/<name>/SKILL.md ← your locally-mined skills (flat, no suffix)
467
+ ```
468
+
469
+ The `--<author>` suffix keeps cross-author entries with the same name
470
+ disjoint and lets Claude Code's single-depth skill loader find pulled
471
+ skills without any symlink trickery. `<root>` is `~/.claude/skills` for
472
+ `--to global` and `<cwd>/.claude/skills` for `--to project`.
473
+
474
+ ```bash
475
+ hivemind skilify pull # all authors, install globally
476
+ hivemind skilify pull --user alice@example.com # only this author
477
+ hivemind skilify pull --users a@x.com,b@y.com # multiple authors (CSV)
478
+ hivemind skilify pull --all-users # explicit "no author filter" (default)
479
+ hivemind skilify pull --to project # install under <cwd>/.claude/skills
480
+ hivemind skilify pull --dry-run # preview, no disk writes
481
+ hivemind skilify pull --force # overwrite even when local version >= remote
482
+ hivemind skilify pull <skill-name> # pull only that skill (combinable with --user)
483
+ ```
484
+
485
+ Every successful pull records an entry in
486
+ `~/.deeplake/state/skilify/pulled.json`. That manifest is the source of
487
+ truth for `unpull` — anything not in the manifest is **never** touched
488
+ by default, even if its directory follows the `<name>--<author>` shape
489
+ (this protects user-authored variant skills like `deploy--blue-green`).
490
+
491
+ ```bash
492
+ hivemind skilify unpull # remove every pulled entry under the install scope
493
+ hivemind skilify unpull --user alice@example.com # remove only this author's pulls
494
+ hivemind skilify unpull --users a@x.com,b@y.com # multiple authors
495
+ hivemind skilify unpull --not-mine # remove all pulls except your own
496
+ hivemind skilify unpull --dry-run # preview, no disk writes
497
+ hivemind skilify unpull --to project # operate on <cwd>/.claude/skills instead of global
498
+ hivemind skilify unpull --all # ALSO remove flat-layout (locally-mined) skills — destructive
499
+ hivemind skilify unpull --legacy-cleanup # ALSO remove pre-`--author`-layout `<projectkey>/` dirs from older skilify versions
500
+ ```
501
+
502
+ Drift handling: if a manifest entry's directory was deleted out-of-band
503
+ (e.g. `rm -rf` by hand), the next `unpull` reports it as `manifest-orphan`
504
+ and prunes the entry from the manifest without errors.
505
+
506
+ Cross-project caveat: same `(name, author)` from two different projects
507
+ collides on disk under the new flat layout — the more recently pulled
508
+ row wins, and the prior `SKILL.md` is preserved as `SKILL.md.bak`. The
509
+ underlying row stays in the Deeplake `skills` table, so re-pulling from
510
+ the other project recovers it.
511
+
459
512
  ### Configuration
460
513
 
461
514
  | Env var | Default | Effect |
package/bundle/cli.js CHANGED
@@ -4710,9 +4710,9 @@ if (process.argv[1] && process.argv[1].endsWith("auth-login.js")) {
4710
4710
  }
4711
4711
 
4712
4712
  // dist/src/commands/skilify.js
4713
- import { readdirSync as readdirSync3, existsSync as existsSync15, readFileSync as readFileSync12, mkdirSync as mkdirSync7, renameSync as renameSync2 } from "node:fs";
4714
- import { homedir as homedir8 } from "node:os";
4715
- import { dirname as dirname2, join as join18 } from "node:path";
4713
+ import { readdirSync as readdirSync4, existsSync as existsSync17, readFileSync as readFileSync13, mkdirSync as mkdirSync8, renameSync as renameSync3 } from "node:fs";
4714
+ import { homedir as homedir10 } from "node:os";
4715
+ import { dirname as dirname3, join as join20 } from "node:path";
4716
4716
 
4717
4717
  // dist/src/skilify/scope-config.js
4718
4718
  import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
@@ -4740,9 +4740,9 @@ function saveScopeConfig(cfg) {
4740
4740
  }
4741
4741
 
4742
4742
  // dist/src/skilify/pull.js
4743
- import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, renameSync } from "node:fs";
4744
- import { homedir as homedir7 } from "node:os";
4745
- import { join as join17 } from "node:path";
4743
+ import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, renameSync as renameSync2 } from "node:fs";
4744
+ import { homedir as homedir8 } from "node:os";
4745
+ import { join as join18 } from "node:path";
4746
4746
 
4747
4747
  // dist/src/skilify/skill-writer.js
4748
4748
  import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync10, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
@@ -4805,7 +4805,99 @@ function parseFrontmatter(text) {
4805
4805
  return { fm, body };
4806
4806
  }
4807
4807
 
4808
+ // dist/src/skilify/manifest.js
4809
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync, writeFileSync as writeFileSync8 } from "node:fs";
4810
+ import { homedir as homedir7 } from "node:os";
4811
+ import { dirname as dirname2, join as join17 } from "node:path";
4812
+ function emptyManifest() {
4813
+ return { version: 1, entries: [] };
4814
+ }
4815
+ function manifestPath() {
4816
+ return join17(homedir7(), ".deeplake", "state", "skilify", "pulled.json");
4817
+ }
4818
+ function loadManifest(path = manifestPath()) {
4819
+ if (!existsSync14(path))
4820
+ return emptyManifest();
4821
+ let raw;
4822
+ try {
4823
+ raw = readFileSync11(path, "utf-8");
4824
+ } catch {
4825
+ return emptyManifest();
4826
+ }
4827
+ try {
4828
+ const parsed = JSON.parse(raw);
4829
+ if (!parsed || typeof parsed !== "object")
4830
+ return emptyManifest();
4831
+ if (parsed.version !== 1 || !Array.isArray(parsed.entries))
4832
+ return emptyManifest();
4833
+ const entries = [];
4834
+ for (const e of parsed.entries) {
4835
+ if (!e || typeof e !== "object")
4836
+ continue;
4837
+ if (typeof e.dirName !== "string" || !e.dirName)
4838
+ continue;
4839
+ if (e.dirName.includes("/") || e.dirName.includes("\\") || e.dirName.includes(".."))
4840
+ continue;
4841
+ if (typeof e.name !== "string" || !e.name)
4842
+ continue;
4843
+ if (typeof e.author !== "string")
4844
+ continue;
4845
+ if (typeof e.installRoot !== "string" || !e.installRoot)
4846
+ continue;
4847
+ if (e.install !== "global" && e.install !== "project")
4848
+ continue;
4849
+ entries.push({
4850
+ dirName: e.dirName,
4851
+ name: e.name,
4852
+ author: e.author,
4853
+ projectKey: typeof e.projectKey === "string" ? e.projectKey : "",
4854
+ remoteVersion: typeof e.remoteVersion === "number" ? e.remoteVersion : 1,
4855
+ install: e.install,
4856
+ installRoot: e.installRoot,
4857
+ pulledAt: typeof e.pulledAt === "string" ? e.pulledAt : (/* @__PURE__ */ new Date()).toISOString()
4858
+ });
4859
+ }
4860
+ return { version: 1, entries };
4861
+ } catch {
4862
+ return emptyManifest();
4863
+ }
4864
+ }
4865
+ function saveManifest(m, path = manifestPath()) {
4866
+ mkdirSync6(dirname2(path), { recursive: true });
4867
+ const tmp = `${path}.tmp`;
4868
+ writeFileSync8(tmp, JSON.stringify(m, null, 2) + "\n", { mode: 384 });
4869
+ renameSync(tmp, path);
4870
+ }
4871
+ function recordPull(entry, path = manifestPath()) {
4872
+ const m = loadManifest(path);
4873
+ const idx = m.entries.findIndex((e) => e.install === entry.install && e.installRoot === entry.installRoot && e.dirName === entry.dirName);
4874
+ if (idx >= 0)
4875
+ m.entries[idx] = entry;
4876
+ else
4877
+ m.entries.push(entry);
4878
+ saveManifest(m, path);
4879
+ }
4880
+ function removePullEntry(install, installRoot, dirName, path = manifestPath()) {
4881
+ const m = loadManifest(path);
4882
+ const before = m.entries.length;
4883
+ m.entries = m.entries.filter((e) => !(e.install === install && e.installRoot === installRoot && e.dirName === dirName));
4884
+ if (m.entries.length !== before)
4885
+ saveManifest(m, path);
4886
+ }
4887
+ function entriesForRoot(m, install, installRoot) {
4888
+ return m.entries.filter((e) => e.install === install && e.installRoot === installRoot);
4889
+ }
4890
+
4808
4891
  // dist/src/skilify/pull.js
4892
+ function assertValidAuthor(author) {
4893
+ if (!author)
4894
+ throw new Error("author is empty");
4895
+ if (author.length > 64)
4896
+ throw new Error(`author too long (${author.length}): ${author.slice(0, 32)}\u2026`);
4897
+ if (!/^[A-Za-z0-9_.\-@]+$/.test(author)) {
4898
+ throw new Error(`author contains invalid characters: ${author}`);
4899
+ }
4900
+ }
4809
4901
  function esc(s) {
4810
4902
  return s.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
4811
4903
  }
@@ -4828,10 +4920,10 @@ function isMissingTableError(message) {
4828
4920
  }
4829
4921
  function resolvePullDestination(install, cwd) {
4830
4922
  if (install === "global")
4831
- return join17(homedir7(), ".claude", "skills");
4923
+ return join18(homedir8(), ".claude", "skills");
4832
4924
  if (!cwd)
4833
4925
  throw new Error("install=project requires a cwd");
4834
- return join17(cwd, ".claude", "skills");
4926
+ return join18(cwd, ".claude", "skills");
4835
4927
  }
4836
4928
  function selectLatestPerName(rows) {
4837
4929
  const seen = /* @__PURE__ */ new Set();
@@ -4897,10 +4989,10 @@ function renderFrontmatter(fm) {
4897
4989
  return lines.join("\n");
4898
4990
  }
4899
4991
  function readLocalVersion(path) {
4900
- if (!existsSync14(path))
4992
+ if (!existsSync15(path))
4901
4993
  return null;
4902
4994
  try {
4903
- const text = readFileSync11(path, "utf-8");
4995
+ const text = readFileSync12(path, "utf-8");
4904
4996
  const parsed = parseFrontmatter(text);
4905
4997
  if (!parsed)
4906
4998
  return null;
@@ -4953,9 +5045,39 @@ async function runPull(opts) {
4953
5045
  summary.skipped++;
4954
5046
  continue;
4955
5047
  }
4956
- const projectKey = String(row.project_key ?? "");
4957
- const skillDir = projectKey ? join17(root, projectKey, name) : join17(root, name);
4958
- const skillFile = join17(skillDir, "SKILL.md");
5048
+ const author = String(row.author ?? "");
5049
+ if (!author) {
5050
+ summary.entries.push({
5051
+ name,
5052
+ remoteVersion: Number(row.version ?? 1),
5053
+ localVersion: null,
5054
+ action: "skipped",
5055
+ destination: "(empty author \u2014 skipped)",
5056
+ author: "",
5057
+ sourceAgent: String(row.source_agent ?? "")
5058
+ });
5059
+ summary.skipped++;
5060
+ continue;
5061
+ }
5062
+ let dirName;
5063
+ try {
5064
+ assertValidAuthor(author);
5065
+ dirName = `${name}--${author}`;
5066
+ } catch (e) {
5067
+ summary.entries.push({
5068
+ name,
5069
+ remoteVersion: Number(row.version ?? 1),
5070
+ localVersion: null,
5071
+ action: "skipped",
5072
+ destination: `(invalid author '${author}' \u2014 skipped)`,
5073
+ author,
5074
+ sourceAgent: String(row.source_agent ?? "")
5075
+ });
5076
+ summary.skipped++;
5077
+ continue;
5078
+ }
5079
+ const skillDir = join18(root, dirName);
5080
+ const skillFile = join18(skillDir, "SKILL.md");
4959
5081
  const remoteVersion = Number(row.version ?? 1);
4960
5082
  const localVersion = readLocalVersion(skillFile);
4961
5083
  const action = decideAction({
@@ -4964,15 +5086,30 @@ async function runPull(opts) {
4964
5086
  force: opts.force ?? false,
4965
5087
  dryRun: opts.dryRun ?? false
4966
5088
  });
5089
+ let manifestError;
4967
5090
  if (action === "wrote") {
4968
- mkdirSync6(skillDir, { recursive: true });
4969
- if (existsSync14(skillFile)) {
5091
+ mkdirSync7(skillDir, { recursive: true });
5092
+ if (existsSync15(skillFile)) {
4970
5093
  try {
4971
- renameSync(skillFile, `${skillFile}.bak`);
5094
+ renameSync2(skillFile, `${skillFile}.bak`);
4972
5095
  } catch {
4973
5096
  }
4974
5097
  }
4975
- writeFileSync8(skillFile, renderSkillFile(row));
5098
+ writeFileSync9(skillFile, renderSkillFile(row));
5099
+ try {
5100
+ recordPull({
5101
+ dirName,
5102
+ name,
5103
+ author,
5104
+ projectKey: String(row.project_key ?? ""),
5105
+ remoteVersion,
5106
+ install: opts.install,
5107
+ installRoot: root,
5108
+ pulledAt: (/* @__PURE__ */ new Date()).toISOString()
5109
+ });
5110
+ } catch (e) {
5111
+ manifestError = e?.message ?? String(e);
5112
+ }
4976
5113
  }
4977
5114
  summary.entries.push({
4978
5115
  name,
@@ -4981,7 +5118,8 @@ async function runPull(opts) {
4981
5118
  action,
4982
5119
  destination: skillFile,
4983
5120
  author: String(row.author ?? ""),
4984
- sourceAgent: String(row.source_agent ?? "")
5121
+ sourceAgent: String(row.source_agent ?? ""),
5122
+ manifestError
4985
5123
  });
4986
5124
  if (action === "wrote")
4987
5125
  summary.wrote++;
@@ -4993,18 +5131,186 @@ async function runPull(opts) {
4993
5131
  return summary;
4994
5132
  }
4995
5133
 
5134
+ // dist/src/skilify/unpull.js
5135
+ import { existsSync as existsSync16, readdirSync as readdirSync3, rmSync as rmSync5, statSync as statSync3 } from "node:fs";
5136
+ import { homedir as homedir9 } from "node:os";
5137
+ import { join as join19 } from "node:path";
5138
+ function resolveUnpullRoot(install, cwd) {
5139
+ if (install === "global")
5140
+ return join19(homedir9(), ".claude", "skills");
5141
+ if (!cwd)
5142
+ throw new Error("cwd required when install === 'project'");
5143
+ return join19(cwd, ".claude", "skills");
5144
+ }
5145
+ function runUnpull(opts) {
5146
+ const root = resolveUnpullRoot(opts.install, opts.cwd);
5147
+ const summary = {
5148
+ scanned: 0,
5149
+ removed: 0,
5150
+ wouldRemove: 0,
5151
+ kept: 0,
5152
+ manifestPruned: 0,
5153
+ entries: []
5154
+ };
5155
+ const userFilter = new Set(opts.users.filter((u) => u.length > 0));
5156
+ const haveUserFilter = userFilter.size > 0;
5157
+ if ((opts.all || opts.legacyCleanup) && (haveUserFilter || opts.notMine)) {
5158
+ const flags = [opts.all && "--all", opts.legacyCleanup && "--legacy-cleanup"].filter(Boolean).join(" / ");
5159
+ const filters = [haveUserFilter && "--user/--users", opts.notMine && "--not-mine"].filter(Boolean).join(" / ");
5160
+ throw new Error(`${flags} cannot be combined with ${filters}: entries removed by ${flags} are not in the manifest and have no author metadata, so the filter would silently fail to apply. Run the filtered unpull first, then ${flags} as a separate invocation.`);
5161
+ }
5162
+ const manifest = loadManifest();
5163
+ const entries = entriesForRoot(manifest, opts.install, root);
5164
+ for (const entry of entries) {
5165
+ summary.scanned++;
5166
+ const path = join19(root, entry.dirName);
5167
+ if (!existsSync16(path)) {
5168
+ if (!opts.dryRun)
5169
+ removePullEntry(opts.install, entry.installRoot, entry.dirName);
5170
+ summary.entries.push({
5171
+ dirName: entry.dirName,
5172
+ kind: "manifest-orphan",
5173
+ author: entry.author,
5174
+ name: entry.name,
5175
+ action: opts.dryRun ? "kept-policy" : "manifest-pruned",
5176
+ reason: opts.dryRun ? "would-prune (orphan, dir missing)" : "directory was already missing",
5177
+ path: ""
5178
+ });
5179
+ if (!opts.dryRun)
5180
+ summary.manifestPruned++;
5181
+ else
5182
+ summary.kept++;
5183
+ continue;
5184
+ }
5185
+ const decision = decideTargetForManifestEntry(entry, opts, userFilter, haveUserFilter);
5186
+ const result = {
5187
+ dirName: entry.dirName,
5188
+ kind: "pulled-manifest",
5189
+ author: entry.author,
5190
+ name: entry.name,
5191
+ action: "kept-policy",
5192
+ path
5193
+ };
5194
+ if (!decision.shouldRemove) {
5195
+ result.reason = decision.reason;
5196
+ summary.kept++;
5197
+ summary.entries.push(result);
5198
+ continue;
5199
+ }
5200
+ if (opts.dryRun) {
5201
+ result.action = "would-remove";
5202
+ summary.wouldRemove++;
5203
+ } else {
5204
+ try {
5205
+ rmSync5(path, { recursive: true, force: true });
5206
+ removePullEntry(opts.install, entry.installRoot, entry.dirName);
5207
+ result.action = "removed";
5208
+ summary.removed++;
5209
+ } catch (e) {
5210
+ result.action = "kept-policy";
5211
+ result.reason = `rm failed: ${e?.message ?? e}`;
5212
+ summary.kept++;
5213
+ }
5214
+ }
5215
+ summary.entries.push(result);
5216
+ }
5217
+ if (existsSync16(root) && (opts.all || opts.legacyCleanup)) {
5218
+ const manifestDirNames = new Set(entries.map((e) => e.dirName));
5219
+ for (const dirName of readdirSync3(root)) {
5220
+ if (manifestDirNames.has(dirName))
5221
+ continue;
5222
+ const path = join19(root, dirName);
5223
+ let st;
5224
+ try {
5225
+ st = statSync3(path);
5226
+ } catch {
5227
+ continue;
5228
+ }
5229
+ if (!st.isDirectory())
5230
+ continue;
5231
+ const isLegacyProjectKey = /^[0-9a-f]{16}$/.test(dirName);
5232
+ const isLocallyMined = !isLegacyProjectKey && /^[A-Za-z0-9_.-]+$/.test(dirName) && !dirName.includes("--");
5233
+ let kind;
5234
+ let shouldRemove = false;
5235
+ let reason;
5236
+ if (isLegacyProjectKey) {
5237
+ kind = "legacy-projectkey";
5238
+ if (opts.legacyCleanup)
5239
+ shouldRemove = true;
5240
+ else
5241
+ reason = "legacy project_key dir (use --legacy-cleanup)";
5242
+ } else if (isLocallyMined) {
5243
+ kind = "locally-mined";
5244
+ if (opts.all)
5245
+ shouldRemove = true;
5246
+ else
5247
+ reason = "locally-mined (use --all to remove)";
5248
+ } else {
5249
+ continue;
5250
+ }
5251
+ summary.scanned++;
5252
+ const result = {
5253
+ dirName,
5254
+ kind,
5255
+ author: null,
5256
+ name: kind === "locally-mined" ? dirName : null,
5257
+ action: "kept-policy",
5258
+ path,
5259
+ reason
5260
+ };
5261
+ if (!shouldRemove) {
5262
+ summary.kept++;
5263
+ summary.entries.push(result);
5264
+ continue;
5265
+ }
5266
+ if (opts.dryRun) {
5267
+ result.action = "would-remove";
5268
+ summary.wouldRemove++;
5269
+ } else {
5270
+ try {
5271
+ rmSync5(path, { recursive: true, force: true });
5272
+ result.action = "removed";
5273
+ summary.removed++;
5274
+ } catch (e) {
5275
+ result.action = "kept-policy";
5276
+ result.reason = `rm failed: ${e?.message ?? e}`;
5277
+ summary.kept++;
5278
+ }
5279
+ }
5280
+ summary.entries.push(result);
5281
+ }
5282
+ }
5283
+ return summary;
5284
+ }
5285
+ function decideTargetForManifestEntry(entry, opts, userFilter, haveUserFilter) {
5286
+ if (haveUserFilter && !userFilter.has(entry.author)) {
5287
+ return { shouldRemove: false, reason: `author '${entry.author}' not in filter` };
5288
+ }
5289
+ if (opts.notMine) {
5290
+ if (!opts.myUsername)
5291
+ return { shouldRemove: false, reason: "--not-mine requires myUsername" };
5292
+ if (entry.author === opts.myUsername) {
5293
+ return { shouldRemove: false, reason: "your own pull (--not-mine excludes self)" };
5294
+ }
5295
+ }
5296
+ return { shouldRemove: true };
5297
+ }
5298
+
4996
5299
  // dist/src/commands/skilify.js
4997
- var STATE_DIR2 = join18(homedir8(), ".deeplake", "state", "skilify");
5300
+ function stateDir() {
5301
+ return join20(homedir10(), ".deeplake", "state", "skilify");
5302
+ }
4998
5303
  function showStatus() {
4999
5304
  const cfg = loadScopeConfig();
5000
5305
  console.log(`scope: ${cfg.scope}`);
5001
5306
  console.log(`team: ${cfg.team.length === 0 ? "(empty)" : cfg.team.join(", ")}`);
5002
5307
  console.log(`install: ${cfg.install} (${cfg.install === "global" ? "~/.claude/skills/" : "<project>/.claude/skills/"})`);
5003
- if (!existsSync15(STATE_DIR2)) {
5308
+ const dir = stateDir();
5309
+ if (!existsSync17(dir)) {
5004
5310
  console.log(`state: (no projects tracked yet)`);
5005
5311
  return;
5006
5312
  }
5007
- const files = readdirSync3(STATE_DIR2).filter((f) => f.endsWith(".json") && f !== "config.json");
5313
+ const files = readdirSync4(dir).filter((f) => f.endsWith(".json") && f !== "config.json" && f !== "pulled.json");
5008
5314
  if (files.length === 0) {
5009
5315
  console.log(`state: (no projects tracked yet)`);
5010
5316
  return;
@@ -5012,7 +5318,7 @@ function showStatus() {
5012
5318
  console.log(`state: ${files.length} project(s) tracked`);
5013
5319
  for (const f of files) {
5014
5320
  try {
5015
- const s = JSON.parse(readFileSync12(join18(STATE_DIR2, f), "utf-8"));
5321
+ const s = JSON.parse(readFileSync13(join20(dir, f), "utf-8"));
5016
5322
  const skills = s.skillsGenerated.length === 0 ? "none" : s.skillsGenerated.join(", ");
5017
5323
  console.log(` - ${s.project} (counter=${s.counter}, last=${s.lastDate ?? "never"}, skills=${skills})`);
5018
5324
  } catch {
@@ -5038,7 +5344,7 @@ function setInstall(loc) {
5038
5344
  }
5039
5345
  const cfg = loadScopeConfig();
5040
5346
  saveScopeConfig({ ...cfg, install: loc });
5041
- const path = loc === "global" ? join18(homedir8(), ".claude", "skills") : "<cwd>/.claude/skills";
5347
+ const path = loc === "global" ? join20(homedir10(), ".claude", "skills") : "<cwd>/.claude/skills";
5042
5348
  console.log(`Install location set to '${loc}'. New skills will be written to ${path}/<name>/SKILL.md.`);
5043
5349
  }
5044
5350
  function promoteSkill(name, cwd) {
@@ -5046,18 +5352,18 @@ function promoteSkill(name, cwd) {
5046
5352
  console.error("Usage: hivemind skilify promote <skill-name>");
5047
5353
  process.exit(1);
5048
5354
  }
5049
- const projectPath = join18(cwd, ".claude", "skills", name);
5050
- const globalPath = join18(homedir8(), ".claude", "skills", name);
5051
- if (!existsSync15(join18(projectPath, "SKILL.md"))) {
5355
+ const projectPath = join20(cwd, ".claude", "skills", name);
5356
+ const globalPath = join20(homedir10(), ".claude", "skills", name);
5357
+ if (!existsSync17(join20(projectPath, "SKILL.md"))) {
5052
5358
  console.error(`Skill '${name}' not found at ${projectPath}/SKILL.md`);
5053
5359
  process.exit(1);
5054
5360
  }
5055
- if (existsSync15(join18(globalPath, "SKILL.md"))) {
5361
+ if (existsSync17(join20(globalPath, "SKILL.md"))) {
5056
5362
  console.error(`Skill '${name}' already exists at ${globalPath}/SKILL.md \u2014 refusing to overwrite. Remove it first or rename the project skill.`);
5057
5363
  process.exit(1);
5058
5364
  }
5059
- mkdirSync7(dirname2(globalPath), { recursive: true });
5060
- renameSync2(projectPath, globalPath);
5365
+ mkdirSync8(dirname3(globalPath), { recursive: true });
5366
+ renameSync3(projectPath, globalPath);
5061
5367
  console.log(`Promoted '${name}' from ${projectPath} \u2192 ${globalPath}.`);
5062
5368
  }
5063
5369
  function teamAdd(name) {
@@ -5114,6 +5420,15 @@ function usage() {
5114
5420
  console.log(" --all-users all authors (default \u2014 equivalent to no filter)");
5115
5421
  console.log(" --dry-run show what would be written, don't touch disk");
5116
5422
  console.log(" --force overwrite even when local version >= remote");
5423
+ console.log(" hivemind skilify unpull [opts] remove skills previously installed by pull");
5424
+ console.log(" Options for unpull:");
5425
+ console.log(" --to <project|global> where to scan (default: global)");
5426
+ console.log(" --user <name> only entries authored by this user");
5427
+ console.log(" --users <a,b,c> only entries authored by these users");
5428
+ console.log(" --not-mine remove all pulled entries except your own");
5429
+ console.log(" --dry-run show what would be removed");
5430
+ console.log(" --all also remove flat-layout (locally-mined) entries");
5431
+ console.log(" --legacy-cleanup also remove pre-`--author`-layout legacy `<projectKey>/` dirs");
5117
5432
  console.log(" hivemind skilify status show per-project state");
5118
5433
  }
5119
5434
  function takeFlagValue(args, flag) {
@@ -5178,7 +5493,7 @@ async function pullSkills(args) {
5178
5493
  console.error(`pull failed: ${e?.message ?? e}`);
5179
5494
  process.exit(1);
5180
5495
  }
5181
- const dest = toRaw === "global" ? join18(homedir8(), ".claude", "skills") : `${process.cwd()}/.claude/skills`;
5496
+ const dest = toRaw === "global" ? join20(homedir10(), ".claude", "skills") : `${process.cwd()}/.claude/skills`;
5182
5497
  const filterDesc = users.length === 0 ? "all users" : users.join(", ");
5183
5498
  console.log(`Destination: ${dest}`);
5184
5499
  console.log(`Filter: ${filterDesc}${skillName ? ` \xB7 skill='${skillName}'` : ""}${dryRun ? " \xB7 dry-run" : ""}${force ? " \xB7 force" : ""}`);
@@ -5187,9 +5502,72 @@ async function pullSkills(args) {
5187
5502
  const tag = e.action === "wrote" ? "\u2713 wrote" : e.action === "dryrun" ? "\u2192 would write" : "\xB7 skipped";
5188
5503
  const ver = e.localVersion === null ? `v${e.remoteVersion} (new)` : `v${e.localVersion} \u2192 v${e.remoteVersion}`;
5189
5504
  console.log(` ${tag.padEnd(15)} ${e.name.padEnd(40)} ${ver.padEnd(20)} (${e.author}/${e.sourceAgent})`);
5505
+ if (e.manifestError) {
5506
+ console.warn(` \u26A0 manifest not updated: ${e.manifestError} \u2014 \`unpull\` will not see this entry until a successful repull.`);
5507
+ }
5190
5508
  }
5191
5509
  console.log(`Result: ${summary.wrote} written, ${summary.dryrun} dry-run, ${summary.skipped} skipped.`);
5192
5510
  }
5511
+ async function unpullSkills(args) {
5512
+ const work = [...args];
5513
+ const toRaw = takeFlagValue(work, "--to") ?? "global";
5514
+ const userOne = takeFlagValue(work, "--user");
5515
+ const usersMany = takeFlagValue(work, "--users");
5516
+ const notMine = takeBooleanFlag(work, "--not-mine");
5517
+ const dryRun = takeBooleanFlag(work, "--dry-run");
5518
+ const all = takeBooleanFlag(work, "--all");
5519
+ const legacyCleanup = takeBooleanFlag(work, "--legacy-cleanup");
5520
+ if (toRaw !== "project" && toRaw !== "global") {
5521
+ throw new Error(`Invalid --to '${toRaw}'. Use 'project' or 'global'.`);
5522
+ }
5523
+ let users = [];
5524
+ if (userOne)
5525
+ users = [userOne];
5526
+ else if (usersMany)
5527
+ users = usersMany.split(",").map((s) => s.trim()).filter(Boolean);
5528
+ let myUsername;
5529
+ if (notMine) {
5530
+ const config = loadConfig();
5531
+ if (!config) {
5532
+ throw new Error("--not-mine requires a logged-in user. Run: hivemind login");
5533
+ }
5534
+ myUsername = config.userName;
5535
+ }
5536
+ const summary = runUnpull({
5537
+ install: toRaw,
5538
+ cwd: toRaw === "project" ? process.cwd() : void 0,
5539
+ users,
5540
+ myUsername,
5541
+ notMine,
5542
+ dryRun,
5543
+ all,
5544
+ legacyCleanup
5545
+ });
5546
+ const dest = toRaw === "global" ? join20(homedir10(), ".claude", "skills") : `${process.cwd()}/.claude/skills`;
5547
+ const filterParts = [];
5548
+ if (users.length > 0)
5549
+ filterParts.push(`users=${users.join(",")}`);
5550
+ if (notMine)
5551
+ filterParts.push("not-mine");
5552
+ if (all)
5553
+ filterParts.push("all");
5554
+ if (legacyCleanup)
5555
+ filterParts.push("legacy-cleanup");
5556
+ if (dryRun)
5557
+ filterParts.push("dry-run");
5558
+ const filterDesc = filterParts.length ? filterParts.join(" \xB7 ") : "(no filter \u2014 all pulled)";
5559
+ console.log(`Scanning: ${dest}`);
5560
+ console.log(`Filter: ${filterDesc}`);
5561
+ console.log(`Scanned ${summary.scanned} dir(s).`);
5562
+ for (const e of summary.entries) {
5563
+ const tag = e.action === "removed" ? "\u2713 removed" : e.action === "would-remove" ? "\u2192 would remove" : e.action === "manifest-pruned" ? "\u26A0 pruned (orphan)" : "\xB7 kept";
5564
+ const id = e.dirName;
5565
+ const note = e.reason ? ` (${e.reason})` : "";
5566
+ console.log(` ${tag.padEnd(20)} ${id.padEnd(50)} [${e.kind}]${note}`);
5567
+ }
5568
+ const prunedNote = summary.manifestPruned > 0 ? `, ${summary.manifestPruned} manifest-pruned` : "";
5569
+ console.log(`Result: ${summary.removed} removed, ${summary.wouldRemove} dry-run, ${summary.kept} kept${prunedNote}.`);
5570
+ }
5193
5571
  function runSkilifyCommand(args) {
5194
5572
  const sub = args[0];
5195
5573
  if (!sub || sub === "status") {
@@ -5215,6 +5593,14 @@ function runSkilifyCommand(args) {
5215
5593
  });
5216
5594
  return;
5217
5595
  }
5596
+ if (sub === "unpull") {
5597
+ unpullSkills(args.slice(1)).catch((e) => {
5598
+ console.error(`unpull error: ${e?.message ?? e}`);
5599
+ process.exit(1);
5600
+ }).catch(() => {
5601
+ });
5602
+ return;
5603
+ }
5218
5604
  if (sub === "team") {
5219
5605
  const action = args[1];
5220
5606
  if (action === "add") {
@@ -5246,13 +5632,13 @@ if (process.argv[1] && process.argv[1].endsWith("skilify.js")) {
5246
5632
 
5247
5633
  // dist/src/cli/update.js
5248
5634
  import { execFileSync as execFileSync4 } from "node:child_process";
5249
- import { existsSync as existsSync16, readFileSync as readFileSync14, realpathSync } from "node:fs";
5250
- import { dirname as dirname4, sep } from "node:path";
5635
+ import { existsSync as existsSync18, readFileSync as readFileSync15, realpathSync } from "node:fs";
5636
+ import { dirname as dirname5, sep } from "node:path";
5251
5637
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5252
5638
 
5253
5639
  // dist/src/utils/version-check.js
5254
- import { readFileSync as readFileSync13 } from "node:fs";
5255
- import { dirname as dirname3, join as join19 } from "node:path";
5640
+ import { readFileSync as readFileSync14 } from "node:fs";
5641
+ import { dirname as dirname4, join as join21 } from "node:path";
5256
5642
  function isNewer(latest, current) {
5257
5643
  const parse = (v) => v.split(".").map(Number);
5258
5644
  const [la, lb, lc] = parse(latest);
@@ -5271,24 +5657,24 @@ function detectInstallKind(argv1) {
5271
5657
  return argv1 ?? process.argv[1] ?? fileURLToPath2(import.meta.url);
5272
5658
  }
5273
5659
  })();
5274
- let dir = dirname4(realArgv1);
5660
+ let dir = dirname5(realArgv1);
5275
5661
  let installDir = null;
5276
5662
  for (let i = 0; i < 10; i++) {
5277
5663
  const pkgPath = `${dir}${sep}package.json`;
5278
5664
  try {
5279
- const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
5665
+ const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
5280
5666
  if (pkg.name === PKG_NAME || pkg.name === "hivemind") {
5281
5667
  installDir = dir;
5282
5668
  break;
5283
5669
  }
5284
5670
  } catch {
5285
5671
  }
5286
- const parent = dirname4(dir);
5672
+ const parent = dirname5(dir);
5287
5673
  if (parent === dir)
5288
5674
  break;
5289
5675
  dir = parent;
5290
5676
  }
5291
- installDir ??= dirname4(realArgv1);
5677
+ installDir ??= dirname5(realArgv1);
5292
5678
  if (realArgv1.includes(`${sep}_npx${sep}`) || realArgv1.includes(`${sep}.npx${sep}`)) {
5293
5679
  return { kind: "npx", installDir };
5294
5680
  }
@@ -5297,10 +5683,10 @@ function detectInstallKind(argv1) {
5297
5683
  }
5298
5684
  let gitDir = installDir;
5299
5685
  for (let i = 0; i < 6; i++) {
5300
- if (existsSync16(`${gitDir}${sep}.git`)) {
5686
+ if (existsSync18(`${gitDir}${sep}.git`)) {
5301
5687
  return { kind: "local-dev", installDir };
5302
5688
  }
5303
- const parent = dirname4(gitDir);
5689
+ const parent = dirname5(gitDir);
5304
5690
  if (parent === gitDir)
5305
5691
  break;
5306
5692
  gitDir = parent;
@@ -5459,6 +5845,11 @@ Skill management (mine + share reusable Claude skills across the org):
5459
5845
  Options: --user <email>, --users a,b,c,
5460
5846
  --all-users, --to <project|global>,
5461
5847
  --dry-run, --force.
5848
+ hivemind skilify unpull Remove skills previously installed by pull.
5849
+ Options: --user, --users, --not-mine,
5850
+ --to <project|global>, --dry-run,
5851
+ --all (also locally-mined),
5852
+ --legacy-cleanup (pre-suffix-author dirs).
5462
5853
  hivemind skilify scope <me|team|org> Set the sharing scope for newly mined skills.
5463
5854
  hivemind skilify install <project|global> Set where new skills are written.
5464
5855
  hivemind skilify promote <name> Move a project skill to the global location.
@@ -140,6 +140,10 @@ SKILLS (skilify) \u2014 mine + share reusable skills across the org:
140
140
  - hivemind skilify pull --dry-run \u2014 preview only
141
141
  - hivemind skilify pull --force \u2014 overwrite local (creates .bak)
142
142
  - hivemind skilify pull <skill-name> \u2014 pull only that skill (combines with --user)
143
+ - hivemind skilify unpull \u2014 remove every skill previously installed by pull
144
+ - hivemind skilify unpull --user <email> \u2014 remove only that author's pulls
145
+ - hivemind skilify unpull --not-mine \u2014 remove all pulls except your own
146
+ - hivemind skilify unpull --dry-run \u2014 preview without touching disk
143
147
  - hivemind skilify scope <me|team|org> \u2014 sharing scope for new skills
144
148
  - hivemind skilify install <project|global> \u2014 default install location
145
149
  - hivemind skilify team add|remove|list <name> \u2014 manage team list`;
@@ -702,6 +702,10 @@ SKILLS (skilify) \u2014 mine + share reusable skills across the org:
702
702
  - hivemind skilify pull --dry-run \u2014 preview only
703
703
  - hivemind skilify pull --force \u2014 overwrite local (creates .bak)
704
704
  - hivemind skilify pull <skill-name> \u2014 pull only that skill (combines with --user)
705
+ - hivemind skilify unpull \u2014 remove every skill previously installed by pull
706
+ - hivemind skilify unpull --user <email> \u2014 remove only that author's pulls
707
+ - hivemind skilify unpull --not-mine \u2014 remove all pulls except your own
708
+ - hivemind skilify unpull --dry-run \u2014 preview without touching disk
705
709
  - hivemind skilify scope <me|team|org> \u2014 sharing scope for new skills
706
710
  - hivemind skilify install <project|global> \u2014 default install location
707
711
  - hivemind skilify team add|remove|list <name> \u2014 manage team list`;
@@ -702,6 +702,10 @@ SKILLS (skilify) \u2014 mine + share reusable skills across the org:
702
702
  - hivemind skilify pull --dry-run \u2014 preview only
703
703
  - hivemind skilify pull --force \u2014 overwrite local (creates .bak)
704
704
  - hivemind skilify pull <skill-name> \u2014 pull only that skill (combines with --user)
705
+ - hivemind skilify unpull \u2014 remove every skill previously installed by pull
706
+ - hivemind skilify unpull --user <email> \u2014 remove only that author's pulls
707
+ - hivemind skilify unpull --not-mine \u2014 remove all pulls except your own
708
+ - hivemind skilify unpull --dry-run \u2014 preview without touching disk
705
709
  - hivemind skilify scope <me|team|org> \u2014 sharing scope for new skills
706
710
  - hivemind skilify install <project|global> \u2014 default install location
707
711
  - hivemind skilify team add|remove|list <name> \u2014 manage team list`;
@@ -1070,7 +1070,7 @@ function extractLatestVersion(body) {
1070
1070
  return typeof v === "string" && v.length > 0 ? v : null;
1071
1071
  }
1072
1072
  function getInstalledVersion() {
1073
- return "0.7.13".length > 0 ? "0.7.13" : null;
1073
+ return "0.7.15".length > 0 ? "0.7.15" : null;
1074
1074
  }
1075
1075
  function isNewer(latest, current) {
1076
1076
  const parse = (v) => v.replace(/-.*$/, "").split(".").map(Number);
@@ -1745,7 +1745,7 @@ ${body.slice(0, 500)}`;
1745
1745
  const hook = (event, handler) => {
1746
1746
  pluginApi.on(event, handler);
1747
1747
  };
1748
- if ('---\nname: hivemind\ndescription: Global team and org memory powered by Activeloop. ALWAYS check BOTH built-in memory AND Hivemind memory when recalling information.\nallowed-tools: hivemind_search, hivemind_read, hivemind_index\n---\n\n# Hivemind Memory\n\nYou have TWO memory sources. ALWAYS check BOTH when the user asks you to recall, remember, or look up ANY information:\n\n1. **Your built-in memory** \u2014 personal per-project notes from the host agent\n2. **Hivemind global memory** \u2014 global memory shared across all sessions, users, and agents in the org, accessed via the tools below\n\n## Memory Structure\n\n```\n/index.md \u2190 START HERE \u2014 table of all sessions\n/summaries/\n <username>/\n <session-id>.md \u2190 AI-generated wiki summary per session\n/sessions/\n <username>/\n <user_org_ws_slug>.jsonl \u2190 raw session data\n```\n\n## How to Search\n\n1. **First**: call `hivemind_index()` \u2014 table of all sessions with dates, projects, descriptions\n2. **If you need details**: call `hivemind_read("/summaries/<username>/<session>.md")`\n3. **If you need raw data**: call `hivemind_read("/sessions/<username>/<file>.jsonl")`\n4. **Keyword search**: call `hivemind_search("keyword")` \u2014 substring search across both summaries and sessions, returns `path:line` hits\n\nDo NOT jump straight to reading raw JSONL files. Always start with `hivemind_index` and summaries.\n\n## Organization Management\n\n- `/hivemind_login` \u2014 sign in via device flow\n- `/hivemind_capture` \u2014 toggle capture on/off (off = no data sent)\n- `/hivemind_whoami` \u2014 show current org and workspace\n- `/hivemind_orgs` \u2014 list organizations\n- `/hivemind_switch_org <name-or-id>` \u2014 switch organization\n- `/hivemind_workspaces` \u2014 list workspaces\n- `/hivemind_switch_workspace <id>` \u2014 switch workspace\n- `/hivemind_version` \u2014 show installed version and check npm for updates\n- `/hivemind_update` \u2014 shows how to install (ask the agent, or run `hivemind update` in your terminal)\n- `/hivemind_autoupdate [on|off]` \u2014 toggle the agent-facing update nudge (on by default: when a newer version is available, the agent is prompted to install it via `exec` if you ask to update)\n\n## Skill Management (skilify)\n\nHivemind also mines reusable Claude skills from agent sessions and stores them in a per-org Deeplake table. Openclaw itself doesn\'t run sessions to mine, but you can pull skills others have already mined for the user. These run in the user\'s terminal (the openclaw plugin does not register them as `/hivemind_*` commands):\n\n- `hivemind skilify` \u2014 show scope/team/install + per-project state\n- `hivemind skilify pull` \u2014 sync skills for the current project from the org table\n- `hivemind skilify pull --user <email>` \u2014 only that author\'s skills\n- `hivemind skilify pull --users a,b,c` \u2014 multiple authors (CSV)\n- `hivemind skilify pull --all-users` \u2014 explicit "no author filter"\n- `hivemind skilify pull --to project|global` \u2014 install location (`<cwd>/.claude/skills/` vs `~/.claude/skills/`)\n- `hivemind skilify pull --dry-run` \u2014 preview without touching disk\n- `hivemind skilify pull --force` \u2014 overwrite local (creates `.bak`)\n- `hivemind skilify pull <skill-name>` \u2014 pull only that one skill (combines with `--user`)\n- `hivemind skilify scope <me|team|org>` \u2014 set sharing scope for new skills\n- `hivemind skilify install <project|global>` \u2014 default install location\n- `hivemind skilify team add|remove|list <name>` \u2014 manage team list\n\nIf the user asks to "pull skills from X", "share skills with the team", or similar, suggest the matching `hivemind skilify` command. Run `hivemind skilify --help` for the full reference.\n\n## Limits\n\nDo NOT delegate to subagents when reading Hivemind memory. If a tool call returns empty after 2 attempts, skip it and move on. Report what you found rather than exhaustively retrying.\n\n## Getting Started\n\nAfter installing the plugin:\n1. Run `/hivemind_login` to authenticate\n2. Run `/hivemind_setup` to enable the memory tools in your openclaw allowlist (one-time, per install)\n3. Start using memory \u2014 ask questions, the agent automatically captures and searches\n\n## Sharing memory\n\nMultiple agents share memory when users are in the same Activeloop organization.\n'.length > 0) {
1748
+ if ('---\nname: hivemind\ndescription: Global team and org memory powered by Activeloop. ALWAYS check BOTH built-in memory AND Hivemind memory when recalling information.\nallowed-tools: hivemind_search, hivemind_read, hivemind_index\n---\n\n# Hivemind Memory\n\nYou have TWO memory sources. ALWAYS check BOTH when the user asks you to recall, remember, or look up ANY information:\n\n1. **Your built-in memory** \u2014 personal per-project notes from the host agent\n2. **Hivemind global memory** \u2014 global memory shared across all sessions, users, and agents in the org, accessed via the tools below\n\n## Memory Structure\n\n```\n/index.md \u2190 START HERE \u2014 table of all sessions\n/summaries/\n <username>/\n <session-id>.md \u2190 AI-generated wiki summary per session\n/sessions/\n <username>/\n <user_org_ws_slug>.jsonl \u2190 raw session data\n```\n\n## How to Search\n\n1. **First**: call `hivemind_index()` \u2014 table of all sessions with dates, projects, descriptions\n2. **If you need details**: call `hivemind_read("/summaries/<username>/<session>.md")`\n3. **If you need raw data**: call `hivemind_read("/sessions/<username>/<file>.jsonl")`\n4. **Keyword search**: call `hivemind_search("keyword")` \u2014 substring search across both summaries and sessions, returns `path:line` hits\n\nDo NOT jump straight to reading raw JSONL files. Always start with `hivemind_index` and summaries.\n\n## Organization Management\n\n- `/hivemind_login` \u2014 sign in via device flow\n- `/hivemind_capture` \u2014 toggle capture on/off (off = no data sent)\n- `/hivemind_whoami` \u2014 show current org and workspace\n- `/hivemind_orgs` \u2014 list organizations\n- `/hivemind_switch_org <name-or-id>` \u2014 switch organization\n- `/hivemind_workspaces` \u2014 list workspaces\n- `/hivemind_switch_workspace <id>` \u2014 switch workspace\n- `/hivemind_version` \u2014 show installed version and check npm for updates\n- `/hivemind_update` \u2014 shows how to install (ask the agent, or run `hivemind update` in your terminal)\n- `/hivemind_autoupdate [on|off]` \u2014 toggle the agent-facing update nudge (on by default: when a newer version is available, the agent is prompted to install it via `exec` if you ask to update)\n\n## Skill Management (skilify)\n\nHivemind also mines reusable Claude skills from agent sessions and stores them in a per-org Deeplake table. Openclaw itself doesn\'t run sessions to mine, but you can pull skills others have already mined for the user. These run in the user\'s terminal (the openclaw plugin does not register them as `/hivemind_*` commands):\n\n- `hivemind skilify` \u2014 show scope/team/install + per-project state\n- `hivemind skilify pull` \u2014 sync skills for the current project from the org table\n- `hivemind skilify pull --user <email>` \u2014 only that author\'s skills\n- `hivemind skilify pull --users a,b,c` \u2014 multiple authors (CSV)\n- `hivemind skilify pull --all-users` \u2014 explicit "no author filter"\n- `hivemind skilify pull --to project|global` \u2014 install location (`<cwd>/.claude/skills/` vs `~/.claude/skills/`)\n- `hivemind skilify pull --dry-run` \u2014 preview without touching disk\n- `hivemind skilify pull --force` \u2014 overwrite local (creates `.bak`)\n- `hivemind skilify pull <skill-name>` \u2014 pull only that one skill (combines with `--user`)\n- `hivemind skilify unpull` \u2014 remove every skill previously installed by pull\n- `hivemind skilify unpull --user <email>` \u2014 remove only that author\'s pulls\n- `hivemind skilify unpull --not-mine` \u2014 remove all pulls except your own\n- `hivemind skilify unpull --dry-run` \u2014 preview without touching disk\n- `hivemind skilify scope <me|team|org>` \u2014 set sharing scope for new skills\n- `hivemind skilify install <project|global>` \u2014 default install location\n- `hivemind skilify team add|remove|list <name>` \u2014 manage team list\n\nIf the user asks to "pull skills from X", "share skills with the team", or similar, suggest the matching `hivemind skilify` command. Run `hivemind skilify --help` for the full reference.\n\n## Limits\n\nDo NOT delegate to subagents when reading Hivemind memory. If a tool call returns empty after 2 attempts, skip it and move on. Report what you found rather than exhaustively retrying.\n\n## Getting Started\n\nAfter installing the plugin:\n1. Run `/hivemind_login` to authenticate\n2. Run `/hivemind_setup` to enable the memory tools in your openclaw allowlist (one-time, per install)\n3. Start using memory \u2014 ask questions, the agent automatically captures and searches\n\n## Sharing memory\n\nMultiple agents share memory when users are in the same Activeloop organization.\n'.length > 0) {
1749
1749
  const setupConfigPromise = loadSetupConfig();
1750
1750
  hook("before_prompt_build", async () => {
1751
1751
  const { detectAllowlistMissing } = await setupConfigPromise;
@@ -1757,7 +1757,7 @@ A newer Hivemind version is available: ${pendingUpdate.current} \u2192 ${pending
1757
1757
  </hivemind-update-available>
1758
1758
  ` : "";
1759
1759
  return {
1760
- prependSystemContext: allowlistNudge + updateNudge + '\n\n<hivemind-skill>\n---\nname: hivemind\ndescription: Global team and org memory powered by Activeloop. ALWAYS check BOTH built-in memory AND Hivemind memory when recalling information.\nallowed-tools: hivemind_search, hivemind_read, hivemind_index\n---\n\n# Hivemind Memory\n\nYou have TWO memory sources. ALWAYS check BOTH when the user asks you to recall, remember, or look up ANY information:\n\n1. **Your built-in memory** \u2014 personal per-project notes from the host agent\n2. **Hivemind global memory** \u2014 global memory shared across all sessions, users, and agents in the org, accessed via the tools below\n\n## Memory Structure\n\n```\n/index.md \u2190 START HERE \u2014 table of all sessions\n/summaries/\n <username>/\n <session-id>.md \u2190 AI-generated wiki summary per session\n/sessions/\n <username>/\n <user_org_ws_slug>.jsonl \u2190 raw session data\n```\n\n## How to Search\n\n1. **First**: call `hivemind_index()` \u2014 table of all sessions with dates, projects, descriptions\n2. **If you need details**: call `hivemind_read("/summaries/<username>/<session>.md")`\n3. **If you need raw data**: call `hivemind_read("/sessions/<username>/<file>.jsonl")`\n4. **Keyword search**: call `hivemind_search("keyword")` \u2014 substring search across both summaries and sessions, returns `path:line` hits\n\nDo NOT jump straight to reading raw JSONL files. Always start with `hivemind_index` and summaries.\n\n## Organization Management\n\n- `/hivemind_login` \u2014 sign in via device flow\n- `/hivemind_capture` \u2014 toggle capture on/off (off = no data sent)\n- `/hivemind_whoami` \u2014 show current org and workspace\n- `/hivemind_orgs` \u2014 list organizations\n- `/hivemind_switch_org <name-or-id>` \u2014 switch organization\n- `/hivemind_workspaces` \u2014 list workspaces\n- `/hivemind_switch_workspace <id>` \u2014 switch workspace\n- `/hivemind_version` \u2014 show installed version and check npm for updates\n- `/hivemind_update` \u2014 shows how to install (ask the agent, or run `hivemind update` in your terminal)\n- `/hivemind_autoupdate [on|off]` \u2014 toggle the agent-facing update nudge (on by default: when a newer version is available, the agent is prompted to install it via `exec` if you ask to update)\n\n## Skill Management (skilify)\n\nHivemind also mines reusable Claude skills from agent sessions and stores them in a per-org Deeplake table. Openclaw itself doesn\'t run sessions to mine, but you can pull skills others have already mined for the user. These run in the user\'s terminal (the openclaw plugin does not register them as `/hivemind_*` commands):\n\n- `hivemind skilify` \u2014 show scope/team/install + per-project state\n- `hivemind skilify pull` \u2014 sync skills for the current project from the org table\n- `hivemind skilify pull --user <email>` \u2014 only that author\'s skills\n- `hivemind skilify pull --users a,b,c` \u2014 multiple authors (CSV)\n- `hivemind skilify pull --all-users` \u2014 explicit "no author filter"\n- `hivemind skilify pull --to project|global` \u2014 install location (`<cwd>/.claude/skills/` vs `~/.claude/skills/`)\n- `hivemind skilify pull --dry-run` \u2014 preview without touching disk\n- `hivemind skilify pull --force` \u2014 overwrite local (creates `.bak`)\n- `hivemind skilify pull <skill-name>` \u2014 pull only that one skill (combines with `--user`)\n- `hivemind skilify scope <me|team|org>` \u2014 set sharing scope for new skills\n- `hivemind skilify install <project|global>` \u2014 default install location\n- `hivemind skilify team add|remove|list <name>` \u2014 manage team list\n\nIf the user asks to "pull skills from X", "share skills with the team", or similar, suggest the matching `hivemind skilify` command. Run `hivemind skilify --help` for the full reference.\n\n## Limits\n\nDo NOT delegate to subagents when reading Hivemind memory. If a tool call returns empty after 2 attempts, skip it and move on. Report what you found rather than exhaustively retrying.\n\n## Getting Started\n\nAfter installing the plugin:\n1. Run `/hivemind_login` to authenticate\n2. Run `/hivemind_setup` to enable the memory tools in your openclaw allowlist (one-time, per install)\n3. Start using memory \u2014 ask questions, the agent automatically captures and searches\n\n## Sharing memory\n\nMultiple agents share memory when users are in the same Activeloop organization.\n\n</hivemind-skill>\n'
1760
+ prependSystemContext: allowlistNudge + updateNudge + '\n\n<hivemind-skill>\n---\nname: hivemind\ndescription: Global team and org memory powered by Activeloop. ALWAYS check BOTH built-in memory AND Hivemind memory when recalling information.\nallowed-tools: hivemind_search, hivemind_read, hivemind_index\n---\n\n# Hivemind Memory\n\nYou have TWO memory sources. ALWAYS check BOTH when the user asks you to recall, remember, or look up ANY information:\n\n1. **Your built-in memory** \u2014 personal per-project notes from the host agent\n2. **Hivemind global memory** \u2014 global memory shared across all sessions, users, and agents in the org, accessed via the tools below\n\n## Memory Structure\n\n```\n/index.md \u2190 START HERE \u2014 table of all sessions\n/summaries/\n <username>/\n <session-id>.md \u2190 AI-generated wiki summary per session\n/sessions/\n <username>/\n <user_org_ws_slug>.jsonl \u2190 raw session data\n```\n\n## How to Search\n\n1. **First**: call `hivemind_index()` \u2014 table of all sessions with dates, projects, descriptions\n2. **If you need details**: call `hivemind_read("/summaries/<username>/<session>.md")`\n3. **If you need raw data**: call `hivemind_read("/sessions/<username>/<file>.jsonl")`\n4. **Keyword search**: call `hivemind_search("keyword")` \u2014 substring search across both summaries and sessions, returns `path:line` hits\n\nDo NOT jump straight to reading raw JSONL files. Always start with `hivemind_index` and summaries.\n\n## Organization Management\n\n- `/hivemind_login` \u2014 sign in via device flow\n- `/hivemind_capture` \u2014 toggle capture on/off (off = no data sent)\n- `/hivemind_whoami` \u2014 show current org and workspace\n- `/hivemind_orgs` \u2014 list organizations\n- `/hivemind_switch_org <name-or-id>` \u2014 switch organization\n- `/hivemind_workspaces` \u2014 list workspaces\n- `/hivemind_switch_workspace <id>` \u2014 switch workspace\n- `/hivemind_version` \u2014 show installed version and check npm for updates\n- `/hivemind_update` \u2014 shows how to install (ask the agent, or run `hivemind update` in your terminal)\n- `/hivemind_autoupdate [on|off]` \u2014 toggle the agent-facing update nudge (on by default: when a newer version is available, the agent is prompted to install it via `exec` if you ask to update)\n\n## Skill Management (skilify)\n\nHivemind also mines reusable Claude skills from agent sessions and stores them in a per-org Deeplake table. Openclaw itself doesn\'t run sessions to mine, but you can pull skills others have already mined for the user. These run in the user\'s terminal (the openclaw plugin does not register them as `/hivemind_*` commands):\n\n- `hivemind skilify` \u2014 show scope/team/install + per-project state\n- `hivemind skilify pull` \u2014 sync skills for the current project from the org table\n- `hivemind skilify pull --user <email>` \u2014 only that author\'s skills\n- `hivemind skilify pull --users a,b,c` \u2014 multiple authors (CSV)\n- `hivemind skilify pull --all-users` \u2014 explicit "no author filter"\n- `hivemind skilify pull --to project|global` \u2014 install location (`<cwd>/.claude/skills/` vs `~/.claude/skills/`)\n- `hivemind skilify pull --dry-run` \u2014 preview without touching disk\n- `hivemind skilify pull --force` \u2014 overwrite local (creates `.bak`)\n- `hivemind skilify pull <skill-name>` \u2014 pull only that one skill (combines with `--user`)\n- `hivemind skilify unpull` \u2014 remove every skill previously installed by pull\n- `hivemind skilify unpull --user <email>` \u2014 remove only that author\'s pulls\n- `hivemind skilify unpull --not-mine` \u2014 remove all pulls except your own\n- `hivemind skilify unpull --dry-run` \u2014 preview without touching disk\n- `hivemind skilify scope <me|team|org>` \u2014 set sharing scope for new skills\n- `hivemind skilify install <project|global>` \u2014 default install location\n- `hivemind skilify team add|remove|list <name>` \u2014 manage team list\n\nIf the user asks to "pull skills from X", "share skills with the team", or similar, suggest the matching `hivemind skilify` command. Run `hivemind skilify --help` for the full reference.\n\n## Limits\n\nDo NOT delegate to subagents when reading Hivemind memory. If a tool call returns empty after 2 attempts, skip it and move on. Report what you found rather than exhaustively retrying.\n\n## Getting Started\n\nAfter installing the plugin:\n1. Run `/hivemind_login` to authenticate\n2. Run `/hivemind_setup` to enable the memory tools in your openclaw allowlist (one-time, per install)\n3. Start using memory \u2014 ask questions, the agent automatically captures and searches\n\n## Sharing memory\n\nMultiple agents share memory when users are in the same Activeloop organization.\n\n</hivemind-skill>\n'
1761
1761
  };
1762
1762
  });
1763
1763
  }
@@ -52,5 +52,5 @@
52
52
  }
53
53
  }
54
54
  },
55
- "version": "0.7.13"
55
+ "version": "0.7.15"
56
56
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hivemind",
3
- "version": "0.7.13",
3
+ "version": "0.7.15",
4
4
  "type": "module",
5
5
  "description": "Hivemind — cloud-backed persistent shared memory for AI agents, powered by DeepLake",
6
6
  "license": "Apache-2.0",
@@ -58,6 +58,10 @@ Hivemind also mines reusable Claude skills from agent sessions and stores them i
58
58
  - `hivemind skilify pull --dry-run` — preview without touching disk
59
59
  - `hivemind skilify pull --force` — overwrite local (creates `.bak`)
60
60
  - `hivemind skilify pull <skill-name>` — pull only that one skill (combines with `--user`)
61
+ - `hivemind skilify unpull` — remove every skill previously installed by pull
62
+ - `hivemind skilify unpull --user <email>` — remove only that author's pulls
63
+ - `hivemind skilify unpull --not-mine` — remove all pulls except your own
64
+ - `hivemind skilify unpull --dry-run` — preview without touching disk
61
65
  - `hivemind skilify scope <me|team|org>` — set sharing scope for new skills
62
66
  - `hivemind skilify install <project|global>` — default install location
63
67
  - `hivemind skilify team add|remove|list <name>` — manage team list
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeplake/hivemind",
3
- "version": "0.7.13",
3
+ "version": "0.7.15",
4
4
  "description": "Cloud-backed persistent shared memory for AI agents powered by Deeplake",
5
5
  "type": "module",
6
6
  "repository": {
@@ -666,6 +666,10 @@ SKILLS (skilify) — mine + share reusable skills across the org. Run these in a
666
666
  - hivemind skilify pull --dry-run — preview only
667
667
  - hivemind skilify pull --force — overwrite local (creates .bak)
668
668
  - hivemind skilify pull <skill-name> — pull only that skill (combines with --user)
669
+ - hivemind skilify unpull — remove every skill previously installed by pull
670
+ - hivemind skilify unpull --user <email> — remove only that author's pulls
671
+ - hivemind skilify unpull --not-mine — remove all pulls except your own
672
+ - hivemind skilify unpull --dry-run — preview without touching disk
669
673
  - hivemind skilify scope <me|team|org> — sharing scope for new skills
670
674
  - hivemind skilify install <project|global> — default install location
671
675
  - hivemind skilify team add|remove|list <name> — manage team list`;