agenthub-multiagent-mcp 1.44.0 → 1.46.0

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.
@@ -0,0 +1,211 @@
1
+ // Installs an opt-in skill pack (add-skill-packs) into ~/.claude/skills/.
2
+ //
3
+ // Unlike the npm-bundled team skills (skillInstaller.ts), packs are NOT
4
+ // force-installed on startup — only on an explicit skill_pack_install call. The
5
+ // pack arrives as a gzip-tar archive from the AgentHub server; we extract it in
6
+ // memory and apply the same hash-safe semantics per file:
7
+ // - missing locally -> install
8
+ // - matches the pack's current hash -> no-op (skipped_current)
9
+ // - matches a hash we previously installed -> update (our prior version)
10
+ // - matches nothing (user edited it) -> skip, unless force=true
11
+ //
12
+ // "What we previously installed" is tracked in a per-pack record under
13
+ // ~/.claude/skills/.packs/<pack>.json so version upgrades don't require force
14
+ // while still never clobbering a file the user hand-edited.
15
+ import { createHash } from "node:crypto";
16
+ import { gunzipSync } from "node:zlib";
17
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
18
+ import { homedir } from "node:os";
19
+ import { dirname, join } from "node:path";
20
+ function sha256(buf) {
21
+ return createHash("sha256").update(buf).digest("hex");
22
+ }
23
+ // parseUstar decodes a (already gunzipped) USTAR tar buffer into its regular
24
+ // files. The AgentHub server writes plain USTAR headers (Go archive/tar) and
25
+ // every pack path is < 100 bytes, so no GNU/PAX long-name records appear. If
26
+ // one ever does, fail loud rather than silently mis-parse.
27
+ function parseUstar(buf) {
28
+ const entries = [];
29
+ let off = 0;
30
+ while (off + 512 <= buf.length) {
31
+ const block = buf.subarray(off, off + 512);
32
+ // Two consecutive zero blocks mark the end; a single zero block is enough
33
+ // to stop in practice.
34
+ if (block.every((b) => b === 0))
35
+ break;
36
+ const name = readCString(block, 0, 100);
37
+ const sizeStr = readCString(block, 124, 12).trim();
38
+ const size = sizeStr === "" ? 0 : parseInt(sizeStr, 8);
39
+ if (Number.isNaN(size) || size < 0) {
40
+ throw new Error(`skill pack: corrupt tar size field for "${name}"`);
41
+ }
42
+ const typeflag = String.fromCharCode(block[156]);
43
+ if (typeflag === "x" || typeflag === "g" || typeflag === "L" || typeflag === "K") {
44
+ throw new Error(`skill pack: archive uses extended (PAX/GNU) headers which are not supported`);
45
+ }
46
+ off += 512;
47
+ const content = Buffer.from(buf.subarray(off, off + size));
48
+ off += Math.ceil(size / 512) * 512;
49
+ // typeflag "0" or NUL = regular file; "5" = directory; others ignored.
50
+ if (typeflag === "0" || typeflag === "\0" || typeflag === "") {
51
+ entries.push({ name, content });
52
+ }
53
+ }
54
+ return entries;
55
+ }
56
+ function readCString(block, start, len) {
57
+ let end = start;
58
+ const limit = start + len;
59
+ while (end < limit && block[end] !== 0)
60
+ end++;
61
+ return block.toString("utf8", start, end);
62
+ }
63
+ // isSafePackPath rejects anything that could escape ~/.claude/. Pack files must
64
+ // live under the "skills/" install root.
65
+ function isSafePackPath(p) {
66
+ if (p.startsWith("/") || p.includes("..") || p.includes("\0"))
67
+ return false;
68
+ if (p.includes("\\"))
69
+ return false; // archive paths are forward-slash only
70
+ return p.startsWith("skills/");
71
+ }
72
+ function recordPath(claudeDir, pack) {
73
+ return join(claudeDir, "skills", ".packs", `${pack}.json`);
74
+ }
75
+ async function readRecord(claudeDir, pack) {
76
+ try {
77
+ const raw = await readFile(recordPath(claudeDir, pack), "utf8");
78
+ const parsed = JSON.parse(raw);
79
+ if (parsed && typeof parsed === "object" && parsed.files)
80
+ return parsed;
81
+ }
82
+ catch {
83
+ // missing/corrupt → treat as no prior install
84
+ }
85
+ return { version: "", files: {} };
86
+ }
87
+ function emptyCounts() {
88
+ return {
89
+ installed: 0,
90
+ updated: 0,
91
+ skipped_current: 0,
92
+ skipped_user_modified: 0,
93
+ overwritten_force: 0,
94
+ would_install: 0,
95
+ would_update: 0,
96
+ would_overwrite: 0,
97
+ error: 0,
98
+ };
99
+ }
100
+ /**
101
+ * installPackFromArchive applies a downloaded pack to ~/.claude/skills/.
102
+ * Hash-safe and idempotent. Returns a per-file action report.
103
+ */
104
+ export async function installPackFromArchive(opts) {
105
+ const force = opts.force ?? false;
106
+ const dryRun = opts.dryRun ?? false;
107
+ const home = opts.targetHome ?? homedir();
108
+ const claudeDir = join(home, ".claude");
109
+ const report = {
110
+ pack: opts.pack,
111
+ version: opts.version,
112
+ actions: [],
113
+ counts: emptyCounts(),
114
+ };
115
+ const push = (a) => {
116
+ report.actions.push(a);
117
+ report.counts[a.action]++;
118
+ };
119
+ // Decompress + index the archive by path.
120
+ let files;
121
+ try {
122
+ const tar = gunzipSync(opts.archive);
123
+ const entries = parseUstar(tar);
124
+ files = new Map(entries.map((e) => [e.name, e.content]));
125
+ }
126
+ catch (err) {
127
+ throw new Error(`skill pack ${opts.pack}: ${err.message}`);
128
+ }
129
+ const prior = await readRecord(claudeDir, opts.pack);
130
+ const newRecord = { version: opts.version, files: {} };
131
+ for (const mf of opts.manifest) {
132
+ const path = mf.path;
133
+ if (!isSafePackPath(path)) {
134
+ push({ path, action: "error", message: "unsafe path rejected" });
135
+ continue;
136
+ }
137
+ const content = files.get(path);
138
+ if (!content) {
139
+ push({ path, action: "error", message: "file missing from archive" });
140
+ continue;
141
+ }
142
+ const bundleHash = sha256(content);
143
+ if (bundleHash !== mf.sha256) {
144
+ push({ path, action: "error", message: "archive hash mismatch (integrity)" });
145
+ continue;
146
+ }
147
+ const priorHashes = prior.files[path] ?? [];
148
+ // Record the current hash for next time regardless of the action taken.
149
+ newRecord.files[path] = dedupeAppend(priorHashes, bundleHash);
150
+ const target = join(claudeDir, path);
151
+ let userBuf = null;
152
+ try {
153
+ userBuf = await readFile(target);
154
+ }
155
+ catch {
156
+ userBuf = null;
157
+ }
158
+ if (userBuf === null) {
159
+ if (dryRun) {
160
+ push({ path, action: "would_install" });
161
+ }
162
+ else {
163
+ await mkdir(dirname(target), { recursive: true });
164
+ await writeFile(target, content);
165
+ push({ path, action: "installed" });
166
+ }
167
+ continue;
168
+ }
169
+ const userHash = sha256(userBuf);
170
+ if (userHash === bundleHash) {
171
+ push({ path, action: "skipped_current" });
172
+ continue;
173
+ }
174
+ if (priorHashes.includes(userHash)) {
175
+ if (dryRun) {
176
+ push({ path, action: "would_update" });
177
+ }
178
+ else {
179
+ await writeFile(target, content);
180
+ push({ path, action: "updated" });
181
+ }
182
+ continue;
183
+ }
184
+ if (force) {
185
+ if (dryRun) {
186
+ push({ path, action: "would_overwrite" });
187
+ }
188
+ else {
189
+ await writeFile(target, content);
190
+ push({ path, action: "overwritten_force" });
191
+ }
192
+ continue;
193
+ }
194
+ push({ path, action: "skipped_user_modified" });
195
+ }
196
+ if (!dryRun) {
197
+ await persistRecord(claudeDir, opts.pack, newRecord);
198
+ }
199
+ return report;
200
+ }
201
+ function dedupeAppend(hashes, hash) {
202
+ if (hashes.includes(hash))
203
+ return hashes;
204
+ return [...hashes, hash];
205
+ }
206
+ async function persistRecord(claudeDir, pack, rec) {
207
+ const p = recordPath(claudeDir, pack);
208
+ await mkdir(dirname(p), { recursive: true });
209
+ await writeFile(p, JSON.stringify(rec, null, 2));
210
+ }
211
+ //# sourceMappingURL=skillPackInstaller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillPackInstaller.js","sourceRoot":"","sources":["../src/skillPackInstaller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=skillPackInstaller.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillPackInstaller.test.d.ts","sourceRoot":"","sources":["../src/skillPackInstaller.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,167 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { mkdtempSync, rmSync, readFileSync, writeFileSync, existsSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { createHash } from "node:crypto";
6
+ import { gzipSync } from "node:zlib";
7
+ import { installPackFromArchive } from "./skillPackInstaller.js";
8
+ function sha256(s) {
9
+ return createHash("sha256").update(s).digest("hex");
10
+ }
11
+ // Build a plain USTAR header for one regular file.
12
+ function tarHeader(name, size) {
13
+ const h = Buffer.alloc(512);
14
+ h.write(name, 0, "utf8");
15
+ h.write("0000644\0", 100);
16
+ h.write("0000000\0", 108);
17
+ h.write("0000000\0", 116);
18
+ h.write(size.toString(8).padStart(11, "0") + "\0", 124);
19
+ h.write("00000000000\0", 136); // mtime
20
+ h.write("ustar\0", 257);
21
+ h.write("00", 263);
22
+ h[156] = "0".charCodeAt(0); // typeflag = regular file
23
+ // checksum: spaces in the field, sum bytes, write 6 octal digits + NUL + space
24
+ for (let i = 148; i < 156; i++)
25
+ h[i] = 0x20;
26
+ let sum = 0;
27
+ for (let i = 0; i < 512; i++)
28
+ sum += h[i];
29
+ h.write(sum.toString(8).padStart(6, "0") + "\0 ", 148);
30
+ return h;
31
+ }
32
+ function makeTarGz(files) {
33
+ const blocks = [];
34
+ for (const f of files) {
35
+ const content = Buffer.from(f.content, "utf8");
36
+ blocks.push(tarHeader(f.name, content.length));
37
+ const padded = Buffer.alloc(Math.ceil(content.length / 512) * 512);
38
+ content.copy(padded);
39
+ blocks.push(padded);
40
+ }
41
+ blocks.push(Buffer.alloc(512));
42
+ blocks.push(Buffer.alloc(512));
43
+ return gzipSync(Buffer.concat(blocks));
44
+ }
45
+ function manifestFor(files) {
46
+ return files.map((f) => ({
47
+ path: f.name,
48
+ sha256: sha256(f.content),
49
+ size: Buffer.byteLength(f.content),
50
+ }));
51
+ }
52
+ describe("installPackFromArchive", () => {
53
+ let home;
54
+ const PATH_A = "skills/demo/SKILL.md";
55
+ const PATH_B = "skills/demo/references/r.md";
56
+ beforeEach(() => {
57
+ home = mkdtempSync(join(tmpdir(), "packinstall-"));
58
+ });
59
+ afterEach(() => {
60
+ rmSync(home, { recursive: true, force: true });
61
+ });
62
+ function targetOf(path) {
63
+ return join(home, ".claude", path);
64
+ }
65
+ it("installs all files on a pristine machine", async () => {
66
+ const files = [
67
+ { name: PATH_A, content: "# A v1" },
68
+ { name: PATH_B, content: "ref v1" },
69
+ ];
70
+ const report = await installPackFromArchive({
71
+ pack: "demo",
72
+ version: "1.0.0",
73
+ manifest: manifestFor(files),
74
+ archive: makeTarGz(files),
75
+ targetHome: home,
76
+ });
77
+ expect(report.counts.installed).toBe(2);
78
+ expect(readFileSync(targetOf(PATH_A), "utf8")).toBe("# A v1");
79
+ expect(readFileSync(targetOf(PATH_B), "utf8")).toBe("ref v1");
80
+ // Record written.
81
+ expect(existsSync(join(home, ".claude", "skills", ".packs", "demo.json"))).toBe(true);
82
+ });
83
+ it("is a no-op on re-install of the same version", async () => {
84
+ const files = [{ name: PATH_A, content: "# A v1" }];
85
+ const opts = {
86
+ pack: "demo",
87
+ version: "1.0.0",
88
+ manifest: manifestFor(files),
89
+ archive: makeTarGz(files),
90
+ targetHome: home,
91
+ };
92
+ await installPackFromArchive(opts);
93
+ const second = await installPackFromArchive(opts);
94
+ expect(second.counts.skipped_current).toBe(1);
95
+ expect(second.counts.installed).toBe(0);
96
+ });
97
+ it("updates a file we previously installed when the version changes", async () => {
98
+ const v1 = [{ name: PATH_A, content: "# A v1" }];
99
+ await installPackFromArchive({
100
+ pack: "demo", version: "1.0.0",
101
+ manifest: manifestFor(v1), archive: makeTarGz(v1), targetHome: home,
102
+ });
103
+ const v2 = [{ name: PATH_A, content: "# A v2" }];
104
+ const report = await installPackFromArchive({
105
+ pack: "demo", version: "2.0.0",
106
+ manifest: manifestFor(v2), archive: makeTarGz(v2), targetHome: home,
107
+ });
108
+ expect(report.counts.updated).toBe(1);
109
+ expect(readFileSync(targetOf(PATH_A), "utf8")).toBe("# A v2");
110
+ });
111
+ it("never clobbers a user-modified file unless forced", async () => {
112
+ const v1 = [{ name: PATH_A, content: "# A v1" }];
113
+ await installPackFromArchive({
114
+ pack: "demo", version: "1.0.0",
115
+ manifest: manifestFor(v1), archive: makeTarGz(v1), targetHome: home,
116
+ });
117
+ // User edits the file.
118
+ writeFileSync(targetOf(PATH_A), "# A user-edited");
119
+ const v2 = [{ name: PATH_A, content: "# A v2" }];
120
+ const skip = await installPackFromArchive({
121
+ pack: "demo", version: "2.0.0",
122
+ manifest: manifestFor(v2), archive: makeTarGz(v2), targetHome: home,
123
+ });
124
+ expect(skip.counts.skipped_user_modified).toBe(1);
125
+ expect(readFileSync(targetOf(PATH_A), "utf8")).toBe("# A user-edited");
126
+ const forced = await installPackFromArchive({
127
+ pack: "demo", version: "2.0.0", force: true,
128
+ manifest: manifestFor(v2), archive: makeTarGz(v2), targetHome: home,
129
+ });
130
+ expect(forced.counts.overwritten_force).toBe(1);
131
+ expect(readFileSync(targetOf(PATH_A), "utf8")).toBe("# A v2");
132
+ });
133
+ it("rejects a file whose archive bytes don't match the manifest hash", async () => {
134
+ const files = [{ name: PATH_A, content: "real content" }];
135
+ const manifest = manifestFor(files);
136
+ manifest[0].sha256 = sha256("tampered"); // mismatch
137
+ const report = await installPackFromArchive({
138
+ pack: "demo", version: "1.0.0",
139
+ manifest, archive: makeTarGz(files), targetHome: home,
140
+ });
141
+ expect(report.counts.error).toBe(1);
142
+ expect(report.actions[0].message).toContain("integrity");
143
+ expect(existsSync(targetOf(PATH_A))).toBe(false);
144
+ });
145
+ it("rejects an unsafe (path-traversal) manifest entry", async () => {
146
+ const files = [{ name: PATH_A, content: "ok" }];
147
+ const manifest = manifestFor(files);
148
+ manifest.push({ path: "../../evil.sh", sha256: sha256("x"), size: 1 });
149
+ const report = await installPackFromArchive({
150
+ pack: "demo", version: "1.0.0",
151
+ manifest, archive: makeTarGz(files), targetHome: home,
152
+ });
153
+ expect(report.counts.error).toBe(1);
154
+ expect(report.actions.find((a) => a.path === "../../evil.sh")?.message).toContain("unsafe");
155
+ });
156
+ it("dry-run writes nothing", async () => {
157
+ const files = [{ name: PATH_A, content: "# A" }];
158
+ const report = await installPackFromArchive({
159
+ pack: "demo", version: "1.0.0", dryRun: true,
160
+ manifest: manifestFor(files), archive: makeTarGz(files), targetHome: home,
161
+ });
162
+ expect(report.counts.would_install).toBe(1);
163
+ expect(existsSync(targetOf(PATH_A))).toBe(false);
164
+ expect(existsSync(join(home, ".claude", "skills", ".packs", "demo.json"))).toBe(false);
165
+ });
166
+ });
167
+ //# sourceMappingURL=skillPackInstaller.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillPackInstaller.test.js","sourceRoot":"","sources":["../src/skillPackInstaller.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAqD,MAAM,cAAc,CAAC;AACrG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AA8B/D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,MAAM,CAAC;IAC5B,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,MAAM,WAAW,CAAC;CACnC;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAwJD,wBAAgB,aAAa,IAAI,IAAI,EAAE,CA0+EtC;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAg1ElB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAqD,MAAM,cAAc,CAAC;AACrG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AA+B/D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,iBAAiB,EAAE,MAAM,MAAM,CAAC;IAChC,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,MAAM,CAAC;IAC5B,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,MAAM,WAAW,CAAC;CACnC;AAED,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAwJD,wBAAgB,aAAa,IAAI,IAAI,EAAE,CA4hFtC;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CA26ElB"}
@@ -8,6 +8,7 @@ import * as brainCapture from "../hooks/brainCapture.js";
8
8
  import { getBrain } from "../brain/backend.js";
9
9
  import open from "open";
10
10
  import { registerOpenSpecVizTools, handleOpenSpecVizToolCall } from "./openspec-viz/index.js";
11
+ import { installPackFromArchive } from "../skillPackInstaller.js";
11
12
  import { getServerTime, useTimeInjection } from "../lib/server-time.js";
12
13
  import { ENG_IC_CHARTER } from "../employee.js";
13
14
  /**
@@ -735,6 +736,28 @@ export function registerTools() {
735
736
  required: ["to_user", "text"],
736
737
  },
737
738
  },
739
+ {
740
+ name: "slack_upload_file",
741
+ description: "Upload a file you produced (PDF, CSV, docx, deck, log, etc.) to a Slack channel or a person's DM (employees only, governed). Generate the file locally first, then call this with the target and the file's local path. target is a Slack channel id (C0123ABC), a #channel-name (the bot must be a member), or a user id (U0123ABC) to deliver it as a DM. For delivering into your current task's thread, use deliver_file instead.",
742
+ inputSchema: {
743
+ type: "object",
744
+ properties: {
745
+ target: {
746
+ type: "string",
747
+ description: "Where to upload: a channel id (C0123ABC), a #channel-name, or a user id (U0123ABC) for a DM",
748
+ },
749
+ file_path: {
750
+ type: "string",
751
+ description: "Absolute local path to the file to upload",
752
+ },
753
+ comment: {
754
+ type: "string",
755
+ description: "Optional short message shown with the file",
756
+ },
757
+ },
758
+ required: ["target", "file_path"],
759
+ },
760
+ },
738
761
  // Discovery tools
739
762
  {
740
763
  name: "list_agents",
@@ -843,6 +866,29 @@ export function registerTools() {
843
866
  },
844
867
  },
845
868
  },
869
+ {
870
+ name: "skill_pack_list",
871
+ description: "List the opt-in skill packs the AgentHub server offers (e.g. cybersecurity, brag) with this agent's install status and whether install is permitted. Skill packs are NOT installed automatically — use skill_pack_install to opt in. Destructive packs require the matching skill-pack:<name> capability.",
872
+ inputSchema: {
873
+ type: "object",
874
+ properties: {},
875
+ },
876
+ },
877
+ {
878
+ name: "skill_pack_install",
879
+ description: "Install an opt-in skill pack into ~/.claude/skills/. Hash-safe (never overwrites files you've edited unless force=true). Destructive packs (e.g. cybersecurity, which ships offensive PoC scripts) require the skill-pack:<name> capability attached to this agent. Returns a per-file install report.",
880
+ inputSchema: {
881
+ type: "object",
882
+ properties: {
883
+ pack: { type: "string", description: "Pack name, e.g. \"cybersecurity\" or \"brag\" (see skill_pack_list)" },
884
+ force: {
885
+ type: "boolean",
886
+ description: "Overwrite locally-modified pack files (default false)",
887
+ },
888
+ },
889
+ required: ["pack"],
890
+ },
891
+ },
846
892
  {
847
893
  name: "api_search",
848
894
  description: "Search the API knowledge graph — endpoints (REST/MCP/GraphQL/gRPC) extracted from indexed repos across the org. Use this BEFORE building a new endpoint to check if it already exists or to match a sibling project's contract. Org-wide read.",
@@ -3257,6 +3303,12 @@ export async function handleToolCall(name, args, client, context) {
3257
3303
  const result = await client.employeeSlackDM(args.to_user, args.text);
3258
3304
  return wrapWithPendingItems(client, agentId, result, context);
3259
3305
  }
3306
+ case "slack_upload_file": {
3307
+ if (!agentId)
3308
+ throw new Error("Not registered. Call agent_register first.");
3309
+ const result = await client.uploadSlackFile(args.target, args.file_path, args.comment);
3310
+ return wrapWithPendingItems(client, agentId, result, context);
3311
+ }
3260
3312
  // Discovery
3261
3313
  case "list_agents": {
3262
3314
  const result = await client.listAgents(args.status);
@@ -3800,6 +3852,82 @@ export async function handleToolCall(name, args, client, context) {
3800
3852
  message: `Filed bug ${result.key} (${result.severity}/${result.bug_state}): ${args.title}`,
3801
3853
  };
3802
3854
  }
3855
+ case "skill_pack_list": {
3856
+ try {
3857
+ const { packs } = await client.listSkillPacks();
3858
+ const lines = packs.map((p) => {
3859
+ const gate = p.requires_capability
3860
+ ? p.permitted
3861
+ ? "permitted"
3862
+ : `needs capability ${p.capability}`
3863
+ : "ungated";
3864
+ return `${p.name} v${p.version} (${p.tier}, ${p.skill_count} skills) — ${gate}`;
3865
+ });
3866
+ return {
3867
+ ok: true,
3868
+ packs,
3869
+ message: packs.length === 0
3870
+ ? "No skill packs available."
3871
+ : `Available skill packs:\n${lines.join("\n")}`,
3872
+ };
3873
+ }
3874
+ catch (err) {
3875
+ const e = err;
3876
+ // Most likely the master switch is off (503 skill_packs_disabled).
3877
+ return {
3878
+ ok: false,
3879
+ error: "skill_packs_unavailable",
3880
+ message: e.message,
3881
+ };
3882
+ }
3883
+ }
3884
+ case "skill_pack_install": {
3885
+ const pack = args.pack?.trim();
3886
+ if (!pack) {
3887
+ return { ok: false, error: "invalid_args", message: "pack is required" };
3888
+ }
3889
+ const force = args.force === true;
3890
+ // Download the archive first — the server enforces the master switch and
3891
+ // (for destructive packs) the capability gate here, returning a coded
3892
+ // error before sending any bytes.
3893
+ let archive;
3894
+ try {
3895
+ archive = await client.fetchSkillPackArchive(pack);
3896
+ }
3897
+ catch (err) {
3898
+ const e = err;
3899
+ if (e.code === "skill_pack_capability_required") {
3900
+ return {
3901
+ ok: false,
3902
+ error: e.code,
3903
+ message: `Installing "${pack}" requires the skill-pack:${pack} capability. Request it via capability_request, then retry.`,
3904
+ };
3905
+ }
3906
+ if (e.code === "skill_packs_disabled" || e.code === "skill_pack_not_found") {
3907
+ return { ok: false, error: e.code, message: e.message };
3908
+ }
3909
+ throw err;
3910
+ }
3911
+ const manifest = await client.getSkillPackManifest(pack);
3912
+ const report = await installPackFromArchive({
3913
+ pack,
3914
+ version: manifest.version,
3915
+ manifest: manifest.files,
3916
+ archive,
3917
+ force,
3918
+ });
3919
+ const c = report.counts;
3920
+ const summary = `${pack} v${manifest.version}: ${c.installed} installed, ${c.updated} updated, ` +
3921
+ `${c.skipped_current} unchanged, ${c.skipped_user_modified} skipped (user-modified), ` +
3922
+ `${c.overwritten_force} overwritten, ${c.error} errors.`;
3923
+ return {
3924
+ ok: c.error === 0,
3925
+ pack,
3926
+ version: manifest.version,
3927
+ counts: c,
3928
+ message: summary,
3929
+ };
3930
+ }
3803
3931
  case "ticket_get": {
3804
3932
  const workingDir = context.getWorkingDir();
3805
3933
  const agentState = state.loadState(workingDir);