@drunkcoding/agents-and-skills 0.0.43 → 0.0.45
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 +5 -0
- package/plugins/multica-tool/scripts/multica-export.mjs +110 -50
- package/plugins/multica-tool/scripts/multica-import.mjs +45 -16
- package/plugins/multica-tool/skills/export/SKILL.md +7 -8
- package/plugins/multica-tool/skills/import/SKILL.md +2 -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.45",
|
|
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.45",
|
|
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.45",
|
|
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.45",
|
|
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.45",
|
|
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.45",
|
|
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"
|
|
@@ -53,6 +53,9 @@ export function resolveWorkspaceId(cli, name) {
|
|
|
53
53
|
export const listRuntimes = (cli) => cli.json(["runtime", "list"]);
|
|
54
54
|
export const listSkills = (cli) => cli.json(["skill", "list"]);
|
|
55
55
|
export const listAgents = (cli) => cli.json(["agent", "list"]);
|
|
56
|
+
// Includes archived agents (archived_at set) — used wherever a name match or an
|
|
57
|
+
// id lookup must also see agents excluded from the default listing above.
|
|
58
|
+
export const listAgentsIncludingArchived = (cli) => cli.json(["agent", "list", "--include-archived"]);
|
|
56
59
|
export const listSquads = (cli) => cli.json(["squad", "list"]);
|
|
57
60
|
export const listWorkspaceMembers = (cli) => cli.json(["workspace", "member", "list"]);
|
|
58
61
|
|
|
@@ -72,6 +75,8 @@ export function getAgent(cli, id) {
|
|
|
72
75
|
const a = cli.json(["agent", "get", id]);
|
|
73
76
|
return {
|
|
74
77
|
id: a.id, name: a.name, description: a.description, instructions: a.instructions,
|
|
78
|
+
// Export-time signal only (never written to a bundle record — see redactAgent).
|
|
79
|
+
archived_at: a.archived_at ?? null,
|
|
75
80
|
model: a.model, visibility: a.visibility, avatar_url: a.avatar_url ?? null,
|
|
76
81
|
service_tier: a.service_tier ?? "",
|
|
77
82
|
permission_mode: a.permission_mode ?? null,
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import * as nodeFs from "node:fs";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
|
-
import { slugify, getSkill, getAgent, getAgentCustomEnv, getSquad, getSquadMembers, listRuntimes, listSkills, listAgents, listSquads, listProjects, getProject, getProjectResources, listWorkspaceMembers, getAutopilot, makeCli, realExec, requireAuth, resolveWorkspaceId } from "./lib.mjs";
|
|
4
|
+
import { slugify, getSkill, getAgent, getAgentCustomEnv, getSquad, getSquadMembers, listRuntimes, listSkills, listAgents, listAgentsIncludingArchived, listSquads, listProjects, getProject, getProjectResources, listWorkspaceMembers, getAutopilot, makeCli, realExec, requireAuth, resolveWorkspaceId } from "./lib.mjs";
|
|
5
5
|
|
|
6
6
|
const nonEmpty = (v) => v && typeof v === "object" && Object.keys(v).length > 0;
|
|
7
7
|
|
|
8
|
+
// Externalize a prose field to a sibling .md next to `jsonRel`
|
|
9
|
+
// (e.g. agents/x.json + ".description.md" → agents/x.description.md), and record
|
|
10
|
+
// the `<field>_file` pointer on `record`. Empty prose writes nothing and adds no
|
|
11
|
+
// key — same sibling-file pattern as avatar_file, applied uniformly to every
|
|
12
|
+
// resource's instructions/description so no prose is ever embedded in the JSON.
|
|
13
|
+
function writeSidecar(fs, outDir, jsonRel, suffix, text, record, key) {
|
|
14
|
+
if (!text) return;
|
|
15
|
+
const rel = jsonRel.replace(/\.json$/, suffix);
|
|
16
|
+
fs.writeFileSync(`${outDir}/${rel}`, text);
|
|
17
|
+
record[key] = rel;
|
|
18
|
+
}
|
|
19
|
+
|
|
8
20
|
// An avatar_url is either an uploaded-image URL (http[s]://…) or an inline
|
|
9
21
|
// "emoji:🦍" marker. Only image URLs carry bytes worth bundling; emoji markers
|
|
10
22
|
// are just carried as strings in the record.
|
|
@@ -31,7 +43,7 @@ export function redactAgent(a) {
|
|
|
31
43
|
// a is a normalized agent from getAgent, with `custom_env`/
|
|
32
44
|
// `custom_env_fetch_failed` attached by the caller (collectAgent) — getAgent
|
|
33
45
|
// itself never fetches custom_env, since it requires a separate audited call.
|
|
34
|
-
const { id, has_custom_env, mcp_config_redacted, custom_env_fetch_failed, mcp_config, custom_env, skills, runtime_id, instructions, ...rest } = a;
|
|
46
|
+
const { id, has_custom_env, mcp_config_redacted, custom_env_fetch_failed, mcp_config, custom_env, skills, runtime_id, instructions, description, archived_at, ...rest } = a;
|
|
35
47
|
const mcpUsable = !mcp_config_redacted && nonEmpty(mcp_config);
|
|
36
48
|
const envUsable = !custom_env_fetch_failed && nonEmpty(custom_env);
|
|
37
49
|
// mcp_config_redacted / custom_env_fetch_failed alone still flag hadSecrets even
|
|
@@ -51,9 +63,10 @@ export function redactAgent(a) {
|
|
|
51
63
|
had_secrets: hadSecrets,
|
|
52
64
|
},
|
|
53
65
|
hadSecrets,
|
|
54
|
-
// instructions are written to
|
|
55
|
-
// never embedded in the JSON record.
|
|
66
|
+
// instructions and description are written to sibling .md files by the caller
|
|
67
|
+
// (see avatar_file), never embedded in the JSON record.
|
|
56
68
|
instructions: instructions ?? "",
|
|
69
|
+
description: description ?? "",
|
|
57
70
|
};
|
|
58
71
|
}
|
|
59
72
|
|
|
@@ -72,9 +85,11 @@ export function buildManifest({ scope, sourceWorkspaceId, skills, agents, squads
|
|
|
72
85
|
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 })),
|
|
73
86
|
squads: (squads ?? []).map((squad) => {
|
|
74
87
|
const file = `squads/${slugify(squad.name)}.json`;
|
|
75
|
-
const entry = { name: squad.name, file,
|
|
76
|
-
// Instructions go to
|
|
88
|
+
const entry = { name: squad.name, file, avatar_url: squad.avatar_url ?? null, leader_name: squad.leader_name, members: squad.members };
|
|
89
|
+
// Instructions and description go to sibling .md files (see squad write
|
|
90
|
+
// loop); only referenced when non-empty.
|
|
77
91
|
if (squad.instructions) entry.instructions_file = file.replace(/\.json$/, ".md");
|
|
92
|
+
if (squad.description) entry.description_file = file.replace(/\.json$/, ".description.md");
|
|
78
93
|
return entry;
|
|
79
94
|
}),
|
|
80
95
|
projects: [...seenProjects.values()].map((p) => ({
|
|
@@ -96,10 +111,15 @@ function collectSkill(cli, id, skills) {
|
|
|
96
111
|
}
|
|
97
112
|
|
|
98
113
|
// Keyed by agent id (so squad leader_id/member_id resolve to names). Stores the
|
|
99
|
-
// normalized agent, its redaction result, and its skill names.
|
|
114
|
+
// normalized agent, its redaction result, and its skill names. Returns
|
|
115
|
+
// `{ archived: true, name }` instead of collecting when the agent is archived —
|
|
116
|
+
// callers reached via a squad/project/explicit id lookup must decide how to
|
|
117
|
+
// handle that (the workspace-listing loop never hits this: listAgents() already
|
|
118
|
+
// excludes archived agents server-side).
|
|
100
119
|
function collectAgent(cli, id, agentsById, skills, providerById) {
|
|
101
120
|
if (agentsById.has(id)) return agentsById.get(id);
|
|
102
121
|
const a = getAgent(cli, id);
|
|
122
|
+
if (a.archived_at) return { archived: true, name: a.name };
|
|
103
123
|
a.source_runtime_provider = providerById.get(a.runtime_id) ?? null;
|
|
104
124
|
a.custom_env = {};
|
|
105
125
|
a.custom_env_fetch_failed = false;
|
|
@@ -117,22 +137,6 @@ function collectAgent(cli, id, agentsById, skills, providerById) {
|
|
|
117
137
|
return entry;
|
|
118
138
|
}
|
|
119
139
|
|
|
120
|
-
// Collect a project's portable metadata + its lead agent (bundled, like a squad
|
|
121
|
-
// leader) and github_repo/other resources. Returns the project bundle record.
|
|
122
|
-
function collectProject(cli, id, agentsById, skills, providerById) {
|
|
123
|
-
const p = getProject(cli, id);
|
|
124
|
-
let lead_name = null;
|
|
125
|
-
if (p.lead_type === "agent" && p.lead_id) {
|
|
126
|
-
lead_name = collectAgent(cli, p.lead_id, agentsById, skills, providerById).raw.name;
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
title: p.title, description: p.description, icon: p.icon,
|
|
130
|
-
priority: p.priority, status: p.status, due_date: p.due_date, start_date: p.start_date,
|
|
131
|
-
source_id: id, lead_type: p.lead_type, lead_name, lead_source_id: p.lead_id,
|
|
132
|
-
resources: getProjectResources(cli, id),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
140
|
export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs = nodeFs, download = fetchBinary }) {
|
|
137
141
|
const skills = new Map(); // name -> normalized skill
|
|
138
142
|
const agentsById = new Map(); // id -> { raw, red, skill_names }
|
|
@@ -144,12 +148,34 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
|
|
|
144
148
|
let providerById = null;
|
|
145
149
|
const getProviderById = () => providerById ??= new Map(listRuntimes(cli).map((r) => [r.id, r.provider]));
|
|
146
150
|
|
|
147
|
-
//
|
|
151
|
+
// Archived agents skipped anywhere below, deduped by (name, path, detail) —
|
|
152
|
+
// the operator-facing "why isn't X in my bundle" report.
|
|
153
|
+
const archivedAgentsSkipped = [];
|
|
154
|
+
const seenArchivedSkip = new Set();
|
|
155
|
+
function recordArchivedSkip(name, path, detail) {
|
|
156
|
+
const key = [name, path, detail ?? ""].join("|");
|
|
157
|
+
if (seenArchivedSkip.has(key)) return;
|
|
158
|
+
seenArchivedSkip.add(key);
|
|
159
|
+
archivedAgentsSkipped.push(detail ? { name, path, detail } : { name, path });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Collect a squad's agents (leader + members) and return the squad bundle
|
|
163
|
+
// object — or null when the leader is archived, since a squad can't import
|
|
164
|
+
// without one (the whole squad is excluded from the bundle, not emitted leaderless).
|
|
148
165
|
function collectOneSquad(squadId) {
|
|
149
166
|
const sq = getSquad(cli, squadId);
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
167
|
+
const leaderEntry = collectAgent(cli, sq.leader_id, agentsById, skills, getProviderById());
|
|
168
|
+
if (leaderEntry.archived) {
|
|
169
|
+
recordArchivedSkip(leaderEntry.name, "squad leader", sq.name);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const allMembers = getSquadMembers(cli, squadId).filter((m) => m.member_type === "agent");
|
|
173
|
+
const members = [];
|
|
174
|
+
for (const m of allMembers) {
|
|
175
|
+
const entry = collectAgent(cli, m.member_id, agentsById, skills, getProviderById());
|
|
176
|
+
if (entry.archived) { recordArchivedSkip(entry.name, "squad member", sq.name); continue; }
|
|
177
|
+
members.push(m);
|
|
178
|
+
}
|
|
153
179
|
const nameOf = (id) => agentsById.get(id)?.raw.name;
|
|
154
180
|
return {
|
|
155
181
|
name: sq.name, description: sq.description, instructions: sq.instructions, avatar_url: sq.avatar_url,
|
|
@@ -158,6 +184,30 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
|
|
|
158
184
|
};
|
|
159
185
|
}
|
|
160
186
|
|
|
187
|
+
// Collect a project's portable metadata + its lead agent (bundled, like a
|
|
188
|
+
// squad leader) and github_repo/other resources. An archived lead is dropped
|
|
189
|
+
// (project still exports, just with no lead set) — unlike a squad, a project
|
|
190
|
+
// has no hard requirement for one.
|
|
191
|
+
function collectProject(id) {
|
|
192
|
+
const p = getProject(cli, id);
|
|
193
|
+
let lead_name = null, lead_type = p.lead_type, lead_source_id = p.lead_id;
|
|
194
|
+
if (p.lead_type === "agent" && p.lead_id) {
|
|
195
|
+
const entry = collectAgent(cli, p.lead_id, agentsById, skills, getProviderById());
|
|
196
|
+
if (entry.archived) {
|
|
197
|
+
recordArchivedSkip(entry.name, "project lead", p.title);
|
|
198
|
+
lead_type = null; lead_source_id = null;
|
|
199
|
+
} else {
|
|
200
|
+
lead_name = entry.raw.name;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
title: p.title, description: p.description, icon: p.icon,
|
|
205
|
+
priority: p.priority, status: p.status, due_date: p.due_date, start_date: p.start_date,
|
|
206
|
+
source_id: id, lead_type, lead_name, lead_source_id,
|
|
207
|
+
resources: getProjectResources(cli, id),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
161
211
|
// Collect a single autopilot: its assignee (agent, or squad + members + skills)
|
|
162
212
|
// and, when present, the destination-portable project title / subscriber names.
|
|
163
213
|
// Webhook trigger secrets (url/token) are never read into the bundle — only
|
|
@@ -166,11 +216,11 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
|
|
|
166
216
|
const ap = getAutopilot(cli, autopilotId);
|
|
167
217
|
let assignee_name = null;
|
|
168
218
|
if (ap.assignee_type === "agent") {
|
|
169
|
-
|
|
219
|
+
const entry = collectAgent(cli, ap.assignee_id, agentsById, skills, getProviderById());
|
|
220
|
+
assignee_name = entry.archived ? null : entry.raw.name;
|
|
170
221
|
} else if (ap.assignee_type === "squad") {
|
|
171
222
|
const sq = collectOneSquad(ap.assignee_id);
|
|
172
|
-
squads.push(sq);
|
|
173
|
-
assignee_name = sq.name;
|
|
223
|
+
if (sq) { squads.push(sq); assignee_name = sq.name; }
|
|
174
224
|
}
|
|
175
225
|
let project_title = null;
|
|
176
226
|
if (ap.project_id) {
|
|
@@ -193,16 +243,24 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
|
|
|
193
243
|
}
|
|
194
244
|
|
|
195
245
|
if (scope === "skill") collectSkill(cli, ids.skillId, skills);
|
|
196
|
-
else if (scope === "agent")
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
246
|
+
else if (scope === "agent") {
|
|
247
|
+
const entry = collectAgent(cli, ids.agentId, agentsById, skills, getProviderById());
|
|
248
|
+
if (entry.archived) throw new Error(`Cannot export agent "${entry.name}": it is archived`);
|
|
249
|
+
}
|
|
250
|
+
else if (scope === "squad") { const sq = collectOneSquad(ids.squadId); if (sq) squads.push(sq); }
|
|
251
|
+
else if (scope === "project") projects.push(collectProject(ids.projectId));
|
|
252
|
+
else if (scope === "projects") for (const p of listProjects(cli)) projects.push(collectProject(p.id));
|
|
200
253
|
else if (scope === "autopilot") autopilots.push(collectOneAutopilot(ids.autopilotId));
|
|
201
254
|
else if (scope === "all") {
|
|
202
255
|
for (const s of listSkills(cli)) collectSkill(cli, s.id, skills);
|
|
203
256
|
for (const a of listAgents(cli)) collectAgent(cli, a.id, agentsById, skills, getProviderById());
|
|
204
|
-
for (const sq of listSquads(cli))
|
|
205
|
-
for (const p of listProjects(cli)) projects.push(collectProject(
|
|
257
|
+
for (const sq of listSquads(cli)) { const built = collectOneSquad(sq.id); if (built) squads.push(built); }
|
|
258
|
+
for (const p of listProjects(cli)) projects.push(collectProject(p.id));
|
|
259
|
+
// listAgents() above already excludes archived agents server-side — surface
|
|
260
|
+
// that exclusion in the report too, not just the squad/project paths.
|
|
261
|
+
for (const a of listAgentsIncludingArchived(cli)) {
|
|
262
|
+
if (a.archived_at) recordArchivedSkip(a.name, "workspace listing");
|
|
263
|
+
}
|
|
206
264
|
}
|
|
207
265
|
|
|
208
266
|
// Orphan-skill cleanup: drop skills that no exported agent references via its
|
|
@@ -261,36 +319,38 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
|
|
|
261
319
|
record.avatar_file = rel;
|
|
262
320
|
}
|
|
263
321
|
}
|
|
264
|
-
// Instructions live in a sibling .md for reviewability
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const rel = entry.file.replace(/\.json$/, ".md");
|
|
268
|
-
fs.writeFileSync(`${outDir}/${rel}`, red.instructions);
|
|
269
|
-
record.instructions_file = rel;
|
|
270
|
-
}
|
|
322
|
+
// Instructions and description each live in a sibling .md for reviewability.
|
|
323
|
+
writeSidecar(fs, outDir, entry.file, ".md", red.instructions, record, "instructions_file");
|
|
324
|
+
writeSidecar(fs, outDir, entry.file, ".description.md", red.description, record, "description_file");
|
|
271
325
|
fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(record, null, 2));
|
|
272
326
|
}
|
|
273
|
-
const
|
|
327
|
+
const squadByName = new Map(squads.map((s) => [s.name, s]));
|
|
274
328
|
for (const entry of manifest.squads) {
|
|
275
329
|
fs.mkdirSync(`${outDir}/squads`, { recursive: true });
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
330
|
+
const s = squadByName.get(entry.name);
|
|
331
|
+
if (entry.instructions_file) fs.writeFileSync(`${outDir}/${entry.instructions_file}`, s?.instructions ?? "");
|
|
332
|
+
if (entry.description_file) fs.writeFileSync(`${outDir}/${entry.description_file}`, s?.description ?? "");
|
|
279
333
|
fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(entry, null, 2));
|
|
280
334
|
}
|
|
281
335
|
const projectByTitle = new Map(projects.map((p) => [p.title, p]));
|
|
282
336
|
for (const entry of manifest.projects) {
|
|
283
337
|
fs.mkdirSync(`${outDir}/projects`, { recursive: true });
|
|
284
|
-
|
|
338
|
+
const record = { ...projectByTitle.get(entry.title) };
|
|
339
|
+
const desc = record.description; delete record.description;
|
|
340
|
+
writeSidecar(fs, outDir, entry.file, ".description.md", desc, record, "description_file");
|
|
341
|
+
fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(record, null, 2));
|
|
285
342
|
}
|
|
286
343
|
const autopilotByTitle = new Map(autopilots.map((a) => [a.title, a]));
|
|
287
344
|
for (const entry of manifest.autopilots) {
|
|
288
345
|
fs.mkdirSync(`${outDir}/autopilots`, { recursive: true });
|
|
289
|
-
|
|
346
|
+
const record = { ...autopilotByTitle.get(entry.title) };
|
|
347
|
+
const desc = record.description; delete record.description;
|
|
348
|
+
writeSidecar(fs, outDir, entry.file, ".description.md", desc, record, "description_file");
|
|
349
|
+
fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(record, null, 2));
|
|
290
350
|
}
|
|
291
351
|
fs.writeFileSync(`${outDir}/manifest.json`, JSON.stringify(manifest, null, 2));
|
|
292
352
|
return {
|
|
293
|
-
manifest, warnings, pruned_skills,
|
|
353
|
+
manifest, warnings, pruned_skills, archivedAgentsSkipped,
|
|
294
354
|
autopilotWebhookTriggers: autopilots.filter((a) => a.had_webhook_trigger).map((a) => a.title),
|
|
295
355
|
};
|
|
296
356
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as nodeFs from "node:fs";
|
|
2
|
-
import { listSkills, listAgents, listSquads, listRuntimes, listWorkspaceMembers, getSquadMembers, findByName, makeCli, realExec, requireAuth, resolveWorkspaceId, listProjects, getProjectResources, findByTitle, listAutopilots, getAutopilot } from "./lib.mjs";
|
|
2
|
+
import { listSkills, listAgents, listAgentsIncludingArchived, listSquads, listRuntimes, listWorkspaceMembers, getSquadMembers, findByName, makeCli, realExec, requireAuth, resolveWorkspaceId, listProjects, getProjectResources, findByTitle, listAutopilots, getAutopilot } from "./lib.mjs";
|
|
3
3
|
|
|
4
4
|
// User-facing selectable types are agents/squads/projects/autopilots; skills follow agents.
|
|
5
5
|
export function parseInclude(raw) {
|
|
@@ -8,15 +8,18 @@ export function parseInclude(raw) {
|
|
|
8
8
|
return set;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
// avatar_file). Legacy bundles carry no
|
|
13
|
-
// inline in the JSON — fall back to that so older exports
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
// A prose field (instructions/description) lives in a sibling .md referenced by
|
|
12
|
+
// `<field>_file` (mirrors avatar_file). Legacy bundles carry no `<field>_file`
|
|
13
|
+
// and keep the prose inline in the JSON — fall back to that so older exports
|
|
14
|
+
// still import.
|
|
15
|
+
function readSidecar(fs, dir, rec, fileKey, inlineKey) {
|
|
16
|
+
if (rec[fileKey] && fs.existsSync(`${dir}/${rec[fileKey]}`)) {
|
|
17
|
+
return fs.readFileSync(`${dir}/${rec[fileKey]}`, "utf8");
|
|
17
18
|
}
|
|
18
|
-
return rec
|
|
19
|
+
return rec[inlineKey] ?? "";
|
|
19
20
|
}
|
|
21
|
+
const readInstructions = (fs, dir, rec) => readSidecar(fs, dir, rec, "instructions_file", "instructions");
|
|
22
|
+
const readDescription = (fs, dir, rec) => readSidecar(fs, dir, rec, "description_file", "description");
|
|
20
23
|
|
|
21
24
|
// Relative paths of every file under root (recursing into subdirs like scripts/).
|
|
22
25
|
function walkSkillFiles(fs, root, rel = "") {
|
|
@@ -86,8 +89,10 @@ export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs =
|
|
|
86
89
|
// Lazy + memoized: destination member user_ids, only listed when an agent needs them.
|
|
87
90
|
let destMemberIds = null;
|
|
88
91
|
const getDestMemberIds = () => destMemberIds ??= new Set(listWorkspaceMembers(cli).map((m) => m.user_id));
|
|
89
|
-
let created = 0, updated = 0;
|
|
90
|
-
|
|
92
|
+
let created = 0, updated = 0, reused = 0;
|
|
93
|
+
// Includes archived agents so a bundle name matching a retired agent restores
|
|
94
|
+
// and reuses it instead of failing to create under the held name.
|
|
95
|
+
const existing = listAgentsIncludingArchived(cli);
|
|
91
96
|
|
|
92
97
|
for (const a of manifest.agents) {
|
|
93
98
|
const rec = JSON.parse(fs.readFileSync(`${dir}/${a.file}`, "utf8"));
|
|
@@ -98,7 +103,8 @@ export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs =
|
|
|
98
103
|
"--visibility", rec.visibility ?? "private",
|
|
99
104
|
"--max-concurrent-tasks", String(rec.max_concurrent_tasks ?? 6),
|
|
100
105
|
];
|
|
101
|
-
|
|
106
|
+
const description = readDescription(fs, dir, rec);
|
|
107
|
+
if (description) common.push("--description", description);
|
|
102
108
|
const instructions = readInstructions(fs, dir, rec);
|
|
103
109
|
if (instructions) common.push("--instructions", instructions);
|
|
104
110
|
if (rec.model) common.push("--model", rec.model);
|
|
@@ -108,7 +114,14 @@ export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs =
|
|
|
108
114
|
if (rec.service_tier) common.push("--service-tier", rec.service_tier);
|
|
109
115
|
const match = findByName(existing, rec.name);
|
|
110
116
|
let id;
|
|
111
|
-
if (match) {
|
|
117
|
+
if (match && match.archived_at) {
|
|
118
|
+
// Restored agent then updated exactly like an active-name match — the
|
|
119
|
+
// matched id is now active, so a re-import of this bundle will find it
|
|
120
|
+
// via the plain `match` branch below and never restore twice.
|
|
121
|
+
cli.run(["agent", "restore", match.id]);
|
|
122
|
+
cli.run(["agent", "update", match.id, "--runtime-id", targetRuntime, ...common]);
|
|
123
|
+
id = match.id; reused++;
|
|
124
|
+
} else if (match) {
|
|
112
125
|
cli.run(["agent", "update", match.id, "--runtime-id", targetRuntime, ...common]);
|
|
113
126
|
id = match.id; updated++;
|
|
114
127
|
} else {
|
|
@@ -178,7 +191,7 @@ export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs =
|
|
|
178
191
|
}
|
|
179
192
|
}
|
|
180
193
|
}
|
|
181
|
-
return { idMap, sourceIdMap, created, updated, secretsApplyFailures, avatarApplyFailures, avatarUnsupported, permissionApplyFailures, permissionUnsupported };
|
|
194
|
+
return { idMap, sourceIdMap, created, updated, reused, secretsApplyFailures, avatarApplyFailures, avatarUnsupported, permissionApplyFailures, permissionUnsupported };
|
|
182
195
|
}
|
|
183
196
|
|
|
184
197
|
// Rewrites `mention://agent/<id>` links (e.g. `[@dev-backend](mention://agent/<id>)`)
|
|
@@ -260,7 +273,8 @@ export function importProjects({ cli, manifest, dir, agentIdMap, fs = nodeFs })
|
|
|
260
273
|
for (const entry of manifest.projects ?? []) {
|
|
261
274
|
const rec = JSON.parse(fs.readFileSync(`${dir}/${entry.file}`, "utf8"));
|
|
262
275
|
const flags = ["--title", rec.title];
|
|
263
|
-
|
|
276
|
+
const description = readDescription(fs, dir, rec);
|
|
277
|
+
if (description) flags.push("--description", description);
|
|
264
278
|
if (rec.icon) flags.push("--icon", rec.icon);
|
|
265
279
|
if (rec.status) flags.push("--status", rec.status);
|
|
266
280
|
if (rec.due_date) flags.push("--due-date", rec.due_date);
|
|
@@ -338,7 +352,8 @@ export function importAutopilots({ cli, manifest, dir, agentIdMap, fs = nodeFs }
|
|
|
338
352
|
|
|
339
353
|
for (const rec of recs) {
|
|
340
354
|
const common = [];
|
|
341
|
-
|
|
355
|
+
const description = readDescription(fs, dir, rec);
|
|
356
|
+
if (description) common.push("--description", description);
|
|
342
357
|
if (rec.issue_title_template) common.push("--issue-title-template", rec.issue_title_template);
|
|
343
358
|
// priority is never present today — the multica CLI/API accepts it on write but
|
|
344
359
|
// never returns it on read, so export can't capture the source's real value.
|
|
@@ -448,7 +463,7 @@ export function importBundle({ cli, dir, runtimeMap, include, fs = nodeFs }) {
|
|
|
448
463
|
: { idMap: new Map(), created: 0, updated: 0 };
|
|
449
464
|
const agentRes = inc.has("agents")
|
|
450
465
|
? importAgents({ cli, manifest, dir, skillIdMap: skillRes.idMap, runtimeMap: effective, fs })
|
|
451
|
-
: { idMap: new Map(), sourceIdMap: new Map(), created: 0, updated: 0, secretsApplyFailures: [], avatarApplyFailures: [], avatarUnsupported: [], permissionApplyFailures: [], permissionUnsupported: [] };
|
|
466
|
+
: { idMap: new Map(), sourceIdMap: new Map(), created: 0, updated: 0, reused: 0, secretsApplyFailures: [], avatarApplyFailures: [], avatarUnsupported: [], permissionApplyFailures: [], permissionUnsupported: [] };
|
|
452
467
|
// Runs after every agent exists so forward-referencing mentions resolve.
|
|
453
468
|
const mentionRes = inc.has("agents")
|
|
454
469
|
? rewriteAgentMentions({ cli, manifest, dir, agentIdMap: agentRes.idMap, sourceIdMap: agentRes.sourceIdMap, fs })
|
|
@@ -460,6 +475,7 @@ export function importBundle({ cli, dir, runtimeMap, include, fs = nodeFs }) {
|
|
|
460
475
|
if (inc.has("squads")) {
|
|
461
476
|
for (const squad of manifest.squads ?? []) {
|
|
462
477
|
squad.instructions = readInstructions(fs, dir, squad);
|
|
478
|
+
squad.description = readDescription(fs, dir, squad);
|
|
463
479
|
const r = importSquad({ cli, squad, agentIdMap: agentRes.idMap, sourceIdMap: agentRes.sourceIdMap });
|
|
464
480
|
if (r.skipped) { squadsSkipped.push(squad.name); continue; }
|
|
465
481
|
squadIdMap.set(squad.name, r.newId);
|
|
@@ -482,6 +498,9 @@ export function importBundle({ cli, dir, runtimeMap, include, fs = nodeFs }) {
|
|
|
482
498
|
include: [...inc],
|
|
483
499
|
created: { skills: skillRes.created, agents: agentRes.created, squads: squadsCreated, projects: projectRes.created, autopilots: autopilotRes.created },
|
|
484
500
|
updated: { skills: skillRes.updated, agents: agentRes.updated, squads: squadsUpdated, projects: projectRes.updated, autopilots: autopilotRes.updated },
|
|
501
|
+
// Agents restored from archived + updated to the bundle's definition — distinct
|
|
502
|
+
// from a plain update (an active-name match). Only agents can be reused today.
|
|
503
|
+
reused: { agents: agentRes.reused },
|
|
485
504
|
mentionsRewritten: mentionRes.updated,
|
|
486
505
|
skillIdMap: Object.fromEntries(skillRes.idMap),
|
|
487
506
|
agentIdMap: Object.fromEntries(agentRes.idMap),
|
|
@@ -531,6 +550,16 @@ export function preflight({ cli, dir, runtimeMap, include, fs = nodeFs }) {
|
|
|
531
550
|
? `${u.srcId} (provider "${u.provider}": ${u.matchCount} matches, expected 1)`
|
|
532
551
|
: `${u.srcId} (no provider recorded)` });
|
|
533
552
|
}
|
|
553
|
+
|
|
554
|
+
// Read-only check, mirrors importAgents' restore-and-reuse match — never
|
|
555
|
+
// calls restore itself, just reports what a real import would do.
|
|
556
|
+
const existingAgents = listAgentsIncludingArchived(cli);
|
|
557
|
+
for (const a of manifest.agents ?? []) {
|
|
558
|
+
const match = findByName(existingAgents, a.name);
|
|
559
|
+
if (match && match.archived_at) {
|
|
560
|
+
incompatibilities.push({ type: "agent-archived-will-restore", detail: `${a.name} (archived in destination — import would restore and reuse it)` });
|
|
561
|
+
}
|
|
562
|
+
}
|
|
534
563
|
}
|
|
535
564
|
|
|
536
565
|
if (inc.has("projects")) {
|
|
@@ -37,13 +37,7 @@ If the user specified an output directory, use it verbatim. Otherwise default to
|
|
|
37
37
|
First resolve `<workspace-name>`:
|
|
38
38
|
|
|
39
39
|
- If the user named a source workspace (the value you would pass as `--workspace <name>`), use that name.
|
|
40
|
-
- Otherwise,
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
multica workspace get --output json
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
and take its `.name`.
|
|
40
|
+
- Otherwise, run `multica workspace get --output json` and take its `.name` (the current default workspace).
|
|
47
41
|
|
|
48
42
|
Slugify the resolved name for filesystem safety — lowercase it, replace each run of non-`[a-z0-9]` characters with a single `-`, and trim leading/trailing `-` (the same rule the scripts use internally).
|
|
49
43
|
|
|
@@ -70,7 +64,12 @@ Pass `--scope all` (with no `--id`) to export the **entire workspace** — every
|
|
|
70
64
|
|
|
71
65
|
Exporting a project (or `projects`/`all`) also **bundles the project's lead agent** so the bundle is self-contained; projects carry metadata only (title, description, icon, priority, status, dates, lead mapping) plus their attached resource records — never issues. On import, only `github_repo` resources are portable and recreated; other resource types are reported and skipped.
|
|
72
66
|
|
|
73
|
-
The script writes `manifest.json`, skill `SKILL.md` files, agent JSON files, and squad JSON files into `<dir>`.
|
|
67
|
+
The script writes `manifest.json`, skill `SKILL.md` files, agent JSON files, and squad JSON files into `<dir>`. Every resource's prose fields are externalized to sibling Markdown files, never embedded in the JSON — so they are easy to read, diff, and edit:
|
|
68
|
+
|
|
69
|
+
- **instructions** (system prompt / charter, agents and squads) → `<slug>.md`, referenced by an `instructions_file` key.
|
|
70
|
+
- **description** (agents, squads, projects, autopilots) → `<slug>.description.md`, referenced by a `description_file` key.
|
|
71
|
+
|
|
72
|
+
An empty field gets no file and no `*_file` key. Skills keep their own layout — the description lives in `SKILL.md` frontmatter and the body is the content.
|
|
74
73
|
|
|
75
74
|
Avatars are captured automatically: an agent's uploaded-image avatar is downloaded into the bundle (`agents/<slug>.avatar.<ext>`) and referenced by `avatar_file`; emoji avatars (agents and squads) and a squad's avatar are recorded as the `avatar_url` string.
|
|
76
75
|
|
|
@@ -13,7 +13,7 @@ Import a local Multica bundle (produced by the export skill) into a target works
|
|
|
13
13
|
You need the exact workspace name as registered in Multica for `--workspace` in the steps below.
|
|
14
14
|
|
|
15
15
|
- If the user named a target workspace, use it.
|
|
16
|
-
- If the user did **not** name one, default to the **basename of the import folder** — e.g. importing from `export/mx-workspace` defaults the target workspace to `mx-workspace`. State the inferred workspace name to the user before continuing.
|
|
16
|
+
- If the user did **not** name one, default to the **basename of the import folder** — e.g. importing from `export/mx-workspace` defaults the target workspace to `mx-workspace`. State the inferred workspace name to the user before continuing. This default is reliable for whole-workspace bundles (`export/<workspace-name>`); a single-resource bundle nested under `export/<workspace-name>/<slug>-<type>` has the resource slug as its basename, not the workspace — name the target workspace explicitly in that case.
|
|
17
17
|
|
|
18
18
|
No existence check is needed here: the Step 2 dry-run fails with `Unknown workspace "<name>"` if the inferred workspace is not present in the target account, at which point ask the user for the correct name.
|
|
19
19
|
|
|
@@ -70,7 +70,7 @@ If it instead aborts with `Unresolved autopilot assignees: ...`, that is not fix
|
|
|
70
70
|
|
|
71
71
|
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.
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Prose fields are read back from each resource's sibling `.md` when a `*_file` key points at it — **instructions** from `<slug>.md` (agents, squads) and **description** from `<slug>.description.md` (agents, squads, projects, autopilots). Editing that Markdown is the supported way to review and enhance the prose before import. Older bundles that predate the split (prose inline in the JSON, no `*_file` key) still import unchanged.
|
|
74
74
|
|
|
75
75
|
Avatars are restored automatically, but **only when the target resource has none** — an existing agent or squad that already carries an avatar is never overwritten. New agents get their bundled image re-uploaded; new squads get their `avatar_url` (emoji or URL) set. An agent whose source avatar was an emoji can't be restored (the CLI has no emoji setter for agents) and is reported as unsupported.
|
|
76
76
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plugin-validator",
|
|
3
3
|
"displayName": "Plugin Validator",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.45",
|
|
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.45",
|
|
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"
|