@andreprado/agentkit 0.1.1 → 0.3.0

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 (97) hide show
  1. package/README.md +8 -77
  2. package/docs/guides/add-channel.md +14 -92
  3. package/docs/guides/add-knowledge.md +0 -21
  4. package/docs/guides/add-tool.md +5 -11
  5. package/docs/guides/channel-security.md +3 -207
  6. package/docs/guides/connect-discord.md +7 -172
  7. package/docs/guides/connect-slack.md +6 -121
  8. package/docs/guides/connect-telegram.md +6 -165
  9. package/docs/guides/connect-whatsapp-evolution.md +6 -116
  10. package/docs/guides/connect-whatsapp-uazapi.md +6 -134
  11. package/docs/guides/connect-whatsapp-zapster.md +6 -202
  12. package/docs/guides/create-agent.md +5 -14
  13. package/docs/guides/debug-channel.md +4 -156
  14. package/docs/guides/improve-local.md +13 -0
  15. package/docs/guides/local-only-migration.md +35 -0
  16. package/docs/guides/replay-local-traces.md +11 -0
  17. package/docs/guides/run-evals.md +2 -4
  18. package/docs/guides/security-rules.md +5 -154
  19. package/docs/guides/use-jev.md +3 -6
  20. package/docs/guides/use-provider.md +5 -6
  21. package/docs/guides/write-feedback.md +10 -0
  22. package/docs/llms-full.txt +27 -448
  23. package/docs/llms.txt +8 -44
  24. package/package.json +3 -5
  25. package/src/cli/commands/channels.ts +8 -1613
  26. package/src/cli/commands/feedback.ts +8 -86
  27. package/src/cli/commands/provider.ts +29 -11
  28. package/src/cli/constants.ts +0 -3
  29. package/src/cli/flags.ts +0 -28
  30. package/src/cli/help.ts +16 -92
  31. package/src/cli/index.ts +15 -1091
  32. package/src/index.ts +6 -158
  33. package/src/providers/codex-auth.ts +16 -2
  34. package/src/providers/pi.ts +36 -19
  35. package/src/runtime/channels/discord.ts +2 -2
  36. package/src/runtime/chat.ts +5 -3
  37. package/src/runtime/config.ts +14 -148
  38. package/src/runtime/database.ts +2 -2
  39. package/src/runtime/dev-server.ts +8 -8
  40. package/src/runtime/env.ts +11 -0
  41. package/src/runtime/improve.ts +2 -262
  42. package/src/runtime/inspect.ts +13 -73
  43. package/src/runtime/knowledge/ingest.ts +1 -1
  44. package/src/runtime/knowledge/tool.ts +16 -2
  45. package/src/runtime/knowledge/vector.ts +1 -1
  46. package/src/runtime/tool-runner.ts +5 -3
  47. package/src/runtime/tools.ts +10 -14
  48. package/src/storage/sqlite.ts +11 -32
  49. package/src/templates/blank.ts +15 -102
  50. package/src/templates/common.ts +60 -0
  51. package/src/templates/dentista.ts +7 -74
  52. package/src/templates/skills/agentkit-capsule/SKILL.md +5 -7
  53. package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -3
  54. package/src/templates/skills/agentkit-channels/SKILL.md +6 -119
  55. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +0 -9
  56. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +1 -64
  57. package/src/templates/skills/agentkit-channels/references/discord.md +2 -92
  58. package/src/templates/skills/agentkit-channels/references/slack.md +2 -55
  59. package/src/templates/skills/agentkit-channels/references/telegram.md +2 -71
  60. package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +2 -56
  61. package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +2 -53
  62. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +2 -70
  63. package/src/templates/skills/agentkit-database/SKILL.md +2 -4
  64. package/src/templates/skills/agentkit-evals/SKILL.md +1 -1
  65. package/src/templates/skills/agentkit-improve/SKILL.md +6 -85
  66. package/src/templates/skills/agentkit-improve/references/trace-packets.md +1 -1
  67. package/src/templates/skills/agentkit-provider/SKILL.md +1 -2
  68. package/src/templates/skills/agentkit-security/SKILL.md +1 -3
  69. package/src/templates/skills/agentkit-tools/SKILL.md +1 -1
  70. package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +1 -2
  71. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +5 -11
  72. package/src/templates/support.ts +8 -92
  73. package/docs/guides/add-managed-composio.md +0 -165
  74. package/docs/guides/improve-from-production.md +0 -151
  75. package/docs/guides/prepare-deploy.md +0 -227
  76. package/docs/guides/replay-production-traces.md +0 -72
  77. package/docs/guides/send-feedback.md +0 -135
  78. package/src/cli/cloud-client.ts +0 -377
  79. package/src/cli/deploy-chat-ui.ts +0 -606
  80. package/src/cli/deploy-readiness.ts +0 -561
  81. package/src/cloud/artifact.ts +0 -139
  82. package/src/cloud/client.ts +0 -80
  83. package/src/cloud/contracts.ts +0 -63
  84. package/src/cloud/index.ts +0 -3
  85. package/src/runtime/build.ts +0 -43
  86. package/src/runtime/core/deploy-state.ts +0 -54
  87. package/src/runtime/core/manifest.ts +0 -283
  88. package/src/runtime/core/targets.ts +0 -133
  89. package/src/runtime/deploy-readiness.ts +0 -135
  90. package/src/runtime/deploy.ts +0 -1
  91. package/src/runtime/integrations/composio.ts +0 -425
  92. package/src/runtime/targets/cloudflare/build.ts +0 -3319
  93. package/src/runtime/targets/container/build.ts +0 -146
  94. package/src/runtime/targets/container/server.ts +0 -33
  95. package/src/runtime/targets/vps/deploy.ts +0 -223
  96. package/src/templates/skills/agentkit-deploy/SKILL.md +0 -52
  97. package/src/templates/skills/agentkit-integrations/SKILL.md +0 -98
@@ -1,1620 +1,15 @@
1
- import { createHmac } from "node:crypto";
2
- import { readFile } from "node:fs/promises";
3
- import { resolve } from "node:path";
4
-
5
- import type { ParsedArgs } from "../args";
6
- import { cloudFetch, cloudGet, cloudPost, readCloudError } from "../cloud-client";
7
- import { parseCloudApiUrl } from "../flags";
8
1
  import { loadAgentCapsule } from "../../runtime/config";
9
- import { readLocalDeployStateIfExists, type LocalDeployState } from "../../runtime/core/deploy-state";
10
2
  import { loadCapsuleEnv } from "../../runtime/env";
11
- import type { AgentChannel } from "../../index";
12
-
13
- type DeployState = LocalDeployState;
14
-
15
- type CloudChannel = {
16
- id: string;
17
- name: string;
18
- type: "website" | "telegram" | "whatsapp" | "discord" | "slack" | "webhook";
19
- provider: "agentkit" | "telegram" | "zapster" | "meta" | "uazapi" | "evolution" | "discord" | "slack" | "generic";
20
- mode?: "interactions" | "bot";
21
- direction?: "inbound" | "outbound";
22
- status: string;
23
- webhook_url: string;
24
- required_secrets: Array<{ name: string; status: string }>;
25
- buffer?: AgentChannel["buffer"];
26
- audio?: AgentChannel["audio"];
27
- reply_to?: Extract<AgentChannel, { replyTo?: unknown }>["replyTo"];
28
- url_secret?: string;
29
- auth?: Extract<AgentChannel, { type: "webhook"; direction: "outbound" }>["auth"];
30
- };
31
-
32
- type CloudDelivery = {
33
- id: string;
34
- channel_id: string;
35
- direction: string;
36
- status: string;
37
- provider: {
38
- event_id?: string;
39
- };
40
- received_at: string;
41
- updated_at?: string;
42
- error?: {
43
- code: string;
44
- message: string;
45
- };
46
- events?: Array<{
47
- state: string;
48
- message: string;
49
- created_at: string;
50
- }>;
51
- };
52
-
53
- type CloudChannelBuffer = {
54
- channel_id: string;
55
- deploy_id: string;
56
- conversation_id: string;
57
- job_id?: string;
58
- delivery_id?: string;
59
- buffered_delivery_ids: string[];
60
- buffered_message_count: number;
61
- buffered_char_count: number;
62
- status: string;
63
- first_received_at: string;
64
- last_received_at: string;
65
- available_at?: string;
66
- age_ms: number;
67
- stale: boolean;
68
- message_preview: string;
69
- };
70
-
71
- type ChannelBufferActionResponse = {
72
- buffer: CloudChannelBuffer | null;
73
- job: {
74
- id: string;
75
- status: string;
76
- conversation_id: string;
77
- } | null;
78
- cleared?: boolean;
79
- retried?: boolean;
80
- };
81
-
82
- type ChannelSetupResponse = {
83
- channel: CloudChannel;
84
- setup?: {
85
- provider: "telegram" | "uazapi" | "evolution";
86
- webhook_url: string;
87
- status: "configured";
88
- bot_username?: string;
89
- webhook_count?: number;
90
- matched_webhook_id?: string;
91
- get_webhook_info?: {
92
- url: string;
93
- pending_update_count: number;
94
- };
95
- };
96
- };
97
-
98
- type ChannelDoctorReport = {
99
- channel: CloudChannel;
100
- checks: Array<{
101
- name: string;
102
- status: "ok" | "warn" | "fail" | "unknown";
103
- message: string;
104
- fix?: string;
105
- }>;
106
- buffers: CloudChannelBuffer[];
107
- last_delivery: {
108
- inbound: CloudDelivery | null;
109
- outbound: CloudDelivery | null;
110
- };
111
- probable_error: string | null;
112
- suggested_fix: string | null;
113
- };
3
+ import type { ParsedArgs } from "../args";
114
4
 
115
5
  export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
116
- await loadLocalEnvIntoProcess(process.cwd());
117
-
118
- const [subcommand, first, second, third] = args.positional;
119
-
120
- if (subcommand === "list") {
121
- const deployState = await readDeployStateIfExists(process.cwd());
122
-
123
- if (deployState) {
124
- const channels = await cloudGet<{ channels: CloudChannel[] }>(
125
- parseCloudApiUrl(args.flags.api),
126
- `/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
127
- );
128
- printChannelsTable(channels.channels);
129
- return;
130
- }
131
-
132
- const capsule = await loadAgentCapsule(process.cwd());
133
- printChannelsTable(
134
- (capsule.config.channels ?? []).map((channel) => ({
135
- id: "",
136
- name: channel.name,
137
- type: channel.type,
138
- provider: channel.provider,
139
- ...(channel.type === "discord" ? { mode: channel.mode } : {}),
140
- status: "local_config",
141
- webhook_url: "",
142
- required_secrets: channel.secrets.map((name) => ({ name, status: "missing" })),
143
- })),
144
- );
145
- return;
146
- }
147
-
148
- if (subcommand === "add") {
149
- const type = first;
150
- const name = second;
151
-
152
- if (!type || !name || !isCliChannelType(type)) {
153
- throw new Error("Usage: agentkit channels add <website|telegram|whatsapp|discord|slack|webhook> <name> [--provider zapster|meta|uazapi|evolution] [--mode interactions|bot]");
154
- }
155
-
156
- const response = await createHostedChannel(type, name, args.flags.provider, args.flags.mode, args.flags.api);
157
- printChannelStatus(response.channel);
158
- printSecretRepairHint(response.channel);
159
- return;
160
- }
161
-
162
- if (subcommand === "connect") {
163
- const type = first;
164
- const name = second;
165
-
166
- if (!type || !name || !isCliChannelType(type)) {
167
- throw new Error("Usage: agentkit channels connect <website|telegram|whatsapp|discord|slack|webhook> <name> [--provider zapster|meta|uazapi|evolution] [--mode interactions|bot]");
168
- }
169
-
170
- const created = await createHostedChannel(type, name, args.flags.provider, args.flags.mode, args.flags.api);
171
- let channel = created.channel;
172
- printChannelStatus(channel);
173
- printSecretRepairHint(channel);
174
- console.log("Channel resource: created_or_updated");
175
-
176
- if (hasMissingRequiredSecrets(channel)) {
177
- console.log("Secrets: missing");
178
- console.log("");
179
- console.log("Connect blocked: set the missing managed secrets, then rerun this same command.");
180
- console.log("Run: npm run agentkit -- secret sync --from-local");
181
- process.exitCode = 1;
182
- return;
183
- }
184
-
185
- console.log("Secrets: validated");
186
-
187
- if (channel.type === "telegram") {
188
- const setup = await applyTelegramWebhook(channel, args.flags.api);
189
- channel = setup.channel ?? channel;
190
- console.log("Telegram setWebhook: OK");
191
- console.log("Telegram webhook confirmed.");
192
- if (setup.setup?.get_webhook_info) {
193
- console.log(`Telegram pending updates: ${setup.setup.get_webhook_info.pending_update_count}`);
194
- }
195
-
196
- const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
197
- printTelegramHumanNextStep(channel.name, setup.setup?.bot_username);
198
- if (!smoke) {
199
- process.exitCode = 1;
200
- }
201
- return;
202
- }
203
-
204
- if (channel.type === "whatsapp" && channel.provider === "uazapi") {
205
- const setup = await applyProviderWebhook(channel, args.flags.api);
206
- channel = setup.channel ?? channel;
207
- console.log("UAZAPI webhook setup: OK");
208
- console.log("UAZAPI webhook confirmed.");
209
- if (setup.setup?.webhook_count !== undefined) {
210
- console.log(`UAZAPI webhooks: ${setup.setup.webhook_count}`);
211
- }
212
-
213
- const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
214
- printUazapiHumanNextStep(channel);
215
- if (!smoke) {
216
- process.exitCode = 1;
217
- }
218
- return;
219
- }
220
-
221
- if (channel.type === "whatsapp" && channel.provider === "evolution") {
222
- const setup = await applyProviderWebhook(channel, args.flags.api);
223
- channel = setup.channel ?? channel;
224
- console.log("Evolution webhook setup: OK");
225
- console.log("Evolution webhook confirmed.");
226
-
227
- const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
228
- printEvolutionHumanNextStep(channel);
229
- if (!smoke) {
230
- process.exitCode = 1;
231
- }
232
- return;
233
- }
234
-
235
- if (channel.type === "discord") {
236
- const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
237
- printDiscordHumanNextStep(channel);
238
- if (!smoke) {
239
- process.exitCode = 1;
240
- }
241
- return;
242
- }
243
-
244
- if (channel.type === "slack") {
245
- const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
246
- printSlackHumanNextStep(channel);
247
- if (!smoke) {
248
- process.exitCode = 1;
249
- }
250
- return;
251
- }
252
-
253
- if (channel.type === "webhook") {
254
- if (channel.direction === "outbound") {
255
- console.log("Outbound webhook: configured");
256
- console.log(`Next human step: make sure ${channel.url_secret ?? "the webhook URL secret"} is set as a managed secret.`);
257
- return;
258
- }
259
-
260
- const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
261
- printGenericWebhookHumanNextStep(channel);
262
- if (!smoke) {
263
- process.exitCode = 1;
264
- }
265
- return;
266
- }
267
-
268
- if (channel.type === "whatsapp" && channel.provider === "zapster") {
269
- const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
270
- console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
271
- if (!smoke) {
272
- process.exitCode = 1;
273
- }
274
- } else if (channel.type === "whatsapp") {
275
- console.log("Smoke: skipped until the provider webhook is configured.");
276
- console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
277
- } else {
278
- const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
279
- console.log(`Next human step: use ${channel.webhook_url} from your website client.`);
280
- if (!smoke) {
281
- process.exitCode = 1;
282
- }
283
- }
284
- return;
285
- }
286
-
287
- if (subcommand === "setup") {
288
- if (!first) {
289
- throw new Error("Usage: agentkit channels setup <name> [--apply]");
290
- }
291
-
292
- const channel = await resolveCloudChannelByName(first, args.flags.api);
293
- printChannelStatus(channel);
294
- if (channel.type === "telegram") {
295
- if (args.flags.apply) {
296
- const setup = await applyTelegramWebhook(channel, args.flags.api);
297
- console.log("Telegram webhook confirmed.");
298
- printChannelStatus(setup.channel ?? channel);
299
- printTelegramHumanNextStep(channel.name, setup.setup?.bot_username);
300
- } else {
301
- console.log(`Setup: call Telegram setWebhook with ${channel.webhook_url}`);
302
- console.log("Use --apply to mutate Telegram webhook settings.");
303
- }
304
- } else if (channel.type === "whatsapp" && channel.provider === "uazapi") {
305
- if (args.flags.apply) {
306
- const setup = await applyProviderWebhook(channel, args.flags.api);
307
- console.log("UAZAPI webhook confirmed.");
308
- printChannelStatus(setup.channel ?? channel);
309
- printUazapiHumanNextStep(setup.channel ?? channel);
310
- } else {
311
- console.log(`Setup: configure UAZAPI /webhook with ${channel.webhook_url}`);
312
- console.log("Use --apply to mutate UAZAPI webhook settings.");
313
- }
314
- } else if (channel.type === "whatsapp" && channel.provider === "evolution") {
315
- if (args.flags.apply) {
316
- const setup = await applyProviderWebhook(channel, args.flags.api);
317
- console.log("Evolution webhook confirmed.");
318
- printChannelStatus(setup.channel ?? channel);
319
- printEvolutionHumanNextStep(setup.channel ?? channel);
320
- } else {
321
- console.log(`Setup: configure Evolution /webhook/set/{instance} with ${channel.webhook_url}`);
322
- console.log("Use --apply to mutate Evolution webhook settings.");
323
- }
324
- } else if (channel.type === "whatsapp") {
325
- console.log(`Next step: paste this URL into ${channel.provider}'s webhook settings.`);
326
- } else if (channel.type === "discord") {
327
- printDiscordHumanNextStep(channel);
328
- } else if (channel.type === "slack") {
329
- printSlackHumanNextStep(channel);
330
- } else if (channel.type === "webhook") {
331
- if (channel.direction === "outbound") {
332
- console.log("Outbound webhook endpoint: configured from managed secrets.");
333
- } else {
334
- printGenericWebhookHumanNextStep(channel);
335
- }
336
- } else {
337
- console.log(`Website endpoint: ${channel.webhook_url}`);
338
- console.log("Use this endpoint from the AgentKit website client or a signed server-to-server test.");
339
- }
340
- return;
341
- }
342
-
343
- if (subcommand === "status") {
344
- if (!first) {
345
- throw new Error("Usage: agentkit channels status <name>");
346
- }
347
-
348
- const channel = await resolveCloudChannelByName(first, args.flags.api);
349
- printChannelStatus(channel);
350
- await printChannelDeliverySummary(channel, args.flags.api);
351
- return;
352
- }
353
-
354
- if (subcommand === "doctor") {
355
- if (!first) {
356
- throw new Error("Usage: agentkit channels doctor <name>");
357
- }
358
-
359
- const channel = await resolveCloudChannelByName(first, args.flags.api);
360
- const report = await cloudGet<{ doctor: ChannelDoctorReport }>(
361
- parseCloudApiUrl(args.flags.api),
362
- `/v1/channels/${encodeURIComponent(channel.id)}/doctor`,
363
- );
364
- printChannelDoctor(report.doctor);
365
-
366
- if (report.doctor.checks.some((check) => check.status === "fail")) {
367
- process.exitCode = 1;
368
- }
369
- return;
370
- }
371
-
372
- if (subcommand === "test") {
373
- if (!first) {
374
- throw new Error('Usage: agentkit channels test <name> [--message "hello"] [--fixture <path>]');
375
- }
376
-
377
- const channel = await resolveCloudChannelByName(first, args.flags.api);
378
- if (channel.type === "webhook" && channel.direction === "outbound") {
379
- throw new Error("Outbound webhook channels do not have an inbound test endpoint. Test them through a source channel that routes replies to this channel.");
380
- }
381
- const apiUrl = parseCloudApiUrl(args.flags.api);
382
- const signed = channel.type === "whatsapp" && channel.provider === "evolution" && !args.flags.fixture
383
- ? await runServerGeneratedChannelTest(channel, apiUrl, args.flags.message)
384
- : await runServerSignedChannelTest(channel, apiUrl, await channelTestBody(channel, args.flags.message, args.flags.fixture));
385
- const response = signed.response;
386
- const payload = signed.payload;
387
-
388
- console.log(JSON.stringify(payload, null, 2));
389
- if (!response.ok) {
390
- process.exitCode = 1;
391
- }
392
- return;
393
- }
394
-
395
- if (subcommand === "test-audio") {
396
- if (!first) {
397
- throw new Error("Usage: agentkit channels test-audio <name> [--fixture voice-note|audio-file|<path>]");
398
- }
399
-
400
- const channel = await resolveCloudChannelByName(first, args.flags.api);
401
- const apiUrl = parseCloudApiUrl(args.flags.api);
402
- const body = await channelAudioTestBody(channel, args.flags.fixture);
403
- const signed = await runServerSignedChannelTest(channel, apiUrl, body);
404
- const response = signed.response;
405
- const payload = signed.payload;
406
-
407
- console.log(JSON.stringify(payload, null, 2));
408
- if (!response.ok) {
409
- process.exitCode = 1;
410
- }
411
- return;
412
- }
413
-
414
- if (subcommand === "deliveries") {
415
- if (first === "list") {
416
- if (!second) {
417
- throw new Error("Usage: agentkit channels deliveries list <name>");
418
- }
419
-
420
- const channel = await resolveCloudChannelByName(second, args.flags.api);
421
- const response = await cloudGet<{ deliveries: CloudDelivery[] }>(
422
- parseCloudApiUrl(args.flags.api),
423
- `/v1/channels/${encodeURIComponent(channel.id)}/deliveries`,
424
- );
425
-
426
- if (response.deliveries.length === 0) {
427
- console.log("No channel deliveries found.");
428
- return;
429
- }
430
-
431
- console.log("ID\tCHANNEL\tDIRECTION\tSTATUS\tPROVIDER_EVENT\tRECEIVED");
432
- for (const delivery of response.deliveries) {
433
- console.log(
434
- [
435
- delivery.id,
436
- delivery.channel_id,
437
- delivery.direction,
438
- delivery.status,
439
- delivery.provider.event_id ?? "",
440
- delivery.received_at,
441
- ].join("\t"),
442
- );
443
- }
444
- return;
445
- }
446
-
447
- if (first === "show") {
448
- if (!second) {
449
- throw new Error("Usage: agentkit channels deliveries show <delivery-id>");
450
- }
451
-
452
- const response = await cloudGet<{ delivery: CloudDelivery }>(
453
- parseCloudApiUrl(args.flags.api),
454
- `/v1/channel-deliveries/${encodeURIComponent(second)}`,
455
- );
456
- console.log(JSON.stringify(response.delivery, null, 2));
457
- return;
458
- }
459
- }
460
-
461
- if (subcommand === "buffers") {
462
- if (first === "list") {
463
- if (!second) {
464
- throw new Error("Usage: agentkit channels buffers list <name>");
465
- }
466
-
467
- const channel = await resolveCloudChannelByName(second, args.flags.api);
468
- const response = await cloudGet<{ buffers: CloudChannelBuffer[] }>(
469
- parseCloudApiUrl(args.flags.api),
470
- `/v1/channels/${encodeURIComponent(channel.id)}/buffers`,
471
- );
472
- printChannelBuffers(response.buffers);
473
- return;
474
- }
475
-
476
- if (first === "show") {
477
- if (!second) {
478
- throw new Error("Usage: agentkit channels buffers show [<name>] <conversation-id>");
479
- }
480
-
481
- const target = await resolveChannelBufferTarget(second, third, args.flags.api);
482
- const response = await cloudGet<{ buffer: CloudChannelBuffer }>(
483
- parseCloudApiUrl(args.flags.api),
484
- `/v1/channels/${encodeURIComponent(target.channel.id)}/buffers/${encodeURIComponent(target.conversationId)}`,
485
- );
486
- console.log(JSON.stringify(response.buffer, null, 2));
487
- return;
488
- }
489
-
490
- if (first === "flush" || first === "clear" || first === "retry") {
491
- if (!second) {
492
- throw new Error(`Usage: agentkit channels buffers ${first} [<name>] <conversation-id>`);
493
- }
494
-
495
- const target = await resolveChannelBufferTarget(second, third, args.flags.api);
496
- const response = await cloudPost<ChannelBufferActionResponse>(
497
- parseCloudApiUrl(args.flags.api),
498
- `/v1/channels/${encodeURIComponent(target.channel.id)}/buffers/${encodeURIComponent(target.conversationId)}/${first}`,
499
- {},
500
- );
501
- printChannelBufferAction(first, response);
502
- return;
503
- }
504
- }
505
-
506
- throw new Error(
507
- "Usage: agentkit channels list | add | connect | setup | status | doctor | test | test-audio | deliveries list | deliveries show | buffers list | buffers show | buffers flush | buffers clear | buffers retry",
508
- );
509
- }
510
-
511
- async function createHostedChannel(
512
- type: "website" | "telegram" | "whatsapp" | "discord" | "slack" | "webhook",
513
- name: string,
514
- providerFlag: string | boolean | undefined,
515
- modeFlag: string | boolean | undefined,
516
- apiFlag: string | boolean | undefined,
517
- ): Promise<{ channel: CloudChannel }> {
518
- const deployState = await readDeployStateRequired(process.cwd());
519
- const provider = channelProviderForCli(type, providerFlag);
520
- const localChannel = await readLocalChannelConfig(process.cwd(), type, provider, name);
521
- const mode = discordModeForCli(type, modeFlag, localChannel);
522
- const payload = {
523
- name,
524
- type,
525
- provider,
526
- ...(mode ? { mode } : {}),
527
- secrets: localChannel?.secrets ?? defaultChannelSecretsForCli(type, provider, mode),
528
- ...(localChannel?.limits ? { limits: channelLimitsForApi(localChannel.limits) } : {}),
529
- ...(localChannel?.buffer ? { buffer: localChannel.buffer } : {}),
530
- ...(localChannel?.audio ? { audio: localChannel.audio } : {}),
531
- ...channelExtraConfigForApi(localChannel),
532
- };
533
-
534
- return cloudPost<{ channel: CloudChannel }>(
535
- parseCloudApiUrl(apiFlag),
536
- `/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
537
- payload,
538
- );
539
- }
540
-
541
- async function runChannelSmoke(
542
- channel: CloudChannel,
543
- apiFlag: string | boolean | undefined,
544
- messageFlag: string | boolean | undefined,
545
- ): Promise<{ status: string; delivery_id?: string }> {
546
- const apiUrl = parseCloudApiUrl(apiFlag);
547
- const { response, payload } = channel.type === "whatsapp" && channel.provider === "evolution"
548
- ? await runServerGeneratedChannelTest(channel, apiUrl, messageFlag)
549
- : await runServerSignedChannelTest(channel, apiUrl, await channelTestBody(channel, messageFlag, undefined));
550
-
551
- if (!response.ok) {
552
- throw new Error(readCloudError(payload, response.status));
553
- }
554
-
555
- if (isRecord(payload) && typeof payload.status === "string") {
556
- return {
557
- status: payload.status,
558
- ...(typeof payload.delivery_id === "string" ? { delivery_id: payload.delivery_id } : {}),
559
- };
560
- }
561
-
562
- return { status: "unknown" };
563
- }
564
-
565
- async function runServerGeneratedChannelTest(
566
- channel: CloudChannel,
567
- apiUrl: string,
568
- messageFlag: string | boolean | undefined,
569
- ): Promise<{ response: Response; payload: unknown }> {
570
- const body: Record<string, unknown> = {};
571
-
572
- if (typeof messageFlag === "string") {
573
- body.message = messageFlag;
574
- }
575
-
576
- const response = await cloudFetch(apiUrl, `/v1/channels/${encodeURIComponent(channel.id)}/test`, {
577
- method: "POST",
578
- headers: { "Content-Type": "application/json" },
579
- body: JSON.stringify(body),
580
- });
581
- const payload = await response.json().catch(() => null);
582
-
583
- if (!response.ok && isUnsupportedChannelTestRoute(payload)) {
584
- return runLocallySignedChannelTest(
585
- channel,
586
- apiUrl,
587
- await channelTestBody(channel, messageFlag, undefined),
588
- );
589
- }
590
-
591
- return { response, payload };
592
- }
593
-
594
- async function runConnectSmoke(
595
- run: () => Promise<{ status: string; delivery_id?: string }>,
596
- ): Promise<{ status: string; delivery_id?: string } | null> {
597
- try {
598
- const smoke = await run();
599
- console.log(`Synthetic smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
600
- return smoke;
601
- } catch (error) {
602
- console.log("Synthetic smoke: failed");
603
- console.log(` ${formatCommandError(error)}`);
604
- return null;
605
- }
606
- }
607
-
608
- async function runServerSignedChannelTest(
609
- channel: CloudChannel,
610
- apiUrl: string,
611
- rawBody: string,
612
- ): Promise<{ response: Response; payload: unknown }> {
613
- const response = await cloudFetch(apiUrl, `/v1/channels/${encodeURIComponent(channel.id)}/test`, {
614
- method: "POST",
615
- headers: { "Content-Type": "application/json" },
616
- body: JSON.stringify({ raw_body: rawBody }),
617
- });
618
- const payload = await response.json().catch(() => null);
619
-
620
- if (!response.ok && isUnsupportedChannelTestRoute(payload)) {
621
- return runLocallySignedChannelTest(channel, apiUrl, rawBody);
622
- }
623
-
624
- return { response, payload };
625
- }
626
-
627
- async function runLocallySignedChannelTest(
628
- channel: CloudChannel,
629
- apiUrl: string,
630
- rawBody: string,
631
- ): Promise<{ response: Response; payload: unknown }> {
632
- const webhookUrl = new URL(channel.webhook_url);
633
-
634
- if (channel.required_secrets.some((secret) => secret.name === "ZAPSTER_WEBHOOK_TOKEN")) {
635
- webhookUrl.searchParams.set("token", process.env.ZAPSTER_WEBHOOK_TOKEN ?? "");
636
- }
637
-
638
- if (channel.required_secrets.some((secret) => secret.name === "UAZAPI_WEBHOOK_TOKEN")) {
639
- webhookUrl.searchParams.set("token", process.env.UAZAPI_WEBHOOK_TOKEN ?? "");
640
- }
641
-
642
- if (channel.required_secrets.some((secret) => secret.name === "EVOLUTION_WEBHOOK_TOKEN")) {
643
- webhookUrl.searchParams.set("token", process.env.EVOLUTION_WEBHOOK_TOKEN ?? "");
644
- }
645
-
646
- const path = `${webhookUrl.pathname}${webhookUrl.search}`;
647
- const headers = channelTestHeaders(channel, rawBody);
648
- const response = await cloudFetch(apiUrl, path, {
649
- method: "POST",
650
- headers,
651
- body: rawBody,
652
- });
653
- const payload = await response.json().catch(() => null);
654
-
655
- return { response, payload };
656
- }
657
-
658
- function isUnsupportedChannelTestRoute(payload: unknown): boolean {
659
- return (
660
- isRecord(payload) &&
661
- isRecord(payload.error) &&
662
- (payload.error.code === "not_found" ||
663
- payload.error.code === "channel_store_not_configured" ||
664
- payload.error.code === "channel_delivery_not_found")
665
- );
666
- }
667
-
668
- async function applyTelegramWebhook(
669
- channel: CloudChannel,
670
- apiFlag: string | boolean | undefined,
671
- ): Promise<ChannelSetupResponse> {
672
- try {
673
- return await cloudPost<ChannelSetupResponse>(
674
- parseCloudApiUrl(apiFlag),
675
- `/v1/channels/${encodeURIComponent(channel.id)}/setup`,
676
- {},
677
- );
678
- } catch (error) {
679
- if (!shouldFallbackToLocalTelegramSetup(error)) {
680
- throw error;
681
- }
682
- }
683
-
684
- const botToken = process.env.TELEGRAM_BOT_TOKEN;
685
- const secretToken = process.env.TELEGRAM_WEBHOOK_SECRET;
686
-
687
- if (!botToken || !secretToken) {
688
- throw new Error("TELEGRAM_BOT_TOKEN and TELEGRAM_WEBHOOK_SECRET must be set to apply Telegram setup.");
689
- }
690
-
691
- let response: Response;
692
- const apiBaseUrl = telegramApiBaseUrlFromEnv();
693
-
694
- try {
695
- response = await fetch(new URL(`/bot${botToken}/setWebhook`, apiBaseUrl).href, {
696
- method: "POST",
697
- headers: { "Content-Type": "application/json" },
698
- body: JSON.stringify({
699
- url: channel.webhook_url,
700
- secret_token: secretToken,
701
- allowed_updates: ["message"],
702
- }),
703
- });
704
- } catch {
705
- throw new Error("Telegram setWebhook request failed before Telegram returned a response.");
706
- }
707
-
708
- const payload = await response.json().catch(() => ({}));
709
-
710
- if (!response.ok || !isTelegramOk(payload)) {
711
- throw new Error(readTelegramSetupError(payload, response.status));
712
- }
713
-
714
- const webhookInfo = await fetch(new URL(`/bot${botToken}/getWebhookInfo`, apiBaseUrl).href)
715
- .then((telegramResponse) => telegramResponse.json())
716
- .catch(() => null);
717
- const webhookUrl =
718
- webhookInfo &&
719
- typeof webhookInfo === "object" &&
720
- "result" in webhookInfo &&
721
- webhookInfo.result &&
722
- typeof webhookInfo.result === "object" &&
723
- "url" in webhookInfo.result &&
724
- typeof webhookInfo.result.url === "string"
725
- ? webhookInfo.result.url
726
- : "";
727
-
728
- if (webhookUrl !== channel.webhook_url) {
729
- throw new Error(`Telegram getWebhookInfo returned ${webhookUrl || "no webhook URL"} instead of ${channel.webhook_url}.`);
730
- }
731
-
732
- return {
733
- channel: {
734
- ...channel,
735
- status: "connected",
736
- },
737
- setup: {
738
- provider: "telegram",
739
- webhook_url: channel.webhook_url,
740
- status: "configured",
741
- get_webhook_info: {
742
- url: webhookUrl,
743
- pending_update_count: 0,
744
- },
745
- },
746
- };
747
- }
748
-
749
- async function applyProviderWebhook(
750
- channel: CloudChannel,
751
- apiFlag: string | boolean | undefined,
752
- ): Promise<ChannelSetupResponse> {
753
- return cloudPost<ChannelSetupResponse>(
754
- parseCloudApiUrl(apiFlag),
755
- `/v1/channels/${encodeURIComponent(channel.id)}/setup`,
756
- {},
757
- );
758
- }
759
-
760
- function shouldFallbackToLocalTelegramSetup(error: unknown): boolean {
761
- if (!(error instanceof Error)) {
762
- return false;
763
- }
764
-
765
- return /Unknown AgentKit Cloud route|does not support channel delivery records yet|does not support channel resources yet/.test(
766
- error.message,
767
- );
768
- }
769
-
770
- function isTelegramOk(payload: unknown): payload is { ok: true } {
771
- return Boolean(payload && typeof payload === "object" && "ok" in payload && payload.ok === true);
772
- }
773
-
774
- function readTelegramSetupError(payload: unknown, status: number): string {
775
- if (payload && typeof payload === "object" && "description" in payload && typeof payload.description === "string") {
776
- return `Telegram setWebhook failed: ${payload.description}`;
777
- }
778
-
779
- return `Telegram setWebhook failed with HTTP ${status}.`;
780
- }
781
-
782
- function telegramApiBaseUrlFromEnv(): string {
783
- return process.env.AGENTKIT_TELEGRAM_API_BASE_URL ?? "https://api.telegram.org";
784
- }
785
-
786
- async function loadLocalEnvIntoProcess(cwd: string): Promise<void> {
787
- const env = await loadCapsuleEnv(cwd, process.env);
788
-
789
- for (const [key, value] of Object.entries(env)) {
790
- if (value !== undefined && process.env[key] === undefined) {
791
- process.env[key] = value;
792
- }
793
- }
794
- }
795
-
796
- function printTelegramHumanNextStep(channelName: string, botUsername: string | undefined): void {
797
- console.log("");
798
- console.log("Next human step:");
799
- console.log(` Open ${botUsername ? `@${botUsername}` : "your Telegram bot"} in Telegram`);
800
- console.log(" Send /start");
801
- console.log(' Send "hello"');
802
- console.log(` Then run: npm run agentkit -- channels deliveries list ${channelName}`);
803
- }
804
-
805
- function printUazapiHumanNextStep(channel: CloudChannel): void {
806
- console.log("");
807
- console.log("Next human step:");
808
- console.log(" Confirm the UAZAPI instance is connected to WhatsApp");
809
- console.log(' Send "hello" from a real WhatsApp contact');
810
- console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
811
- }
812
-
813
- function printEvolutionHumanNextStep(channel: CloudChannel): void {
814
- console.log("");
815
- console.log("Next human step:");
816
- console.log(" Confirm the Evolution API instance is connected to WhatsApp");
817
- console.log(' Send "hello" from a real WhatsApp contact');
818
- console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
819
- }
820
-
821
- function printDiscordHumanNextStep(channel: CloudChannel): void {
822
- console.log("");
823
- console.log("Next human step:");
824
-
825
- if (channel.mode === "bot") {
826
- console.log(" Open the Discord Developer Portal for your application");
827
- console.log(" Bot page: enable Message Content Intent");
828
- console.log(" Installation page: install the app into your server with bot permissions");
829
- console.log(" Grant the bot View Channel, Read Message History, and Send Messages in the channels it should answer");
830
- console.log(' Send "hello" in an allowed server channel');
831
- console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
832
- return;
833
- }
834
-
835
- console.log(" Open the Discord Developer Portal for your application");
836
- console.log(` Paste ${channel.webhook_url} into Interactions Endpoint URL`);
837
- console.log(" Register a slash command with a string option named message, text, prompt, question, query, or input");
838
- console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
839
- }
840
-
841
- function printSlackHumanNextStep(channel: CloudChannel): void {
842
- console.log("");
843
- console.log("Next human step:");
844
- console.log(" Open the Slack app configuration");
845
- console.log(" Event Subscriptions: enable events");
846
- console.log(` Request URL: paste ${channel.webhook_url}`);
847
- console.log(" Subscribe to bot events: app_mention and message.im");
848
- console.log(" OAuth scopes: app_mentions:read, im:history, chat:write");
849
- console.log(" Install or reinstall the app to the workspace");
850
- console.log(' Mention the app with "hello" or send it a direct message');
851
- console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
852
- }
853
-
854
- function printGenericWebhookHumanNextStep(channel: CloudChannel): void {
855
- console.log("");
856
- console.log("Next human step:");
857
- console.log(` Point n8n, Make, Zapier, or your server to ${channel.webhook_url}`);
858
- console.log(" Send JSON with event_id, external_id, and message");
859
- console.log(" Authenticate with Authorization: Bearer <AGENTKIT_WEBHOOK_SECRET>");
860
- console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
861
- }
862
-
863
- function isRecord(value: unknown): value is Record<string, unknown> {
864
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
865
- }
866
-
867
- function formatCommandError(error: unknown): string {
868
- return error instanceof Error ? error.message : String(error);
869
- }
870
-
871
- async function readDeployStateIfExists(cwd: string): Promise<DeployState | null> {
872
- return readLocalDeployStateIfExists(cwd);
873
- }
874
-
875
- function isCliChannelType(value: string): value is "website" | "telegram" | "whatsapp" | "discord" | "slack" | "webhook" {
876
- return value === "website" || value === "telegram" || value === "whatsapp" || value === "discord" || value === "slack" || value === "webhook";
877
- }
878
-
879
- async function readDeployStateRequired(cwd: string): Promise<DeployState> {
880
- const state = await readDeployStateIfExists(cwd);
881
-
882
- if (!state?.deploy_id) {
883
- throw new Error("No .agentkit/deploy.json found. Run agentkit deploy before creating hosted channels.");
884
- }
885
-
886
- return state;
887
- }
888
-
889
- function printChannelsTable(channels: CloudChannel[]): void {
890
- if (channels.length === 0) {
891
- console.log("No channels found.");
892
- return;
893
- }
894
-
895
- console.log("NAME\tTYPE\tPROVIDER\tSTATUS\tLAST_EVENT");
896
- for (const channel of channels) {
897
- console.log([channel.name, channel.type, channel.provider, channel.status, ""].join("\t"));
898
- }
899
- }
900
-
901
- function printChannelStatus(channel: CloudChannel): void {
902
- console.log(`Channel: ${channel.name}`);
903
- if (channel.id) {
904
- console.log(`ID: ${channel.id}`);
905
- }
906
- console.log(`Type: ${channel.type}`);
907
- console.log(`Provider: ${channel.provider}`);
908
- if (channel.type === "discord") {
909
- console.log(`Mode: ${channel.mode ?? "interactions"}`);
910
- }
911
- if (channel.type === "webhook" && channel.direction) {
912
- console.log(`Direction: ${channel.direction}`);
913
- }
914
- console.log(`Status: ${channel.status}`);
915
- if (channel.webhook_url) {
916
- console.log(`Webhook URL: ${channel.webhook_url}`);
917
- }
918
- console.log("Required secrets:");
919
- if (
920
- channel.required_secrets.length === 0 &&
921
- defaultChannelSecretsForCli(channel.type, channel.provider, channel.mode).length > 0
922
- ) {
923
- console.log(
924
- ` control-plane channel metadata bug: required_secrets is empty for ${channel.type}/${channel.provider}`,
925
- );
926
- } else {
927
- for (const secret of channel.required_secrets) {
928
- console.log(` ${secret.name}: ${secret.status}`);
929
- }
930
- }
931
- if (channel.buffer) {
932
- if (channel.buffer.mode === "off") {
933
- console.log("Buffer: off");
934
- } else {
935
- console.log(
936
- `Buffer: debounce quiet=${channel.buffer.quietWindowMs}ms max_wait=${channel.buffer.maxWaitMs}ms max_messages=${channel.buffer.maxMessages} max_chars=${channel.buffer.maxChars}`,
937
- );
938
- }
939
- }
940
- }
941
-
942
- function printSecretRepairHint(channel: CloudChannel): void {
943
- if (
944
- channel.required_secrets.length > 0 ||
945
- defaultChannelSecretsForCli(channel.type, channel.provider, channel.mode).length === 0
946
- ) {
947
- return;
948
- }
949
-
950
- console.log(
951
- "Hint: control-plane channel metadata bug. Rerun add/connect against the updated control-plane to repair required_secrets.",
952
- );
953
- }
954
-
955
- function hasMissingRequiredSecrets(channel: CloudChannel): boolean {
956
- const expected = defaultChannelSecretsForCli(channel.type, channel.provider, channel.mode);
957
-
958
- if (channel.required_secrets.length === 0 && expected.length > 0) {
959
- return true;
960
- }
961
-
962
- return channel.required_secrets.some((secret) => secret.status !== "set");
963
- }
964
-
965
- async function printChannelDeliverySummary(
966
- channel: CloudChannel,
967
- apiFlag: string | boolean | undefined,
968
- ): Promise<void> {
969
- const response = await cloudGet<{ deliveries: CloudDelivery[] }>(
970
- parseCloudApiUrl(apiFlag),
971
- `/v1/channels/${encodeURIComponent(channel.id)}/deliveries`,
972
- );
973
- const inbound = [...response.deliveries].reverse().find((delivery) => delivery.direction === "inbound");
974
- const outbound = [...response.deliveries].reverse().find((delivery) => delivery.direction === "outbound");
975
-
976
- console.log("Recent delivery:");
977
- console.log(` Last inbound: ${inbound ? `${inbound.status} ${inbound.received_at}` : "none"}`);
978
- console.log(` Last outbound: ${outbound ? `${outbound.status} ${outbound.received_at}` : "none"}`);
979
- console.log("Provider health: not_checked");
980
- }
981
-
982
- function printChannelDoctor(report: ChannelDoctorReport): void {
983
- console.log(`Channel doctor: ${report.channel.name}`);
984
- console.log("");
985
- console.log("Checks:");
986
- for (const check of report.checks) {
987
- console.log(` ${formatCheckStatus(check.status)} ${check.name}: ${check.message}`);
988
- if (check.fix) {
989
- console.log(` Fix: ${check.fix}`);
990
- }
991
- }
992
-
993
- console.log("");
994
- console.log("Last delivery:");
995
- console.log(
996
- ` Inbound: ${report.last_delivery.inbound ? `${report.last_delivery.inbound.status} ${report.last_delivery.inbound.updated_at ?? report.last_delivery.inbound.received_at}` : "none"}`,
997
- );
998
- console.log(
999
- ` Outbound: ${report.last_delivery.outbound ? `${report.last_delivery.outbound.status} ${report.last_delivery.outbound.updated_at ?? report.last_delivery.outbound.received_at}` : "none"}`,
1000
- );
1001
-
1002
- if (report.buffers.length > 0) {
1003
- console.log("");
1004
- console.log("Buffers:");
1005
- for (const buffer of report.buffers) {
1006
- console.log(
1007
- ` ${buffer.conversation_id}: ${buffer.status} messages=${buffer.buffered_message_count} stale=${buffer.stale ? "yes" : "no"}`,
1008
- );
1009
- }
1010
- }
1011
-
1012
- if (report.probable_error) {
1013
- console.log("");
1014
- console.log(`Probable error: ${report.probable_error}`);
1015
- }
1016
- if (report.suggested_fix) {
1017
- console.log(`Suggested fix: ${report.suggested_fix}`);
1018
- }
1019
- }
1020
-
1021
- function formatCheckStatus(status: ChannelDoctorReport["checks"][number]["status"]): string {
1022
- if (status === "ok") {
1023
- return "OK";
1024
- }
1025
-
1026
- if (status === "warn") {
1027
- return "WARN";
1028
- }
1029
-
1030
- if (status === "fail") {
1031
- return "FAIL";
1032
- }
1033
-
1034
- return "UNKNOWN";
1035
- }
1036
-
1037
- function printChannelBuffers(buffers: CloudChannelBuffer[]): void {
1038
- if (buffers.length === 0) {
1039
- console.log("No channel buffers found.");
1040
- return;
1041
- }
1042
-
1043
- console.log("CONVERSATION\tSTATUS\tMESSAGES\tCHARS\tSTALE\tAVAILABLE_AT\tJOB");
1044
- for (const buffer of buffers) {
1045
- console.log(
1046
- [
1047
- buffer.conversation_id,
1048
- buffer.status,
1049
- String(buffer.buffered_message_count),
1050
- String(buffer.buffered_char_count),
1051
- buffer.stale ? "yes" : "no",
1052
- buffer.available_at ?? "",
1053
- buffer.job_id ?? "",
1054
- ].join("\t"),
1055
- );
1056
- }
1057
- }
1058
-
1059
- function printChannelBufferAction(action: string, response: ChannelBufferActionResponse): void {
1060
- const conversationId = response.buffer?.conversation_id ?? response.job?.conversation_id ?? "unknown";
1061
-
1062
- if (action === "flush") {
1063
- console.log(
1064
- response.job
1065
- ? `Flushed buffer ${conversationId}; queued job ${response.job.id}.`
1066
- : `No queued buffer was flushed for ${conversationId}.`,
1067
- );
1068
- return;
6
+ if (args.positional[0] !== "list") {
7
+ throw new Error("Usage: agentkit channels list. Configure channels in agentkit.config.ts and test authenticated webhooks against agentkit dev.");
1069
8
  }
1070
-
1071
- if (action === "clear") {
1072
- console.log(
1073
- response.cleared
1074
- ? `Cleared buffer ${conversationId}.`
1075
- : `No queued buffer was cleared for ${conversationId}.`,
1076
- );
1077
- return;
1078
- }
1079
-
1080
- console.log(
1081
- response.retried && response.job
1082
- ? `Retried channel job ${response.job.id} for ${conversationId}.`
1083
- : `No failed or dead-lettered job was retried for ${conversationId}.`,
1084
- );
1085
- }
1086
-
1087
- async function resolveCloudChannelByName(name: string, apiFlag: string | boolean | undefined): Promise<CloudChannel> {
1088
- const channels = await listCloudChannelsForCurrentDeploy(apiFlag);
1089
- const channel = channels.find((candidate) => candidate.name === name);
1090
-
1091
- if (!channel) {
1092
- const deployState = await readDeployStateRequired(process.cwd());
1093
- throw new Error(`Channel "${name}" was not found for deploy ${deployState.deploy_id}.`);
1094
- }
1095
-
1096
- return channel;
1097
- }
1098
-
1099
- async function listCloudChannelsForCurrentDeploy(apiFlag: string | boolean | undefined): Promise<CloudChannel[]> {
1100
- const deployState = await readDeployStateRequired(process.cwd());
1101
- const response = await cloudGet<{ channels: CloudChannel[] }>(
1102
- parseCloudApiUrl(apiFlag),
1103
- `/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
1104
- );
1105
-
1106
- return response.channels;
1107
- }
1108
-
1109
- async function resolveChannelBufferTarget(
1110
- first: string,
1111
- second: string | undefined,
1112
- apiFlag: string | boolean | undefined,
1113
- ): Promise<{ channel: CloudChannel; conversationId: string }> {
1114
- if (second) {
1115
- return {
1116
- channel: await resolveCloudChannelByName(first, apiFlag),
1117
- conversationId: second,
1118
- };
1119
- }
1120
-
1121
- const conversationId = first;
1122
- const channels = await listCloudChannelsForCurrentDeploy(apiFlag);
1123
- const matches: CloudChannel[] = [];
1124
-
1125
- for (const channel of channels) {
1126
- const response = await cloudGet<{ buffers: CloudChannelBuffer[] }>(
1127
- parseCloudApiUrl(apiFlag),
1128
- `/v1/channels/${encodeURIComponent(channel.id)}/buffers`,
1129
- );
1130
-
1131
- if (response.buffers.some((buffer) => buffer.conversation_id === conversationId)) {
1132
- matches.push(channel);
1133
- }
1134
- }
1135
-
1136
- if (matches.length === 1) {
1137
- return {
1138
- channel: matches[0],
1139
- conversationId,
1140
- };
1141
- }
1142
-
1143
- if (matches.length > 1) {
1144
- throw new Error(
1145
- `Conversation ${conversationId} has buffers in multiple channels. Use: agentkit channels buffers show <name> ${conversationId}`,
1146
- );
1147
- }
1148
-
1149
- throw new Error(`No channel buffer found for conversation ${conversationId}.`);
1150
- }
1151
-
1152
- function channelProviderForCli(type: string, providerFlag: string | boolean | undefined): string {
1153
- if (type === "website") {
1154
- return "agentkit";
1155
- }
1156
-
1157
- if (type === "telegram") {
1158
- return "telegram";
1159
- }
1160
-
1161
- if (type === "discord") {
1162
- return "discord";
1163
- }
1164
-
1165
- if (type === "slack") {
1166
- if (providerFlag !== undefined) {
1167
- throw new Error("--provider is not supported for Slack channels.");
1168
- }
1169
-
1170
- return "slack";
1171
- }
1172
-
1173
- if (type === "webhook") {
1174
- if (providerFlag !== undefined) {
1175
- throw new Error("--provider is not supported for generic webhook channels.");
1176
- }
1177
-
1178
- return "generic";
1179
- }
1180
-
1181
- const provider = providerFlag ?? "zapster";
1182
-
1183
- if (provider !== "zapster" && provider !== "meta" && provider !== "uazapi" && provider !== "evolution") {
1184
- throw new Error('WhatsApp provider must be "zapster", "meta", "uazapi", or "evolution".');
9
+ const capsule = await loadAgentCapsule(process.cwd());
10
+ const env = await loadCapsuleEnv(capsule.root);
11
+ console.log("NAME\tTYPE\tPROVIDER\tMISSING SECRETS");
12
+ for (const channel of capsule.config.channels ?? []) {
13
+ console.log([channel.name, channel.type, channel.provider, channel.secrets.filter(name => !env[name]).join(", ") || "none"].join("\t"));
1185
14
  }
1186
-
1187
- return provider;
1188
- }
1189
-
1190
- function discordModeForCli(
1191
- type: string,
1192
- modeFlag: string | boolean | undefined,
1193
- localChannel: AgentChannel | null,
1194
- ): "interactions" | "bot" | undefined {
1195
- if (type !== "discord") {
1196
- if (modeFlag !== undefined) {
1197
- throw new Error("--mode is only supported for Discord channels.");
1198
- }
1199
-
1200
- return undefined;
1201
- }
1202
-
1203
- if (modeFlag !== undefined) {
1204
- if (modeFlag !== "interactions" && modeFlag !== "bot") {
1205
- throw new Error('Discord mode must be "interactions" or "bot".');
1206
- }
1207
-
1208
- return modeFlag;
1209
- }
1210
-
1211
- return localChannel?.type === "discord" ? localChannel.mode : "interactions";
1212
- }
1213
-
1214
- function defaultChannelSecretsForCli(type: string, provider: string, mode?: "interactions" | "bot"): string[] {
1215
- if (type === "website") {
1216
- return ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"];
1217
- }
1218
-
1219
- if (type === "telegram") {
1220
- return ["TELEGRAM_BOT_TOKEN", "TELEGRAM_WEBHOOK_SECRET"];
1221
- }
1222
-
1223
- if (type === "whatsapp" && provider === "zapster") {
1224
- return ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID", "ZAPSTER_WEBHOOK_TOKEN"];
1225
- }
1226
-
1227
- if (type === "whatsapp" && provider === "meta") {
1228
- return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
1229
- }
1230
-
1231
- if (type === "whatsapp" && provider === "uazapi") {
1232
- return ["UAZAPI_BASE_URL", "UAZAPI_TOKEN", "UAZAPI_WEBHOOK_TOKEN"];
1233
- }
1234
-
1235
- if (type === "whatsapp" && provider === "evolution") {
1236
- return ["EVOLUTION_API_BASE_URL", "EVOLUTION_API_KEY", "EVOLUTION_INSTANCE_NAME", "EVOLUTION_WEBHOOK_TOKEN"];
1237
- }
1238
-
1239
- if (type === "discord") {
1240
- return mode === "bot" ? ["DISCORD_BOT_TOKEN"] : ["DISCORD_PUBLIC_KEY"];
1241
- }
1242
-
1243
- if (type === "slack") {
1244
- return ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"];
1245
- }
1246
-
1247
- if (type === "webhook") {
1248
- return ["AGENTKIT_WEBHOOK_SECRET"];
1249
- }
1250
-
1251
- return [];
1252
- }
1253
-
1254
- function channelExtraConfigForApi(channel: AgentChannel | null): Record<string, unknown> {
1255
- if (!channel) {
1256
- return {};
1257
- }
1258
-
1259
- return {
1260
- ...("direction" in channel && channel.direction ? { direction: channel.direction } : {}),
1261
- ...(channel.replyTo ? { reply_to: channel.replyTo } : {}),
1262
- ...("urlSecret" in channel && channel.urlSecret ? { url_secret: channel.urlSecret } : {}),
1263
- ...("auth" in channel && channel.auth ? { auth: channel.auth } : {}),
1264
- };
1265
- }
1266
-
1267
- async function readLocalChannelConfig(
1268
- cwd: string,
1269
- type: string,
1270
- provider: string,
1271
- name: string,
1272
- ): Promise<AgentChannel | null> {
1273
- const capsule = await loadAgentCapsule(cwd);
1274
- return (
1275
- (capsule.config.channels ?? []).find(
1276
- (channel) => channel.name === name && channel.type === type && channel.provider === provider,
1277
- ) ?? null
1278
- );
1279
- }
1280
-
1281
- function channelLimitsForApi(limits: NonNullable<AgentChannel["limits"]>): {
1282
- messages_per_day?: number;
1283
- usd_per_day?: number;
1284
- } {
1285
- return {
1286
- ...(limits.messagesPerDay ? { messages_per_day: limits.messagesPerDay } : {}),
1287
- ...(limits.usdPerDay ? { usd_per_day: limits.usdPerDay } : {}),
1288
- };
1289
- }
1290
-
1291
- async function channelTestBody(
1292
- channel: CloudChannel,
1293
- messageFlag: string | boolean | undefined,
1294
- fixtureFlag: string | boolean | undefined,
1295
- ): Promise<string> {
1296
- if (typeof fixtureFlag === "string") {
1297
- return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
1298
- }
1299
-
1300
- const message = typeof messageFlag === "string" ? messageFlag : "hello";
1301
-
1302
- if (channel.type === "telegram") {
1303
- return JSON.stringify({
1304
- update_id: Date.now(),
1305
- message: {
1306
- message_id: 1,
1307
- chat: { id: 456, type: "private" },
1308
- from: { id: 123, first_name: "AgentKit" },
1309
- text: message,
1310
- },
1311
- });
1312
- }
1313
-
1314
- if (channel.type === "whatsapp" && channel.provider === "uazapi") {
1315
- return JSON.stringify({
1316
- EventType: "messages",
1317
- event: "messages",
1318
- message: {
1319
- id: `uaz_msg_${Date.now()}`,
1320
- messageid: `uaz_wamid_${Date.now()}`,
1321
- chatid: "5511999999999@s.whatsapp.net",
1322
- sender: "5511999999999@s.whatsapp.net",
1323
- senderName: "AgentKit",
1324
- fromMe: false,
1325
- wasSentByApi: false,
1326
- messageType: "text",
1327
- text: message,
1328
- messageTimestamp: Date.now(),
1329
- },
1330
- });
1331
- }
1332
-
1333
- if (channel.type === "whatsapp" && channel.provider === "evolution") {
1334
- return JSON.stringify({
1335
- event: "MESSAGES_UPSERT",
1336
- instance: process.env.EVOLUTION_INSTANCE_NAME ?? "local",
1337
- data: {
1338
- key: {
1339
- remoteJid: "5511999999999@s.whatsapp.net",
1340
- fromMe: false,
1341
- id: `evo_wamid_${Date.now()}`,
1342
- },
1343
- pushName: "AgentKit",
1344
- messageType: "conversation",
1345
- message: {
1346
- conversation: message,
1347
- },
1348
- messageTimestamp: Math.floor(Date.now() / 1000),
1349
- },
1350
- });
1351
- }
1352
-
1353
- if (channel.type === "whatsapp") {
1354
- return JSON.stringify({
1355
- event_id: `evt_${Date.now()}`,
1356
- instance_id: "local",
1357
- message: {
1358
- id: `msg_${Date.now()}`,
1359
- from: "5511999999999",
1360
- type: "text",
1361
- text: { body: message },
1362
- },
1363
- });
1364
- }
1365
-
1366
- if (channel.type === "discord") {
1367
- if (channel.mode === "bot") {
1368
- return JSON.stringify({
1369
- id: `discord_message_${Date.now()}`,
1370
- guild_id: "agentkit_guild",
1371
- channel_id: "agentkit_channel",
1372
- author: {
1373
- id: "agentkit_user",
1374
- username: "agentkit",
1375
- },
1376
- member: {
1377
- nick: "AgentKit",
1378
- },
1379
- content: message,
1380
- mentions: [],
1381
- timestamp: new Date().toISOString(),
1382
- });
1383
- }
1384
-
1385
- return JSON.stringify({
1386
- id: `discord_interaction_${Date.now()}`,
1387
- application_id: "agentkit_discord_app",
1388
- type: 2,
1389
- data: {
1390
- id: "agentkit_command",
1391
- name: "ask",
1392
- type: 1,
1393
- options: [
1394
- {
1395
- name: "message",
1396
- type: 3,
1397
- value: message,
1398
- },
1399
- ],
1400
- },
1401
- guild_id: "agentkit_guild",
1402
- channel_id: "agentkit_channel",
1403
- member: {
1404
- user: {
1405
- id: "agentkit_user",
1406
- username: "agentkit",
1407
- },
1408
- },
1409
- token: `agentkit_interaction_token_${Date.now()}`,
1410
- version: 1,
1411
- });
1412
- }
1413
-
1414
- if (channel.type === "slack") {
1415
- const now = Date.now();
1416
-
1417
- return JSON.stringify({
1418
- token: "deprecated-verification-token",
1419
- team_id: "T_AGENTKIT",
1420
- api_app_id: "A_AGENTKIT",
1421
- type: "event_callback",
1422
- event_id: `Ev${now}`,
1423
- event_time: Math.floor(now / 1000),
1424
- event: {
1425
- type: "app_mention",
1426
- user: "UAGENTKITUSER",
1427
- text: `<@UAGENTKITBOT> ${message}`,
1428
- ts: `${Math.floor(now / 1000)}.000100`,
1429
- channel: "C_AGENTKIT",
1430
- event_ts: `${Math.floor(now / 1000)}.000100`,
1431
- },
1432
- authorizations: [],
1433
- is_ext_shared_channel: false,
1434
- });
1435
- }
1436
-
1437
- if (channel.type === "webhook") {
1438
- return JSON.stringify({
1439
- event_id: `evt_${Date.now()}`,
1440
- external_id: "agentkit-webhook-test",
1441
- message,
1442
- });
1443
- }
1444
-
1445
- return JSON.stringify({
1446
- event_id: `web_${Date.now()}`,
1447
- visitor_id: "visitor_test",
1448
- message: {
1449
- id: `msg_${Date.now()}`,
1450
- text: message,
1451
- },
1452
- });
1453
- }
1454
-
1455
- async function channelAudioTestBody(
1456
- channel: CloudChannel,
1457
- fixtureFlag: string | boolean | undefined,
1458
- ): Promise<string> {
1459
- if (typeof fixtureFlag === "string" && isExplicitFixturePath(fixtureFlag)) {
1460
- return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
1461
- }
1462
-
1463
- const fixture = typeof fixtureFlag === "string" ? fixtureFlag : "voice-note";
1464
-
1465
- if (channel.type === "telegram") {
1466
- if (fixture !== "voice-note" && fixture !== "telegram-voice-note" && fixture !== "telegram-audio-message") {
1467
- throw new Error('Telegram audio smoke supports --fixture voice-note or --fixture telegram-voice-note.');
1468
- }
1469
-
1470
- return JSON.stringify({
1471
- update_id: Date.now(),
1472
- message: {
1473
- message_id: 1,
1474
- date: Math.floor(Date.now() / 1000),
1475
- chat: { id: 456, type: "private" },
1476
- from: { id: 123, first_name: "AgentKit" },
1477
- voice: {
1478
- file_id: `agentkit_voice_${Date.now()}`,
1479
- file_unique_id: `agentkit_voice_unique_${Date.now()}`,
1480
- duration: 12,
1481
- mime_type: "audio/ogg",
1482
- file_size: 42,
1483
- },
1484
- },
1485
- });
1486
- }
1487
-
1488
- if (channel.type === "whatsapp" && channel.provider === "zapster") {
1489
- if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "zapster-audio" && fixture !== "zapster-audio-message") {
1490
- throw new Error('Zapster audio smoke supports --fixture voice-note, --fixture audio-file, or --fixture zapster-audio.');
1491
- }
1492
-
1493
- return JSON.stringify({
1494
- event_id: `zap_evt_audio_${Date.now()}`,
1495
- instance_id: "local",
1496
- message: {
1497
- id: `wamid.audio.${Date.now()}`,
1498
- from: "5511999999999",
1499
- type: "audio",
1500
- audio: {
1501
- id: `media_audio_${Date.now()}`,
1502
- url: `https://api.zapsterapi.com/media/audio/media_audio_${Date.now()}.ogg`,
1503
- mime_type: "audio/ogg",
1504
- duration: 12,
1505
- file_size: 42,
1506
- ...(fixture === "voice-note" ? { ptt: "true" } : {}),
1507
- },
1508
- },
1509
- });
1510
- }
1511
-
1512
- if (channel.type === "whatsapp" && channel.provider === "uazapi") {
1513
- if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "uazapi-audio" && fixture !== "uazapi-audio-message") {
1514
- throw new Error('UAZAPI audio smoke supports --fixture voice-note, --fixture audio-file, or --fixture uazapi-audio.');
1515
- }
1516
-
1517
- return JSON.stringify({
1518
- EventType: "messages",
1519
- event: "messages",
1520
- message: {
1521
- id: `uaz_audio_${Date.now()}`,
1522
- messageid: `uaz_wamid_audio_${Date.now()}`,
1523
- chatid: "5511999999999@s.whatsapp.net",
1524
- sender: "5511999999999@s.whatsapp.net",
1525
- senderName: "AgentKit",
1526
- fromMe: false,
1527
- wasSentByApi: false,
1528
- messageType: fixture === "voice-note" ? "ptt" : "audio",
1529
- mimetype: "audio/ogg",
1530
- duration: 12,
1531
- file_size: 42,
1532
- messageTimestamp: Date.now(),
1533
- },
1534
- });
1535
- }
1536
-
1537
- if (channel.type === "whatsapp" && channel.provider === "evolution") {
1538
- if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "evolution-audio" && fixture !== "evolution-audio-message") {
1539
- throw new Error(
1540
- 'Evolution audio smoke supports --fixture voice-note, --fixture audio-file, --fixture evolution-audio, or --fixture evolution-audio-message.',
1541
- );
1542
- }
1543
-
1544
- return JSON.stringify({
1545
- event: "MESSAGES_UPSERT",
1546
- instance: process.env.EVOLUTION_INSTANCE_NAME ?? "local",
1547
- data: {
1548
- key: {
1549
- remoteJid: "5511999999999@s.whatsapp.net",
1550
- fromMe: false,
1551
- id: `evo_wamid_audio_${Date.now()}`,
1552
- },
1553
- pushName: "AgentKit",
1554
- messageType: "audioMessage",
1555
- message: {
1556
- audioMessage: {
1557
- mimetype: "audio/ogg",
1558
- seconds: 12,
1559
- ptt: fixture === "voice-note",
1560
- fileLength: 42,
1561
- },
1562
- },
1563
- messageTimestamp: Math.floor(Date.now() / 1000),
1564
- },
1565
- });
1566
- }
1567
-
1568
- throw new Error(`Audio smoke is not supported for ${channel.type}/${channel.provider}.`);
1569
- }
1570
-
1571
- function isExplicitFixturePath(value: string): boolean {
1572
- return value.includes("/") || value.startsWith(".") || value.endsWith(".json");
1573
- }
1574
-
1575
- function channelTestHeaders(channel: CloudChannel, rawBody: string): Headers {
1576
- const headers = new Headers({ "Content-Type": "application/json" });
1577
-
1578
- if (channel.type === "website") {
1579
- const token = process.env.AGENTKIT_WEBSITE_CHANNEL_TOKEN;
1580
-
1581
- if (token) {
1582
- headers.set("Authorization", `Bearer ${token}`);
1583
- }
1584
- }
1585
-
1586
- if (channel.type === "telegram") {
1587
- headers.set("X-Telegram-Bot-Api-Secret-Token", process.env.TELEGRAM_WEBHOOK_SECRET ?? "");
1588
- }
1589
-
1590
- if (channel.type === "whatsapp" && channel.provider === "zapster") {
1591
- headers.set("X-Instance-ID", process.env.ZAPSTER_INSTANCE_ID ?? "");
1592
- headers.set("X-Webhook-ID", process.env.ZAPSTER_WEBHOOK_ID ?? "");
1593
- headers.set("X-Message-ID", `cli_msg_${Date.now()}`);
1594
- headers.set("X-Attempt-Count", "1");
1595
- headers.set("User-Agent", "Zapsterapi/agentkit-cli");
1596
- }
1597
-
1598
- if (channel.type === "slack") {
1599
- const signingSecret = process.env.SLACK_SIGNING_SECRET;
1600
-
1601
- if (signingSecret) {
1602
- const timestamp = String(Math.floor(Date.now() / 1000));
1603
- const signature = createHmac("sha256", signingSecret)
1604
- .update(`v0:${timestamp}:${rawBody}`)
1605
- .digest("hex");
1606
- headers.set("X-Slack-Request-Timestamp", timestamp);
1607
- headers.set("X-Slack-Signature", `v0=${signature}`);
1608
- }
1609
- }
1610
-
1611
- if (channel.type === "webhook") {
1612
- const secret = process.env.AGENTKIT_WEBHOOK_SECRET;
1613
-
1614
- if (secret) {
1615
- headers.set("Authorization", `Bearer ${secret}`);
1616
- }
1617
- }
1618
-
1619
- return headers;
1620
15
  }