@drunkcoding/agents-and-skills 0.0.35 → 0.0.37

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.
@@ -12,7 +12,7 @@
12
12
  "name": "tech-graph",
13
13
  "source": "./plugins/tech-graph",
14
14
  "description": "6-step wizard for technical diagrams (SVG/PNG) via fireworks-tech-graph",
15
- "version": "0.0.35",
15
+ "version": "0.0.37",
16
16
  "category": "diagram",
17
17
  "keywords": [
18
18
  "diagram",
@@ -26,7 +26,7 @@
26
26
  "name": "html-effectiveness",
27
27
  "source": "./plugins/html-effectiveness",
28
28
  "description": "Generate self-contained interactive HTML reports from 20 upstream templates via a conversational agent.",
29
- "version": "0.0.35",
29
+ "version": "0.0.37",
30
30
  "category": "reports",
31
31
  "keywords": [
32
32
  "html",
@@ -41,7 +41,7 @@
41
41
  "name": "plugin-validator",
42
42
  "source": "./plugins/plugin-validator",
43
43
  "description": "Orchestrated validator for Claude Code plugins — validates skills, agents, commands, and hooks across every plugin under plugins/**.",
44
- "version": "0.0.35",
44
+ "version": "0.0.37",
45
45
  "category": "tooling",
46
46
  "keywords": [
47
47
  "validation",
@@ -57,7 +57,7 @@
57
57
  "name": "team-share",
58
58
  "source": "./plugins/team-share",
59
59
  "description": "Onboard your team with an interactive setup menu: install CodeGraph, build the Understand-Anything knowledge graph, and share Claude Code settings — run any combination, all idempotent.",
60
- "version": "0.0.35",
60
+ "version": "0.0.37",
61
61
  "category": "tooling",
62
62
  "keywords": [
63
63
  "onboarding",
@@ -73,7 +73,7 @@
73
73
  "name": "multica-tool",
74
74
  "source": "./plugins/multica-tool",
75
75
  "description": "Export, import, and sync Multica skills, agents, and squads between workspaces.",
76
- "version": "0.0.35",
76
+ "version": "0.0.37",
77
77
  "category": "workflow",
78
78
  "keywords": [
79
79
  "multica",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drunkcoding/agents-and-skills",
3
- "version": "0.0.35",
3
+ "version": "0.0.37",
4
4
  "description": "Personal collection of Claude Code skills and agents, installable via `npx skills`.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "html-effectiveness",
3
3
  "displayName": "HTML Effectiveness Reports",
4
- "version": "0.0.35",
4
+ "version": "0.0.37",
5
5
  "description": "Generate self-contained interactive HTML reports from 20 upstream templates via a conversational agent.",
6
6
  "author": {
7
7
  "name": "Steven Hoang"
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "multica-tool",
3
- "version": "0.0.35",
3
+ "version": "0.0.37",
4
4
  "description": "Export, import, and sync Multica skills, agents, and squads between workspaces via the multica CLI."
5
5
  }
@@ -8,17 +8,17 @@ export function slugify(name) {
8
8
  return s || "unnamed";
9
9
  }
10
10
 
11
- export function realExec(args) {
12
- return spawnSync("multica", args, { encoding: "utf8" });
11
+ export function realExec(args, opts = {}) {
12
+ return spawnSync("multica", args, { encoding: "utf8", ...opts });
13
13
  }
14
14
 
15
15
  export function makeCli(exec, { workspaceId } = {}) {
16
- function run(args) {
16
+ function run(args, opts) {
17
17
  let full = args;
18
18
  if (workspaceId) {
19
19
  full = [...args, "--workspace-id", workspaceId];
20
20
  }
21
- const res = exec(full);
21
+ const res = exec(full, opts);
22
22
  if (res.status !== 0) throw new Error(res.stderr?.trim() || `multica exited ${res.status}`);
23
23
  return res.stdout;
24
24
  }
@@ -78,10 +78,18 @@ export function getAgent(cli, id) {
78
78
  runtimeId: a.runtime_id,
79
79
  hasCustomEnv: a.has_custom_env,
80
80
  mcpConfig: a.mcp_config,
81
+ mcpConfigRedacted: !!a.mcp_config_redacted,
81
82
  skills: (a.skills ?? []).map((sk) => ({ id: sk.id, name: sk.name })),
82
83
  };
83
84
  }
84
85
 
86
+ // Custom env is never included in `agent get` — only `has_custom_env`/`custom_env_key_count`.
87
+ // Reading actual values requires this dedicated, audited, owner/admin-only command.
88
+ export function getAgentCustomEnv(cli, id) {
89
+ const r = cli.json(["agent", "env", "get", id]);
90
+ return r.custom_env ?? {};
91
+ }
92
+
85
93
  export function getSquad(cli, id) {
86
94
  const s = cli.json(["squad", "get", id]);
87
95
  return { id: s.id, name: s.name, description: s.description, instructions: s.instructions, leaderId: s.leader_id };
@@ -1,15 +1,32 @@
1
1
  import * as nodeFs from "node:fs";
2
2
  import { dirname } from "node:path";
3
- import { slugify, getSkill, getAgent, getSquad, getSquadMembers, listRuntimes, makeCli, realExec, requireAuth, resolveWorkspaceId } from "./lib.mjs";
3
+ import { slugify, getSkill, getAgent, getAgentCustomEnv, getSquad, getSquadMembers, listRuntimes, makeCli, realExec, requireAuth, resolveWorkspaceId } from "./lib.mjs";
4
4
 
5
5
  const nonEmpty = (v) => v && typeof v === "object" && Object.keys(v).length > 0;
6
6
 
7
7
  export function redactAgent(a) {
8
- // a is a normalized (camelCase) agent from getAgent.
9
- const { id, hasCustomEnv, mcpConfig, skills, runtimeId, ...rest } = a;
10
- const hadSecrets = !!hasCustomEnv || nonEmpty(mcpConfig);
8
+ // a is a normalized (camelCase) agent from getAgent, with `customEnv`/
9
+ // `customEnvFetchFailed` attached by the caller (collectAgent) getAgent
10
+ // itself never fetches custom_env, since it requires a separate audited call.
11
+ const { id, hasCustomEnv, mcpConfigRedacted, customEnvFetchFailed, mcpConfig, customEnv, skills, runtimeId, ...rest } = a;
12
+ const mcpUsable = !mcpConfigRedacted && nonEmpty(mcpConfig);
13
+ const envUsable = !customEnvFetchFailed && nonEmpty(customEnv);
14
+ // mcpConfigRedacted / customEnvFetchFailed alone still flag hadSecrets even
15
+ // when unusable — the user should know something was present at the source
16
+ // but couldn't be captured, not just silently see an empty bundle.
17
+ const hadSecrets = mcpUsable || envUsable || !!mcpConfigRedacted || !!customEnvFetchFailed;
11
18
  return {
12
- record: { ...rest, sourceRuntimeId: runtimeId, skillNames: [], hadSecrets },
19
+ // sourceId lets import-time mention rewriting map stale `mention://agent/<id>`
20
+ // links (in this or another agent's/squad's instructions) to the new id.
21
+ record: {
22
+ ...rest,
23
+ sourceId: id,
24
+ sourceRuntimeId: runtimeId,
25
+ skillNames: [],
26
+ mcpConfig: mcpUsable ? mcpConfig : null,
27
+ customEnv: envUsable ? customEnv : null,
28
+ hadSecrets,
29
+ },
13
30
  hadSecrets,
14
31
  };
15
32
  }
@@ -24,7 +41,7 @@ export function buildManifest({ scope, sourceWorkspaceId, skills, agents, squad
24
41
  scope,
25
42
  sourceWorkspaceId,
26
43
  skills: [...seenSkills.values()].map((s) => ({ name: s.name, dir: `skills/${slugify(s.name)}`, sourceId: s.sourceId })),
27
- agents: [...seenAgents.values()].map((a) => ({ name: a.name, file: `agents/${slugify(a.name)}.json`, sourceRuntimeId: a.sourceRuntimeId, sourceRuntimeProvider: a.sourceRuntimeProvider ?? null, skillNames: a.skillNames, hadSecrets: !!a.hadSecrets })),
44
+ agents: [...seenAgents.values()].map((a) => ({ name: a.name, file: `agents/${slugify(a.name)}.json`, sourceId: a.sourceId, sourceRuntimeId: a.sourceRuntimeId, sourceRuntimeProvider: a.sourceRuntimeProvider ?? null, skillNames: a.skillNames, hadSecrets: !!a.hadSecrets })),
28
45
  squads: squad ? [{ name: squad.name, file: `squads/${slugify(squad.name)}.json`, description: squad.description ?? "", instructions: squad.instructions ?? "", leaderName: squad.leaderName, members: squad.members }] : [],
29
46
  };
30
47
  }
@@ -41,6 +58,15 @@ function collectAgent(cli, id, agentsById, skills, providerById) {
41
58
  if (agentsById.has(id)) return agentsById.get(id);
42
59
  const a = getAgent(cli, id);
43
60
  a.sourceRuntimeProvider = providerById.get(a.runtimeId) ?? null;
61
+ a.customEnv = {};
62
+ a.customEnvFetchFailed = false;
63
+ if (a.hasCustomEnv) {
64
+ try {
65
+ a.customEnv = getAgentCustomEnv(cli, id);
66
+ } catch {
67
+ a.customEnvFetchFailed = true; // e.g. insufficient permission — non-fatal, warned via hadSecrets
68
+ }
69
+ }
44
70
  const skillNames = a.skills.map((sk) => collectSkill(cli, sk.id, skills));
45
71
  const red = redactAgent(a);
46
72
  const entry = { raw: a, red, skillNames };
@@ -77,7 +103,7 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
77
103
  const manifest = buildManifest({
78
104
  scope, sourceWorkspaceId,
79
105
  skills: [...skills.values()].map((s) => ({ name: s.name, sourceId: s.id })),
80
- agents: [...agentsById.values()].map((a) => ({ name: a.raw.name, sourceRuntimeId: a.raw.runtimeId, sourceRuntimeProvider: a.raw.sourceRuntimeProvider, skillNames: a.skillNames, hadSecrets: a.red.hadSecrets })),
106
+ agents: [...agentsById.values()].map((a) => ({ name: a.raw.name, sourceId: a.raw.id, sourceRuntimeId: a.raw.runtimeId, sourceRuntimeProvider: a.raw.sourceRuntimeProvider, skillNames: a.skillNames, hadSecrets: a.red.hadSecrets })),
81
107
  squad,
82
108
  });
83
109
 
@@ -57,6 +57,8 @@ export function importSkills({ cli, manifest, dir, fs = nodeFs }) {
57
57
 
58
58
  export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs = nodeFs }) {
59
59
  const idMap = new Map();
60
+ const sourceIdMap = new Map(); // source agent id -> new agent id, for mention rewriting
61
+ const secretsApplyFailures = [];
60
62
  let created = 0, updated = 0;
61
63
  const existing = listAgents(cli);
62
64
 
@@ -69,6 +71,7 @@ export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs =
69
71
  "--visibility", rec.visibility ?? "private",
70
72
  "--max-concurrent-tasks", String(rec.maxConcurrentTasks ?? 6),
71
73
  ];
74
+ if (rec.description) common.push("--description", rec.description);
72
75
  if (rec.instructions) common.push("--instructions", rec.instructions);
73
76
  if (rec.model) common.push("--model", rec.model);
74
77
  if (rec.thinkingLevel) common.push("--thinking-level", rec.thinkingLevel);
@@ -84,18 +87,77 @@ export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs =
84
87
  id = JSON.parse(out).id; created++;
85
88
  }
86
89
  idMap.set(rec.name, id);
90
+ if (rec.sourceId) sourceIdMap.set(rec.sourceId, id);
87
91
  const skillIds = (rec.skillNames ?? []).map((n) => skillIdMap.get(n)).filter(Boolean);
88
92
  cli.run(["agent", "skills", "set", id, "--skill-ids", skillIds.join(",")]);
93
+
94
+ // mcp_config/custom_env carry real secrets. Each is applied via its OWN
95
+ // follow-up call, never bundled into the create/update call above — that
96
+ // keeps a rejected secret from failing the whole agent create/update, and
97
+ // sidesteps the fact that only one stdin payload can be read per process
98
+ // anyway. `agent update --mcp-config-stdin` works on a freshly-created id
99
+ // too, so no create/update branching is needed here.
100
+ const hasMcpConfig = rec.mcpConfig && Object.keys(rec.mcpConfig).length > 0;
101
+ if (hasMcpConfig) {
102
+ try {
103
+ cli.run(["agent", "update", id, "--mcp-config-stdin"], { input: JSON.stringify(rec.mcpConfig) });
104
+ } catch {
105
+ secretsApplyFailures.push(rec.name);
106
+ }
107
+ }
108
+ // custom_env has no flag on `agent update` at all — `agent env set` is the
109
+ // only way to set it on an existing agent, so it's always a follow-up call.
110
+ const hasCustomEnv = rec.customEnv && Object.keys(rec.customEnv).length > 0;
111
+ if (hasCustomEnv) {
112
+ try {
113
+ cli.run(["agent", "env", "set", id, "--custom-env-stdin"], { input: JSON.stringify(rec.customEnv) });
114
+ } catch {
115
+ secretsApplyFailures.push(rec.name);
116
+ }
117
+ }
89
118
  }
90
- return { idMap, created, updated };
119
+ return { idMap, sourceIdMap, created, updated, secretsApplyFailures };
120
+ }
121
+
122
+ // Rewrites `mention://agent/<id>` links (e.g. `[@dev-backend](mention://agent/<id>)`)
123
+ // from a source-workspace agent id to its id in the destination workspace.
124
+ // Mentions with no entry in idMap (agent outside this bundle) are left as-is.
125
+ const MENTION_RE = /mention:\/\/agent\/([^)\s]+)/g;
126
+
127
+ export function rewriteMentions(text, idMap) {
128
+ if (!text) return text;
129
+ return text.replace(MENTION_RE, (full, oldId) => {
130
+ const newId = idMap.get(oldId);
131
+ return newId ? `mention://agent/${newId}` : full;
132
+ });
133
+ }
134
+
135
+ // Second pass over agent instructions: an agent's instructions may @mention a
136
+ // sibling agent from the same bundle by its SOURCE id, which is only knowable
137
+ // once every agent in the bundle has been created/updated (idMap complete) —
138
+ // so this must run after the importAgents loop above, not inside it.
139
+ export function rewriteAgentMentions({ cli, manifest, dir, agentIdMap, sourceIdMap, fs = nodeFs }) {
140
+ let updated = 0;
141
+ for (const a of manifest.agents) {
142
+ const rec = JSON.parse(fs.readFileSync(`${dir}/${a.file}`, "utf8"));
143
+ if (!rec.instructions) continue;
144
+ const rewritten = rewriteMentions(rec.instructions, sourceIdMap);
145
+ if (rewritten === rec.instructions) continue;
146
+ cli.run(["agent", "update", agentIdMap.get(rec.name), "--instructions", rewritten]);
147
+ updated++;
148
+ }
149
+ return { updated };
91
150
  }
92
151
 
93
- export function importSquad({ cli, squad, agentIdMap }) {
152
+ export function importSquad({ cli, squad, agentIdMap, sourceIdMap }) {
94
153
  const existing = listSquads(cli);
95
154
  const leaderId = agentIdMap.get(squad.leaderName);
96
155
  const match = findByName(existing, squad.name);
97
156
  let id, created = 0, updated = 0;
98
- const instr = squad.instructions ? ["--instructions", squad.instructions] : [];
157
+ // Squad instructions commonly list @mentions of teammate agents by their
158
+ // SOURCE id — rewrite to the destination ids before the squad is created.
159
+ const instructions = sourceIdMap ? rewriteMentions(squad.instructions, sourceIdMap) : squad.instructions;
160
+ const instr = instructions ? ["--instructions", instructions] : [];
99
161
  if (match) {
100
162
  cli.run(["squad", "update", match.id, "--leader", leaderId, "--description", squad.description ?? "", ...instr]);
101
163
  id = match.id; updated++;
@@ -162,16 +224,20 @@ export function importBundle({ cli, dir, runtimeMap, fs = nodeFs }) {
162
224
 
163
225
  const skillRes = importSkills({ cli, manifest, dir, fs });
164
226
  const agentRes = importAgents({ cli, manifest, dir, skillIdMap: skillRes.idMap, runtimeMap: effective, fs });
227
+ // Runs after every agent exists so forward-referencing mentions resolve.
228
+ const mentionRes = rewriteAgentMentions({ cli, manifest, dir, agentIdMap: agentRes.idMap, sourceIdMap: agentRes.sourceIdMap, fs });
165
229
  let squadRes = { newId: null, created: 0, updated: 0 };
166
- if (manifest.squads?.length) squadRes = importSquad({ cli, squad: manifest.squads[0], agentIdMap: agentRes.idMap });
230
+ if (manifest.squads?.length) squadRes = importSquad({ cli, squad: manifest.squads[0], agentIdMap: agentRes.idMap, sourceIdMap: agentRes.sourceIdMap });
167
231
 
168
232
  return {
169
233
  created: { skills: skillRes.created, agents: agentRes.created, squads: squadRes.created },
170
234
  updated: { skills: skillRes.updated, agents: agentRes.updated, squads: squadRes.updated },
235
+ mentionsRewritten: mentionRes.updated,
171
236
  skillIdMap: Object.fromEntries(skillRes.idMap),
172
237
  agentIdMap: Object.fromEntries(agentRes.idMap),
173
238
  squadId: squadRes.newId,
174
239
  secretsReminder: (manifest.agents ?? []).filter((a) => a.hadSecrets).map((a) => a.name),
240
+ secretsApplyFailures: agentRes.secretsApplyFailures,
175
241
  };
176
242
  }
177
243
 
@@ -58,4 +58,4 @@ Parse the JSON output from the script and report:
58
58
 
59
59
  - Directory written to.
60
60
  - Count of skills, agents, and squads exported.
61
- - If `warnings` is non-empty, surface every agent name verbatim with this message: "WARNING: the following agents had custom environment variables or MCP config that were NOT exported re-add secrets manually after import: `<agent-name>`."
61
+ - If `warnings` is non-empty, surface every agent name verbatim with this message: "WARNING: the following agents' exported files contain custom environment variables or MCP config in PLAINTEXT treat the export directory as sensitive (avoid committing it to a public repo, restrict file permissions, delete it once the import is done): `<agent-name>`."
@@ -38,6 +38,8 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/multica-import.mjs" \
38
38
  --runtime-map <srcId1=dstId1,srcId2=dstId2,...>
39
39
  ```
40
40
 
41
+ The import also rewrites any `mention://agent/<id>` link inside squad and agent instructions (e.g. `[@dev-backend](mention://agent/<id>)`) from the source agent's id to its new id in the target workspace — the CLI does this automatically for every agent captured in the bundle; no extra flag needed. Mentions pointing to an agent outside the bundle are left untouched.
42
+
41
43
  ## Step 3 — Report results
42
44
 
43
45
  Parse the JSON output and report:
@@ -45,4 +47,6 @@ Parse the JSON output and report:
45
47
  - Created and updated counts for skills, agents, and squads.
46
48
  - Name-to-ID maps for skills and agents (`skillIdMap`, `agentIdMap`).
47
49
  - Squad ID if a squad was imported.
48
- - If `secretsReminder` is non-empty, surface every agent name verbatim with: "WARNING: the following agents had custom environment variables or MCP config that were NOT exported — re-add secrets manually in the Multica UI: `<agent-name>`."
50
+ - `mentionsRewritten`: how many agents had an agent-mention link rewritten to its new id.
51
+ - If `secretsReminder` is non-empty, surface every agent name verbatim with: "WARNING: the following agents' bundle files contained custom environment variables or MCP config in PLAINTEXT — the source export directory should be treated as sensitive: `<agent-name>`."
52
+ - If `secretsApplyFailures` is non-empty, surface every agent name verbatim with: "WARNING: mcp_config or custom_env failed to apply to the following agents during import (the agent itself was still created/updated) — set them manually in the Multica UI: `<agent-name>`."
@@ -50,4 +50,5 @@ Parse the JSON output and report:
50
50
  - Created and updated counts for skills, agents, and squads.
51
51
  - Name-to-ID maps (`skillIdMap`, `agentIdMap`).
52
52
  - Squad ID if a squad was synced.
53
- - If `secretsReminder` is non-empty, surface every agent name verbatim with: "WARNING: the following agents had custom environment variables or MCP config that were NOT exported re-add secrets manually in the Multica UI: `<agent-name>`."
53
+ - If `secretsReminder` is non-empty, surface every agent name verbatim with: "WARNING: the following agents' bundle files contained custom environment variables or MCP config in PLAINTEXT the temporary export directory (already cleaned up) briefly held these secrets in plaintext: `<agent-name>`."
54
+ - If `secretsApplyFailures` is non-empty, surface every agent name verbatim with: "WARNING: mcp_config or custom_env failed to apply to the following agents during sync (the agent itself was still created/updated) — set them manually in the Multica UI: `<agent-name>`."
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "plugin-validator",
3
3
  "displayName": "Plugin Validator",
4
- "version": "0.0.35",
4
+ "version": "0.0.37",
5
5
  "description": "Orchestrated validator for Claude Code plugins — validates skills, agents, commands, and hooks across every plugin under plugins/**.",
6
6
  "author": {
7
7
  "name": "Steven Hoang"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "team-share",
3
3
  "displayName": "Team Share",
4
- "version": "0.0.35",
4
+ "version": "0.0.37",
5
5
  "description": "Onboard your team with an interactive setup menu: install CodeGraph, build the Understand-Anything knowledge graph, and share Claude Code settings — run any combination, all idempotent.",
6
6
  "author": {
7
7
  "name": "Steven Hoang"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tech-graph",
3
- "version": "0.0.35",
3
+ "version": "0.0.37",
4
4
  "description": "Step-by-step wizard for generating technical diagrams as SVG+PNG.",
5
5
  "author": {
6
6
  "name": "steven"