@drunkcoding/agents-and-skills 0.0.43 → 0.0.44

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.43",
15
+ "version": "0.0.44",
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.43",
29
+ "version": "0.0.44",
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.43",
44
+ "version": "0.0.44",
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.43",
60
+ "version": "0.0.44",
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.43",
76
+ "version": "0.0.44",
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.43",
3
+ "version": "0.0.44",
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.43",
4
+ "version": "0.0.44",
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.43",
3
+ "version": "0.0.44",
4
4
  "description": "Export, import, and sync Multica skills, agents, and squads between workspaces via the multica CLI."
5
5
  }
@@ -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,7 +1,7 @@
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
 
@@ -31,7 +31,7 @@ export function redactAgent(a) {
31
31
  // a is a normalized agent from getAgent, with `custom_env`/
32
32
  // `custom_env_fetch_failed` attached by the caller (collectAgent) — getAgent
33
33
  // 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;
34
+ const { id, has_custom_env, mcp_config_redacted, custom_env_fetch_failed, mcp_config, custom_env, skills, runtime_id, instructions, archived_at, ...rest } = a;
35
35
  const mcpUsable = !mcp_config_redacted && nonEmpty(mcp_config);
36
36
  const envUsable = !custom_env_fetch_failed && nonEmpty(custom_env);
37
37
  // mcp_config_redacted / custom_env_fetch_failed alone still flag hadSecrets even
@@ -96,10 +96,15 @@ function collectSkill(cli, id, skills) {
96
96
  }
97
97
 
98
98
  // 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.
99
+ // normalized agent, its redaction result, and its skill names. Returns
100
+ // `{ archived: true, name }` instead of collecting when the agent is archived —
101
+ // callers reached via a squad/project/explicit id lookup must decide how to
102
+ // handle that (the workspace-listing loop never hits this: listAgents() already
103
+ // excludes archived agents server-side).
100
104
  function collectAgent(cli, id, agentsById, skills, providerById) {
101
105
  if (agentsById.has(id)) return agentsById.get(id);
102
106
  const a = getAgent(cli, id);
107
+ if (a.archived_at) return { archived: true, name: a.name };
103
108
  a.source_runtime_provider = providerById.get(a.runtime_id) ?? null;
104
109
  a.custom_env = {};
105
110
  a.custom_env_fetch_failed = false;
@@ -117,22 +122,6 @@ function collectAgent(cli, id, agentsById, skills, providerById) {
117
122
  return entry;
118
123
  }
119
124
 
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
125
  export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs = nodeFs, download = fetchBinary }) {
137
126
  const skills = new Map(); // name -> normalized skill
138
127
  const agentsById = new Map(); // id -> { raw, red, skill_names }
@@ -144,12 +133,34 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
144
133
  let providerById = null;
145
134
  const getProviderById = () => providerById ??= new Map(listRuntimes(cli).map((r) => [r.id, r.provider]));
146
135
 
147
- // Collect a squad's agents (leader + members) and return the squad bundle object.
136
+ // Archived agents skipped anywhere below, deduped by (name, path, detail)
137
+ // the operator-facing "why isn't X in my bundle" report.
138
+ const archivedAgentsSkipped = [];
139
+ const seenArchivedSkip = new Set();
140
+ function recordArchivedSkip(name, path, detail) {
141
+ const key = [name, path, detail ?? ""].join("|");
142
+ if (seenArchivedSkip.has(key)) return;
143
+ seenArchivedSkip.add(key);
144
+ archivedAgentsSkipped.push(detail ? { name, path, detail } : { name, path });
145
+ }
146
+
147
+ // Collect a squad's agents (leader + members) and return the squad bundle
148
+ // object — or null when the leader is archived, since a squad can't import
149
+ // without one (the whole squad is excluded from the bundle, not emitted leaderless).
148
150
  function collectOneSquad(squadId) {
149
151
  const sq = getSquad(cli, squadId);
150
- const members = getSquadMembers(cli, squadId).filter((m) => m.member_type === "agent");
151
- for (const m of members) collectAgent(cli, m.member_id, agentsById, skills, getProviderById());
152
- if (!agentsById.has(sq.leader_id)) collectAgent(cli, sq.leader_id, agentsById, skills, getProviderById());
152
+ const leaderEntry = collectAgent(cli, sq.leader_id, agentsById, skills, getProviderById());
153
+ if (leaderEntry.archived) {
154
+ recordArchivedSkip(leaderEntry.name, "squad leader", sq.name);
155
+ return null;
156
+ }
157
+ const allMembers = getSquadMembers(cli, squadId).filter((m) => m.member_type === "agent");
158
+ const members = [];
159
+ for (const m of allMembers) {
160
+ const entry = collectAgent(cli, m.member_id, agentsById, skills, getProviderById());
161
+ if (entry.archived) { recordArchivedSkip(entry.name, "squad member", sq.name); continue; }
162
+ members.push(m);
163
+ }
153
164
  const nameOf = (id) => agentsById.get(id)?.raw.name;
154
165
  return {
155
166
  name: sq.name, description: sq.description, instructions: sq.instructions, avatar_url: sq.avatar_url,
@@ -158,6 +169,30 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
158
169
  };
159
170
  }
160
171
 
172
+ // Collect a project's portable metadata + its lead agent (bundled, like a
173
+ // squad leader) and github_repo/other resources. An archived lead is dropped
174
+ // (project still exports, just with no lead set) — unlike a squad, a project
175
+ // has no hard requirement for one.
176
+ function collectProject(id) {
177
+ const p = getProject(cli, id);
178
+ let lead_name = null, lead_type = p.lead_type, lead_source_id = p.lead_id;
179
+ if (p.lead_type === "agent" && p.lead_id) {
180
+ const entry = collectAgent(cli, p.lead_id, agentsById, skills, getProviderById());
181
+ if (entry.archived) {
182
+ recordArchivedSkip(entry.name, "project lead", p.title);
183
+ lead_type = null; lead_source_id = null;
184
+ } else {
185
+ lead_name = entry.raw.name;
186
+ }
187
+ }
188
+ return {
189
+ title: p.title, description: p.description, icon: p.icon,
190
+ priority: p.priority, status: p.status, due_date: p.due_date, start_date: p.start_date,
191
+ source_id: id, lead_type, lead_name, lead_source_id,
192
+ resources: getProjectResources(cli, id),
193
+ };
194
+ }
195
+
161
196
  // Collect a single autopilot: its assignee (agent, or squad + members + skills)
162
197
  // and, when present, the destination-portable project title / subscriber names.
163
198
  // Webhook trigger secrets (url/token) are never read into the bundle — only
@@ -166,11 +201,11 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
166
201
  const ap = getAutopilot(cli, autopilotId);
167
202
  let assignee_name = null;
168
203
  if (ap.assignee_type === "agent") {
169
- assignee_name = collectAgent(cli, ap.assignee_id, agentsById, skills, getProviderById()).raw.name;
204
+ const entry = collectAgent(cli, ap.assignee_id, agentsById, skills, getProviderById());
205
+ assignee_name = entry.archived ? null : entry.raw.name;
170
206
  } else if (ap.assignee_type === "squad") {
171
207
  const sq = collectOneSquad(ap.assignee_id);
172
- squads.push(sq);
173
- assignee_name = sq.name;
208
+ if (sq) { squads.push(sq); assignee_name = sq.name; }
174
209
  }
175
210
  let project_title = null;
176
211
  if (ap.project_id) {
@@ -193,16 +228,24 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
193
228
  }
194
229
 
195
230
  if (scope === "skill") collectSkill(cli, ids.skillId, skills);
196
- else if (scope === "agent") collectAgent(cli, ids.agentId, agentsById, skills, getProviderById());
197
- else if (scope === "squad") squads.push(collectOneSquad(ids.squadId));
198
- else if (scope === "project") projects.push(collectProject(cli, ids.projectId, agentsById, skills, getProviderById()));
199
- else if (scope === "projects") for (const p of listProjects(cli)) projects.push(collectProject(cli, p.id, agentsById, skills, getProviderById()));
231
+ else if (scope === "agent") {
232
+ const entry = collectAgent(cli, ids.agentId, agentsById, skills, getProviderById());
233
+ if (entry.archived) throw new Error(`Cannot export agent "${entry.name}": it is archived`);
234
+ }
235
+ else if (scope === "squad") { const sq = collectOneSquad(ids.squadId); if (sq) squads.push(sq); }
236
+ else if (scope === "project") projects.push(collectProject(ids.projectId));
237
+ else if (scope === "projects") for (const p of listProjects(cli)) projects.push(collectProject(p.id));
200
238
  else if (scope === "autopilot") autopilots.push(collectOneAutopilot(ids.autopilotId));
201
239
  else if (scope === "all") {
202
240
  for (const s of listSkills(cli)) collectSkill(cli, s.id, skills);
203
241
  for (const a of listAgents(cli)) collectAgent(cli, a.id, agentsById, skills, getProviderById());
204
- for (const sq of listSquads(cli)) squads.push(collectOneSquad(sq.id));
205
- for (const p of listProjects(cli)) projects.push(collectProject(cli, p.id, agentsById, skills, getProviderById()));
242
+ for (const sq of listSquads(cli)) { const built = collectOneSquad(sq.id); if (built) squads.push(built); }
243
+ for (const p of listProjects(cli)) projects.push(collectProject(p.id));
244
+ // listAgents() above already excludes archived agents server-side — surface
245
+ // that exclusion in the report too, not just the squad/project paths.
246
+ for (const a of listAgentsIncludingArchived(cli)) {
247
+ if (a.archived_at) recordArchivedSkip(a.name, "workspace listing");
248
+ }
206
249
  }
207
250
 
208
251
  // Orphan-skill cleanup: drop skills that no exported agent references via its
@@ -290,7 +333,7 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
290
333
  }
291
334
  fs.writeFileSync(`${outDir}/manifest.json`, JSON.stringify(manifest, null, 2));
292
335
  return {
293
- manifest, warnings, pruned_skills,
336
+ manifest, warnings, pruned_skills, archivedAgentsSkipped,
294
337
  autopilotWebhookTriggers: autopilots.filter((a) => a.had_webhook_trigger).map((a) => a.title),
295
338
  };
296
339
  }
@@ -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) {
@@ -86,8 +86,10 @@ export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs =
86
86
  // Lazy + memoized: destination member user_ids, only listed when an agent needs them.
87
87
  let destMemberIds = null;
88
88
  const getDestMemberIds = () => destMemberIds ??= new Set(listWorkspaceMembers(cli).map((m) => m.user_id));
89
- let created = 0, updated = 0;
90
- const existing = listAgents(cli);
89
+ let created = 0, updated = 0, reused = 0;
90
+ // Includes archived agents so a bundle name matching a retired agent restores
91
+ // and reuses it instead of failing to create under the held name.
92
+ const existing = listAgentsIncludingArchived(cli);
91
93
 
92
94
  for (const a of manifest.agents) {
93
95
  const rec = JSON.parse(fs.readFileSync(`${dir}/${a.file}`, "utf8"));
@@ -108,7 +110,14 @@ export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs =
108
110
  if (rec.service_tier) common.push("--service-tier", rec.service_tier);
109
111
  const match = findByName(existing, rec.name);
110
112
  let id;
111
- if (match) {
113
+ if (match && match.archived_at) {
114
+ // Restored agent then updated exactly like an active-name match — the
115
+ // matched id is now active, so a re-import of this bundle will find it
116
+ // via the plain `match` branch below and never restore twice.
117
+ cli.run(["agent", "restore", match.id]);
118
+ cli.run(["agent", "update", match.id, "--runtime-id", targetRuntime, ...common]);
119
+ id = match.id; reused++;
120
+ } else if (match) {
112
121
  cli.run(["agent", "update", match.id, "--runtime-id", targetRuntime, ...common]);
113
122
  id = match.id; updated++;
114
123
  } else {
@@ -178,7 +187,7 @@ export function importAgents({ cli, manifest, dir, skillIdMap, runtimeMap, fs =
178
187
  }
179
188
  }
180
189
  }
181
- return { idMap, sourceIdMap, created, updated, secretsApplyFailures, avatarApplyFailures, avatarUnsupported, permissionApplyFailures, permissionUnsupported };
190
+ return { idMap, sourceIdMap, created, updated, reused, secretsApplyFailures, avatarApplyFailures, avatarUnsupported, permissionApplyFailures, permissionUnsupported };
182
191
  }
183
192
 
184
193
  // Rewrites `mention://agent/<id>` links (e.g. `[@dev-backend](mention://agent/<id>)`)
@@ -448,7 +457,7 @@ export function importBundle({ cli, dir, runtimeMap, include, fs = nodeFs }) {
448
457
  : { idMap: new Map(), created: 0, updated: 0 };
449
458
  const agentRes = inc.has("agents")
450
459
  ? 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: [] };
460
+ : { idMap: new Map(), sourceIdMap: new Map(), created: 0, updated: 0, reused: 0, secretsApplyFailures: [], avatarApplyFailures: [], avatarUnsupported: [], permissionApplyFailures: [], permissionUnsupported: [] };
452
461
  // Runs after every agent exists so forward-referencing mentions resolve.
453
462
  const mentionRes = inc.has("agents")
454
463
  ? rewriteAgentMentions({ cli, manifest, dir, agentIdMap: agentRes.idMap, sourceIdMap: agentRes.sourceIdMap, fs })
@@ -482,6 +491,9 @@ export function importBundle({ cli, dir, runtimeMap, include, fs = nodeFs }) {
482
491
  include: [...inc],
483
492
  created: { skills: skillRes.created, agents: agentRes.created, squads: squadsCreated, projects: projectRes.created, autopilots: autopilotRes.created },
484
493
  updated: { skills: skillRes.updated, agents: agentRes.updated, squads: squadsUpdated, projects: projectRes.updated, autopilots: autopilotRes.updated },
494
+ // Agents restored from archived + updated to the bundle's definition — distinct
495
+ // from a plain update (an active-name match). Only agents can be reused today.
496
+ reused: { agents: agentRes.reused },
485
497
  mentionsRewritten: mentionRes.updated,
486
498
  skillIdMap: Object.fromEntries(skillRes.idMap),
487
499
  agentIdMap: Object.fromEntries(agentRes.idMap),
@@ -531,6 +543,16 @@ export function preflight({ cli, dir, runtimeMap, include, fs = nodeFs }) {
531
543
  ? `${u.srcId} (provider "${u.provider}": ${u.matchCount} matches, expected 1)`
532
544
  : `${u.srcId} (no provider recorded)` });
533
545
  }
546
+
547
+ // Read-only check, mirrors importAgents' restore-and-reuse match — never
548
+ // calls restore itself, just reports what a real import would do.
549
+ const existingAgents = listAgentsIncludingArchived(cli);
550
+ for (const a of manifest.agents ?? []) {
551
+ const match = findByName(existingAgents, a.name);
552
+ if (match && match.archived_at) {
553
+ incompatibilities.push({ type: "agent-archived-will-restore", detail: `${a.name} (archived in destination — import would restore and reuse it)` });
554
+ }
555
+ }
534
556
  }
535
557
 
536
558
  if (inc.has("projects")) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "plugin-validator",
3
3
  "displayName": "Plugin Validator",
4
- "version": "0.0.43",
4
+ "version": "0.0.44",
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.43",
4
+ "version": "0.0.44",
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.43",
3
+ "version": "0.0.44",
4
4
  "description": "Step-by-step wizard for generating technical diagrams as SVG+PNG.",
5
5
  "author": {
6
6
  "name": "steven"