@drunkcoding/agents-and-skills 0.0.44 → 0.0.46

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.
Files changed (107) hide show
  1. package/.claude-plugin/marketplace.json +2 -47
  2. package/README.md +3 -6
  3. package/package.json +4 -1
  4. package/plugins/multica-tool/.claude-plugin/plugin.json +1 -1
  5. package/plugins/multica-tool/scripts/lib.mjs +53 -4
  6. package/plugins/multica-tool/scripts/multica-export.mjs +170 -50
  7. package/plugins/multica-tool/scripts/multica-import.mjs +209 -27
  8. package/plugins/multica-tool/skills/export/SKILL.md +91 -22
  9. package/plugins/multica-tool/skills/import/SKILL.md +24 -9
  10. package/plugins/multica-tool/skills/sync/SKILL.md +1 -1
  11. package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
  12. package/plugins/html-effectiveness/.claude-plugin/plugin.json +0 -17
  13. package/plugins/html-effectiveness/README.md +0 -28
  14. package/plugins/html-effectiveness/agents/report-builder.md +0 -66
  15. package/plugins/html-effectiveness/assets/base.css +0 -51
  16. package/plugins/html-effectiveness/assets/base.js +0 -11
  17. package/plugins/html-effectiveness/assets/charts.js +0 -1
  18. package/plugins/html-effectiveness/assets/components.css +0 -10
  19. package/plugins/html-effectiveness/commands/html-report.md +0 -13
  20. package/plugins/html-effectiveness/docs/template-gallery.md +0 -28
  21. package/plugins/html-effectiveness/package.json +0 -6
  22. package/plugins/html-effectiveness/scripts/_tmp.js +0 -37
  23. package/plugins/html-effectiveness/scripts/catalog.js +0 -47
  24. package/plugins/html-effectiveness/scripts/extract.js +0 -104
  25. package/plugins/html-effectiveness/scripts/mustache.js +0 -90
  26. package/plugins/html-effectiveness/scripts/render.js +0 -176
  27. package/plugins/html-effectiveness/skills/html-effectiveness/SKILL.md +0 -94
  28. package/plugins/html-effectiveness/templates/01-exploration-code-approaches.html.tmpl +0 -400
  29. package/plugins/html-effectiveness/templates/02-exploration-visual-designs.html.tmpl +0 -456
  30. package/plugins/html-effectiveness/templates/03-code-review-pr.html.tmpl +0 -581
  31. package/plugins/html-effectiveness/templates/04-code-understanding.html.tmpl +0 -443
  32. package/plugins/html-effectiveness/templates/05-design-system.html.tmpl +0 -578
  33. package/plugins/html-effectiveness/templates/06-component-variants.html.tmpl +0 -557
  34. package/plugins/html-effectiveness/templates/07-prototype-animation.html.tmpl +0 -417
  35. package/plugins/html-effectiveness/templates/08-prototype-interaction.html.tmpl +0 -363
  36. package/plugins/html-effectiveness/templates/09-slide-deck.html.tmpl +0 -535
  37. package/plugins/html-effectiveness/templates/10-svg-illustrations.html.tmpl +0 -444
  38. package/plugins/html-effectiveness/templates/11-status-report.html.tmpl +0 -152
  39. package/plugins/html-effectiveness/templates/12-incident-report.html.tmpl +0 -547
  40. package/plugins/html-effectiveness/templates/13-flowchart-diagram.html.tmpl +0 -361
  41. package/plugins/html-effectiveness/templates/14-research-feature-explainer.html.tmpl +0 -348
  42. package/plugins/html-effectiveness/templates/15-research-concept-explainer.html.tmpl +0 -334
  43. package/plugins/html-effectiveness/templates/16-implementation-plan.html.tmpl +0 -657
  44. package/plugins/html-effectiveness/templates/17-pr-writeup.html.tmpl +0 -553
  45. package/plugins/html-effectiveness/templates/18-editor-triage-board.html.tmpl +0 -541
  46. package/plugins/html-effectiveness/templates/19-editor-feature-flags.html.tmpl +0 -627
  47. package/plugins/html-effectiveness/templates/20-editor-prompt-tuner.html.tmpl +0 -687
  48. package/plugins/html-effectiveness/templates/manifest.json +0 -441
  49. package/plugins/team-share/.claude-plugin/plugin.json +0 -18
  50. package/plugins/team-share/README.md +0 -46
  51. package/plugins/team-share/agents/team-share.md +0 -53
  52. package/plugins/team-share/commands/team-share.md +0 -4
  53. package/plugins/team-share/skills/claude-config/SKILL.md +0 -143
  54. package/plugins/team-share/skills/codegraph-setup/SKILL.md +0 -81
  55. package/plugins/team-share/skills/understand-setup/SKILL.md +0 -115
  56. package/plugins/tech-graph/.claude-plugin/plugin.json +0 -17
  57. package/plugins/tech-graph/README.md +0 -66
  58. package/plugins/tech-graph/agents/tech-graph.md +0 -97
  59. package/plugins/tech-graph/commands/tech-graph.md +0 -13
  60. package/plugins/tech-graph/scripts/check-deps.sh +0 -55
  61. package/plugins/tech-graph/skills/tech-graph/LICENSE +0 -21
  62. package/plugins/tech-graph/skills/tech-graph/README.md +0 -555
  63. package/plugins/tech-graph/skills/tech-graph/README.zh.md +0 -514
  64. package/plugins/tech-graph/skills/tech-graph/SKILL.md +0 -263
  65. package/plugins/tech-graph/skills/tech-graph/agents/openai.yaml +0 -4
  66. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style1-flat.png +0 -0
  67. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style2-dark.png +0 -0
  68. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
  69. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style4-notion.png +0 -0
  70. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style5-glass.png +0 -0
  71. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style6-claude.png +0 -0
  72. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style7-openai.png +0 -0
  73. package/plugins/tech-graph/skills/tech-graph/fixtures/agent-memory-types-style4.json +0 -181
  74. package/plugins/tech-graph/skills/tech-graph/fixtures/api-flow-style7.json +0 -40
  75. package/plugins/tech-graph/skills/tech-graph/fixtures/mem0-style1.json +0 -297
  76. package/plugins/tech-graph/skills/tech-graph/fixtures/microservices-style3.json +0 -64
  77. package/plugins/tech-graph/skills/tech-graph/fixtures/multi-agent-style5.json +0 -45
  78. package/plugins/tech-graph/skills/tech-graph/fixtures/system-architecture-style6.json +0 -48
  79. package/plugins/tech-graph/skills/tech-graph/fixtures/tool-call-style2.json +0 -182
  80. package/plugins/tech-graph/skills/tech-graph/package.json +0 -42
  81. package/plugins/tech-graph/skills/tech-graph/references/diagram-types.md +0 -171
  82. package/plugins/tech-graph/skills/tech-graph/references/icons.md +0 -406
  83. package/plugins/tech-graph/skills/tech-graph/references/style-1-flat-icon.md +0 -108
  84. package/plugins/tech-graph/skills/tech-graph/references/style-2-dark-terminal.md +0 -107
  85. package/plugins/tech-graph/skills/tech-graph/references/style-3-blueprint.md +0 -113
  86. package/plugins/tech-graph/skills/tech-graph/references/style-4-notion-clean.md +0 -95
  87. package/plugins/tech-graph/skills/tech-graph/references/style-5-glassmorphism.md +0 -125
  88. package/plugins/tech-graph/skills/tech-graph/references/style-6-claude-official.md +0 -209
  89. package/plugins/tech-graph/skills/tech-graph/references/style-7-openai.md +0 -215
  90. package/plugins/tech-graph/skills/tech-graph/references/style-diagram-matrix.md +0 -135
  91. package/plugins/tech-graph/skills/tech-graph/references/svg-layout-best-practices.md +0 -100
  92. package/plugins/tech-graph/skills/tech-graph/references/svg-to-png.md +0 -115
  93. package/plugins/tech-graph/skills/tech-graph/scripts/README.md +0 -269
  94. package/plugins/tech-graph/skills/tech-graph/scripts/generate-diagram.sh +0 -181
  95. package/plugins/tech-graph/skills/tech-graph/scripts/generate-from-template.py +0 -1587
  96. package/plugins/tech-graph/skills/tech-graph/scripts/test-all-styles.sh +0 -143
  97. package/plugins/tech-graph/skills/tech-graph/scripts/validate-svg.sh +0 -306
  98. package/plugins/tech-graph/skills/tech-graph/templates/agent-architecture.svg +0 -28
  99. package/plugins/tech-graph/skills/tech-graph/templates/architecture.svg +0 -23
  100. package/plugins/tech-graph/skills/tech-graph/templates/comparison-matrix.svg +0 -14
  101. package/plugins/tech-graph/skills/tech-graph/templates/data-flow.svg +0 -28
  102. package/plugins/tech-graph/skills/tech-graph/templates/er-diagram.svg +0 -21
  103. package/plugins/tech-graph/skills/tech-graph/templates/flowchart.svg +0 -21
  104. package/plugins/tech-graph/skills/tech-graph/templates/sequence.svg +0 -20
  105. package/plugins/tech-graph/skills/tech-graph/templates/state-machine.svg +0 -20
  106. package/plugins/tech-graph/skills/tech-graph/templates/timeline.svg +0 -19
  107. package/plugins/tech-graph/skills/tech-graph/templates/use-case.svg +0 -21
@@ -8,40 +8,11 @@
8
8
  "description": "Personal agents and skills marketplace"
9
9
  },
10
10
  "plugins": [
11
- {
12
- "name": "tech-graph",
13
- "source": "./plugins/tech-graph",
14
- "description": "6-step wizard for technical diagrams (SVG/PNG) via fireworks-tech-graph",
15
- "version": "0.0.44",
16
- "category": "diagram",
17
- "keywords": [
18
- "diagram",
19
- "svg",
20
- "architecture",
21
- "uml",
22
- "flowchart"
23
- ]
24
- },
25
- {
26
- "name": "html-effectiveness",
27
- "source": "./plugins/html-effectiveness",
28
- "description": "Generate self-contained interactive HTML reports from 20 upstream templates via a conversational agent.",
29
- "version": "0.0.44",
30
- "category": "reports",
31
- "keywords": [
32
- "html",
33
- "report",
34
- "status",
35
- "incident",
36
- "slide-deck",
37
- "decision-matrix"
38
- ]
39
- },
40
11
  {
41
12
  "name": "plugin-validator",
42
13
  "source": "./plugins/plugin-validator",
43
14
  "description": "Orchestrated validator for Claude Code plugins — validates skills, agents, commands, and hooks across every plugin under plugins/**.",
44
- "version": "0.0.44",
15
+ "version": "0.0.46",
45
16
  "category": "tooling",
46
17
  "keywords": [
47
18
  "validation",
@@ -53,27 +24,11 @@
53
24
  "hooks"
54
25
  ]
55
26
  },
56
- {
57
- "name": "team-share",
58
- "source": "./plugins/team-share",
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.44",
61
- "category": "tooling",
62
- "keywords": [
63
- "onboarding",
64
- "team",
65
- "claude",
66
- "settings",
67
- "codegraph",
68
- "understand-anything",
69
- "git-lfs"
70
- ]
71
- },
72
27
  {
73
28
  "name": "multica-tool",
74
29
  "source": "./plugins/multica-tool",
75
30
  "description": "Export, import, and sync Multica skills, agents, and squads between workspaces.",
76
- "version": "0.0.44",
31
+ "version": "0.0.46",
77
32
  "category": "workflow",
78
33
  "keywords": [
79
34
  "multica",
package/README.md CHANGED
@@ -8,11 +8,8 @@ Published on npm as [`@drunkcoding/agents-and-skills`](https://www.npmjs.com/pac
8
8
 
9
9
  | Plugin | Description |
10
10
  |--------|-------------|
11
- | [`tech-graph`](plugins/tech-graph) | 6-step wizard for technical diagrams (SVG + PNG). |
12
- | [`html-effectiveness`](plugins/html-effectiveness) | Conversational agent that generates self-contained interactive HTML reports from 20 templates. |
13
11
  | [`team-superpower`](plugins/team-superpower) | Shape-adaptive engineering team running the Superpowers skill chain — up to 8 roles (designer, planner, software-architect, security-engineer, backend-developer, frontend-developer, qa-engineer, reviewer); spawns 7 or 8 depending on stack (`full-stack` / `be-only` / `fe-only`) declared in `CLAUDE.md`. Test/build commands, contract publish + sync, security checklist, and CI gate before merge are all driven by the project's `CLAUDE.md` `team-superpower` block. |
14
12
  | [`plugin-validator`](plugins/plugin-validator) | Orchestrated validator that checks every plugin's skills, agents, commands, and hooks for spec compliance — runs in parallel and proposes batched fixes. |
15
- | [`team-share`](plugins/team-share) | Onboarding helper that shares Claude plugin settings, scaffolds `CLAUDE.md` when missing, refreshes the Understand-Anything graph, and stages the resulting repo artifacts for review. |
16
13
  | [`auto-power`](plugins/auto-power) | Single-command hands-off pipeline that wraps `obra/superpowers`. Auto-answers safe clarifying questions during spec, then runs plan → arch+sec → impl → verify → review → ff-merge with no further touchpoints. Checkpointed and resumable. Escalates on substantive failures. |
17
14
  | [`multica-tool`](plugins/multica-tool) | Export, import, and sync Multica skills, agents, and squads between workspaces. |
18
15
 
@@ -52,8 +49,8 @@ Add the marketplace once:
52
49
  Then install any plugin individually:
53
50
 
54
51
  ```text
55
- /plugin install tech-graph@drunkcoding
56
- /plugin install html-effectiveness@drunkcoding --scope local
52
+ /plugin install plugin-validator@drunkcoding
53
+ /plugin install multica-tool@drunkcoding --scope local
57
54
  ```
58
55
 
59
56
  Reload after install:
@@ -69,7 +66,7 @@ Only the plugin you install loads; others stay dormant.
69
66
  Install into a project's `.claude/` instead of your user profile:
70
67
 
71
68
  ```text
72
- /plugin install tech-graph@drunkcoding --scope project
69
+ /plugin install plugin-validator@drunkcoding --scope project
73
70
  ```
74
71
 
75
72
  #### Manage installed plugins
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drunkcoding/agents-and-skills",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "description": "Personal collection of Claude Code skills and agents, installable via `npx skills`.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -26,6 +26,9 @@
26
26
  "README.md",
27
27
  "LICENSE"
28
28
  ],
29
+ "scripts": {
30
+ "build:opencode": "node plugins/multica-tool/scripts/build-dist.mjs"
31
+ },
29
32
  "private": false,
30
33
  "publishConfig": {
31
34
  "access": "public"
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "multica-tool",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "description": "Export, import, and sync Multica skills, agents, and squads between workspaces via the multica CLI."
5
5
  }
@@ -59,6 +59,21 @@ export const listAgentsIncludingArchived = (cli) => cli.json(["agent", "list", "
59
59
  export const listSquads = (cli) => cli.json(["squad", "list"]);
60
60
  export const listWorkspaceMembers = (cli) => cli.json(["workspace", "member", "list"]);
61
61
 
62
+ // --- Workspace MCP server library ------------------------------------------
63
+ // `workspace mcp list` returns only id/name/transport — the server entry JSON
64
+ // (command/args/env, and any token inside it) is write-only: `workspace mcp
65
+ // add/update` take it, nothing reads it back. So the library travels as a
66
+ // NAME + TRANSPORT roster only, and import reports each entry as needing its
67
+ // config re-entered by hand rather than pretending it round-trips.
68
+ export const listWorkspaceMcpServers = (cli) =>
69
+ (cli.json(["workspace", "mcp", "list"]) ?? []).map((s) => ({ id: s.id, name: s.name, transport: s.transport }));
70
+
71
+ // Which workspace MCP servers an agent uses, and whether each is on. Keyed by
72
+ // NAME (the server id is re-minted per workspace); `agent mcp add/enable/disable`
73
+ // take a server id, which import resolves from the destination library by name.
74
+ export const listAgentMcpServers = (cli, agentId) =>
75
+ (cli.json(["agent", "mcp", "list", agentId]) ?? []).map((s) => ({ name: s.name, enabled: !!s.enabled }));
76
+
62
77
  // Get-wrappers: the ONLY place that knows the raw CLI field names — an
63
78
  // explicit allow-list, so unexpected/internal CLI fields never leak into a
64
79
  // bundle. Field names mirror the CLI's own snake_case; nothing is renamed.
@@ -137,16 +152,19 @@ export const listAutopilots = (cli) => cli.json(["autopilot", "list"]).autopilot
137
152
 
138
153
  // `autopilot get` wraps the record as {autopilot, collaborators, triggers} — only
139
154
  // autopilot + triggers are portable; collaborators/can_write/can_manage_access are
140
- // caller-relative and never bundled. `priority` is accepted by `autopilot
141
- // create`/`update` but never present in this response (verified live) kept as a
142
- // field anyway so this starts round-tripping automatically if the API adds it.
155
+ // caller-relative and never bundled. `priority` is deliberately absent: the API
156
+ // never returns it on read, and as of multica 0.4.36 `autopilot create`/`update`
157
+ // no longer accept `--priority` either so it can be neither captured nor
158
+ // restored. Import reports every autopilot under `priorityNotCaptured`.
143
159
  export function getAutopilot(cli, id) {
144
160
  const r = cli.json(["autopilot", "get", id]);
145
161
  const a = r.autopilot;
146
162
  return {
147
163
  id: a.id, title: a.title, description: a.description ?? "",
164
+ // Source status (active|paused). Import never auto-activates — it is captured
165
+ // so the operator can be told which autopilots were live at the source.
166
+ status: a.status ?? null,
148
167
  execution_mode: a.execution_mode, issue_title_template: a.issue_title_template ?? null,
149
- priority: a.priority ?? null,
150
168
  project_id: a.project_id ?? null,
151
169
  assignee_id: a.assignee_id, assignee_type: a.assignee_type,
152
170
  subscribers: (a.subscribers ?? []).map((s) => ({ user_id: s.user_id, user_type: s.user_type })),
@@ -156,3 +174,34 @@ export function getAutopilot(cli, id) {
156
174
  })),
157
175
  };
158
176
  }
177
+
178
+ // --- Workspace-scoped issue taxonomy (multica 0.4.36) -----------------------
179
+ // Labels and custom properties are defined per WORKSPACE, not per project — the
180
+ // CLI has no project-scoped variant of either. They are bundled with project
181
+ // exports (and `all`) so a migrated project lands somewhere its issues can
182
+ // actually be labelled and filled in.
183
+
184
+ // `id` is kept: `label update` addresses a label by id only.
185
+ // `resource_type`/`usage_count`/timestamps are server-owned with no CLI setter.
186
+ // `description` IS captured, but `label create`/`update` expose no
187
+ // `--description` flag — import reports it as unrestorable rather than dropping
188
+ // it silently.
189
+ export const listLabels = (cli) =>
190
+ (cli.json(["label", "list"]) ?? []).map((l) => ({
191
+ id: l.id, name: l.name, color: l.color, description: l.description ?? "",
192
+ }));
193
+
194
+ // Archived definitions included so an archived property round-trips as archived
195
+ // instead of silently reappearing in every picker at the destination.
196
+ // No `id`: `property get/update/archive/unarchive` all accept `<id-or-name>`,
197
+ // and names are what issue values reference, so name is the portable key.
198
+ // `position`/`usage_count` are server-assigned and have no CLI setter.
199
+ export const listProperties = (cli) =>
200
+ (cli.json(["property", "list", "--include-archived"]) ?? []).map((p) => ({
201
+ name: p.name, type: p.type, description: p.description ?? "", icon: p.icon ?? "",
202
+ archived: !!p.archived,
203
+ // Only select/multi_select carry options; every other type reports null.
204
+ // Option ids are re-minted at the destination — `property update` re-matches
205
+ // existing options BY NAME, so name+color is the whole portable payload.
206
+ options: (p.config?.options ?? []).map((o) => ({ name: o.name, color: o.color ?? null })),
207
+ }));
@@ -1,10 +1,30 @@
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, listAgentsIncludingArchived, 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, listAutopilots, getAutopilot, listLabels, listProperties, listWorkspaceMcpServers, listAgentMcpServers, makeCli, realExec, requireAuth, resolveWorkspaceId } from "./lib.mjs";
5
+
6
+ // Export levels for a WHOLE-WORKSPACE export, lowest tier first. A level pulls
7
+ // in its own tier plus every tier below it, so `project` is the full workspace
8
+ // and `skill` is skills alone. Single-resource exports (`--scope <type> --id`)
9
+ // ignore this and always bundle what that one resource needs.
10
+ export const LEVELS = ["skill", "agent", "squad", "project"];
11
+ export const levelRank = (level) => LEVELS.indexOf(level);
12
+ export const levelAtLeast = (level, tier) => levelRank(level) >= levelRank(tier);
5
13
 
6
14
  const nonEmpty = (v) => v && typeof v === "object" && Object.keys(v).length > 0;
7
15
 
16
+ // Externalize a prose field to a sibling .md next to `jsonRel`
17
+ // (e.g. agents/x.json + ".description.md" → agents/x.description.md), and record
18
+ // the `<field>_file` pointer on `record`. Empty prose writes nothing and adds no
19
+ // key — same sibling-file pattern as avatar_file, applied uniformly to every
20
+ // resource's instructions/description so no prose is ever embedded in the JSON.
21
+ function writeSidecar(fs, outDir, jsonRel, suffix, text, record, key) {
22
+ if (!text) return;
23
+ const rel = jsonRel.replace(/\.json$/, suffix);
24
+ fs.writeFileSync(`${outDir}/${rel}`, text);
25
+ record[key] = rel;
26
+ }
27
+
8
28
  // An avatar_url is either an uploaded-image URL (http[s]://…) or an inline
9
29
  // "emoji:🦍" marker. Only image URLs carry bytes worth bundling; emoji markers
10
30
  // are just carried as strings in the record.
@@ -31,7 +51,7 @@ export function redactAgent(a) {
31
51
  // a is a normalized agent from getAgent, with `custom_env`/
32
52
  // `custom_env_fetch_failed` attached by the caller (collectAgent) — getAgent
33
53
  // 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, archived_at, ...rest } = a;
54
+ 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
55
  const mcpUsable = !mcp_config_redacted && nonEmpty(mcp_config);
36
56
  const envUsable = !custom_env_fetch_failed && nonEmpty(custom_env);
37
57
  // mcp_config_redacted / custom_env_fetch_failed alone still flag hadSecrets even
@@ -51,13 +71,14 @@ export function redactAgent(a) {
51
71
  had_secrets: hadSecrets,
52
72
  },
53
73
  hadSecrets,
54
- // instructions are written to a sibling .md by the caller (see avatar_file),
55
- // never embedded in the JSON record.
74
+ // instructions and description are written to sibling .md files by the caller
75
+ // (see avatar_file), never embedded in the JSON record.
56
76
  instructions: instructions ?? "",
77
+ description: description ?? "",
57
78
  };
58
79
  }
59
80
 
60
- export function buildManifest({ scope, sourceWorkspaceId, skills, agents, squads, projects, autopilots }) {
81
+ export function buildManifest({ scope, level, sourceWorkspaceId, skills, agents, squads, projects, autopilots, labels, properties, mcpServers }) {
61
82
  const seenSkills = new Map();
62
83
  for (const s of skills) if (!seenSkills.has(s.name)) seenSkills.set(s.name, s);
63
84
  const seenAgents = new Map();
@@ -67,14 +88,18 @@ export function buildManifest({ scope, sourceWorkspaceId, skills, agents, squads
67
88
  return {
68
89
  version: "1",
69
90
  scope,
91
+ // null for a single-resource export; the requested tier for a workspace export.
92
+ level: level ?? null,
70
93
  source_workspace_id: sourceWorkspaceId,
71
94
  skills: [...seenSkills.values()].map((s) => ({ name: s.name, dir: `skills/${slugify(s.name)}`, source_id: s.source_id })),
72
95
  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
96
  squads: (squads ?? []).map((squad) => {
74
97
  const file = `squads/${slugify(squad.name)}.json`;
75
- const entry = { name: squad.name, file, description: squad.description ?? "", avatar_url: squad.avatar_url ?? null, leader_name: squad.leader_name, members: squad.members };
76
- // Instructions go to a sibling .md (see squad write loop); only referenced when non-empty.
98
+ const entry = { name: squad.name, file, avatar_url: squad.avatar_url ?? null, leader_name: squad.leader_name, members: squad.members };
99
+ // Instructions and description go to sibling .md files (see squad write
100
+ // loop); only referenced when non-empty.
77
101
  if (squad.instructions) entry.instructions_file = file.replace(/\.json$/, ".md");
102
+ if (squad.description) entry.description_file = file.replace(/\.json$/, ".description.md");
78
103
  return entry;
79
104
  }),
80
105
  projects: [...seenProjects.values()].map((p) => ({
@@ -86,6 +111,30 @@ export function buildManifest({ scope, sourceWorkspaceId, skills, agents, squads
86
111
  assignee_type: ap.assignee_type, assignee_name: ap.assignee_name,
87
112
  project_title: ap.project_title, had_webhook_trigger: ap.had_webhook_trigger,
88
113
  })),
114
+ // Labels, properties and the workspace MCP roster are workspace-wide sets
115
+ // with no per-resource file to point at, so each gets its own flat folder
116
+ // (labels/, properties/, mcp/) and the manifest carries only a pointer plus
117
+ // a count. Import reads the pointer, falling back to a legacy bundle's
118
+ // inline array. `id` is dropped from labels: both labels and properties are
119
+ // matched by NAME at the destination.
120
+ labels_file: (labels ?? []).length ? "labels/labels.json" : null,
121
+ labels_count: (labels ?? []).length,
122
+ properties_file: (properties ?? []).length ? "properties/properties.json" : null,
123
+ properties_count: (properties ?? []).length,
124
+ mcp_servers_file: (mcpServers ?? []).length ? "mcp/servers.json" : null,
125
+ mcp_servers_count: (mcpServers ?? []).length,
126
+ };
127
+ }
128
+
129
+ // The payloads written to labels/, properties/ and mcp/. Kept next to
130
+ // buildManifest so the manifest pointers and the files they name can't drift.
131
+ export function buildTaxonomyFiles({ labels, properties, mcpServers }) {
132
+ return {
133
+ "labels/labels.json": (labels ?? []).map(({ id, ...rest }) => rest),
134
+ "properties/properties.json": properties ?? [],
135
+ // The server entry JSON is not readable via the CLI — only name/transport
136
+ // travel, so import re-creates the roster as a to-do list, not a copy.
137
+ "mcp/servers.json": (mcpServers ?? []).map(({ id, ...rest }) => rest),
89
138
  };
90
139
  }
91
140
 
@@ -115,6 +164,13 @@ function collectAgent(cli, id, agentsById, skills, providerById) {
115
164
  a.custom_env_fetch_failed = true; // e.g. insufficient permission — non-fatal, warned via hadSecrets
116
165
  }
117
166
  }
167
+ // Workspace MCP servers assigned to this agent (name + enabled). Separate from
168
+ // `mcp_config`, which is the agent's own inline server JSON.
169
+ try {
170
+ a.mcp_servers = listAgentMcpServers(cli, id);
171
+ } catch {
172
+ a.mcp_servers = []; // e.g. insufficient permission — non-fatal
173
+ }
118
174
  const skill_names = a.skills.map((sk) => collectSkill(cli, sk.id, skills));
119
175
  const red = redactAgent(a);
120
176
  const entry = { raw: a, red, skill_names };
@@ -122,7 +178,7 @@ function collectAgent(cli, id, agentsById, skills, providerById) {
122
178
  return entry;
123
179
  }
124
180
 
125
- export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs = nodeFs, download = fetchBinary }) {
181
+ export function exportResource({ cli, scope, level, ids, outDir, sourceWorkspaceId, fs = nodeFs, download = fetchBinary }) {
126
182
  const skills = new Map(); // name -> normalized skill
127
183
  const agentsById = new Map(); // id -> { raw, red, skill_names }
128
184
  const squads = [];
@@ -169,6 +225,12 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
169
225
  };
170
226
  }
171
227
 
228
+ // A squad can be reached twice in one export (listed at level `squad`, then
229
+ // again as an autopilot's assignee) — keep exactly one copy, by name.
230
+ function pushSquad(built) {
231
+ if (built && !squads.some((s) => s.name === built.name)) squads.push(built);
232
+ }
233
+
172
234
  // Collect a project's portable metadata + its lead agent (bundled, like a
173
235
  // squad leader) and github_repo/other resources. An archived lead is dropped
174
236
  // (project still exports, just with no lead set) — unlike a squad, a project
@@ -193,19 +255,26 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
193
255
  };
194
256
  }
195
257
 
196
- // Collect a single autopilot: its assignee (agent, or squad + members + skills)
197
- // and, when present, the destination-portable project title / subscriber names.
258
+ // Collect a single autopilot. Its assignee is BUNDLED (like a squad leader or a
259
+ // project lead) so the bundle is self-contained import resolves the assignee
260
+ // by name, which only works if that agent/squad travels with it or already
261
+ // exists at the destination.
198
262
  // Webhook trigger secrets (url/token) are never read into the bundle — only
199
263
  // kind/label/enabled, matching the existing agent MCP/env redaction pattern.
200
264
  function collectOneAutopilot(autopilotId) {
201
265
  const ap = getAutopilot(cli, autopilotId);
202
266
  let assignee_name = null;
203
267
  if (ap.assignee_type === "agent") {
204
- const entry = collectAgent(cli, ap.assignee_id, agentsById, skills, getProviderById());
205
- assignee_name = entry.archived ? null : entry.raw.name;
268
+ try {
269
+ const entry = collectAgent(cli, ap.assignee_id, agentsById, skills, getProviderById());
270
+ if (entry.archived) recordArchivedSkip(entry.name, "autopilot assignee", ap.title);
271
+ else assignee_name = entry.raw.name;
272
+ } catch { assignee_name = null; }
206
273
  } else if (ap.assignee_type === "squad") {
207
- const sq = collectOneSquad(ap.assignee_id);
208
- if (sq) { squads.push(sq); assignee_name = sq.name; }
274
+ try {
275
+ const built = collectOneSquad(ap.assignee_id);
276
+ if (built) { pushSquad(built); assignee_name = built.name; }
277
+ } catch { assignee_name = null; }
209
278
  }
210
279
  let project_title = null;
211
280
  if (ap.project_id) {
@@ -218,6 +287,7 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
218
287
  }
219
288
  return {
220
289
  title: ap.title, source_id: ap.id, description: ap.description,
290
+ status: ap.status,
221
291
  execution_mode: ap.execution_mode, issue_title_template: ap.issue_title_template,
222
292
  priority: ap.priority, project_title, assignee_type: ap.assignee_type, assignee_name,
223
293
  subscriber_names, had_webhook_trigger: ap.triggers.some((t) => t.kind === "webhook"),
@@ -227,33 +297,51 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
227
297
  };
228
298
  }
229
299
 
300
+ // Labels, custom properties and the workspace MCP roster are workspace-wide,
301
+ // not per-project — three cheap list calls, bundled only where issue-bearing
302
+ // work travels: a single `project` export, or a workspace export at level
303
+ // `project`. Never for a lone skill/agent/squad/autopilot.
304
+ const wantsTaxonomy = scope === "project" || (scope === "workspace" && levelAtLeast(level, "project"));
305
+ const labels = wantsTaxonomy ? listLabels(cli) : [];
306
+ const properties = wantsTaxonomy ? listProperties(cli) : [];
307
+ const mcpServers = wantsTaxonomy ? listWorkspaceMcpServers(cli) : [];
308
+
230
309
  if (scope === "skill") collectSkill(cli, ids.skillId, skills);
231
310
  else if (scope === "agent") {
232
311
  const entry = collectAgent(cli, ids.agentId, agentsById, skills, getProviderById());
233
312
  if (entry.archived) throw new Error(`Cannot export agent "${entry.name}": it is archived`);
234
313
  }
235
- else if (scope === "squad") { const sq = collectOneSquad(ids.squadId); if (sq) squads.push(sq); }
314
+ else if (scope === "squad") pushSquad(collectOneSquad(ids.squadId));
236
315
  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));
238
316
  else if (scope === "autopilot") autopilots.push(collectOneAutopilot(ids.autopilotId));
239
- else if (scope === "all") {
317
+ else if (scope === "workspace") {
318
+ // Tiers are cumulative: every level bundles skills; `agent` and up add every
319
+ // agent; `squad` and up add every squad; `project` adds projects + autopilots
320
+ // (plus the taxonomy above).
240
321
  for (const s of listSkills(cli)) collectSkill(cli, s.id, skills);
241
- for (const a of listAgents(cli)) collectAgent(cli, a.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");
322
+ if (levelAtLeast(level, "agent")) {
323
+ for (const a of listAgents(cli)) collectAgent(cli, a.id, agentsById, skills, getProviderById());
324
+ // listAgents() above already excludes archived agents server-side — surface
325
+ // that exclusion in the report too, not just the squad/project paths.
326
+ for (const a of listAgentsIncludingArchived(cli)) {
327
+ if (a.archived_at) recordArchivedSkip(a.name, "workspace listing");
328
+ }
329
+ }
330
+ if (levelAtLeast(level, "squad")) {
331
+ for (const sq of listSquads(cli)) pushSquad(collectOneSquad(sq.id));
332
+ }
333
+ if (levelAtLeast(level, "project")) {
334
+ for (const p of listProjects(cli)) projects.push(collectProject(p.id));
335
+ for (const ap of listAutopilots(cli)) autopilots.push(collectOneAutopilot(ap.id));
248
336
  }
249
337
  }
250
338
 
251
339
  // Orphan-skill cleanup: drop skills that no exported agent references via its
252
- // skill_names. Only `all` ever produces these standalone workspace skills
253
- // from listSkills that no agent uses. Skipped for `skill` scope: its one
254
- // skill is the explicit target, not an orphan.
340
+ // skill_names. Skipped for `scope: skill` (its one skill IS the target, not an
341
+ // orphan) and for a workspace export at level `skill`, where the skills tier is
342
+ // itself what was asked for.
255
343
  const pruned_skills = [];
256
- if (scope !== "skill") {
344
+ if (scope !== "skill" && !(scope === "workspace" && level === "skill")) {
257
345
  const referenced = new Set();
258
346
  for (const a of agentsById.values()) for (const n of a.skill_names) referenced.add(n);
259
347
  for (const name of [...skills.keys()]) {
@@ -262,12 +350,15 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
262
350
  }
263
351
 
264
352
  const manifest = buildManifest({
265
- scope, sourceWorkspaceId,
353
+ scope, level, sourceWorkspaceId,
266
354
  skills: [...skills.values()].map((s) => ({ name: s.name, source_id: s.id })),
267
355
  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 })),
268
356
  squads,
269
357
  projects,
270
358
  autopilots,
359
+ labels,
360
+ properties,
361
+ mcpServers,
271
362
  });
272
363
 
273
364
  const warnings = [];
@@ -304,37 +395,57 @@ export function exportResource({ cli, scope, ids, outDir, sourceWorkspaceId, fs
304
395
  record.avatar_file = rel;
305
396
  }
306
397
  }
307
- // Instructions live in a sibling .md for reviewability (same sibling-file
308
- // pattern as avatar_file); only written when non-empty.
309
- if (red.instructions) {
310
- const rel = entry.file.replace(/\.json$/, ".md");
311
- fs.writeFileSync(`${outDir}/${rel}`, red.instructions);
312
- record.instructions_file = rel;
313
- }
398
+ // Instructions and description each live in a sibling .md for reviewability.
399
+ writeSidecar(fs, outDir, entry.file, ".md", red.instructions, record, "instructions_file");
400
+ writeSidecar(fs, outDir, entry.file, ".description.md", red.description, record, "description_file");
314
401
  fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(record, null, 2));
315
402
  }
316
- const squadInstrByName = new Map(squads.map((s) => [s.name, s.instructions ?? ""]));
403
+ const squadByName = new Map(squads.map((s) => [s.name, s]));
317
404
  for (const entry of manifest.squads) {
318
405
  fs.mkdirSync(`${outDir}/squads`, { recursive: true });
319
- if (entry.instructions_file) {
320
- fs.writeFileSync(`${outDir}/${entry.instructions_file}`, squadInstrByName.get(entry.name) ?? "");
321
- }
406
+ const s = squadByName.get(entry.name);
407
+ if (entry.instructions_file) fs.writeFileSync(`${outDir}/${entry.instructions_file}`, s?.instructions ?? "");
408
+ if (entry.description_file) fs.writeFileSync(`${outDir}/${entry.description_file}`, s?.description ?? "");
322
409
  fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(entry, null, 2));
323
410
  }
324
411
  const projectByTitle = new Map(projects.map((p) => [p.title, p]));
325
412
  for (const entry of manifest.projects) {
326
413
  fs.mkdirSync(`${outDir}/projects`, { recursive: true });
327
- fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(projectByTitle.get(entry.title), null, 2));
414
+ const record = { ...projectByTitle.get(entry.title) };
415
+ const desc = record.description; delete record.description;
416
+ writeSidecar(fs, outDir, entry.file, ".description.md", desc, record, "description_file");
417
+ fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(record, null, 2));
328
418
  }
329
419
  const autopilotByTitle = new Map(autopilots.map((a) => [a.title, a]));
330
420
  for (const entry of manifest.autopilots) {
331
421
  fs.mkdirSync(`${outDir}/autopilots`, { recursive: true });
332
- fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(autopilotByTitle.get(entry.title), null, 2));
422
+ const record = { ...autopilotByTitle.get(entry.title) };
423
+ const desc = record.description; delete record.description;
424
+ writeSidecar(fs, outDir, entry.file, ".description.md", desc, record, "description_file");
425
+ fs.writeFileSync(`${outDir}/${entry.file}`, JSON.stringify(record, null, 2));
426
+ }
427
+ // labels/, properties/, mcp/ — one flat folder per object type, matching the
428
+ // per-type folders written above. Only written when the manifest points at them.
429
+ const taxonomyFiles = buildTaxonomyFiles({ labels, properties, mcpServers });
430
+ for (const rel of [manifest.labels_file, manifest.properties_file, manifest.mcp_servers_file]) {
431
+ if (!rel) continue;
432
+ fs.mkdirSync(`${outDir}/${dirname(rel)}`, { recursive: true });
433
+ fs.writeFileSync(`${outDir}/${rel}`, JSON.stringify(taxonomyFiles[rel], null, 2));
333
434
  }
334
435
  fs.writeFileSync(`${outDir}/manifest.json`, JSON.stringify(manifest, null, 2));
335
436
  return {
336
437
  manifest, warnings, pruned_skills, archivedAgentsSkipped,
438
+ // Autopilots that were live at the source. Import always lands them paused,
439
+ // so activation is a deliberate manual step at the destination.
440
+ autopilotsActiveAtSource: autopilots.filter((a) => a.status === "active").map((a) => a.title),
441
+ // Only name/transport are readable via `workspace mcp list` — the server
442
+ // entry JSON (and any token in it) must be re-entered at the destination.
443
+ mcpServerConfigsNotPortable: mcpServers.map((m) => m.name),
337
444
  autopilotWebhookTriggers: autopilots.filter((a) => a.had_webhook_trigger).map((a) => a.title),
445
+ // Captured in the bundle for review, but `label create`/`update` have no
446
+ // --description flag — flagged at export time so the gap is known before the
447
+ // migration, not discovered at the far end.
448
+ labelDescriptionsNotPortable: labels.filter((l) => l.description).map((l) => l.name),
338
449
  };
339
450
  }
340
451
 
@@ -342,14 +453,24 @@ function main() {
342
453
  const args = process.argv.slice(2);
343
454
  const get = (flag) => { const i = args.indexOf(flag); return i !== -1 ? args[i + 1] : null; };
344
455
 
345
- const scope = get("--scope");
456
+ const rawScope = get("--scope");
457
+ const rawLevel = get("--level");
346
458
  const id = get("--id");
347
459
  const out = get("--out");
348
460
  const workspace = get("--workspace"); // optional: source workspace name
349
461
 
350
- if (!scope || !out || (scope !== "all" && scope !== "projects" && !id)) {
351
- console.error("Usage: multica-export.mjs --scope <skill|agent|squad|project|projects|autopilot|all> --id <id> --out <dir> [--workspace <name>] (--id not needed for --scope all|projects)");
352
- process.exit(1);
462
+ const USAGE = "Usage: multica-export.mjs --out <dir> [--level skill|agent|squad|project] [--workspace <name>]\n" +
463
+ " or: multica-export.mjs --scope <skill|agent|squad|project|autopilot> --id <id> --out <dir> [--workspace <name>]\n" +
464
+ " --level exports the WHOLE workspace down to that tier (default: squad); --scope exports one named resource.";
465
+
466
+ // No --scope means a whole-workspace export driven by --level (default squad).
467
+ const scope = rawScope ?? "workspace";
468
+ const level = scope === "workspace" ? (rawLevel ?? "squad") : null;
469
+
470
+ if (rawScope && rawLevel) { console.error("--scope and --level are mutually exclusive.\n" + USAGE); process.exit(1); }
471
+ if (!out || (scope !== "workspace" && !id)) { console.error(USAGE); process.exit(1); }
472
+ if (scope === "workspace" && !LEVELS.includes(level)) {
473
+ console.error(`Unknown level "${level}" — use ${LEVELS.join("|")}`); process.exit(1);
353
474
  }
354
475
 
355
476
  requireAuth(realExec);
@@ -364,12 +485,11 @@ function main() {
364
485
  else if (scope === "agent") ids.agentId = id;
365
486
  else if (scope === "squad") ids.squadId = id;
366
487
  else if (scope === "project") ids.projectId = id;
367
- else if (scope === "projects") { /* all projects — no id */ }
368
488
  else if (scope === "autopilot") ids.autopilotId = id;
369
- else if (scope === "all") { /* whole workspace — no id */ }
370
- else { console.error(`Unknown scope "${scope}" — use skill|agent|squad|project|projects|autopilot|all`); process.exit(1); }
489
+ else if (scope === "workspace") { /* whole workspace — no id */ }
490
+ else { console.error(`Unknown scope "${scope}" — use skill|agent|squad|project|autopilot`); process.exit(1); }
371
491
 
372
- const result = exportResource({ cli, scope, ids, outDir: out, sourceWorkspaceId, fs: nodeFs });
492
+ const result = exportResource({ cli, scope, level, ids, outDir: out, sourceWorkspaceId, fs: nodeFs });
373
493
  console.log(JSON.stringify(result, null, 2));
374
494
  }
375
495