pilotswarm 0.5.28 → 0.5.30

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 (135) hide show
  1. package/mcp/dist/bin/pilotswarm-mcp.js +4 -0
  2. package/mcp/dist/bin/pilotswarm-mcp.js.map +1 -1
  3. package/mcp/dist/src/context.d.ts +43 -10
  4. package/mcp/dist/src/context.d.ts.map +1 -1
  5. package/mcp/dist/src/context.js +15 -7
  6. package/mcp/dist/src/context.js.map +1 -1
  7. package/mcp/dist/src/resources/artifacts.d.ts.map +1 -1
  8. package/mcp/dist/src/resources/artifacts.js +4 -3
  9. package/mcp/dist/src/resources/artifacts.js.map +1 -1
  10. package/mcp/dist/src/server.d.ts.map +1 -1
  11. package/mcp/dist/src/server.js +2 -0
  12. package/mcp/dist/src/server.js.map +1 -1
  13. package/mcp/dist/src/tools/agent-packages.d.ts +14 -0
  14. package/mcp/dist/src/tools/agent-packages.d.ts.map +1 -0
  15. package/mcp/dist/src/tools/agent-packages.js +126 -0
  16. package/mcp/dist/src/tools/agent-packages.js.map +1 -0
  17. package/mcp/dist/src/tools/agents.d.ts.map +1 -1
  18. package/mcp/dist/src/tools/agents.js +18 -0
  19. package/mcp/dist/src/tools/agents.js.map +1 -1
  20. package/mcp/dist/src/tools/artifacts.d.ts.map +1 -1
  21. package/mcp/dist/src/tools/artifacts.js +12 -11
  22. package/mcp/dist/src/tools/artifacts.js.map +1 -1
  23. package/mcp/dist/src/tools/capabilities.js +1 -1
  24. package/mcp/dist/src/tools/capabilities.js.map +1 -1
  25. package/mcp/dist/src/tools/observability.js +1 -1
  26. package/mcp/dist/src/tools/observability.js.map +1 -1
  27. package/mcp/dist/src/tools/sessions.js +2 -2
  28. package/mcp/dist/src/tools/sessions.js.map +1 -1
  29. package/mcp/dist/src/tools/system.d.ts.map +1 -1
  30. package/mcp/dist/src/tools/system.js +56 -5
  31. package/mcp/dist/src/tools/system.js.map +1 -1
  32. package/mcp/dist/src/tools/turn-control.js +1 -1
  33. package/mcp/dist/src/tools/turn-control.js.map +1 -1
  34. package/mcp/dist/src/util/command.d.ts +3 -3
  35. package/mcp/dist/src/util/command.d.ts.map +1 -1
  36. package/mcp/dist/src/util/command.js +1 -1
  37. package/mcp/dist/src/util/command.js.map +1 -1
  38. package/package.json +6 -6
  39. package/tui/bin/tui.js +6 -0
  40. package/tui/src/agents-cli.js +442 -0
  41. package/tui/src/app.js +44 -1
  42. package/tui/src/node-sdk-transport.js +386 -5
  43. package/tui/src/plugin-config.js +17 -0
  44. package/ui/core/src/commands.js +7 -0
  45. package/ui/core/src/controller.js +498 -33
  46. package/ui/core/src/formatting.js +60 -16
  47. package/ui/core/src/index.js +2 -0
  48. package/ui/core/src/reducer.js +421 -34
  49. package/ui/core/src/repo-import.js +298 -0
  50. package/ui/core/src/repo-links.js +54 -0
  51. package/ui/core/src/selectors.js +879 -91
  52. package/ui/core/src/session-tree.js +109 -8
  53. package/ui/core/src/state.js +108 -2
  54. package/ui/core/src/themes/helpers.js +4 -1
  55. package/ui/core/src/themes/index.js +4 -10
  56. package/ui/core/src/themes/workspace-dark-rich.js +65 -0
  57. package/ui/core/src/themes/workspace-dark.js +65 -0
  58. package/ui/react/src/components.js +181 -4
  59. package/ui/react/src/web-app.js +1410 -91
  60. package/web/api/router.js +20 -2
  61. package/web/authz.js +4 -10
  62. package/web/dist/assets/{abnfDiagram-VRR7QNED-DLV45wH4.js → abnfDiagram-VRR7QNED-Cimc0f-u.js} +1 -1
  63. package/web/dist/assets/{arc-CE4xAYdE.js → arc-D8-_Dx5J.js} +1 -1
  64. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-DfzR8-9F.js → architectureDiagram-ZJ3FMSHR-lzMAuQHJ.js} +1 -1
  65. package/web/dist/assets/{blockDiagram-677ZJIJ3-C2pZdqvH.js → blockDiagram-677ZJIJ3-Dp2CWjVS.js} +1 -1
  66. package/web/dist/assets/{c4Diagram-LMCZKHZV-CKRspTan.js → c4Diagram-LMCZKHZV-DF93UkOy.js} +1 -1
  67. package/web/dist/assets/channel-OwWeiCox.js +1 -0
  68. package/web/dist/assets/{chunk-2Q5K7J3B-CNjLrfHT.js → chunk-2Q5K7J3B-pbsQRM-F.js} +1 -1
  69. package/web/dist/assets/{chunk-32BRIVSS-Bv5EwyaJ.js → chunk-32BRIVSS-B_f-gG1O.js} +1 -1
  70. package/web/dist/assets/{chunk-5VM5RSS4-NDBesEbJ.js → chunk-5VM5RSS4-gqlpJDmt.js} +1 -1
  71. package/web/dist/assets/{chunk-EX3LRPZG-DHQht9ty.js → chunk-EX3LRPZG-Cms7hPmM.js} +1 -1
  72. package/web/dist/assets/{chunk-JWPE2WC7-r0OQpJRF.js → chunk-JWPE2WC7-Boq5PtPI.js} +1 -1
  73. package/web/dist/assets/{chunk-MOJQB5TN-fcwIDgtl.js → chunk-MOJQB5TN-DVn9i8zt.js} +1 -1
  74. package/web/dist/assets/{chunk-RYQCIY6F-x7XQlDO1.js → chunk-RYQCIY6F-a1vKs3UX.js} +1 -1
  75. package/web/dist/assets/{chunk-V7JOEXUC-U8ZNVy1m.js → chunk-V7JOEXUC-i60p5fHd.js} +1 -1
  76. package/web/dist/assets/{chunk-VR4S4FIN-DLEdPLcN.js → chunk-VR4S4FIN-a8p0o-8B.js} +1 -1
  77. package/web/dist/assets/{chunk-XXDRQBXY-TeyAtB-i.js → chunk-XXDRQBXY-BwP1yBKd.js} +1 -1
  78. package/web/dist/assets/classDiagram-OUVF2IWQ-CDaP7nuC.js +1 -0
  79. package/web/dist/assets/classDiagram-v2-EOCWNBFH-CDaP7nuC.js +1 -0
  80. package/web/dist/assets/{cose-bilkent-JH36ORCC-B3bAl1-Z.js → cose-bilkent-JH36ORCC-HW6kGgze.js} +1 -1
  81. package/web/dist/assets/{cynefin-VYW2F7L2-DexP8nG-.js → cynefin-VYW2F7L2-YUFMn-vE.js} +1 -1
  82. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-Doa2XnSs.js → cynefinDiagram-TSTJHNR4-Brdy17i4.js} +1 -1
  83. package/web/dist/assets/{dagre-VKFMJZFB-DZMBKQcJ.js → dagre-VKFMJZFB-CAiSJJj-.js} +1 -1
  84. package/web/dist/assets/{diagram-FQU43EPY-BkzibmSq.js → diagram-FQU43EPY-B63Cqe0J.js} +1 -1
  85. package/web/dist/assets/{diagram-G47NLZAW-CihyHR3a.js → diagram-G47NLZAW-ByktrDJ2.js} +1 -1
  86. package/web/dist/assets/{diagram-NH7WQ7WH-OMDUUxwW.js → diagram-NH7WQ7WH-BLSfRMoE.js} +1 -1
  87. package/web/dist/assets/{diagram-OA4YK3LP-Lc0imJba.js → diagram-OA4YK3LP-DNVu-zpk.js} +1 -1
  88. package/web/dist/assets/{diagram-WEI45ONY-Dlcy8hPe.js → diagram-WEI45ONY-CNf4Sz-I.js} +1 -1
  89. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-Dh3u17Dg.js → ebnfDiagram-CCIWWBDH-9CT3ergx.js} +1 -1
  90. package/web/dist/assets/{erDiagram-Q63AITRT-C0OhlUC2.js → erDiagram-Q63AITRT-BPJHAMgl.js} +1 -1
  91. package/web/dist/assets/{flowDiagram-23GEKE2U-CADKxT-x.js → flowDiagram-23GEKE2U-BVtpr6Iu.js} +1 -1
  92. package/web/dist/assets/{ganttDiagram-NO4QXBWP-iVVdxWB7.js → ganttDiagram-NO4QXBWP-BTHnv9sV.js} +1 -1
  93. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-BwDUec_a.js → gitGraphDiagram-IHSO6WYX-BfmtqbL5.js} +1 -1
  94. package/web/dist/assets/index-B37T1c2s.css +1 -0
  95. package/web/dist/assets/index-Bmb8qK39.js +24 -0
  96. package/web/dist/assets/{infoDiagram-FWYZ7A6U-CGPUif0s.js → infoDiagram-FWYZ7A6U-kU4i4HRu.js} +1 -1
  97. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-QYnrl-KA.js → ishikawaDiagram-FXEZZL3T-Ch7i-E8O.js} +1 -1
  98. package/web/dist/assets/{journeyDiagram-5HDEW3XC-CeKixxL0.js → journeyDiagram-5HDEW3XC-CPQxp0ps.js} +1 -1
  99. package/web/dist/assets/{kanban-definition-HUTT4EX6-CeYokFBS.js → kanban-definition-HUTT4EX6-BBgMVqoJ.js} +1 -1
  100. package/web/dist/assets/{linear-BbA6_gnz.js → linear-rwksvzXJ.js} +1 -1
  101. package/web/dist/assets/{mermaid.core--xGRoQTa.js → mermaid.core-o7PszRUc.js} +4 -4
  102. package/web/dist/assets/{mindmap-definition-LN4V7U3C-D53u8h0z.js → mindmap-definition-LN4V7U3C-wgOTdzlq.js} +1 -1
  103. package/web/dist/assets/{papaparse.min-BU0o7jXn.js → papaparse.min-BRXtjEI6.js} +1 -1
  104. package/web/dist/assets/{pegDiagram-2B236MQR-B7QSyG-3.js → pegDiagram-2B236MQR-BkhQqqTm.js} +1 -1
  105. package/web/dist/assets/{pieDiagram-ENE6RG2P-XtSGL7GG.js → pieDiagram-ENE6RG2P-CcMnvxUz.js} +1 -1
  106. package/web/dist/assets/pilotswarm-DYXtzuUN.js +99 -0
  107. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-LZoYSbTH.js → quadrantDiagram-ABIIQ3AL-_KD1QeEr.js} +1 -1
  108. package/web/dist/assets/{railroadDiagram-RFXS5EU6-B5FREx2U.js → railroadDiagram-RFXS5EU6-CqI0olIe.js} +1 -1
  109. package/web/dist/assets/{requirementDiagram-TGXJPOKE-1VRFkBQX.js → requirementDiagram-TGXJPOKE-1JxRwusF.js} +1 -1
  110. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-J456gWvu.js → sankeyDiagram-HTMAVEWB-9IpFoKzi.js} +1 -1
  111. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-CaCcw6KO.js → sequenceDiagram-DBY2YBRQ-teyObSQ9.js} +1 -1
  112. package/web/dist/assets/{sizeCapture-X5ZJPWSS-DClsP3_O.js → sizeCapture-X5ZJPWSS-DVhU2J_6.js} +1 -1
  113. package/web/dist/assets/{stateDiagram-2N3HPSRC-D6VyrKhd.js → stateDiagram-2N3HPSRC-DHPoWpIQ.js} +1 -1
  114. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-DJjZWYLS.js +1 -0
  115. package/web/dist/assets/{swimlanes-5IMT3BWC-q678iAuo.js → swimlanes-5IMT3BWC-BxTtDQoB.js} +2 -2
  116. package/web/dist/assets/swimlanesDiagram-G3AALYLV-CV12vDcK.js +8 -0
  117. package/web/dist/assets/{timeline-definition-FHXFAJF6-WnPK_zhe.js → timeline-definition-FHXFAJF6-D0wXZx-4.js} +1 -1
  118. package/web/dist/assets/{vennDiagram-L72KCM5P-WpumuLld.js → vennDiagram-L72KCM5P-BSFyI3iV.js} +1 -1
  119. package/web/dist/assets/{wardleyDiagram-EHGQE667-CBrYLUm_.js → wardleyDiagram-EHGQE667-BN-S7hh0.js} +1 -1
  120. package/web/dist/assets/{xychartDiagram-FW5EYKEG-DbX1rnDW.js → xychartDiagram-FW5EYKEG-nDNuPs7o.js} +1 -1
  121. package/web/dist/index.html +3 -3
  122. package/web/runtime.js +36 -4
  123. package/ui/core/src/themes/daylight.js +0 -62
  124. package/ui/core/src/themes/light-high-contrast.js +0 -65
  125. package/ui/core/src/themes/paper-ink.js +0 -62
  126. package/ui/core/src/themes/solarized-ops.js +0 -59
  127. package/ui/core/src/themes/tokyo-night.js +0 -56
  128. package/web/dist/assets/channel-D7p-E4El.js +0 -1
  129. package/web/dist/assets/classDiagram-OUVF2IWQ-yFZ-1yMb.js +0 -1
  130. package/web/dist/assets/classDiagram-v2-EOCWNBFH-yFZ-1yMb.js +0 -1
  131. package/web/dist/assets/index-BGWTu1KK.css +0 -1
  132. package/web/dist/assets/index-Djfdr7ts.js +0 -24
  133. package/web/dist/assets/pilotswarm-CtO819QH.js +0 -94
  134. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-D7eKFhHb.js +0 -1
  135. package/web/dist/assets/swimlanesDiagram-G3AALYLV-DlVHQBQx.js +0 -8
package/tui/bin/tui.js CHANGED
@@ -11,6 +11,12 @@ if (argv[0] === "auth") {
11
11
  console.error(`[pilotswarm] ${error?.message || error}`);
12
12
  return 1;
13
13
  });
14
+ } else if (argv[0] === "agents") {
15
+ const { runAgentsCommand } = await import("../src/agents-cli.js");
16
+ process.exitCode = await runAgentsCommand(argv.slice(1)).catch((error) => {
17
+ console.error(`[pilotswarm] ${error?.message || error}`);
18
+ return 1;
19
+ });
14
20
  } else {
15
21
  const { parseCliIntoEnv } = await import("../src/bootstrap-env.js");
16
22
  const config = parseCliIntoEnv(argv);
@@ -0,0 +1,442 @@
1
+ /**
2
+ * `pilotswarm agents …` — the agent-packages CLI
3
+ * (docs/proposals/agent-packages.md).
4
+ *
5
+ * Web-API mode is the default, like every other surface: the deployment's
6
+ * own authorization decides what you can see and change, and the CLI needs
7
+ * no datastore credentials. `pilotswarm auth login --api-url <url>` once,
8
+ * then point at the deployment with --api-url or PILOTSWARM_API_URL.
9
+ *
10
+ * Direct-store mode (--store <postgres-url>) is break-glass. It bypasses
11
+ * authentication and authorization entirely — anyone holding the URL owns
12
+ * the database — and it is what local-dev tooling and the test suite use.
13
+ * It is also the path for packages above the 2 MB inline-upload envelope.
14
+ *
15
+ * pilotswarm agents validate ./my-agents
16
+ * pilotswarm agents push ./my-agents [--shared|--user] [--json]
17
+ * pilotswarm agents list | show <name> | pin <name>@<semver>
18
+ * pilotswarm agents promote <name> | demote <name>
19
+ * pilotswarm agents enable <name> | disable <name>
20
+ * pilotswarm agents tree <name> [--semver <v>]
21
+ * pilotswarm agents cat <name> <file> [--semver <v>]
22
+ * pilotswarm agents rm <name> --yes
23
+ */
24
+
25
+ import * as fs from "node:fs";
26
+ import * as os from "node:os";
27
+ import * as path from "node:path";
28
+ import {
29
+ PgSessionCatalog,
30
+ PilotSwarmManagementClient,
31
+ createSessionBlobStore,
32
+ FilesystemArtifactStore,
33
+ validateAgentPackageDir,
34
+ stageAgentPackageDir,
35
+ publishAgentPackageDir,
36
+ deleteAgentPackageEverywhere,
37
+ readAgentPackageTarGz,
38
+ fetchAgentPackageTarGz,
39
+ AgentPackageValidationError,
40
+ } from "pilotswarm-sdk";
41
+ import { bootstrapApiAuth } from "./auth/cli.js";
42
+
43
+ /** The server's inline-upload envelope (node-sdk-transport uploadAgentPackage). */
44
+ const UPLOAD_MAX_BYTES = 2 * 1024 * 1024;
45
+ const UPLOAD_MAX_FILES = 2000;
46
+
47
+ /** Never worth uploading, and `.git` alone can dwarf the 2 MB envelope. */
48
+ const SKIP_DIRS = new Set([".git", "node_modules", ".DS_Store"]);
49
+
50
+ function parseArgs(argv) {
51
+ const flags = {};
52
+ const positional = [];
53
+ for (let i = 0; i < argv.length; i++) {
54
+ const arg = argv[i];
55
+ if (arg === "--shared") flags.scope = "shared";
56
+ else if (arg === "--user") flags.scope = "user";
57
+ else if (arg === "--json") flags.json = true;
58
+ else if (arg === "--yes" || arg === "-y") flags.yes = true;
59
+ else if (arg === "--store") flags.store = argv[++i];
60
+ else if (arg === "--schema") flags.schema = argv[++i];
61
+ else if (arg === "--api-url") flags.apiUrl = argv[++i];
62
+ else if (arg === "--semver") flags.semver = argv[++i];
63
+ else if (arg === "--help" || arg === "-h") flags.help = true;
64
+ else positional.push(arg);
65
+ }
66
+ return { flags, positional };
67
+ }
68
+
69
+ const USAGE = `pilotswarm agents — manage registry agent packages
70
+
71
+ Usage:
72
+ pilotswarm agents validate <dir>
73
+ pilotswarm agents push <dir> [--shared|--user] [--json]
74
+ pilotswarm agents list [--json]
75
+ pilotswarm agents show <name> [--json]
76
+ pilotswarm agents pin <name>@<semver>
77
+ pilotswarm agents promote <name> | demote <name>
78
+ pilotswarm agents enable <name> | disable <name>
79
+ pilotswarm agents tree <name> [--semver <v>] [--json]
80
+ pilotswarm agents cat <name> <file> [--semver <v>]
81
+ pilotswarm agents rm <name> --yes
82
+
83
+ Connection (web API — the default):
84
+ --api-url <url> defaults to PILOTSWARM_API_URL
85
+ sign in first: pilotswarm auth login --api-url <url>
86
+
87
+ Connection (direct store — break-glass; no authn/authz):
88
+ --store <postgres-url> defaults to DATABASE_URL when no API URL is set
89
+ --schema <cms-schema> defaults to PILOTSWARM_CMS_SCHEMA
90
+
91
+ Scope:
92
+ --user (default) only you see the package's agents
93
+ --shared every user can create sessions from them`;
94
+
95
+ /**
96
+ * Web unless direct is explicitly asked for. `--store` always wins; otherwise
97
+ * an API URL wins over a stray DATABASE_URL, so a shell that happens to carry
98
+ * datastore credentials no longer silently bypasses the deployment's authz.
99
+ */
100
+ function resolveMode(flags) {
101
+ if (flags.store) return { mode: "direct", store: flags.store };
102
+ const apiUrl = String(flags.apiUrl || process.env.PILOTSWARM_API_URL || "").trim();
103
+ if (apiUrl) return { mode: "web", apiUrl: apiUrl.replace(/\/+$/, "") };
104
+ if (process.env.DATABASE_URL) return { mode: "direct", store: process.env.DATABASE_URL };
105
+ throw new Error(
106
+ "no deployment configured — pass --api-url <url> (or set PILOTSWARM_API_URL), "
107
+ + "or --store <postgres-url> for direct break-glass access",
108
+ );
109
+ }
110
+
111
+ async function makeWebContext({ apiUrl }) {
112
+ const { getAccessToken } = await bootstrapApiAuth(apiUrl, { interactive: true });
113
+ const client = new PilotSwarmManagementClient({ apiUrl, getAccessToken });
114
+ await client.start();
115
+ return { mode: "web", client, close: () => client.stop().catch(() => {}) };
116
+ }
117
+
118
+ async function makeDirectContext({ store }, flags) {
119
+ const catalog = await PgSessionCatalog.create(store, flags.schema || process.env.PILOTSWARM_CMS_SCHEMA || undefined);
120
+ try {
121
+ await catalog.initialize();
122
+ const sessionStateDir = process.env.SESSION_STATE_DIR
123
+ || path.join(os.homedir(), ".copilot", "session-state");
124
+ const artifactStore = createSessionBlobStore(process.env, { sessionStateDir })
125
+ ?? new FilesystemArtifactStore(path.join(path.dirname(sessionStateDir), "artifacts"));
126
+ return { mode: "direct", catalog, artifactStore, close: () => catalog.close() };
127
+ } catch (error) {
128
+ // Close the pool on init failure or the CLI hangs on idle pg clients
129
+ // instead of exiting non-zero.
130
+ await catalog.close().catch(() => {});
131
+ throw error;
132
+ }
133
+ }
134
+
135
+ async function makeContext(flags) {
136
+ const resolved = resolveMode(flags);
137
+ if (resolved.mode === "web") return makeWebContext(resolved);
138
+ // Say so, every time. Direct mode bypasses authentication and
139
+ // authorization entirely, and its output is indistinguishable from the
140
+ // web path — a shell that happens to carry DATABASE_URL would otherwise
141
+ // silently downgrade an intended-authorized command with no visible sign.
142
+ console.error(
143
+ `[pilotswarm agents] direct-store mode — no authentication or authorization (${flags.store ? "--store" : "DATABASE_URL"}).`
144
+ + " Pass --api-url <url> (or set PILOTSWARM_API_URL) to go through a deployment.",
145
+ );
146
+ return makeDirectContext(resolved, flags);
147
+ }
148
+
149
+ /**
150
+ * Build the web-mode upload payload: validate AND collect from the SAME
151
+ * staged tree, so the uploaded bytes are the validated bytes — the invariant
152
+ * `publishAgentPackageDir` maintains for the direct path.
153
+ *
154
+ * A manifest-mode package stages only the files its plugin.json declares.
155
+ * Walking the original directory instead would upload whatever else happens
156
+ * to sit beside it — files validation never saw, and enough of them to blow
157
+ * the 2 MB envelope on a package that is comfortably under it.
158
+ *
159
+ * Throws AgentPackageValidationError so the caller can print the issues.
160
+ */
161
+ export async function buildUploadPayload(rootDir) {
162
+ const staged = stageAgentPackageDir(rootDir);
163
+ try {
164
+ const validation = await validateAgentPackageDir(staged.dir, { preStaged: staged.staged });
165
+ if (!validation.ok) throw new AgentPackageValidationError(validation);
166
+ return collectUploadFiles(staged.dir);
167
+ } finally {
168
+ staged.cleanup();
169
+ }
170
+ }
171
+
172
+ /** Collect a package dir as the upload envelope: [{ path, contentBase64 }]. */
173
+ export function collectUploadFiles(rootDir) {
174
+ const files = [];
175
+ let total = 0;
176
+ const walk = (dir, prefix) => {
177
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
178
+ if (SKIP_DIRS.has(entry.name)) continue;
179
+ const abs = path.join(dir, entry.name);
180
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
181
+ if (entry.isDirectory()) { walk(abs, rel); continue; }
182
+ if (!entry.isFile()) continue;
183
+ const bytes = fs.readFileSync(abs);
184
+ total += bytes.length;
185
+ if (files.length >= UPLOAD_MAX_FILES) {
186
+ throw new Error(`package has more than ${UPLOAD_MAX_FILES} files — push it with --store instead`);
187
+ }
188
+ if (total > UPLOAD_MAX_BYTES) {
189
+ throw new Error(
190
+ `package exceeds the ${(UPLOAD_MAX_BYTES / 1024 / 1024).toFixed(0)} MB inline-upload envelope — `
191
+ + "push it with --store <postgres-url> (direct mode packs up to 16 MB compressed)",
192
+ );
193
+ }
194
+ files.push({ path: rel, contentBase64: bytes.toString("base64") });
195
+ }
196
+ };
197
+ walk(rootDir, "");
198
+ if (files.length === 0) throw new Error(`no files found under ${rootDir}`);
199
+ return files;
200
+ }
201
+
202
+ function printIssues(issues, stream = console.error) {
203
+ for (const issue of issues) {
204
+ stream(` - [${issue.code}] ${issue.message}`);
205
+ }
206
+ }
207
+
208
+ function formatPackageLine(pkg) {
209
+ const scope = pkg.scope === "shared" ? "shared" : "user ";
210
+ const active = pkg.active
211
+ ? `${pkg.active.semver} · ${pkg.active.sha256.slice(0, 7)}`
212
+ : "(no active version)";
213
+ const state = pkg.enabled ? "" : " [disabled]";
214
+ return `${pkg.enabled ? "●" : "○"} ${pkg.name.padEnd(28)} ${scope} ${active}${state}`;
215
+ }
216
+
217
+ /** Dates cross the Web API as ISO strings and arrive as Dates in direct mode. */
218
+ function asDay(value) {
219
+ if (!value) return "";
220
+ const date = value instanceof Date ? value : new Date(value);
221
+ return Number.isNaN(date.getTime()) ? "" : date.toISOString().slice(0, 10);
222
+ }
223
+
224
+ export async function runAgentsCommand(argv) {
225
+ const { flags, positional } = parseArgs(argv);
226
+ const [command, ...rest] = positional;
227
+ if (flags.help || !command) {
228
+ console.log(USAGE);
229
+ return command ? 0 : 1;
230
+ }
231
+
232
+ // ── validate: purely local, no connection needed ──────────────
233
+ if (command === "validate") {
234
+ const dir = rest[0];
235
+ if (!dir) { console.error("usage: pilotswarm agents validate <dir>"); return 1; }
236
+ const result = await validateAgentPackageDir(path.resolve(dir));
237
+ if (flags.json) {
238
+ console.log(JSON.stringify(result, null, 2));
239
+ return result.ok ? 0 : 1;
240
+ }
241
+ if (result.ok) {
242
+ const m = result.manifest;
243
+ console.log(`✓ ${m.name}@${m.version} — ${m.agents.length} agent(s), ${m.skills.length} skill(s), ${m.mcpServers.length} MCP server(s)${m.hasTools ? ", tools module" : ""}`);
244
+ } else {
245
+ console.error(`✗ package failed validation (${result.errors.length} error${result.errors.length === 1 ? "" : "s"}):`);
246
+ printIssues(result.errors);
247
+ }
248
+ if (result.warnings.length > 0) {
249
+ console.error("warnings:");
250
+ printIssues(result.warnings, console.error);
251
+ }
252
+ return result.ok ? 0 : 1;
253
+ }
254
+
255
+ const ctx = await makeContext(flags);
256
+ const web = ctx.mode === "web";
257
+ const actor = null; // direct store mode = operator = admin
258
+ const isAdmin = true;
259
+ const createdBy = `${os.userInfo().username}@cli`;
260
+ try {
261
+ switch (command) {
262
+ case "push": {
263
+ const dir = rest[0];
264
+ if (!dir) { console.error("usage: pilotswarm agents push <dir> [--shared|--user]"); return 1; }
265
+ const resolvedDir = path.resolve(dir);
266
+ const scope = flags.scope ?? "user";
267
+ let outcome;
268
+ try {
269
+ if (web) {
270
+ outcome = await ctx.client.ops.uploadAgentPackage({
271
+ files: await buildUploadPayload(resolvedDir),
272
+ scope,
273
+ });
274
+ } else {
275
+ outcome = await publishAgentPackageDir(ctx, {
276
+ dir: resolvedDir,
277
+ scope,
278
+ owner: actor,
279
+ createdBy,
280
+ isAdmin,
281
+ });
282
+ }
283
+ } catch (error) {
284
+ if (error instanceof AgentPackageValidationError) {
285
+ console.error(`✗ ${resolvedDir} failed validation:`);
286
+ printIssues(error.validation.errors);
287
+ return 1;
288
+ }
289
+ throw error;
290
+ }
291
+ if (flags.json) {
292
+ const { manifest: _m, warnings, ...summary } = outcome;
293
+ console.log(JSON.stringify({ ...summary, warnings: warnings ?? [] }, null, 2));
294
+ } else {
295
+ const verb = outcome.status === "noop" ? "already up to date" : "published";
296
+ console.log(`✓ ${outcome.name}@${outcome.semver} ${verb} (${(outcome.sizeBytes / 1024).toFixed(1)} KB, sha ${outcome.sha256.slice(0, 12)}, scope ${scope})`);
297
+ for (const warning of outcome.warnings ?? []) console.error(` ! ${warning.message}`);
298
+ }
299
+ return 0;
300
+ }
301
+ case "list": {
302
+ const packages = web
303
+ ? await ctx.client.ops.listAgentPackages()
304
+ : await ctx.catalog.listAgentPackages(actor, isAdmin);
305
+ if (flags.json) { console.log(JSON.stringify(packages, null, 2)); return 0; }
306
+ if (packages.length === 0) { console.log("no agent packages registered"); return 0; }
307
+ for (const pkg of packages) console.log(formatPackageLine(pkg));
308
+ return 0;
309
+ }
310
+ case "show": {
311
+ const name = rest[0];
312
+ if (!name) { console.error("usage: pilotswarm agents show <name>"); return 1; }
313
+ const detail = web
314
+ ? await ctx.client.ops.getAgentPackage({ name })
315
+ : await ctx.catalog.getAgentPackage(name, actor, isAdmin);
316
+ if (!detail) { console.error(`package "${name}" not found`); return 1; }
317
+ if (flags.json) { console.log(JSON.stringify(detail, null, 2)); return 0; }
318
+ console.log(`${detail.name} · ${detail.scope} · ${detail.enabled ? "enabled" : "disabled"}`);
319
+ if (detail.createdBy) console.log(`created by ${detail.createdBy} on ${asDay(detail.createdAt)}`);
320
+ console.log("versions:");
321
+ for (const version of detail.versions) {
322
+ const marker = version.versionId === detail.activeVersionId ? "●" : " ";
323
+ console.log(` ${marker} ${version.semver.padEnd(14)} ${version.sha256.slice(0, 12)} ${asDay(version.createdAt)}`);
324
+ }
325
+ const agents = Array.isArray(detail.versions[0]?.manifest?.agents) ? detail.versions[0].manifest.agents : [];
326
+ if (agents.length > 0) console.log(`agents: ${agents.map((a) => a.name).join(", ")}`);
327
+ return 0;
328
+ }
329
+ case "pin": {
330
+ let name = rest[0];
331
+ let semver = rest[1];
332
+ if (name?.includes("@")) [name, semver] = name.split("@");
333
+ if (!name || !semver) { console.error("usage: pilotswarm agents pin <name>@<semver>"); return 1; }
334
+ if (web) await ctx.client.ops.pinAgentPackageVersion({ name, semver });
335
+ else await ctx.catalog.pinAgentPackageVersion(name, semver, actor, isAdmin);
336
+ console.log(`✓ ${name} active version pinned to ${semver} — fleet converges on the next epoch poll`);
337
+ return 0;
338
+ }
339
+ case "promote":
340
+ case "demote": {
341
+ const name = rest[0];
342
+ if (!name) { console.error(`usage: pilotswarm agents ${command} <name>`); return 1; }
343
+ const scope = command === "promote" ? "shared" : "user";
344
+ if (web) await ctx.client.ops.setAgentPackageScope({ name, scope });
345
+ else await ctx.catalog.setAgentPackageScope(name, scope, actor, isAdmin);
346
+ console.log(`✓ ${name} is now ${scope}${scope === "shared" ? " — visible to every user" : " — visible only to its owner"} (running agents unaffected)`);
347
+ return 0;
348
+ }
349
+ case "enable":
350
+ case "disable": {
351
+ const name = rest[0];
352
+ if (!name) { console.error(`usage: pilotswarm agents ${command} <name>`); return 1; }
353
+ const enabled = command === "enable";
354
+ if (web) await ctx.client.ops.setAgentPackageEnabled({ name, enabled });
355
+ else await ctx.catalog.setAgentPackageEnabled(name, enabled, actor, isAdmin);
356
+ console.log(`✓ ${name} ${enabled ? "enabled" : "disabled"} fleet-wide — workers converge on the next epoch poll`);
357
+ return 0;
358
+ }
359
+ case "tree": {
360
+ const name = rest[0];
361
+ if (!name) { console.error("usage: pilotswarm agents tree <name> [--semver <v>]"); return 1; }
362
+ const tree = web
363
+ ? await ctx.client.ops.getAgentPackageTree({ name, semver: flags.semver })
364
+ : await directTree(ctx, name, flags.semver, actor, isAdmin);
365
+ if (flags.json) { console.log(JSON.stringify(tree, null, 2)); return 0; }
366
+ const entries = Array.isArray(tree) ? tree : (tree?.files ?? tree?.entries ?? []);
367
+ if (entries.length === 0) { console.log("(empty package)"); return 0; }
368
+ for (const entry of entries) {
369
+ const p = typeof entry === "string" ? entry : entry.path;
370
+ const size = typeof entry === "string" ? null : entry.size;
371
+ console.log(size == null ? ` ${p}` : ` ${String(size).padStart(8)} ${p}`);
372
+ }
373
+ return 0;
374
+ }
375
+ case "cat": {
376
+ const name = rest[0];
377
+ const filePath = rest[1];
378
+ if (!name || !filePath) { console.error("usage: pilotswarm agents cat <name> <file> [--semver <v>]"); return 1; }
379
+ const file = web
380
+ ? await ctx.client.ops.getAgentPackageFile({ name, semver: flags.semver, filePath })
381
+ : await directFile(ctx, name, flags.semver, filePath, actor, isAdmin);
382
+ if (flags.json) { console.log(JSON.stringify(file, null, 2)); return 0; }
383
+ if (file?.binary) { console.error(`${filePath} is binary (${file.size ?? "?"} bytes)`); return 1; }
384
+ const text = typeof file === "string" ? file : (file?.content ?? file?.text ?? "");
385
+ process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
386
+ return 0;
387
+ }
388
+ case "rm": {
389
+ const name = rest[0];
390
+ if (!name) { console.error("usage: pilotswarm agents rm <name> --yes"); return 1; }
391
+ if (!flags.yes) {
392
+ console.error(`refusing to delete "${name}" without --yes (removes every version and its artifacts; live sessions using its agents will fail resolution on their next turn)`);
393
+ return 1;
394
+ }
395
+ if (web) await ctx.client.ops.deleteAgentPackage({ name });
396
+ else await deleteAgentPackageEverywhere(ctx, name, actor, isAdmin);
397
+ console.log(`✓ ${name} deleted (registry rows and artifacts)`);
398
+ return 0;
399
+ }
400
+ default:
401
+ console.error(`unknown agents command: ${command}\n`);
402
+ console.log(USAGE);
403
+ return 1;
404
+ }
405
+ } finally {
406
+ await ctx.close();
407
+ }
408
+ }
409
+
410
+ /** Direct-mode tree/cat read the tarball the same way the portal ops do. */
411
+ async function loadDirectPackageEntries(ctx, name, semver, actor, isAdmin) {
412
+ const detail = await ctx.catalog.getAgentPackage(name, actor, isAdmin);
413
+ if (!detail) throw Object.assign(new Error(`package "${name}" not found`), { code: "NOT_FOUND" });
414
+ const version = semver
415
+ ? detail.versions.find((v) => v.semver === semver)
416
+ : detail.versions.find((v) => v.versionId === detail.activeVersionId) ?? detail.versions[0];
417
+ if (!version) {
418
+ throw Object.assign(new Error(`package "${name}" has no ${semver ? `version ${semver}` : "versions"}`), { code: "NOT_FOUND" });
419
+ }
420
+ const targz = await fetchAgentPackageTarGz(ctx.artifactStore, version.artifactFilename, version.sha256);
421
+ return readAgentPackageTarGz(targz);
422
+ }
423
+
424
+ // readAgentPackageTarGz yields { name, body } — NOT { path, body }. Reading
425
+ // `entry.path` silently produced undefined for every entry, so `tree` printed
426
+ // a column of "undefined" and `cat` could never match a file.
427
+ export async function directTree(ctx, name, semver, actor, isAdmin) {
428
+ const entries = await loadDirectPackageEntries(ctx, name, semver, actor, isAdmin);
429
+ return entries.map((entry) => ({ path: entry.name, size: entry.body.length }));
430
+ }
431
+
432
+ export async function directFile(ctx, name, semver, filePath, actor, isAdmin) {
433
+ const entries = await loadDirectPackageEntries(ctx, name, semver, actor, isAdmin);
434
+ const entry = entries.find((e) => e.name === filePath);
435
+ if (!entry) throw Object.assign(new Error(`${filePath} not found in ${name}`), { code: "NOT_FOUND" });
436
+ // A NUL byte in the first block is the usual binary tell — it is also what
437
+ // makes grep silently treat a source file as unsearchable.
438
+ if (entry.body.subarray(0, 4096).includes(0)) {
439
+ return { binary: true, size: entry.body.length };
440
+ }
441
+ return { content: entry.body.toString("utf8"), size: entry.body.length };
442
+ }
package/tui/src/app.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { useInput, useStdin } from "ink";
3
3
  import { UiPlatformProvider, SharedPilotSwarmApp } from "pilotswarm/ui-react";
4
- import { UI_COMMANDS } from "pilotswarm/ui-core";
4
+ import { UI_COMMANDS, selectNodeMapView } from "pilotswarm/ui-core";
5
5
  import { PILOTSWARM_CLI_VERSION_LABEL } from "./version.js";
6
6
 
7
7
  const MOUSE_INPUT_PATTERN = /\u001b\[<(\d+);(\d+);(\d+)([mM])/gu;
@@ -325,10 +325,47 @@ export function PilotSwarmTuiApp({ controller, platform, onRequestExit }) {
325
325
  return;
326
326
  }
327
327
  if (adminVisible) {
328
+ const adminSection = ["packages", "workers"].includes(adminState?.section) ? adminState.section : "ghcp";
328
329
  if (key.escape) {
329
330
  controller.handleCommand(UI_COMMANDS.CLOSE_ADMIN_CONSOLE).catch(() => {});
330
331
  return;
331
332
  }
333
+ // Settings-tree section toggles (Agents ⟷ GitHub Keys ⟷ Workers).
334
+ if (plainShortcut && input === "a" && adminSection !== "packages") {
335
+ controller.handleCommand(UI_COMMANDS.ADMIN_SHOW_PACKAGES).catch(() => {});
336
+ return;
337
+ }
338
+ if (plainShortcut && input === "g" && adminSection !== "ghcp") {
339
+ controller.handleCommand(UI_COMMANDS.ADMIN_SHOW_GHCP).catch(() => {});
340
+ return;
341
+ }
342
+ if (plainShortcut && input === "w" && adminSection !== "workers"
343
+ && adminState?.profile?.isAdmin) {
344
+ controller.handleCommand(UI_COMMANDS.ADMIN_SHOW_WORKERS).catch(() => {});
345
+ return;
346
+ }
347
+ if (adminSection === "workers") {
348
+ if (plainShortcut && input === "r") {
349
+ controller.handleCommand(UI_COMMANDS.ADMIN_WORKERS_REFRESH).catch(() => {});
350
+ return;
351
+ }
352
+ return;
353
+ }
354
+ if (adminSection === "packages") {
355
+ if (plainShortcut && (input === "j" || key.downArrow)) {
356
+ controller.handleCommand(UI_COMMANDS.ADMIN_PACKAGES_NEXT).catch(() => {});
357
+ return;
358
+ }
359
+ if (plainShortcut && (input === "k" || key.upArrow)) {
360
+ controller.handleCommand(UI_COMMANDS.ADMIN_PACKAGES_PREV).catch(() => {});
361
+ return;
362
+ }
363
+ if (plainShortcut && input === "r") {
364
+ controller.handleCommand(UI_COMMANDS.ADMIN_PACKAGES_REFRESH).catch(() => {});
365
+ return;
366
+ }
367
+ return;
368
+ }
332
369
  if (plainShortcut && input === "e") {
333
370
  controller.handleCommand(UI_COMMANDS.ADMIN_BEGIN_EDIT_GHCP_KEY).catch(() => {});
334
371
  return;
@@ -585,6 +622,12 @@ export function PilotSwarmTuiApp({ controller, platform, onRequestExit }) {
585
622
  controller.handleCommand(UI_COMMANDS.TOGGLE_STATS_VIEW).catch(() => {});
586
623
  return;
587
624
  }
625
+ // Node Map: digits pick a node by its listed ordinal (toggle to clear).
626
+ if (focus === "inspector" && inspectorTab === "nodes" && plainShortcut && /^[1-9]$/.test(input || "")) {
627
+ const node = selectNodeMapView(controller.getState()).nodes[Number(input) - 1];
628
+ if (node) controller.selectNodeMapNode(node.label);
629
+ return;
630
+ }
588
631
  if (focus === "inspector" && inspectorTab === "files" && input === "f") {
589
632
  controller.handleCommand(UI_COMMANDS.OPEN_FILES_FILTER).catch(() => {});
590
633
  return;