pilotswarm 0.5.42 → 0.5.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.
Files changed (111) hide show
  1. package/mcp/README.md +25 -1
  2. package/mcp/dist/src/auth.d.ts.map +1 -1
  3. package/mcp/dist/src/auth.js +5 -3
  4. package/mcp/dist/src/auth.js.map +1 -1
  5. package/mcp/dist/src/resources/models.d.ts.map +1 -1
  6. package/mcp/dist/src/resources/models.js +21 -14
  7. package/mcp/dist/src/resources/models.js.map +1 -1
  8. package/mcp/dist/src/server.d.ts.map +1 -1
  9. package/mcp/dist/src/server.js +2 -0
  10. package/mcp/dist/src/server.js.map +1 -1
  11. package/mcp/dist/src/tools/agent-packages.d.ts.map +1 -1
  12. package/mcp/dist/src/tools/agent-packages.js +28 -16
  13. package/mcp/dist/src/tools/agent-packages.js.map +1 -1
  14. package/mcp/dist/src/tools/models.d.ts.map +1 -1
  15. package/mcp/dist/src/tools/models.js +55 -47
  16. package/mcp/dist/src/tools/models.js.map +1 -1
  17. package/mcp/dist/src/tools/providers.d.ts +18 -0
  18. package/mcp/dist/src/tools/providers.d.ts.map +1 -0
  19. package/mcp/dist/src/tools/providers.js +284 -0
  20. package/mcp/dist/src/tools/providers.js.map +1 -0
  21. package/mcp/dist/src/tools/sessions.d.ts.map +1 -1
  22. package/mcp/dist/src/tools/sessions.js +10 -1
  23. package/mcp/dist/src/tools/sessions.js.map +1 -1
  24. package/mcp/dist/src/tools/system.js +2 -2
  25. package/mcp/dist/src/tools/system.js.map +1 -1
  26. package/mcp/dist/src/util/respond.d.ts.map +1 -1
  27. package/mcp/dist/src/util/respond.js +6 -0
  28. package/mcp/dist/src/util/respond.js.map +1 -1
  29. package/package.json +3 -3
  30. package/tui/README.md +8 -5
  31. package/tui/src/agents-cli.js +80 -78
  32. package/tui/src/app.js +119 -17
  33. package/tui/src/node-sdk-transport.js +150 -347
  34. package/ui/core/src/controller.js +1079 -23
  35. package/ui/core/src/reducer.js +649 -1
  36. package/ui/core/src/selectors.js +891 -6
  37. package/ui/core/src/state.js +163 -3
  38. package/ui/react/src/components.js +146 -4
  39. package/ui/react/src/web-app.js +1732 -50
  40. package/web/README.md +4 -4
  41. package/web/api/router.js +33 -0
  42. package/web/dist/assets/{abnfDiagram-VRR7QNED-Jh9nLlRI.js → abnfDiagram-VRR7QNED-67Q0WWge.js} +1 -1
  43. package/web/dist/assets/{arc-CmN4jFMW.js → arc-BvcYFngS.js} +1 -1
  44. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-DgUSVwl7.js → architectureDiagram-ZJ3FMSHR-CPBN0rk8.js} +1 -1
  45. package/web/dist/assets/{blockDiagram-677ZJIJ3-CCJEXNgp.js → blockDiagram-677ZJIJ3-D_hp1Oah.js} +1 -1
  46. package/web/dist/assets/{c4Diagram-LMCZKHZV-DG11zUre.js → c4Diagram-LMCZKHZV-DAOVGMIW.js} +1 -1
  47. package/web/dist/assets/channel-DiW7lUT8.js +1 -0
  48. package/web/dist/assets/{chunk-2Q5K7J3B-Cl8MUWGh.js → chunk-2Q5K7J3B-DajZuaot.js} +1 -1
  49. package/web/dist/assets/{chunk-32BRIVSS-BaLutEQY.js → chunk-32BRIVSS-C6pXBsgt.js} +1 -1
  50. package/web/dist/assets/{chunk-5VM5RSS4-BOPcnMeW.js → chunk-5VM5RSS4-780edrmi.js} +1 -1
  51. package/web/dist/assets/{chunk-EX3LRPZG-bY2tdfJ2.js → chunk-EX3LRPZG-C0e4NVuz.js} +1 -1
  52. package/web/dist/assets/{chunk-JWPE2WC7-CASBfQW6.js → chunk-JWPE2WC7-uRe83I6C.js} +1 -1
  53. package/web/dist/assets/{chunk-MOJQB5TN-WrEHfiAj.js → chunk-MOJQB5TN-B0WPoPjh.js} +1 -1
  54. package/web/dist/assets/{chunk-RYQCIY6F-XQoZii_K.js → chunk-RYQCIY6F-eRFuHHyG.js} +1 -1
  55. package/web/dist/assets/{chunk-V7JOEXUC-BvA5V7VF.js → chunk-V7JOEXUC-C8FDXprk.js} +1 -1
  56. package/web/dist/assets/{chunk-VR4S4FIN-SK_jCKYo.js → chunk-VR4S4FIN-DPFpLQdB.js} +1 -1
  57. package/web/dist/assets/{chunk-XXDRQBXY-CtZZImS8.js → chunk-XXDRQBXY-CoGQOPKW.js} +1 -1
  58. package/web/dist/assets/classDiagram-OUVF2IWQ-CpAhBd5x.js +1 -0
  59. package/web/dist/assets/classDiagram-v2-EOCWNBFH-CpAhBd5x.js +1 -0
  60. package/web/dist/assets/{cose-bilkent-JH36ORCC-PRXhsdiX.js → cose-bilkent-JH36ORCC-BTBSfO5v.js} +1 -1
  61. package/web/dist/assets/{cynefin-VYW2F7L2-CzUocqvb.js → cynefin-VYW2F7L2-DgTkQEoK.js} +1 -1
  62. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-DYGEqQ23.js → cynefinDiagram-TSTJHNR4-DiBMlKoP.js} +1 -1
  63. package/web/dist/assets/{dagre-VKFMJZFB-CAyb_zBo.js → dagre-VKFMJZFB-Qv-4dcbk.js} +1 -1
  64. package/web/dist/assets/{diagram-FQU43EPY-CvpXMmL-.js → diagram-FQU43EPY-CgyAb7yx.js} +1 -1
  65. package/web/dist/assets/{diagram-G47NLZAW-DBQRZJS0.js → diagram-G47NLZAW-BxN-KcGp.js} +1 -1
  66. package/web/dist/assets/{diagram-NH7WQ7WH-BGzD61gL.js → diagram-NH7WQ7WH-BiellpWz.js} +1 -1
  67. package/web/dist/assets/{diagram-OA4YK3LP-DqQf4oMW.js → diagram-OA4YK3LP-C8Ktgrwv.js} +1 -1
  68. package/web/dist/assets/{diagram-WEI45ONY-CYHoWtg7.js → diagram-WEI45ONY-Bj5K1X91.js} +1 -1
  69. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-oMOMgcs8.js → ebnfDiagram-CCIWWBDH-DNQI2XT9.js} +1 -1
  70. package/web/dist/assets/{erDiagram-Q63AITRT-DaZaseAH.js → erDiagram-Q63AITRT-D5Xkllu6.js} +1 -1
  71. package/web/dist/assets/{flowDiagram-23GEKE2U-BIe9TtAL.js → flowDiagram-23GEKE2U-DJpTtMuj.js} +1 -1
  72. package/web/dist/assets/{ganttDiagram-NO4QXBWP-DitrY3hM.js → ganttDiagram-NO4QXBWP-C-EQlCJf.js} +1 -1
  73. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-DRjIH_99.js → gitGraphDiagram-IHSO6WYX-C8vpSbvW.js} +1 -1
  74. package/web/dist/assets/index-C2ioXNJJ.js +24 -0
  75. package/web/dist/assets/index-CVYm02UI.css +1 -0
  76. package/web/dist/assets/{infoDiagram-FWYZ7A6U-BJCx4g9_.js → infoDiagram-FWYZ7A6U-CKNzsR3r.js} +1 -1
  77. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-CLxmaTWp.js → ishikawaDiagram-FXEZZL3T-CjyQq3lf.js} +1 -1
  78. package/web/dist/assets/{journeyDiagram-5HDEW3XC-D2_VI8CC.js → journeyDiagram-5HDEW3XC-CK3EEkJt.js} +1 -1
  79. package/web/dist/assets/{kanban-definition-HUTT4EX6-DnUq9E7I.js → kanban-definition-HUTT4EX6-CHQ8YkPx.js} +1 -1
  80. package/web/dist/assets/{linear-mKMwWrAB.js → linear-CUYirs1h.js} +1 -1
  81. package/web/dist/assets/{mermaid.core-CApAWULn.js → mermaid.core-Di8sQC4g.js} +4 -4
  82. package/web/dist/assets/{mindmap-definition-LN4V7U3C-D2twxIMO.js → mindmap-definition-LN4V7U3C-D38dmHP5.js} +1 -1
  83. package/web/dist/assets/{papaparse.min-BhDLFNak.js → papaparse.min-DWBU_pXS.js} +1 -1
  84. package/web/dist/assets/{pegDiagram-2B236MQR-D2RtIeDP.js → pegDiagram-2B236MQR-BU759QCo.js} +1 -1
  85. package/web/dist/assets/{pieDiagram-ENE6RG2P-vonumKaj.js → pieDiagram-ENE6RG2P-BbpfHbsj.js} +1 -1
  86. package/web/dist/assets/pilotswarm-DrM3pXOV.js +97 -0
  87. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CAZL1kJH.js → quadrantDiagram-ABIIQ3AL-BYZ3XHZn.js} +1 -1
  88. package/web/dist/assets/{railroadDiagram-RFXS5EU6-CgpDomFp.js → railroadDiagram-RFXS5EU6-D18gkZBJ.js} +1 -1
  89. package/web/dist/assets/{requirementDiagram-TGXJPOKE-9B6yfsXV.js → requirementDiagram-TGXJPOKE-D6vo26Ib.js} +1 -1
  90. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-B_FAnwxi.js → sankeyDiagram-HTMAVEWB-CxQwXnRE.js} +1 -1
  91. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-BbL6OCms.js → sequenceDiagram-DBY2YBRQ-CfGisw4A.js} +1 -1
  92. package/web/dist/assets/{sizeCapture-X5ZJPWSS-DIRKFU8M.js → sizeCapture-X5ZJPWSS-BDK7-yA-.js} +1 -1
  93. package/web/dist/assets/{stateDiagram-2N3HPSRC-pUX5NF32.js → stateDiagram-2N3HPSRC-8IyidYSV.js} +1 -1
  94. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-F7qYkvpr.js +1 -0
  95. package/web/dist/assets/{swimlanes-5IMT3BWC-DO7BjQ4-.js → swimlanes-5IMT3BWC-CgNqXCvM.js} +2 -2
  96. package/web/dist/assets/swimlanesDiagram-G3AALYLV-CUKrHLkY.js +8 -0
  97. package/web/dist/assets/{timeline-definition-FHXFAJF6-BcbYRQ7s.js → timeline-definition-FHXFAJF6-DAJsGj6d.js} +1 -1
  98. package/web/dist/assets/{vennDiagram-L72KCM5P-CUt_orN5.js → vennDiagram-L72KCM5P-DDVdvG5k.js} +1 -1
  99. package/web/dist/assets/{wardleyDiagram-EHGQE667-BlUPWQdi.js → wardleyDiagram-EHGQE667-CiAs_m72.js} +1 -1
  100. package/web/dist/assets/{xychartDiagram-FW5EYKEG-CDW7A5_U.js → xychartDiagram-FW5EYKEG-D6OLJtsD.js} +1 -1
  101. package/web/dist/index.html +3 -3
  102. package/web/runtime.js +220 -2
  103. package/web/server.js +35 -7
  104. package/web/dist/assets/channel-BQBGhjIw.js +0 -1
  105. package/web/dist/assets/classDiagram-OUVF2IWQ-BIr20mVO.js +0 -1
  106. package/web/dist/assets/classDiagram-v2-EOCWNBFH-BIr20mVO.js +0 -1
  107. package/web/dist/assets/index-Cl6-pI9g.js +0 -24
  108. package/web/dist/assets/index-DRLA5aNR.css +0 -1
  109. package/web/dist/assets/pilotswarm-DETFHWYc.js +0 -97
  110. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-J_okbpZN.js +0 -1
  111. package/web/dist/assets/swimlanesDiagram-G3AALYLV-gTxQyRZa.js +0 -8
package/tui/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # pilotswarm-cli
1
+ # PilotSwarm TUI
2
2
 
3
3
  Terminal UI for PilotSwarm.
4
4
 
5
5
  Install:
6
6
 
7
7
  ```bash
8
- npm install pilotswarm-cli
8
+ npm install pilotswarm
9
9
  ```
10
10
 
11
11
  For app-specific worker modules or direct SDK imports, also add:
@@ -20,12 +20,15 @@ Run locally against a plugin directory:
20
20
  npx pilotswarm local --env .env --plugin ./plugin --worker ./worker-module.js
21
21
  ```
22
22
 
23
- `pilotswarm-cli` provides the shipped TUI. Your app customizes it with `plugin/plugin.json`, `plugin/agents/*.agent.md`, `plugin/skills/*/SKILL.md`, and optional worker-side tools.
23
+ The `pilotswarm` package provides the shipped TUI (`pilotswarm-cli` remains a
24
+ bin alias). Your app customizes it with `plugin/plugin.json`,
25
+ `plugin/agents/*.agent.md`, `plugin/skills/*/SKILL.md`, and optional worker-side
26
+ tools.
24
27
 
25
28
  The shipped files inspector supports shared artifact browsing, download, local open, and delete flows. Binary artifacts download intact rather than being coerced through UTF-8 text previews.
26
29
 
27
- Portal/runtime helpers that are intentionally shared with `pilotswarm-web`
28
- are exported from `pilotswarm-cli/portal`.
30
+ Portal/runtime helpers are exported from `pilotswarm/host`; shared UI layers
31
+ are exported from `pilotswarm/ui-core` and `pilotswarm/ui-react`.
29
32
 
30
33
  Common docs:
31
34
 
@@ -26,19 +26,17 @@ import * as fs from "node:fs";
26
26
  import * as os from "node:os";
27
27
  import * as path from "node:path";
28
28
  import {
29
- PgSessionCatalog,
30
29
  PilotSwarmManagementClient,
31
30
  createSessionBlobStore,
32
31
  FilesystemArtifactStore,
33
32
  validateAgentPackageDir,
34
33
  stageAgentPackageDir,
35
- publishAgentPackageDir,
36
- deleteAgentPackageEverywhere,
37
- readAgentPackageTarGz,
38
- fetchAgentPackageTarGz,
34
+ listBundledAgentNames,
35
+ loadAgentFiles,
39
36
  AgentPackageValidationError,
40
37
  } from "pilotswarm-sdk";
41
38
  import { bootstrapApiAuth } from "./auth/cli.js";
39
+ import { getPluginDirsFromEnv } from "./plugin-config.js";
42
40
 
43
41
  /** The server's inline-upload envelope (node-sdk-transport uploadAgentPackage). */
44
42
  const UPLOAD_MAX_BYTES = 2 * 1024 * 1024;
@@ -60,6 +58,7 @@ function parseArgs(argv) {
60
58
  else if (arg === "--schema") flags.schema = argv[++i];
61
59
  else if (arg === "--api-url") flags.apiUrl = argv[++i];
62
60
  else if (arg === "--semver") flags.semver = argv[++i];
61
+ else if (arg === "--output" || arg === "-o") flags.output = argv[++i];
63
62
  else if (arg === "--help" || arg === "-h") flags.help = true;
64
63
  else positional.push(arg);
65
64
  }
@@ -78,6 +77,7 @@ Usage:
78
77
  pilotswarm agents enable <name> | disable <name>
79
78
  pilotswarm agents tree <name> [--semver <v>] [--json]
80
79
  pilotswarm agents cat <name> <file> [--semver <v>]
80
+ pilotswarm agents download <name> [--semver <v>] [--output <file>]
81
81
  pilotswarm agents rm <name> --yes
82
82
 
83
83
  Connection (web API — the default):
@@ -90,7 +90,10 @@ Connection (direct store — break-glass; no authn/authz):
90
90
 
91
91
  Scope:
92
92
  --user (default) only you see the package's agents
93
- --shared every user can create sessions from them`;
93
+ --shared every user can create sessions from them
94
+
95
+ For show/pin/promote/demote/enable/disable/tree/cat/download/rm, --user or
96
+ --shared selects which same-named package copy to target.`;
94
97
 
95
98
  /**
96
99
  * Web unless direct is explicitly asked for. `--store` always wins; otherwise
@@ -116,18 +119,20 @@ async function makeWebContext({ apiUrl }) {
116
119
  }
117
120
 
118
121
  async function makeDirectContext({ store }, flags) {
119
- const catalog = await PgSessionCatalog.create(store, flags.schema || process.env.PILOTSWARM_CMS_SCHEMA || undefined);
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
+ const client = new PilotSwarmManagementClient({
127
+ store,
128
+ cmsSchema: flags.schema || process.env.PILOTSWARM_CMS_SCHEMA || undefined,
129
+ artifactStore,
130
+ });
120
131
  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() };
132
+ await client.start();
133
+ return { mode: "direct", client, close: () => client.stop() };
127
134
  } 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(() => {});
135
+ await client.stop().catch(() => {});
131
136
  throw error;
132
137
  }
133
138
  }
@@ -221,6 +226,28 @@ function asDay(value) {
221
226
  return Number.isNaN(date.getTime()) ? "" : date.toISOString().slice(0, 10);
222
227
  }
223
228
 
229
+ export function reservedAgentNamesForCli() {
230
+ const names = new Set(listBundledAgentNames());
231
+ for (const pluginDir of getPluginDirsFromEnv()) {
232
+ for (const agent of loadAgentFiles(pluginDir)) {
233
+ if (agent?.name) names.add(agent.name);
234
+ if (agent?.id) names.add(agent.id);
235
+ }
236
+ }
237
+ return [...names];
238
+ }
239
+
240
+ export function packageFileForCli(file) {
241
+ if (file?.encoding === "base64" || file?.binary === true) {
242
+ return { binary: true, size: file?.size ?? 0, text: "" };
243
+ }
244
+ return {
245
+ binary: false,
246
+ size: file?.size ?? 0,
247
+ text: typeof file === "string" ? file : (file?.content ?? file?.text ?? ""),
248
+ };
249
+ }
250
+
224
251
  export async function runAgentsCommand(argv) {
225
252
  const { flags, positional } = parseArgs(argv);
226
253
  const [command, ...rest] = positional;
@@ -257,6 +284,7 @@ export async function runAgentsCommand(argv) {
257
284
  const actor = null; // direct store mode = operator = admin
258
285
  const isAdmin = true;
259
286
  const createdBy = `${os.userInfo().username}@cli`;
287
+ const selector = flags.scope ? { scope: flags.scope } : null;
260
288
  try {
261
289
  switch (command) {
262
290
  case "push": {
@@ -267,18 +295,16 @@ export async function runAgentsCommand(argv) {
267
295
  let outcome;
268
296
  try {
269
297
  if (web) {
270
- outcome = await ctx.client.ops.uploadAgentPackage({
271
- files: await buildUploadPayload(resolvedDir),
272
- scope,
273
- });
298
+ outcome = await ctx.client.uploadAgentPackage(
299
+ await buildUploadPayload(resolvedDir), scope, actor, isAdmin,
300
+ );
274
301
  } else {
275
- outcome = await publishAgentPackageDir(ctx, {
276
- dir: resolvedDir,
277
- scope,
278
- owner: actor,
279
- createdBy,
280
- isAdmin,
281
- });
302
+ outcome = await ctx.client.publishAgentPackageDirectory(
303
+ resolvedDir, scope, actor, isAdmin, {
304
+ createdBy,
305
+ reservedAgentNames: reservedAgentNamesForCli(),
306
+ },
307
+ );
282
308
  }
283
309
  } catch (error) {
284
310
  if (error instanceof AgentPackageValidationError) {
@@ -299,9 +325,7 @@ export async function runAgentsCommand(argv) {
299
325
  return 0;
300
326
  }
301
327
  case "list": {
302
- const packages = web
303
- ? await ctx.client.ops.listAgentPackages()
304
- : await ctx.catalog.listAgentPackages(actor, isAdmin);
328
+ const packages = await ctx.client.listAgentPackages(actor, isAdmin);
305
329
  if (flags.json) { console.log(JSON.stringify(packages, null, 2)); return 0; }
306
330
  if (packages.length === 0) { console.log("no agent packages registered"); return 0; }
307
331
  for (const pkg of packages) console.log(formatPackageLine(pkg));
@@ -310,9 +334,7 @@ export async function runAgentsCommand(argv) {
310
334
  case "show": {
311
335
  const name = rest[0];
312
336
  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);
337
+ const detail = await ctx.client.getAgentPackage(name, actor, isAdmin, selector);
316
338
  if (!detail) { console.error(`package "${name}" not found`); return 1; }
317
339
  if (flags.json) { console.log(JSON.stringify(detail, null, 2)); return 0; }
318
340
  console.log(`${detail.name} · ${detail.scope} · ${detail.enabled ? "enabled" : "disabled"}`);
@@ -331,8 +353,7 @@ export async function runAgentsCommand(argv) {
331
353
  let semver = rest[1];
332
354
  if (name?.includes("@")) [name, semver] = name.split("@");
333
355
  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);
356
+ await ctx.client.pinAgentPackageVersion(name, semver, actor, isAdmin, selector);
336
357
  console.log(`✓ ${name} active version pinned to ${semver} — fleet converges on the next epoch poll`);
337
358
  return 0;
338
359
  }
@@ -341,8 +362,7 @@ export async function runAgentsCommand(argv) {
341
362
  const name = rest[0];
342
363
  if (!name) { console.error(`usage: pilotswarm agents ${command} <name>`); return 1; }
343
364
  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);
365
+ await ctx.client.setAgentPackageScope(name, scope, actor, isAdmin, selector);
346
366
  console.log(`✓ ${name} is now ${scope}${scope === "shared" ? " — visible to every user" : " — visible only to its owner"} (running agents unaffected)`);
347
367
  return 0;
348
368
  }
@@ -351,17 +371,14 @@ export async function runAgentsCommand(argv) {
351
371
  const name = rest[0];
352
372
  if (!name) { console.error(`usage: pilotswarm agents ${command} <name>`); return 1; }
353
373
  const enabled = command === "enable";
354
- if (web) await ctx.client.ops.setAgentPackageEnabled({ name, enabled });
355
- else await ctx.catalog.setAgentPackageEnabled(name, enabled, actor, isAdmin);
374
+ await ctx.client.setAgentPackageEnabled(name, enabled, actor, isAdmin, selector);
356
375
  console.log(`✓ ${name} ${enabled ? "enabled" : "disabled"} fleet-wide — workers converge on the next epoch poll`);
357
376
  return 0;
358
377
  }
359
378
  case "tree": {
360
379
  const name = rest[0];
361
380
  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);
381
+ const tree = await ctx.client.getAgentPackageTree(name, flags.semver ?? null, actor, isAdmin, selector);
365
382
  if (flags.json) { console.log(JSON.stringify(tree, null, 2)); return 0; }
366
383
  const entries = Array.isArray(tree) ? tree : (tree?.files ?? tree?.entries ?? []);
367
384
  if (entries.length === 0) { console.log("(empty package)"); return 0; }
@@ -376,13 +393,20 @@ export async function runAgentsCommand(argv) {
376
393
  const name = rest[0];
377
394
  const filePath = rest[1];
378
395
  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);
396
+ const file = await ctx.client.getAgentPackageFile(name, flags.semver ?? null, filePath, actor, isAdmin, selector);
382
397
  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`);
398
+ const formatted = packageFileForCli(file);
399
+ if (formatted.binary) { console.error(`${filePath} is binary (${formatted.size || "?"} bytes)`); return 1; }
400
+ process.stdout.write(formatted.text.endsWith("\n") ? formatted.text : `${formatted.text}\n`);
401
+ return 0;
402
+ }
403
+ case "download": {
404
+ const name = rest[0];
405
+ if (!name) { console.error("usage: pilotswarm agents download <name> [--semver <v>] [--output <file>]"); return 1; }
406
+ const pkg = await ctx.client.downloadAgentPackage(name, flags.semver ?? null, actor, isAdmin, selector);
407
+ const output = path.resolve(flags.output || pkg.filename || `${name}.tgz`);
408
+ fs.writeFileSync(output, Buffer.from(pkg.body));
409
+ console.log(`✓ ${name}@${pkg.semver} downloaded to ${output} (sha ${pkg.sha256.slice(0, 12)})`);
386
410
  return 0;
387
411
  }
388
412
  case "rm": {
@@ -392,8 +416,7 @@ export async function runAgentsCommand(argv) {
392
416
  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
417
  return 1;
394
418
  }
395
- if (web) await ctx.client.ops.deleteAgentPackage({ name });
396
- else await deleteAgentPackageEverywhere(ctx, name, actor, isAdmin);
419
+ await ctx.client.deleteAgentPackage(name, actor, isAdmin, selector);
397
420
  console.log(`✓ ${name} deleted (registry rows and artifacts)`);
398
421
  return 0;
399
422
  }
@@ -407,36 +430,15 @@ export async function runAgentsCommand(argv) {
407
430
  }
408
431
  }
409
432
 
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
433
  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 }));
434
+ const tree = await ctx.client.getAgentPackageTree(name, semver ?? null, actor, isAdmin);
435
+ return tree?.files ?? tree ?? [];
430
436
  }
431
437
 
432
438
  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 };
439
+ const file = await ctx.client.getAgentPackageFile(name, semver ?? null, filePath, actor, isAdmin);
440
+ const formatted = packageFileForCli(file);
441
+ return formatted.binary
442
+ ? { binary: true, size: formatted.size }
443
+ : { content: formatted.text, size: formatted.size };
442
444
  }
package/tui/src/app.js CHANGED
@@ -291,15 +291,39 @@ export function PilotSwarmTuiApp({ controller, platform, onRequestExit }) {
291
291
  }
292
292
 
293
293
  // ── Admin Console keybindings ─────────────────────────
294
- // The Admin Console replaces the workspace, so its input
295
- // handling must run before the modal/normal path. The GHCP key
296
- // editor overlay (`adminGhcpKey.editing`) acts like a modal:
297
- // route every printable / navigation key into the cursor-aware
298
- // controller mutators, and only Esc / Enter exit composition.
294
+ // The Admin Console replaces the workspace, so its input handling
295
+ // runs before the modal/normal path. Provider creation is a shared
296
+ // two-step name/credential wizard; the credential field is masked
297
+ // and ui-core clears it before awaiting the create call.
299
298
  const adminState = controller.getState().admin;
300
299
  const adminVisible = Boolean(adminState?.visible);
300
+ const adminProviderCreating = Boolean(adminState?.modelProviders?.create?.editing);
301
301
  const adminEditing = Boolean(adminState?.ghcpKey?.editing);
302
302
  const adminSaving = Boolean(adminState?.ghcpKey?.saving);
303
+ if (adminProviderCreating) {
304
+ const create = adminState.modelProviders.create;
305
+ if (key.escape) {
306
+ controller.cancelAdminProviderCreate();
307
+ return;
308
+ }
309
+ if (create.saving) return;
310
+ if (key.tab && create.stage === "name") {
311
+ controller.cycleAdminProviderCreateType();
312
+ return;
313
+ }
314
+ if (key.return) {
315
+ if (create.stage === "credential") controller.saveAdminProviderCreate().catch(() => {});
316
+ else controller.advanceAdminProviderCreate();
317
+ return;
318
+ }
319
+ if (key.leftArrow) { controller.moveAdminProviderCreateCursor(-1); return; }
320
+ if (key.rightArrow) { controller.moveAdminProviderCreateCursor(1); return; }
321
+ if (key.home) { controller.moveAdminProviderCreateCursorToBoundary("start"); return; }
322
+ if (key.end) { controller.moveAdminProviderCreateCursorToBoundary("end"); return; }
323
+ if (key.backspace || key.delete) { controller.deleteAdminProviderCreateChar(); return; }
324
+ if (!key.ctrl && !key.meta && input) controller.insertAdminProviderCreateText(input);
325
+ return;
326
+ }
303
327
  if (adminEditing) {
304
328
  // Block all input while saving so we don't enqueue mutations
305
329
  // against a stale draft. Esc still cancels in case the
@@ -325,18 +349,37 @@ export function PilotSwarmTuiApp({ controller, platform, onRequestExit }) {
325
349
  return;
326
350
  }
327
351
  if (adminVisible) {
328
- const adminSection = ["packages", "workers"].includes(adminState?.section) ? adminState.section : "ghcp";
352
+ const adminSection = ["providers", "packages", "workers"].includes(adminState?.section)
353
+ ? adminState.section
354
+ : "providers";
355
+ if (modal?.type === "confirm") {
356
+ if (key.escape || input === "n") {
357
+ controller.handleCommand(UI_COMMANDS.CLOSE_MODAL).catch(() => {});
358
+ return;
359
+ }
360
+ if (key.return || input === "y") {
361
+ controller.handleCommand(UI_COMMANDS.MODAL_CONFIRM).catch(() => {});
362
+ return;
363
+ }
364
+ return;
365
+ }
329
366
  if (key.escape) {
330
367
  controller.handleCommand(UI_COMMANDS.CLOSE_ADMIN_CONSOLE).catch(() => {});
331
368
  return;
332
369
  }
333
- // Settings-tree section toggles (Agents ⟷ GitHub Keys ⟷ Workers).
370
+ // Settings-tree section toggles (Model Providers ⟷ Agents ⟷ Workers).
334
371
  if (plainShortcut && input === "a" && adminSection !== "packages") {
335
372
  controller.handleCommand(UI_COMMANDS.ADMIN_SHOW_PACKAGES).catch(() => {});
336
373
  return;
337
374
  }
338
- if (plainShortcut && input === "g" && adminSection !== "ghcp") {
339
- controller.handleCommand(UI_COMMANDS.ADMIN_SHOW_GHCP).catch(() => {});
375
+ if (plainShortcut && input === "m") {
376
+ controller.setAdminSection("providers");
377
+ controller.setAdminModelProviderPage("mine");
378
+ return;
379
+ }
380
+ if (plainShortcut && input === "M" && adminState?.profile?.isAdmin) {
381
+ controller.setAdminSection("providers");
382
+ controller.setAdminModelProviderPage("shared");
340
383
  return;
341
384
  }
342
385
  if (plainShortcut && input === "w" && adminSection !== "workers"
@@ -351,6 +394,73 @@ export function PilotSwarmTuiApp({ controller, platform, onRequestExit }) {
351
394
  }
352
395
  return;
353
396
  }
397
+ if (adminSection === "providers") {
398
+ const selection = adminState.modelProviders?.selection || {};
399
+ const providerPage = adminState.modelProviders?.page === "shared" ? "shared" : "mine";
400
+ if (key.tab && providerPage === "shared") {
401
+ controller.setAdminModelProviderSelection({
402
+ focus: selection.focus === "agents" ? "providers" : "agents",
403
+ });
404
+ return;
405
+ }
406
+ if (plainShortcut && (input === "j" || key.downArrow)) {
407
+ controller.stepAdminModelProviderSelection(1);
408
+ return;
409
+ }
410
+ if (plainShortcut && (input === "k" || key.upArrow)) {
411
+ controller.stepAdminModelProviderSelection(-1);
412
+ return;
413
+ }
414
+ if (plainShortcut && input === "e" && providerPage === "mine") {
415
+ controller.beginAdminCreateGithubProvider();
416
+ return;
417
+ }
418
+ if (plainShortcut && input === "E" && providerPage === "shared" && adminState?.profile?.isAdmin) {
419
+ controller.beginAdminCreateProvider({ shared: true });
420
+ return;
421
+ }
422
+ if (plainShortcut && input === "d") {
423
+ controller.requestDeleteSelectedAdminProvider();
424
+ return;
425
+ }
426
+ if (plainShortcut && input === "r") {
427
+ controller.refreshAdminModelProviders().catch(() => {});
428
+ return;
429
+ }
430
+ if (plainShortcut && input === "u" && providerPage === "mine") {
431
+ controller.cycleAdminModelDefault("user").catch(() => {});
432
+ return;
433
+ }
434
+ if (plainShortcut && input === "l" && providerPage === "shared" && adminState?.profile?.isAdmin) {
435
+ controller.cycleAdminModelDefault("cluster").catch(() => {});
436
+ return;
437
+ }
438
+ if (plainShortcut && input === "s" && providerPage === "shared" && adminState?.profile?.isAdmin) {
439
+ controller.cycleAdminModelDefault("system").catch(() => {});
440
+ return;
441
+ }
442
+ if (plainShortcut && input === "C" && adminState?.profile?.isAdmin) {
443
+ controller.cycleAdminModelDefault("system", { restartDisposition: "complete" }).catch(() => {});
444
+ return;
445
+ }
446
+ if (plainShortcut && input === "T" && adminState?.profile?.isAdmin) {
447
+ controller.cycleAdminModelDefault("system", { restartDisposition: "terminate" }).catch(() => {});
448
+ return;
449
+ }
450
+ if (plainShortcut && input === "H" && adminState?.profile?.isAdmin) {
451
+ controller.cycleAdminModelDefault("system", { restartDisposition: "hard_delete" }).catch(() => {});
452
+ return;
453
+ }
454
+ if (plainShortcut && input === "t" && providerPage === "mine" && adminState?.profile?.isAdmin) {
455
+ controller.toggleSelectedAdminProviderSystemUse().catch(() => {});
456
+ return;
457
+ }
458
+ if (plainShortcut && input === "o" && providerPage === "shared" && adminState?.profile?.isAdmin) {
459
+ controller.cycleSelectedAdminSystemAgentOverride().catch(() => {});
460
+ return;
461
+ }
462
+ return;
463
+ }
354
464
  if (adminSection === "packages") {
355
465
  if (plainShortcut && (input === "j" || key.downArrow)) {
356
466
  controller.handleCommand(UI_COMMANDS.ADMIN_PACKAGES_NEXT).catch(() => {});
@@ -366,14 +476,6 @@ export function PilotSwarmTuiApp({ controller, platform, onRequestExit }) {
366
476
  }
367
477
  return;
368
478
  }
369
- if (plainShortcut && input === "e") {
370
- controller.handleCommand(UI_COMMANDS.ADMIN_BEGIN_EDIT_GHCP_KEY).catch(() => {});
371
- return;
372
- }
373
- if (plainShortcut && input === "c" && adminState?.profile?.githubCopilotKeySet) {
374
- controller.handleCommand(UI_COMMANDS.ADMIN_CLEAR_GHCP_KEY).catch(() => {});
375
- return;
376
- }
377
479
  if (plainShortcut && input === "r") {
378
480
  controller.handleCommand(UI_COMMANDS.ADMIN_REFRESH_PROFILE).catch(() => {});
379
481
  return;