@polpo-ai/cli 0.15.71 → 0.15.73

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.
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Agent-native conversational Channel management.
3
+ *
4
+ * Credentials are intentionally absent from this command surface. Providers
5
+ * that need authorization return an expiring secure setup URL.
6
+ */
7
+ import { randomUUID } from "node:crypto";
8
+ import * as clack from "@clack/prompts";
9
+ import pc from "picocolors";
10
+ import { createApiClient } from "./api.js";
11
+ import { loadProjectId } from "./project-context.js";
12
+ import { requireAuth } from "../../util/auth.js";
13
+ import { friendlyError } from "../../util/errors.js";
14
+ class ChannelCliApiError extends Error {
15
+ code;
16
+ status;
17
+ constructor(message, code, status) {
18
+ super(message);
19
+ this.code = code;
20
+ this.status = status;
21
+ this.name = "ChannelCliApiError";
22
+ }
23
+ }
24
+ export function conversationChannelPath(...segments) {
25
+ return `/v1/channels${segments.length ? `/${segments.map(encodeURIComponent).join("/")}` : ""}`;
26
+ }
27
+ function parseJsonObject(value, label) {
28
+ if (!value)
29
+ return undefined;
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(value);
33
+ }
34
+ catch {
35
+ throw new Error(`${label} must be valid JSON.`);
36
+ }
37
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
38
+ throw new Error(`${label} must be a JSON object.`);
39
+ }
40
+ return parsed;
41
+ }
42
+ function dataFrom(response) {
43
+ if (response.status < 200 || response.status >= 300) {
44
+ throw new ChannelCliApiError(response.data?.error ?? `Channel API returned HTTP ${response.status}`, response.data?.code ?? "CHANNEL_API_ERROR", response.status);
45
+ }
46
+ return response.data?.data;
47
+ }
48
+ function printResult(value, json) {
49
+ if (json) {
50
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
51
+ return;
52
+ }
53
+ if (Array.isArray(value)) {
54
+ if (value.length === 0) {
55
+ clack.log.info("No Channels found.");
56
+ return;
57
+ }
58
+ clack.log.info(value.map((item) => {
59
+ const detail = item.status ?? item.availability ?? item.agentName ?? "";
60
+ return ` ${pc.bold(item.name ?? item.label ?? item.id)}${detail ? pc.dim(` ${detail}`) : ""}`;
61
+ }).join("\n"));
62
+ return;
63
+ }
64
+ const result = value;
65
+ if (result?.status === "setup_required" || result?.setup?.url) {
66
+ clack.log.info(`${pc.bold("Secure setup required")}\n${result.setup.url}\n${pc.dim(`Expires ${result.setup.expiresAt}`)}`);
67
+ return;
68
+ }
69
+ clack.log.info(JSON.stringify(value, null, 2));
70
+ }
71
+ async function withChannelClient(operation, options, action) {
72
+ try {
73
+ const creds = await requireAuth({ context: `${operation} requires an authenticated session.` });
74
+ const projectId = loadProjectId();
75
+ if (!projectId)
76
+ throw new Error("No project linked. Run polpo create or polpo link first.");
77
+ await action(createApiClient(creds, projectId));
78
+ }
79
+ catch (error) {
80
+ if (options.json) {
81
+ process.stderr.write(`${JSON.stringify({
82
+ ok: false,
83
+ code: error instanceof ChannelCliApiError ? error.code : "CLI_ERROR",
84
+ error: error instanceof Error ? error.message : String(error),
85
+ ...(error instanceof ChannelCliApiError ? { status: error.status } : {}),
86
+ })}\n`);
87
+ process.exitCode = 1;
88
+ return;
89
+ }
90
+ clack.log.error(pc.red(friendlyError(error instanceof Error ? error.message : String(error))));
91
+ process.exitCode = 1;
92
+ }
93
+ }
94
+ export function registerChannelsCommand(program) {
95
+ const channels = program
96
+ .command("channels")
97
+ .description("Configure conversational Channels and agent Routes");
98
+ channels.command("providers")
99
+ .description("List supported Channel providers")
100
+ .option("--json", "Print JSON")
101
+ .action((opts) => withChannelClient("Listing Channel providers", opts, async (client) => {
102
+ printResult(dataFrom(await client.get(conversationChannelPath("providers"))), Boolean(opts.json));
103
+ }));
104
+ channels.command("list")
105
+ .description("List project Channels")
106
+ .option("--provider <provider>", "Filter by provider")
107
+ .option("--status <status>", "Filter by status")
108
+ .option("--connection <id>", "Filter by Connection id")
109
+ .option("--json", "Print JSON")
110
+ .action((opts) => withChannelClient("Listing Channels", opts, async (client) => {
111
+ const query = new URLSearchParams();
112
+ if (opts.provider)
113
+ query.set("provider", opts.provider);
114
+ if (opts.status)
115
+ query.set("status", opts.status);
116
+ if (opts.connection)
117
+ query.set("connectionId", opts.connection);
118
+ const suffix = query.size ? `?${query.toString()}` : "";
119
+ printResult(dataFrom(await client.get(`${conversationChannelPath()}${suffix}`)), Boolean(opts.json));
120
+ }));
121
+ channels.command("get <channel-id>")
122
+ .description("Inspect a Channel without exposing credentials")
123
+ .option("--json", "Print JSON")
124
+ .action((channelId, opts) => withChannelClient("Getting a Channel", opts, async (client) => {
125
+ printResult(dataFrom(await client.get(conversationChannelPath(channelId))), Boolean(opts.json));
126
+ }));
127
+ channels.command("add <provider>")
128
+ .description("Configure a Channel and grant it to an agent")
129
+ .requiredOption("--agent <name>", "Agent receiving the Channel route")
130
+ .option("--connection <id>", "Existing project Connection id")
131
+ .option("--destination <id>", "Provider destination id, when known")
132
+ .option("--name <name>", "Channel display name")
133
+ .option("--priority <number>", "Route priority", "100")
134
+ .option("--settings <json>", "Channel settings as a JSON object")
135
+ .option("--idempotency-key <key>", "Stable retry key")
136
+ .option("--json", "Print JSON")
137
+ .action((provider, opts) => withChannelClient("Configuring a Channel", opts, async (client) => {
138
+ const priority = Number(opts.priority);
139
+ if (!Number.isSafeInteger(priority))
140
+ throw new Error("--priority must be an integer.");
141
+ const body = {
142
+ provider,
143
+ agentName: opts.agent,
144
+ connectionId: opts.connection,
145
+ externalChannelId: opts.destination,
146
+ idempotencyKey: opts.idempotencyKey ?? randomUUID(),
147
+ name: opts.name,
148
+ priority,
149
+ settings: parseJsonObject(opts.settings, "--settings"),
150
+ };
151
+ printResult(dataFrom(await client.post(conversationChannelPath("configure"), body)), Boolean(opts.json));
152
+ }));
153
+ channels.command("update <channel-id>")
154
+ .description("Update Channel name, settings, or status")
155
+ .option("--name <name>", "Channel display name")
156
+ .option("--status <status>", "active or disabled")
157
+ .option("--settings <json>", "Channel settings as a JSON object")
158
+ .option("--json", "Print JSON")
159
+ .action((channelId, opts) => withChannelClient("Updating a Channel", opts, async (client) => {
160
+ const body = {
161
+ ...(opts.name ? { name: opts.name } : {}),
162
+ ...(opts.status ? { status: opts.status } : {}),
163
+ ...(opts.settings ? { settings: parseJsonObject(opts.settings, "--settings") } : {}),
164
+ };
165
+ if (Object.keys(body).length === 0)
166
+ throw new Error("Provide --name, --status, or --settings.");
167
+ printResult(dataFrom(await client.patch(conversationChannelPath(channelId), body)), Boolean(opts.json));
168
+ }));
169
+ channels.command("test <channel-id>")
170
+ .description("Test an active Channel")
171
+ .option("--json", "Print JSON")
172
+ .action((channelId, opts) => withChannelClient("Testing a Channel", opts, async (client) => {
173
+ printResult(dataFrom(await client.post(conversationChannelPath(channelId, "test"))), Boolean(opts.json));
174
+ }));
175
+ channels.command("remove <channel-id>")
176
+ .alias("rm")
177
+ .description("Remove a Channel and its Routes, but keep its Connection")
178
+ .option("--yes", "Skip confirmation")
179
+ .option("--json", "Print JSON")
180
+ .action((channelId, opts) => withChannelClient("Removing a Channel", opts, async (client) => {
181
+ if (!opts.yes) {
182
+ if (!process.stdin.isTTY)
183
+ throw new Error("Use --yes in non-interactive environments.");
184
+ const confirmed = await clack.confirm({ message: `Remove Channel ${channelId}?` });
185
+ if (clack.isCancel(confirmed) || !confirmed)
186
+ return;
187
+ }
188
+ printResult(dataFrom(await client.delete(conversationChannelPath(channelId))), Boolean(opts.json));
189
+ }));
190
+ const routes = channels.command("routes").description("Manage agent Routes for a Channel");
191
+ routes.command("list <channel-id>")
192
+ .option("--json", "Print JSON")
193
+ .action((channelId, opts) => withChannelClient("Listing Channel Routes", opts, async (client) => {
194
+ printResult(dataFrom(await client.get(conversationChannelPath(channelId, "routes"))), Boolean(opts.json));
195
+ }));
196
+ routes.command("add <channel-id>")
197
+ .requiredOption("--agent <name>", "Agent name")
198
+ .option("--destination <id>", "Route-specific destination")
199
+ .option("--priority <number>", "Route priority", "100")
200
+ .option("--disabled", "Create the Route disabled")
201
+ .option("--json", "Print JSON")
202
+ .action((channelId, opts) => withChannelClient("Adding a Channel Route", opts, async (client) => {
203
+ const priority = Number(opts.priority);
204
+ if (!Number.isSafeInteger(priority))
205
+ throw new Error("--priority must be an integer.");
206
+ printResult(dataFrom(await client.post(conversationChannelPath(channelId, "routes"), {
207
+ agentName: opts.agent,
208
+ externalChannelId: opts.destination,
209
+ enabled: !opts.disabled,
210
+ priority,
211
+ })), Boolean(opts.json));
212
+ }));
213
+ routes.command("remove <channel-id> <route-id>")
214
+ .alias("rm")
215
+ .option("--yes", "Skip confirmation")
216
+ .option("--json", "Print JSON")
217
+ .action((channelId, routeId, opts) => withChannelClient("Removing a Channel Route", opts, async (client) => {
218
+ if (!opts.yes) {
219
+ if (!process.stdin.isTTY)
220
+ throw new Error("Use --yes in non-interactive environments.");
221
+ const confirmed = await clack.confirm({ message: `Remove Route ${routeId}?` });
222
+ if (clack.isCancel(confirmed) || !confirmed)
223
+ return;
224
+ }
225
+ printResult(dataFrom(await client.delete(conversationChannelPath(channelId, "routes", routeId))), Boolean(opts.json));
226
+ }));
227
+ channels.command("setup-status <setup-id>")
228
+ .description("Inspect a resumable secure setup")
229
+ .option("--json", "Print JSON")
230
+ .action((setupId, opts) => withChannelClient("Getting Channel setup status", opts, async (client) => {
231
+ printResult(dataFrom(await client.get(conversationChannelPath("setups", setupId))), Boolean(opts.json));
232
+ }));
233
+ }
234
+ //# sourceMappingURL=channels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channels.js","sourceRoot":"","sources":["../../../src/commands/cloud/channels.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { Command } from "commander";
3
+ import { conversationChannelPath, registerChannelsCommand } from "./channels.js";
4
+ describe("Channels CLI", () => {
5
+ it("encodes every resource path segment", () => {
6
+ expect(conversationChannelPath("channel/a", "routes", "route b"))
7
+ .toBe("/v1/channels/channel%2Fa/routes/route%20b");
8
+ });
9
+ it("exposes lifecycle and route commands without credential flags", () => {
10
+ const program = new Command();
11
+ program.exitOverride();
12
+ registerChannelsCommand(program);
13
+ const help = program.commands.find((command) => command.name() === "channels").helpInformation();
14
+ expect(help).toContain("providers");
15
+ expect(help).toContain("add");
16
+ expect(help).toContain("routes");
17
+ expect(help).toContain("setup-status");
18
+ expect(help).not.toMatch(/--(?:token|secret|password|api-key|verify-token)/i);
19
+ const channels = program.commands.find((command) => command.name() === "channels");
20
+ const add = channels.commands.find((command) => command.name() === "add");
21
+ expect(add.registeredArguments[0]?.required).toBe(true);
22
+ expect(add.options.map((option) => option.long)).not.toEqual(expect.arrayContaining([
23
+ "--token",
24
+ "--secret",
25
+ "--password",
26
+ "--api-key",
27
+ "--verify-token",
28
+ ]));
29
+ });
30
+ });
31
+ //# sourceMappingURL=channels.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channels.test.js","sourceRoot":"","sources":["../../../src/commands/cloud/channels.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAEjF,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,uBAAuB,CAAC,WAAW,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;aAC9D,IAAI,CAAC,2CAA2C,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QAC9B,OAAO,CAAC,YAAY,EAAE,CAAC;QACvB,uBAAuB,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,UAAU,CAAE,CAAC,eAAe,EAAE,CAAC;QAElG,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAC;QAE9E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,UAAU,CAAE,CAAC;QACpF,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,KAAK,CAAE,CAAC;QAC3E,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC;YAClF,SAAS;YACT,UAAU;YACV,YAAY;YACZ,WAAW;YACX,gBAAgB;SACjB,CAAC,CAAC,CAAC;IACN,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -7,7 +7,7 @@
7
7
  * - memory.md + memory/<agent>.md (knowledge base)
8
8
  * - playbooks/ (mission templates)
9
9
  * - missions/ (mission definitions)
10
- * - skills/ (SKILL.md files)
10
+ * - skills/ (complete Agent Skills bundles)
11
11
  * - vault.enc (encrypted credentials)
12
12
  *
13
13
  * Opt-in (with flags):
@@ -33,6 +33,7 @@ import { listLoopSourceFiles, loadLoopDeployPayload } from "../../util/loops.js"
33
33
  import { prepareScheduleDeployments } from "../../util/schedules.js";
34
34
  import { readPolpoConfig, writePolpoConfig } from "../../util/polpo-config.js";
35
35
  import { collectCustomToolSourceArtifact, extractCustomToolName, } from "../../util/custom-tool-source.js";
36
+ import { collectLocalSkillBundle } from "../../util/runtime-skill-bundle.js";
36
37
  function emptyResult() {
37
38
  return {
38
39
  created: 0,
@@ -448,84 +449,41 @@ async function deploySkills(client, polpoDir, opts) {
448
449
  const skillFile = path.join(skillsDir, entry.name, "SKILL.md");
449
450
  if (!fs.existsSync(skillFile))
450
451
  continue;
451
- const raw = fs.readFileSync(skillFile, "utf-8");
452
- const fmMatch = raw.match(/^---\n([\s\S]*?)\n---/);
453
- let name = entry.name;
454
- let description = "";
455
- let allowedTools;
456
- if (fmMatch) {
457
- const lines = fmMatch[1].split("\n");
458
- let currentArray = null;
459
- for (const line of lines) {
460
- const arrayItem = line.match(/^\s+-\s+(.+)/);
461
- if (arrayItem && currentArray) {
462
- currentArray.push(arrayItem[1].trim());
463
- continue;
464
- }
465
- currentArray = null;
466
- const kv = line.match(/^(\w[\w-]*)\s*:\s*(.+)?/);
467
- if (kv) {
468
- const key = kv[1];
469
- const val = kv[2]?.trim();
470
- if (key === "name" && val)
471
- name = val;
472
- if (key === "description" && val)
473
- description = val;
474
- if (key === "allowed-tools" || key === "allowedTools") {
475
- allowedTools = [];
476
- currentArray = allowedTools;
477
- }
478
- }
479
- }
452
+ let localBundle;
453
+ try {
454
+ localBundle = collectLocalSkillBundle(path.join(skillsDir, entry.name), entry.name);
480
455
  }
481
- const bodyMatch = raw.match(/^---\n[\s\S]*?\n---\n?([\s\S]*)$/);
482
- const content = bodyMatch ? bodyMatch[1].trim() : raw.trim();
483
- const localSkill = { name, description, content };
456
+ catch (error) {
457
+ result.errors.push(`skill "${entry.name}": ${error instanceof Error ? error.message : String(error)}`);
458
+ result.failed++;
459
+ continue;
460
+ }
461
+ const name = localBundle.name;
484
462
  const remote = existingSkills[name];
485
- const action = await resolveDeployConflict(localSkill, remote ? { name: remote.name, description: remote.description, content: remote.content } : null, `skill "${name}"`, opts);
463
+ let remoteBundle = null;
464
+ if (remote) {
465
+ const bundleRes = await client.get(`/v1/skills/${encodeURIComponent(name)}/bundle`);
466
+ if (bundleRes.status === 200)
467
+ remoteBundle = bundleRes.data?.data ?? bundleRes.data;
468
+ }
469
+ const action = await resolveDeployConflict(localBundle, remoteBundle, `skill "${name}"`, opts);
486
470
  if (action === "skip") {
487
471
  result.skipped++;
488
472
  continue;
489
473
  }
490
- // The server doesn't expose `PUT /v1/skills/{name}` for in-place updates —
491
- // only POST /v1/skills/create + DELETE /v1/skills/{name} exist. So
492
- // "update existing skill" is implemented as delete-then-recreate. The
493
- // DELETE failure is logged but we still try the create: if it succeeds
494
- // the user ends up with a fresh copy, which is the intent.
495
- if (remote) {
496
- const delRes = await client.delete(`/v1/skills/${encodeURIComponent(name)}`);
497
- if (delRes.status >= 400 && delRes.status !== 404) {
498
- const msg = readErrorBody(delRes.data, delRes.status);
499
- result.errors.push(`skill "${name}": delete-before-update failed — ${friendlyError(msg)}`);
500
- result.failed++;
501
- continue;
502
- }
503
- const res = await client.post("/v1/skills/create", {
504
- name, description, content,
505
- ...(allowedTools?.length ? { allowedTools } : {}),
506
- });
507
- if (res.status >= 200 && res.status < 300) {
474
+ const res = await client.put(`/v1/skills/${encodeURIComponent(name)}/bundle`, {
475
+ files: localBundle.files,
476
+ });
477
+ if (res.status >= 200 && res.status < 300) {
478
+ if (remote)
508
479
  result.updated++;
509
- }
510
- else {
511
- const msg = readErrorBody(res.data, res.status);
512
- result.errors.push(`skill "${name}": recreate failed — ${friendlyError(msg)}`);
513
- result.failed++;
514
- }
480
+ else
481
+ result.created++;
515
482
  }
516
483
  else {
517
- const res = await client.post("/v1/skills/create", {
518
- name, description, content,
519
- ...(allowedTools?.length ? { allowedTools } : {}),
520
- });
521
- if (res.status >= 200 && res.status < 300) {
522
- result.created++;
523
- }
524
- else {
525
- const msg = readErrorBody(res.data, res.status);
526
- result.errors.push(`skill "${name}": ${friendlyError(msg)}`);
527
- result.failed++;
528
- }
484
+ const msg = readErrorBody(res.data, res.status);
485
+ result.errors.push(`skill "${name}": bundle deploy failed — ${friendlyError(msg)}`);
486
+ result.failed++;
529
487
  }
530
488
  }
531
489
  return result;