@andreprado/agentkit 0.1.0-alpha.15 → 0.1.0-alpha.17

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 (41) hide show
  1. package/README.md +8 -0
  2. package/docs/guides/add-channel.md +65 -0
  3. package/docs/guides/add-managed-composio.md +137 -0
  4. package/docs/guides/channel-security.md +32 -0
  5. package/docs/guides/channels-production-handoff.md +1 -1
  6. package/docs/guides/connect-telegram.md +60 -0
  7. package/docs/guides/connect-whatsapp-zapster.md +65 -0
  8. package/docs/guides/prepare-deploy.md +3 -1
  9. package/docs/llms-full.txt +13 -1
  10. package/docs/llms.txt +7 -0
  11. package/package.json +1 -1
  12. package/src/cli/commands/channels.ts +121 -7
  13. package/src/cli/commands/transcribe.ts +171 -0
  14. package/src/cli/deploy-readiness.ts +59 -3
  15. package/src/cli/help.ts +15 -0
  16. package/src/cli/index.ts +130 -0
  17. package/src/create-project.ts +4 -32
  18. package/src/index.ts +231 -0
  19. package/src/runtime/channel-test-harness.ts +2 -0
  20. package/src/runtime/channels/telegram.ts +326 -10
  21. package/src/runtime/channels/whatsapp-zapster.ts +319 -0
  22. package/src/runtime/channels.ts +47 -1
  23. package/src/runtime/chat.ts +2 -1
  24. package/src/runtime/config.ts +214 -4
  25. package/src/runtime/core/manifest.ts +61 -2
  26. package/src/runtime/deploy-readiness.ts +31 -1
  27. package/src/runtime/dev-server.ts +72 -4
  28. package/src/runtime/inspect.ts +142 -4
  29. package/src/runtime/integrations/composio.ts +257 -0
  30. package/src/runtime/skills.ts +95 -0
  31. package/src/runtime/targets/cloudflare/build.ts +162 -5
  32. package/src/runtime/tool-runner.ts +2 -1
  33. package/src/runtime/transcription.ts +483 -0
  34. package/src/templates/skills/agentkit-capsule/SKILL.md +1 -0
  35. package/src/templates/skills/agentkit-channels/SKILL.md +36 -1
  36. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +16 -1
  37. package/src/templates/skills/agentkit-channels/references/telegram.md +34 -0
  38. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +29 -0
  39. package/src/templates/skills/agentkit-deploy/SKILL.md +1 -1
  40. package/src/templates/skills/agentkit-integrations/SKILL.md +66 -0
  41. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +3 -2
@@ -20,6 +20,7 @@ type CloudChannel = {
20
20
  webhook_url: string;
21
21
  required_secrets: Array<{ name: string; status: string }>;
22
22
  buffer?: AgentChannel["buffer"];
23
+ audio?: AgentChannel["audio"];
23
24
  };
24
25
 
25
26
  type CloudDelivery = {
@@ -161,8 +162,10 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
161
162
  let channel = created.channel;
162
163
  printChannelStatus(channel);
163
164
  printSecretRepairHint(channel);
165
+ console.log("Channel resource: created_or_updated");
164
166
 
165
167
  if (hasMissingRequiredSecrets(channel)) {
168
+ console.log("Secrets: missing");
166
169
  console.log("");
167
170
  console.log("Connect blocked: set the missing managed secrets, then rerun this same command.");
168
171
  console.log("Run: npm run agentkit -- secret sync --from-local");
@@ -170,31 +173,40 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
170
173
  return;
171
174
  }
172
175
 
176
+ console.log("Secrets: validated");
177
+
173
178
  if (channel.type === "telegram") {
174
179
  const setup = await applyTelegramWebhook(channel, args.flags.api);
175
180
  channel = setup.channel ?? channel;
181
+ console.log("Telegram setWebhook: OK");
176
182
  console.log("Telegram webhook confirmed.");
177
183
  if (setup.setup?.get_webhook_info) {
178
184
  console.log(`Telegram pending updates: ${setup.setup.get_webhook_info.pending_update_count}`);
179
185
  }
180
186
 
181
- const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
182
- console.log(`Smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
187
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
183
188
  printTelegramHumanNextStep(channel.name, setup.setup?.bot_username);
189
+ if (!smoke) {
190
+ process.exitCode = 1;
191
+ }
184
192
  return;
185
193
  }
186
194
 
187
195
  if (channel.type === "whatsapp" && channel.provider === "zapster") {
188
- const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
189
- console.log(`Webhook smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
196
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
190
197
  console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
198
+ if (!smoke) {
199
+ process.exitCode = 1;
200
+ }
191
201
  } else if (channel.type === "whatsapp") {
192
202
  console.log("Smoke: skipped until the provider webhook is configured.");
193
203
  console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
194
204
  } else {
195
- const smoke = await runChannelSmoke(channel, args.flags.api, args.flags.message);
196
- console.log(`Smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
205
+ const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
197
206
  console.log(`Next human step: use ${channel.webhook_url} from your website client.`);
207
+ if (!smoke) {
208
+ process.exitCode = 1;
209
+ }
198
210
  }
199
211
  return;
200
212
  }
@@ -273,6 +285,25 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
273
285
  return;
274
286
  }
275
287
 
288
+ if (subcommand === "test-audio") {
289
+ if (!first) {
290
+ throw new Error("Usage: agentkit channels test-audio <name> [--fixture voice-note|audio-file|<path>]");
291
+ }
292
+
293
+ const channel = await resolveCloudChannelByName(first, args.flags.api);
294
+ const apiUrl = parseCloudApiUrl(args.flags.api);
295
+ const body = await channelAudioTestBody(channel, args.flags.fixture);
296
+ const signed = await runServerSignedChannelTest(channel, apiUrl, body);
297
+ const response = signed.response;
298
+ const payload = signed.payload;
299
+
300
+ console.log(JSON.stringify(payload, null, 2));
301
+ if (!response.ok) {
302
+ process.exitCode = 1;
303
+ }
304
+ return;
305
+ }
306
+
276
307
  if (subcommand === "deliveries") {
277
308
  if (first === "list") {
278
309
  if (!second) {
@@ -366,7 +397,7 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
366
397
  }
367
398
 
368
399
  throw new Error(
369
- "Usage: agentkit channels list | add | connect | setup | status | doctor | test | deliveries list | deliveries show | buffers list | buffers show | buffers flush | buffers clear | buffers retry",
400
+ "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",
370
401
  );
371
402
  }
372
403
 
@@ -386,6 +417,7 @@ async function createHostedChannel(
386
417
  secrets: localChannel?.secrets ?? defaultChannelSecretsForCli(type, provider),
387
418
  ...(localChannel?.limits ? { limits: channelLimitsForApi(localChannel.limits) } : {}),
388
419
  ...(localChannel?.buffer ? { buffer: localChannel.buffer } : {}),
420
+ ...(localChannel?.audio ? { audio: localChannel.audio } : {}),
389
421
  };
390
422
 
391
423
  return cloudPost<{ channel: CloudChannel }>(
@@ -417,6 +449,20 @@ async function runChannelSmoke(
417
449
  return { status: "unknown" };
418
450
  }
419
451
 
452
+ async function runConnectSmoke(
453
+ run: () => Promise<{ status: string; delivery_id?: string }>,
454
+ ): Promise<{ status: string; delivery_id?: string } | null> {
455
+ try {
456
+ const smoke = await run();
457
+ console.log(`Synthetic smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
458
+ return smoke;
459
+ } catch (error) {
460
+ console.log("Synthetic smoke: failed");
461
+ console.log(` ${formatCommandError(error)}`);
462
+ return null;
463
+ }
464
+ }
465
+
420
466
  async function runServerSignedChannelTest(
421
467
  channel: CloudChannel,
422
468
  apiUrl: string,
@@ -599,6 +645,10 @@ function isRecord(value: unknown): value is Record<string, unknown> {
599
645
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
600
646
  }
601
647
 
648
+ function formatCommandError(error: unknown): string {
649
+ return error instanceof Error ? error.message : String(error);
650
+ }
651
+
602
652
  async function readDeployStateIfExists(cwd: string): Promise<DeployState | null> {
603
653
  return readLocalDeployStateIfExists(cwd);
604
654
  }
@@ -972,6 +1022,70 @@ async function channelTestBody(
972
1022
  });
973
1023
  }
974
1024
 
1025
+ async function channelAudioTestBody(
1026
+ channel: CloudChannel,
1027
+ fixtureFlag: string | boolean | undefined,
1028
+ ): Promise<string> {
1029
+ if (typeof fixtureFlag === "string" && isExplicitFixturePath(fixtureFlag)) {
1030
+ return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
1031
+ }
1032
+
1033
+ const fixture = typeof fixtureFlag === "string" ? fixtureFlag : "voice-note";
1034
+
1035
+ if (channel.type === "telegram") {
1036
+ if (fixture !== "voice-note" && fixture !== "telegram-voice-note" && fixture !== "telegram-audio-message") {
1037
+ throw new Error('Telegram audio smoke supports --fixture voice-note or --fixture telegram-voice-note.');
1038
+ }
1039
+
1040
+ return JSON.stringify({
1041
+ update_id: Date.now(),
1042
+ message: {
1043
+ message_id: 1,
1044
+ date: Math.floor(Date.now() / 1000),
1045
+ chat: { id: 456, type: "private" },
1046
+ from: { id: 123, first_name: "AgentKit" },
1047
+ voice: {
1048
+ file_id: `agentkit_voice_${Date.now()}`,
1049
+ file_unique_id: `agentkit_voice_unique_${Date.now()}`,
1050
+ duration: 12,
1051
+ mime_type: "audio/ogg",
1052
+ file_size: 42,
1053
+ },
1054
+ },
1055
+ });
1056
+ }
1057
+
1058
+ if (channel.type === "whatsapp" && channel.provider === "zapster") {
1059
+ if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "zapster-audio" && fixture !== "zapster-audio-message") {
1060
+ throw new Error('Zapster audio smoke supports --fixture voice-note, --fixture audio-file, or --fixture zapster-audio.');
1061
+ }
1062
+
1063
+ return JSON.stringify({
1064
+ event_id: `zap_evt_audio_${Date.now()}`,
1065
+ instance_id: "local",
1066
+ message: {
1067
+ id: `wamid.audio.${Date.now()}`,
1068
+ from: "5511999999999",
1069
+ type: "audio",
1070
+ audio: {
1071
+ id: `media_audio_${Date.now()}`,
1072
+ url: `https://api.zapsterapi.com/media/audio/media_audio_${Date.now()}.ogg`,
1073
+ mime_type: "audio/ogg",
1074
+ duration: 12,
1075
+ file_size: 42,
1076
+ ...(fixture === "voice-note" ? { ptt: "true" } : {}),
1077
+ },
1078
+ },
1079
+ });
1080
+ }
1081
+
1082
+ throw new Error(`Audio smoke is not supported for ${channel.type}/${channel.provider}.`);
1083
+ }
1084
+
1085
+ function isExplicitFixturePath(value: string): boolean {
1086
+ return value.includes("/") || value.startsWith(".") || value.endsWith(".json");
1087
+ }
1088
+
975
1089
  function channelTestHeaders(channel: CloudChannel): Headers {
976
1090
  const headers = new Headers({ "Content-Type": "application/json" });
977
1091
 
@@ -0,0 +1,171 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename, extname, resolve } from "node:path";
3
+
4
+ import type { TranscriptionProviderName } from "../../index";
5
+ import { findAgentCapsuleRoot, loadAgentCapsule } from "../../runtime/config";
6
+ import { loadCapsuleEnv } from "../../runtime/env";
7
+ import {
8
+ defaultTranscriptionSecret,
9
+ resolveTranscriptionConfig,
10
+ transcribeAudio,
11
+ type ResolvedTranscriptionConfig,
12
+ } from "../../runtime/transcription";
13
+ import type { ParsedArgs } from "../args";
14
+
15
+ type SmokeFixture = {
16
+ audio: Uint8Array;
17
+ filename: string;
18
+ mimeType: string;
19
+ source: string;
20
+ };
21
+
22
+ const defaultModels: Record<TranscriptionProviderName, string> = {
23
+ test: "fake",
24
+ openai: "gpt-4o-mini-transcribe",
25
+ groq: "whisper-large-v3-turbo",
26
+ };
27
+
28
+ // Short macOS-generated spoken "hello" sample encoded as M4A. M4A is supported by OpenAI and Groq.
29
+ const builtinSmokeM4aBase64 =
30
+ "AAAAHGZ0eXBNNEEgAAAAAE00QSBtcDQyaXNvbQAAA3ptb292AAAAbG12aGQAAAAA5kTZseZE2bEAAFYiAAA4AAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACDHRyYWsAAABcdGtoZAAAAAfmRNmx5kTZsQAAAAEAAAAAAAA4AAAAAAAAAAAAAAAAAAEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAahtZGlhAAAAIG1kaGQAAAAA5kTZseZE2bEAAFYiAAA4AAAAAAAAAAAiaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAAAAAAABXm1pbmYAAAAQc21oZAAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABInN0YmwAAAB2c3RzZAAAAAAAAAABAAAAZm1wNGEAAAAAAAAAAQAAAAAAAAAAAAIAEAAAAABWIgAAAAAAM2VzZHMAAAAAA4CAgCIAAAAEgICAFEAUABgAAAAU+AAAfQAFgICAAhOIBoCAgAECAAAAD3NidGQAAAAASTE2AAAAGHN0dHMAAAAAAAAAAQAAAA4AAAQAAAAAKHN0c2MAAAAAAAAAAgAAAAEAAAALAAAAAQAAAAIAAAADAAAAAQAAAExzdHN6AAAAAAAAAAAAAAAOAAAABAAAAbkAAADiAAAA0wAAAL8AAADLAAAAzAAAAJMAAACmAAAA5wAAALcAAACnAAAAtgAAADoAAAAYc3RjbwAAAAAAAAACAAAQAAAAGJ8AAAD6dWR0YQAAAPJtZXRhAAAAAAAAACJoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAADEaWxzdAAAALwtLS0tAAAAHG1lYW4AAAAAY29tLmFwcGxlLmlUdW5lcwAAABRuYW1lAAAAAGlUdW5TTVBCAAAAhGRhdGEAAAABAAAAACAwMDAwMDAwMCAwMDAwMDg0MCAwMDAwMDEwOCAwMDAwMDAwMDAwMDAyRUI4IDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwAAAMYmZyZWUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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";
31
+
32
+ export async function handleTranscribeCommand(args: ParsedArgs): Promise<void> {
33
+ const [subcommand] = args.positional;
34
+
35
+ if (subcommand !== "smoke") {
36
+ throw new Error("Usage: agentkit transcribe smoke [--provider test|openai|groq] [--model <model>] [--fixture <audio-file>]");
37
+ }
38
+
39
+ const root = await findAgentCapsuleRoot(process.cwd());
40
+ const capsule = await loadAgentCapsule(root);
41
+ const env = compactEnv(await loadCapsuleEnv(root, process.env));
42
+ const configured = resolveTranscriptionConfig(capsule.config.transcription);
43
+ const provider = parseProviderFlag(args.flags.provider, configured);
44
+ const model = typeof args.flags.model === "string" ? args.flags.model : configured.enabled && configured.provider === provider ? configured.model : defaultModels[provider];
45
+ const secret = typeof args.flags.secret === "string"
46
+ ? args.flags.secret
47
+ : configured.enabled && configured.provider === provider
48
+ ? configured.secret
49
+ : defaultTranscriptionSecret(provider);
50
+ const fixture = await loadSmokeFixture(provider, args.flags.fixture);
51
+
52
+ const config: ResolvedTranscriptionConfig = {
53
+ enabled: true,
54
+ provider,
55
+ model,
56
+ secret,
57
+ language: typeof args.flags.language === "string" ? args.flags.language : configured.enabled ? configured.language : null,
58
+ prompt: typeof args.flags.prompt === "string" ? args.flags.prompt : configured.enabled ? configured.prompt : null,
59
+ limits: configured.enabled ? configured.limits : {},
60
+ rawAudioTtlSeconds: configured.enabled ? configured.rawAudioTtlSeconds : 3600,
61
+ };
62
+ const result = await transcribeAudio({
63
+ config,
64
+ secrets: env,
65
+ audio: fixture.audio,
66
+ filename: fixture.filename,
67
+ mimeType: fixture.mimeType,
68
+ });
69
+
70
+ console.log("Transcription smoke");
71
+ console.log(`Provider: ${provider}`);
72
+ console.log(`Model: ${model}`);
73
+ console.log(`Fixture: ${fixture.source} (${fixture.mimeType})`);
74
+
75
+ if (result.ok) {
76
+ console.log("Status: ok");
77
+ console.log(`Transcript: ${JSON.stringify(result.text)}`);
78
+ return;
79
+ }
80
+
81
+ console.log("Status: failed");
82
+ console.log(`Code: ${result.code}`);
83
+ console.log(`Message: ${result.message}`);
84
+ process.exitCode = 1;
85
+ }
86
+
87
+ function parseProviderFlag(
88
+ value: string | boolean | undefined,
89
+ configured: ResolvedTranscriptionConfig,
90
+ ): TranscriptionProviderName {
91
+ if (value === undefined || value === false) {
92
+ return configured.enabled ? configured.provider : "groq";
93
+ }
94
+
95
+ if (value === "test" || value === "openai" || value === "groq") {
96
+ return value;
97
+ }
98
+
99
+ throw new Error("Usage: agentkit transcribe smoke [--provider test|openai|groq] [--model <model>] [--fixture <audio-file>]");
100
+ }
101
+
102
+ function compactEnv(env: Record<string, string | undefined>): Record<string, string> {
103
+ const compact: Record<string, string> = {};
104
+
105
+ for (const [name, value] of Object.entries(env)) {
106
+ if (value !== undefined) {
107
+ compact[name] = value;
108
+ }
109
+ }
110
+
111
+ return compact;
112
+ }
113
+
114
+ async function loadSmokeFixture(
115
+ provider: TranscriptionProviderName,
116
+ fixtureFlag: string | boolean | undefined,
117
+ ): Promise<SmokeFixture> {
118
+ if (typeof fixtureFlag === "string") {
119
+ const filename = resolve(process.cwd(), fixtureFlag);
120
+ return {
121
+ audio: new Uint8Array(await readFile(filename)),
122
+ filename: basename(filename),
123
+ mimeType: mimeTypeForFilename(filename),
124
+ source: fixtureFlag,
125
+ };
126
+ }
127
+
128
+ if (provider === "test") {
129
+ return {
130
+ audio: new TextEncoder().encode("hello"),
131
+ filename: "agentkit-smoke.ogg",
132
+ mimeType: "audio/ogg",
133
+ source: "built-in text fixture",
134
+ };
135
+ }
136
+
137
+ return {
138
+ audio: new Uint8Array(Buffer.from(builtinSmokeM4aBase64, "base64")),
139
+ filename: "agentkit-transcribe-smoke.m4a",
140
+ mimeType: "audio/m4a",
141
+ source: "built-in spoken hello",
142
+ };
143
+ }
144
+
145
+ function mimeTypeForFilename(filename: string): string {
146
+ const extension = extname(filename).toLowerCase();
147
+
148
+ if (extension === ".ogg" || extension === ".opus") {
149
+ return "audio/ogg";
150
+ }
151
+ if (extension === ".mp3" || extension === ".mpeg" || extension === ".mpga") {
152
+ return "audio/mpeg";
153
+ }
154
+ if (extension === ".m4a") {
155
+ return "audio/m4a";
156
+ }
157
+ if (extension === ".mp4") {
158
+ return "audio/mp4";
159
+ }
160
+ if (extension === ".wav") {
161
+ return "audio/wav";
162
+ }
163
+ if (extension === ".webm") {
164
+ return "audio/webm";
165
+ }
166
+ if (extension === ".flac") {
167
+ return "audio/flac";
168
+ }
169
+
170
+ return "application/octet-stream";
171
+ }
@@ -2,6 +2,7 @@ import { findAgentCapsuleRoot, loadAgentCapsule } from "../runtime/config";
2
2
  import { loadDeployReadinessContext, type DeployReadinessContext } from "../runtime/deploy-readiness";
3
3
  import { loadCapsuleEnv } from "../runtime/env";
4
4
  import { isAgentKitError } from "../runtime/errors";
5
+ import { MANAGED_COMPOSIO_ENTITLEMENT } from "../runtime/integrations/composio";
5
6
  import {
6
7
  cloudApiRequest,
7
8
  formatCloudAuthSource,
@@ -53,6 +54,22 @@ export async function checkDeployReadiness(options: {
53
54
  });
54
55
  }
55
56
 
57
+ const nodeMajor = majorNodeVersion(process.versions.node);
58
+ if (nodeMajor !== null && nodeMajor < 24) {
59
+ checks.push({
60
+ status: "warn",
61
+ title: "Node version",
62
+ message: `@andreprado/agentkit requires Node >=24; current Node is ${process.versions.node}.`,
63
+ action: "use Node 24+ before running direct node imports or production container commands",
64
+ });
65
+ } else {
66
+ checks.push({
67
+ status: "pass",
68
+ title: "Node version",
69
+ message: `Node ${process.versions.node} satisfies @andreprado/agentkit engines.node >=24.`,
70
+ });
71
+ }
72
+
56
73
  if (!auth?.token) {
57
74
  checks.push({
58
75
  status: options.requireLogin ? "fail" : "warn",
@@ -104,6 +121,9 @@ export async function checkDeployReadiness(options: {
104
121
  const hasCloudflareAlpha = capabilities.some(
105
122
  (capability) => capability.name === "cloudflare_deploy_alpha" && capability.status === "active",
106
123
  );
124
+ const hasManagedComposio = capabilities.some(
125
+ (capability) => capability.name === MANAGED_COMPOSIO_ENTITLEMENT && capability.status === "active",
126
+ );
107
127
 
108
128
  checks.push({
109
129
  status: hasCloudflareAlpha ? "pass" : "fail",
@@ -113,6 +133,17 @@ export async function checkDeployReadiness(options: {
113
133
  : "This account is missing the cloudflare_deploy_alpha entitlement.",
114
134
  ...(hasCloudflareAlpha ? {} : { action: "log in with an invited AgentKit Cloud alpha token" }),
115
135
  });
136
+
137
+ if (context.integrations.managedComposio.enabled) {
138
+ checks.push({
139
+ status: hasManagedComposio ? "pass" : "fail",
140
+ title: "Managed Composio entitlement",
141
+ message: hasManagedComposio
142
+ ? `${MANAGED_COMPOSIO_ENTITLEMENT} is active.`
143
+ : `This capsule uses AgentKit-managed Composio, but this account is missing ${MANAGED_COMPOSIO_ENTITLEMENT}.`,
144
+ ...(hasManagedComposio ? {} : { action: "upgrade the AgentKit Cloud account or ask an operator to grant managed_composio" }),
145
+ });
146
+ }
116
147
  } catch (error) {
117
148
  checks.push({
118
149
  status: "fail",
@@ -184,6 +215,25 @@ export async function checkDeployReadiness(options: {
184
215
  });
185
216
  }
186
217
 
218
+ if (context.integrations.managedComposio.enabled) {
219
+ if (!options.requireLogin) {
220
+ checks.push({
221
+ status: "fail",
222
+ title: "Managed Composio access",
223
+ message: "AgentKit-managed Composio is paid hosted infrastructure and cannot be used by anonymous deploys.",
224
+ action: "run agentkit deploy without --anonymous after logging in with a paid AgentKit Cloud account",
225
+ });
226
+ } else {
227
+ checks.push({
228
+ status: "info",
229
+ title: "Managed Composio",
230
+ message: `Configured for ${context.integrations.managedComposio.toolkits.join(", ")} with ${
231
+ context.integrations.managedComposio.toolCount
232
+ } allowed action${context.integrations.managedComposio.toolCount === 1 ? "" : "s"}. AgentKit Cloud injects COMPOSIO_API_KEY.`,
233
+ });
234
+ }
235
+ }
236
+
187
237
  if (context.accessMode === "private") {
188
238
  checks.push({
189
239
  status: "info",
@@ -350,20 +400,26 @@ async function resolveCloudProjectId(apiUrl: string, projectName: string): Promi
350
400
 
351
401
  function secretSetCommand(secret: string, source: DeployReadinessContext["localSecrets"][string] | undefined): string {
352
402
  if (source?.dotenv) {
353
- return `agentkit secret set ${secret} --from-local-env`;
403
+ return `npm run agentkit -- secret set ${secret} --from-local-env`;
354
404
  }
355
405
 
356
406
  if (source?.environment) {
357
- return `agentkit secret set ${secret} --from-env`;
407
+ return `npm run agentkit -- secret set ${secret} --from-env`;
358
408
  }
359
409
 
360
- return `agentkit secret set ${secret} --stdin`;
410
+ return `npm run agentkit -- secret set ${secret} --stdin`;
361
411
  }
362
412
 
363
413
  function normalizeOptionalNumber(value: number | null | undefined): number | undefined {
364
414
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
365
415
  }
366
416
 
417
+ function majorNodeVersion(version: string): number | null {
418
+ const [major] = version.split(".");
419
+ const parsed = Number(major);
420
+ return Number.isInteger(parsed) ? parsed : null;
421
+ }
422
+
367
423
  function isString(value: unknown): value is string {
368
424
  return typeof value === "string";
369
425
  }
package/src/cli/help.ts CHANGED
@@ -41,6 +41,7 @@ Deploy:
41
41
 
42
42
  More:
43
43
  agentkit channels ...
44
+ agentkit integrations status
44
45
  agentkit access token ...
45
46
  agentkit env ...
46
47
  agentkit docs full
@@ -82,6 +83,7 @@ Usage:
82
83
  agentkit channels status <name> [--api <url>]
83
84
  agentkit channels doctor <name> [--api <url>]
84
85
  agentkit channels test <name> [--message <text>] [--fixture <path>] [--api <url>]
86
+ agentkit channels test-audio <name> [--fixture voice-note|audio-file|<path>] [--api <url>]
85
87
  agentkit channels deliveries list <name> [--api <url>]
86
88
  agentkit channels deliveries show <delivery-id> [--api <url>]
87
89
  agentkit channels buffers list <name> [--api <url>]
@@ -89,6 +91,10 @@ Usage:
89
91
  agentkit channels buffers flush [<name>] <conversation-id> [--api <url>]
90
92
  agentkit channels buffers clear [<name>] <conversation-id> [--api <url>]
91
93
  agentkit channels buffers retry [<name>] <conversation-id> [--api <url>]
94
+ agentkit integrations status [--api <url>]
95
+ agentkit integrations connect composio [--toolkit <slug>] [--callback-url <url>] [--api <url>]
96
+ agentkit skills status
97
+ agentkit skills sync
92
98
  agentkit inspect
93
99
  agentkit build [--target cloudflare|container]
94
100
  agentkit login --token <token> [--api <url>]
@@ -99,6 +105,7 @@ Usage:
99
105
  agentkit deploy status [--api <url>]
100
106
  agentkit deploy pause [--api <url>]
101
107
  agentkit deploy resume [--api <url>]
108
+ agentkit transcribe smoke [--provider test|openai|groq] [--model <model>] [--fixture <audio-file>]
102
109
  agentkit secret set <NAME> <VALUE> [--api <url>]
103
110
  agentkit secret set <NAME> --stdin [--api <url>]
104
111
  agentkit secret set <NAME> --from-env [ENV_NAME] [--api <url>]
@@ -135,6 +142,9 @@ Commands:
135
142
  eval Run local eval files or create one from a conversation
136
143
  conversations List, inspect, and trace stored local conversations
137
144
  channels Manage hosted channel resources
145
+ integrations Manage hosted AgentKit-managed integrations
146
+ skills Check or refresh local AgentKit skill files
147
+ transcribe Smoke-test audio transcription providers
138
148
  inspect Print machine-readable Agent Capsule state
139
149
  build Build a hosted deploy artifact
140
150
  login Store an AgentKit Cloud alpha token outside the capsule
@@ -173,11 +183,16 @@ export function renderCommandHelp(command: string): string {
173
183
  eval: "agentkit eval run | agentkit eval from-conversation <conversation-id> [--out <path>] [--force]",
174
184
  conversations:
175
185
  "agentkit conversations list | agentkit conversations show <conversation-id> | agentkit conversations trace <conversation-id>",
186
+ channels:
187
+ "agentkit channels list | add | connect | setup | status | doctor | test | test-audio | deliveries | buffers",
188
+ skills: "agentkit skills status | agentkit skills sync",
189
+ transcribe: "agentkit transcribe smoke [--provider test|openai|groq] [--model <model>] [--fixture <audio-file>]",
176
190
  deploy:
177
191
  'agentkit deploy [--smoke "hello"] | agentkit deploy smoke [--message "hello"] | agentkit deploy doctor | status | pause | resume',
178
192
  secret:
179
193
  "agentkit secret set <NAME> --stdin | --from-env [ENV_NAME] | --from-local-env | agentkit secret sync --from-local | list | unset <NAME>",
180
194
  access: "agentkit access token create <name> [--out <path>] | list | revoke <token-id>",
195
+ integrations: "agentkit integrations status | agentkit integrations connect composio [--toolkit <slug>]",
181
196
  env: "agentkit env set <NAME> --stdin | --from-env [ENV_NAME] | list | unset <NAME>",
182
197
  docs: "agentkit docs path | llms | full",
183
198
  handoff: "agentkit handoff codex [goal] | agentkit handoff claude [goal]",