@drunkcoding/agents-and-skills 0.0.36 → 0.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +5 -5
- package/package.json +1 -1
- package/plugins/html-effectiveness/.claude-plugin/plugin.json +1 -1
- package/plugins/multica-tool/.claude-plugin/plugin.json +1 -1
- package/plugins/multica-tool/scripts/lib.mjs +24 -15
- package/plugins/multica-tool/scripts/multica-export.mjs +49 -23
- package/plugins/multica-tool/scripts/multica-import.mjs +85 -20
- package/plugins/multica-tool/skills/export/SKILL.md +1 -1
- package/plugins/multica-tool/skills/import/SKILL.md +7 -3
- package/plugins/multica-tool/skills/sync/SKILL.md +3 -2
- package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
- package/plugins/team-share/.claude-plugin/plugin.json +1 -1
- package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
|
@@ -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.
|
|
15
|
+
"version": "0.0.38",
|
|
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.
|
|
29
|
+
"version": "0.0.38",
|
|
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.
|
|
44
|
+
"version": "0.0.38",
|
|
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.
|
|
60
|
+
"version": "0.0.38",
|
|
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.
|
|
76
|
+
"version": "0.0.38",
|
|
77
77
|
"category": "workflow",
|
|
78
78
|
"keywords": [
|
|
79
79
|
"multica",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "html-effectiveness",
|
|
3
3
|
"displayName": "HTML Effectiveness Reports",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.38",
|
|
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"
|
|
@@ -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
|
}
|
|
@@ -55,8 +55,9 @@ export const listSkills = (cli) => cli.json(["skill", "list"]);
|
|
|
55
55
|
export const listAgents = (cli) => cli.json(["agent", "list"]);
|
|
56
56
|
export const listSquads = (cli) => cli.json(["squad", "list"]);
|
|
57
57
|
|
|
58
|
-
// Get-wrappers: the ONLY place that knows the raw CLI field names
|
|
59
|
-
//
|
|
58
|
+
// Get-wrappers: the ONLY place that knows the raw CLI field names — an
|
|
59
|
+
// explicit allow-list, so unexpected/internal CLI fields never leak into a
|
|
60
|
+
// bundle. Field names mirror the CLI's own snake_case; nothing is renamed.
|
|
60
61
|
export function getSkill(cli, id) {
|
|
61
62
|
const s = cli.json(["skill", "get", id]);
|
|
62
63
|
return {
|
|
@@ -71,23 +72,31 @@ export function getAgent(cli, id) {
|
|
|
71
72
|
return {
|
|
72
73
|
id: a.id, name: a.name, description: a.description, instructions: a.instructions,
|
|
73
74
|
model: a.model, visibility: a.visibility,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
max_concurrent_tasks: a.max_concurrent_tasks,
|
|
76
|
+
runtime_config: a.runtime_config,
|
|
77
|
+
custom_args: a.custom_args,
|
|
78
|
+
thinking_level: a.thinking_level,
|
|
79
|
+
runtime_id: a.runtime_id,
|
|
80
|
+
has_custom_env: a.has_custom_env,
|
|
81
|
+
mcp_config: a.mcp_config,
|
|
82
|
+
mcp_config_redacted: !!a.mcp_config_redacted,
|
|
81
83
|
skills: (a.skills ?? []).map((sk) => ({ id: sk.id, name: sk.name })),
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
// Custom env is never included in `agent get` — only `has_custom_env`/`custom_env_key_count`.
|
|
88
|
+
// Reading actual values requires this dedicated, audited, owner/admin-only command.
|
|
89
|
+
export function getAgentCustomEnv(cli, id) {
|
|
90
|
+
const r = cli.json(["agent", "env", "get", id]);
|
|
91
|
+
return r.custom_env ?? {};
|
|
92
|
+
}
|
|
93
|
+
|
|
85
94
|
export function getSquad(cli, id) {
|
|
86
95
|
const s = cli.json(["squad", "get", id]);
|
|
87
|
-
return { id: s.id, name: s.name, description: s.description, instructions: s.instructions,
|
|
96
|
+
return { id: s.id, name: s.name, description: s.description, instructions: s.instructions, leader_id: s.leader_id };
|
|
88
97
|
}
|
|
89
98
|
|
|
90
99
|
export const getSquadMembers = (cli, id) =>
|
|
91
100
|
(cli.json(["squad", "member", "list", id]) ?? []).map((m) => ({
|
|
92
|
-
|
|
101
|
+
member_id: m.member_id, member_type: m.member_type, role: m.role || "member",
|
|
93
102
|
}));
|
|
@@ -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
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
// a is a normalized agent from getAgent, with `custom_env`/
|
|
9
|
+
// `custom_env_fetch_failed` attached by the caller (collectAgent) — getAgent
|
|
10
|
+
// itself never fetches custom_env, since it requires a separate audited call.
|
|
11
|
+
const { id, has_custom_env, mcp_config_redacted, custom_env_fetch_failed, mcp_config, custom_env, skills, runtime_id, ...rest } = a;
|
|
12
|
+
const mcpUsable = !mcp_config_redacted && nonEmpty(mcp_config);
|
|
13
|
+
const envUsable = !custom_env_fetch_failed && nonEmpty(custom_env);
|
|
14
|
+
// mcp_config_redacted / custom_env_fetch_failed 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 || !!mcp_config_redacted || !!custom_env_fetch_failed;
|
|
11
18
|
return {
|
|
12
|
-
|
|
19
|
+
// source_id 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
|
+
source_id: id,
|
|
24
|
+
source_runtime_id: runtime_id,
|
|
25
|
+
skill_names: [],
|
|
26
|
+
mcp_config: mcpUsable ? mcp_config : null,
|
|
27
|
+
custom_env: envUsable ? custom_env : null,
|
|
28
|
+
had_secrets: hadSecrets,
|
|
29
|
+
},
|
|
13
30
|
hadSecrets,
|
|
14
31
|
};
|
|
15
32
|
}
|
|
@@ -22,10 +39,10 @@ export function buildManifest({ scope, sourceWorkspaceId, skills, agents, squad
|
|
|
22
39
|
return {
|
|
23
40
|
version: "1",
|
|
24
41
|
scope,
|
|
25
|
-
sourceWorkspaceId,
|
|
26
|
-
skills: [...seenSkills.values()].map((s) => ({ name: s.name, dir: `skills/${slugify(s.name)}`,
|
|
27
|
-
agents: [...seenAgents.values()].map((a) => ({ name: a.name, file: `agents/${slugify(a.name)}.json`,
|
|
28
|
-
squads: squad ? [{ name: squad.name, file: `squads/${slugify(squad.name)}.json`, description: squad.description ?? "", instructions: squad.instructions ?? "",
|
|
42
|
+
source_workspace_id: sourceWorkspaceId,
|
|
43
|
+
skills: [...seenSkills.values()].map((s) => ({ name: s.name, dir: `skills/${slugify(s.name)}`, source_id: s.source_id })),
|
|
44
|
+
agents: [...seenAgents.values()].map((a) => ({ name: a.name, file: `agents/${slugify(a.name)}.json`, source_id: a.source_id, source_runtime_id: a.source_runtime_id, source_runtime_provider: a.source_runtime_provider ?? null, skill_names: a.skill_names, had_secrets: !!a.had_secrets })),
|
|
45
|
+
squads: squad ? [{ name: squad.name, file: `squads/${slugify(squad.name)}.json`, description: squad.description ?? "", instructions: squad.instructions ?? "", leader_name: squad.leader_name, members: squad.members }] : [],
|
|
29
46
|
};
|
|
30
47
|
}
|
|
31
48
|
|
|
@@ -35,22 +52,31 @@ function collectSkill(cli, id, skills) {
|
|
|
35
52
|
return s.name;
|
|
36
53
|
}
|
|
37
54
|
|
|
38
|
-
// Keyed by agent id (so squad
|
|
55
|
+
// Keyed by agent id (so squad leader_id/member_id resolve to names). Stores the
|
|
39
56
|
// normalized agent, its redaction result, and its skill names.
|
|
40
57
|
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
|
-
a.
|
|
44
|
-
|
|
60
|
+
a.source_runtime_provider = providerById.get(a.runtime_id) ?? null;
|
|
61
|
+
a.custom_env = {};
|
|
62
|
+
a.custom_env_fetch_failed = false;
|
|
63
|
+
if (a.has_custom_env) {
|
|
64
|
+
try {
|
|
65
|
+
a.custom_env = getAgentCustomEnv(cli, id);
|
|
66
|
+
} catch {
|
|
67
|
+
a.custom_env_fetch_failed = true; // e.g. insufficient permission — non-fatal, warned via hadSecrets
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const skill_names = a.skills.map((sk) => collectSkill(cli, sk.id, skills));
|
|
45
71
|
const red = redactAgent(a);
|
|
46
|
-
const entry = { raw: a, red,
|
|
72
|
+
const entry = { raw: a, red, skill_names };
|
|
47
73
|
agentsById.set(id, entry);
|
|
48
74
|
return entry;
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs = nodeFs }) {
|
|
52
78
|
const skills = new Map(); // name -> normalized skill
|
|
53
|
-
const agentsById = new Map(); // id -> { raw, red,
|
|
79
|
+
const agentsById = new Map(); // id -> { raw, red, skill_names }
|
|
54
80
|
let squad = null;
|
|
55
81
|
// Lazy + memoized: only fetched when an agent is actually collected (skips
|
|
56
82
|
// the extra CLI call on skill-only exports).
|
|
@@ -61,23 +87,23 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
|
|
|
61
87
|
if (scope === "agent") collectAgent(cli, ids.agentId, agentsById, skills, getProviderById());
|
|
62
88
|
if (scope === "squad") {
|
|
63
89
|
const sq = getSquad(cli, ids.squadId);
|
|
64
|
-
const members = getSquadMembers(cli, ids.squadId).filter((m) => m.
|
|
65
|
-
for (const m of members) collectAgent(cli, m.
|
|
66
|
-
if (!agentsById.has(sq.
|
|
90
|
+
const members = getSquadMembers(cli, ids.squadId).filter((m) => m.member_type === "agent");
|
|
91
|
+
for (const m of members) collectAgent(cli, m.member_id, agentsById, skills, getProviderById());
|
|
92
|
+
if (!agentsById.has(sq.leader_id)) collectAgent(cli, sq.leader_id, agentsById, skills, getProviderById());
|
|
67
93
|
const nameOf = (id) => agentsById.get(id)?.raw.name;
|
|
68
94
|
squad = {
|
|
69
95
|
name: sq.name,
|
|
70
96
|
description: sq.description,
|
|
71
97
|
instructions: sq.instructions,
|
|
72
|
-
|
|
73
|
-
members: members.map((m) => ({
|
|
98
|
+
leader_name: nameOf(sq.leader_id),
|
|
99
|
+
members: members.map((m) => ({ agent_name: nameOf(m.member_id), role: m.role })),
|
|
74
100
|
};
|
|
75
101
|
}
|
|
76
102
|
|
|
77
103
|
const manifest = buildManifest({
|
|
78
104
|
scope, sourceWorkspaceId,
|
|
79
|
-
skills: [...skills.values()].map((s) => ({ name: s.name,
|
|
80
|
-
agents: [...agentsById.values()].map((a) => ({ name: a.raw.name,
|
|
105
|
+
skills: [...skills.values()].map((s) => ({ name: s.name, source_id: s.id })),
|
|
106
|
+
agents: [...agentsById.values()].map((a) => ({ name: a.raw.name, source_id: a.raw.id, source_runtime_id: a.raw.runtime_id, source_runtime_provider: a.raw.source_runtime_provider, skill_names: a.skill_names, had_secrets: a.red.hadSecrets })),
|
|
81
107
|
squad,
|
|
82
108
|
});
|
|
83
109
|
|
|
@@ -99,8 +125,8 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
|
|
|
99
125
|
// Index agent entries by name for the manifest writing loop.
|
|
100
126
|
const agentByName = new Map([...agentsById.values()].map((a) => [a.raw.name, a]));
|
|
101
127
|
for (const entry of manifest.agents) {
|
|
102
|
-
const { raw, red,
|
|
103
|
-
const record = { ...red.record,
|
|
128
|
+
const { raw, red, skill_names } = agentByName.get(entry.name);
|
|
129
|
+
const record = { ...red.record, skill_names };
|
|
104
130
|
if (red.hadSecrets) warnings.push(raw.name);
|
|
105
131
|
fs.mkdirSync(`${outDir}/agents`, { recursive: true });
|
|
106
132
|
fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(record, null, 2));
|
|
@@ -57,24 +57,26 @@ 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
|
|
|
63
65
|
for (const a of manifest.agents) {
|
|
64
66
|
const rec = JSON.parse(fs.readFileSync(`${dir}/${a.file}`, "utf8"));
|
|
65
|
-
const targetRuntime = runtimeMap.get(rec.
|
|
66
|
-
if (!targetRuntime) throw new Error(`Unmapped runtime "${rec.
|
|
67
|
+
const targetRuntime = runtimeMap.get(rec.source_runtime_id);
|
|
68
|
+
if (!targetRuntime) throw new Error(`Unmapped runtime "${rec.source_runtime_id}" for agent "${rec.name}"`);
|
|
67
69
|
// Only pass optional flags when present — `--model ""` would CLEAR the model.
|
|
68
70
|
const common = [
|
|
69
71
|
"--visibility", rec.visibility ?? "private",
|
|
70
|
-
"--max-concurrent-tasks", String(rec.
|
|
72
|
+
"--max-concurrent-tasks", String(rec.max_concurrent_tasks ?? 6),
|
|
71
73
|
];
|
|
72
74
|
if (rec.description) common.push("--description", rec.description);
|
|
73
75
|
if (rec.instructions) common.push("--instructions", rec.instructions);
|
|
74
76
|
if (rec.model) common.push("--model", rec.model);
|
|
75
|
-
if (rec.
|
|
76
|
-
if (rec.
|
|
77
|
-
if (Array.isArray(rec.
|
|
77
|
+
if (rec.thinking_level) common.push("--thinking-level", rec.thinking_level);
|
|
78
|
+
if (rec.runtime_config && Object.keys(rec.runtime_config).length) common.push("--runtime-config", JSON.stringify(rec.runtime_config));
|
|
79
|
+
if (Array.isArray(rec.custom_args) && rec.custom_args.length) common.push("--custom-args", JSON.stringify(rec.custom_args));
|
|
78
80
|
const match = findByName(existing, rec.name);
|
|
79
81
|
let id;
|
|
80
82
|
if (match) {
|
|
@@ -85,18 +87,77 @@ export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs =
|
|
|
85
87
|
id = JSON.parse(out).id; created++;
|
|
86
88
|
}
|
|
87
89
|
idMap.set(rec.name, id);
|
|
88
|
-
|
|
90
|
+
if (rec.source_id) sourceIdMap.set(rec.source_id, id);
|
|
91
|
+
const skillIds = (rec.skill_names ?? []).map((n) => skillIdMap.get(n)).filter(Boolean);
|
|
89
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.mcp_config && Object.keys(rec.mcp_config).length > 0;
|
|
101
|
+
if (hasMcpConfig) {
|
|
102
|
+
try {
|
|
103
|
+
cli.run(["agent", "update", id, "--mcp-config-stdin"], { input: JSON.stringify(rec.mcp_config) });
|
|
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.custom_env && Object.keys(rec.custom_env).length > 0;
|
|
111
|
+
if (hasCustomEnv) {
|
|
112
|
+
try {
|
|
113
|
+
cli.run(["agent", "env", "set", id, "--custom-env-stdin"], { input: JSON.stringify(rec.custom_env) });
|
|
114
|
+
} catch {
|
|
115
|
+
secretsApplyFailures.push(rec.name);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
90
118
|
}
|
|
91
|
-
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 };
|
|
92
150
|
}
|
|
93
151
|
|
|
94
|
-
export function importSquad({ cli, squad, agentIdMap }) {
|
|
152
|
+
export function importSquad({ cli, squad, agentIdMap, sourceIdMap }) {
|
|
95
153
|
const existing = listSquads(cli);
|
|
96
|
-
const leaderId = agentIdMap.get(squad.
|
|
154
|
+
const leaderId = agentIdMap.get(squad.leader_name);
|
|
97
155
|
const match = findByName(existing, squad.name);
|
|
98
156
|
let id, created = 0, updated = 0;
|
|
99
|
-
|
|
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] : [];
|
|
100
161
|
if (match) {
|
|
101
162
|
cli.run(["squad", "update", match.id, "--leader", leaderId, "--description", squad.description ?? "", ...instr]);
|
|
102
163
|
id = match.id; updated++;
|
|
@@ -105,10 +166,10 @@ export function importSquad({ cli, squad, agentIdMap }) {
|
|
|
105
166
|
id = JSON.parse(out).id; created++;
|
|
106
167
|
}
|
|
107
168
|
// Add non-leader members, skipping any already present so re-runs are idempotent.
|
|
108
|
-
const present = new Set(getSquadMembers(cli, id).map((m) => m.
|
|
169
|
+
const present = new Set(getSquadMembers(cli, id).map((m) => m.member_id));
|
|
109
170
|
for (const m of squad.members) {
|
|
110
|
-
if (m.
|
|
111
|
-
const memberId = agentIdMap.get(m.
|
|
171
|
+
if (m.agent_name === squad.leader_name) continue;
|
|
172
|
+
const memberId = agentIdMap.get(m.agent_name);
|
|
112
173
|
if (present.has(memberId)) continue;
|
|
113
174
|
cli.run(["squad", "member", "add", id, "--member-id", memberId, "--role", m.role, "--type", "agent"]);
|
|
114
175
|
}
|
|
@@ -116,15 +177,15 @@ export function importSquad({ cli, squad, agentIdMap }) {
|
|
|
116
177
|
}
|
|
117
178
|
|
|
118
179
|
export function collectSourceRuntimes(manifest) {
|
|
119
|
-
return [...new Set((manifest.agents ?? []).map((a) => a.
|
|
180
|
+
return [...new Set((manifest.agents ?? []).map((a) => a.source_runtime_id).filter(Boolean))];
|
|
120
181
|
}
|
|
121
182
|
|
|
122
|
-
//
|
|
183
|
+
// source_runtime_id -> provider (e.g. "claude", "opencode"), from whichever agent recorded it.
|
|
123
184
|
function collectRuntimeProviders(manifest) {
|
|
124
185
|
const map = new Map();
|
|
125
186
|
for (const a of manifest.agents ?? []) {
|
|
126
|
-
if (a.
|
|
127
|
-
map.set(a.
|
|
187
|
+
if (a.source_runtime_id && a.source_runtime_provider && !map.has(a.source_runtime_id)) {
|
|
188
|
+
map.set(a.source_runtime_id, a.source_runtime_provider);
|
|
128
189
|
}
|
|
129
190
|
}
|
|
130
191
|
return map;
|
|
@@ -163,16 +224,20 @@ export function importBundle({ cli, dir, runtimeMap, fs = nodeFs }) {
|
|
|
163
224
|
|
|
164
225
|
const skillRes = importSkills({ cli, manifest, dir, fs });
|
|
165
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 });
|
|
166
229
|
let squadRes = { newId: null, created: 0, updated: 0 };
|
|
167
|
-
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 });
|
|
168
231
|
|
|
169
232
|
return {
|
|
170
233
|
created: { skills: skillRes.created, agents: agentRes.created, squads: squadRes.created },
|
|
171
234
|
updated: { skills: skillRes.updated, agents: agentRes.updated, squads: squadRes.updated },
|
|
235
|
+
mentionsRewritten: mentionRes.updated,
|
|
172
236
|
skillIdMap: Object.fromEntries(skillRes.idMap),
|
|
173
237
|
agentIdMap: Object.fromEntries(agentRes.idMap),
|
|
174
238
|
squadId: squadRes.newId,
|
|
175
|
-
secretsReminder: (manifest.agents ?? []).filter((a) => a.
|
|
239
|
+
secretsReminder: (manifest.agents ?? []).filter((a) => a.had_secrets).map((a) => a.name),
|
|
240
|
+
secretsApplyFailures: agentRes.secretsApplyFailures,
|
|
176
241
|
};
|
|
177
242
|
}
|
|
178
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
|
|
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>`."
|
|
@@ -25,11 +25,11 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/multica-import.mjs" \
|
|
|
25
25
|
If it aborts with `Unmapped runtimes: ...` (0 or 2+ runtimes share that provider in the target workspace, or the bundle predates provider capture), resolve manually:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
cat <folder>/manifest.json # note each distinct
|
|
28
|
+
cat <folder>/manifest.json # note each distinct source_runtime_id
|
|
29
29
|
multica runtime list --output json # list target workspace runtimes
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Ask the user to pick a matching target runtime by name or ID for each unmapped `
|
|
32
|
+
Ask the user to pick a matching target runtime by name or ID for each unmapped `source_runtime_id`, then re-run with an explicit map (explicit entries always take precedence over auto-mapping):
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
node "${CLAUDE_PLUGIN_ROOT}/scripts/multica-import.mjs" \
|
|
@@ -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
|
-
-
|
|
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>`."
|
|
@@ -33,7 +33,7 @@ If it aborts with `Unmapped runtimes: ...` (0 or 2+ runtimes share that provider
|
|
|
33
33
|
multica runtime list --workspace-id <dest-ws-id> --output json # list destination workspace runtimes
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Ask the user to select a matching target runtime by name or ID for each unmapped `
|
|
36
|
+
Ask the user to select a matching target runtime by name or ID for each unmapped `source_runtime_id`, then re-run with an explicit map (explicit entries always take precedence over auto-mapping):
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
node "${CLAUDE_PLUGIN_ROOT}/scripts/multica-sync.mjs" \
|
|
@@ -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
|
|
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.
|
|
4
|
+
"version": "0.0.38",
|
|
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.
|
|
4
|
+
"version": "0.0.38",
|
|
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"
|