@andreprado/agentkit 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (142) hide show
  1. package/README.md +68 -6
  2. package/docs/guides/add-channel.md +189 -7
  3. package/docs/guides/add-knowledge.md +144 -0
  4. package/docs/guides/add-managed-composio.md +163 -0
  5. package/docs/guides/add-tool.md +1 -1
  6. package/docs/guides/channel-security.md +128 -32
  7. package/docs/guides/connect-discord.md +178 -0
  8. package/docs/guides/connect-slack.md +126 -0
  9. package/docs/guides/connect-telegram.md +78 -1
  10. package/docs/guides/connect-whatsapp-evolution.md +121 -0
  11. package/docs/guides/connect-whatsapp-uazapi.md +126 -0
  12. package/docs/guides/connect-whatsapp-zapster.md +112 -8
  13. package/docs/guides/create-agent.md +45 -4
  14. package/docs/guides/debug-channel.md +147 -0
  15. package/docs/guides/improve-from-production.md +151 -0
  16. package/docs/guides/prepare-deploy.md +47 -17
  17. package/docs/guides/replay-production-traces.md +72 -0
  18. package/docs/guides/run-evals.md +147 -20
  19. package/docs/guides/security-rules.md +7 -6
  20. package/docs/guides/send-feedback.md +135 -0
  21. package/docs/guides/use-provider.md +27 -3
  22. package/docs/llms-full.txt +348 -55
  23. package/docs/llms.txt +62 -7
  24. package/package.json +2 -5
  25. package/src/cli/args.ts +57 -0
  26. package/src/cli/cloud-client.ts +377 -0
  27. package/src/cli/commands/channels.ts +1586 -0
  28. package/src/cli/commands/feedback.ts +438 -0
  29. package/src/cli/commands/knowledge.ts +136 -0
  30. package/src/cli/commands/transcribe.ts +171 -0
  31. package/src/cli/constants.ts +4 -0
  32. package/src/cli/deploy-chat-ui.ts +535 -0
  33. package/src/cli/deploy-readiness.ts +481 -0
  34. package/src/cli/flags.ts +162 -0
  35. package/src/cli/help.ts +236 -0
  36. package/src/cli/index.ts +1167 -1005
  37. package/src/cli/process.ts +31 -0
  38. package/src/cloud/artifact.ts +139 -0
  39. package/src/cloud/client.ts +80 -0
  40. package/src/cloud/contracts.ts +63 -0
  41. package/src/cloud/index.ts +3 -0
  42. package/src/create-project.ts +21 -6
  43. package/src/index.ts +517 -8
  44. package/src/providers/pi.ts +70 -16
  45. package/src/providers/test.ts +88 -1
  46. package/src/providers/types.ts +7 -0
  47. package/src/runtime/channel-buffer.ts +30 -0
  48. package/src/runtime/channel-test-harness.ts +21 -1
  49. package/src/runtime/channels/discord.ts +896 -0
  50. package/src/runtime/channels/generic-webhook.ts +225 -0
  51. package/src/runtime/channels/slack.ts +646 -0
  52. package/src/runtime/channels/telegram.ts +466 -23
  53. package/src/runtime/channels/whatsapp-evolution.ts +1357 -0
  54. package/src/runtime/channels/whatsapp-meta.ts +9 -0
  55. package/src/runtime/channels/whatsapp-uazapi.ts +1327 -0
  56. package/src/runtime/channels/whatsapp-zapster.ts +677 -40
  57. package/src/runtime/channels.ts +87 -4
  58. package/src/runtime/chat.ts +130 -38
  59. package/src/runtime/config.ts +519 -19
  60. package/src/runtime/core/manifest.ts +103 -5
  61. package/src/runtime/core/targets.ts +5 -5
  62. package/src/runtime/database.ts +93 -2
  63. package/src/runtime/db-commands.ts +9 -0
  64. package/src/runtime/deploy-readiness.ts +46 -4
  65. package/src/runtime/deploy.ts +1 -1
  66. package/src/runtime/dev-server.ts +779 -45
  67. package/src/runtime/env.ts +8 -3
  68. package/src/runtime/evals.ts +589 -43
  69. package/src/runtime/improve.ts +868 -0
  70. package/src/runtime/inspect.ts +194 -4
  71. package/src/runtime/integrations/composio.ts +423 -0
  72. package/src/runtime/knowledge/chunk.ts +333 -0
  73. package/src/runtime/knowledge/config.ts +135 -0
  74. package/src/runtime/knowledge/embeddings.ts +133 -0
  75. package/src/runtime/knowledge/ingest.ts +521 -0
  76. package/src/runtime/knowledge/prompt-policy.ts +30 -0
  77. package/src/runtime/knowledge/retrieve.ts +303 -0
  78. package/src/runtime/knowledge/schema.ts +100 -0
  79. package/src/runtime/knowledge/tool.ts +64 -0
  80. package/src/runtime/knowledge/vector.ts +258 -0
  81. package/src/runtime/prompt-context.ts +141 -0
  82. package/src/runtime/runtime-contract.ts +86 -8
  83. package/src/runtime/skills.ts +95 -0
  84. package/src/runtime/spec.ts +152 -0
  85. package/src/runtime/sync.ts +144 -0
  86. package/src/runtime/targets/cloudflare/build.ts +1468 -203
  87. package/src/runtime/targets/container/server.ts +1 -1
  88. package/src/runtime/targets/vps/deploy.ts +26 -9
  89. package/src/runtime/tool-runner.ts +9 -1
  90. package/src/runtime/tools.ts +128 -2
  91. package/src/runtime/traces.ts +41 -0
  92. package/src/runtime/transcription.ts +483 -0
  93. package/src/storage/sqlite.ts +149 -3
  94. package/src/templates/blank.ts +76 -17
  95. package/src/templates/dentista.ts +1011 -0
  96. package/src/templates/index.ts +2 -0
  97. package/src/templates/skills/agentkit-build-agent/SKILL.md +52 -0
  98. package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +21 -0
  99. package/src/templates/skills/agentkit-build-agent/templates/sales-qualifier.instructions.md +17 -0
  100. package/src/templates/skills/agentkit-build-agent/templates/support-agent.instructions.md +16 -0
  101. package/src/templates/skills/agentkit-capsule/SKILL.md +70 -0
  102. package/src/templates/skills/agentkit-capsule/references/docs-router.md +15 -0
  103. package/src/templates/skills/agentkit-channels/SKILL.md +127 -0
  104. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +65 -0
  105. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +66 -0
  106. package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
  107. package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
  108. package/src/templates/skills/agentkit-channels/references/telegram.md +72 -0
  109. package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +57 -0
  110. package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +61 -0
  111. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +77 -0
  112. package/src/templates/skills/agentkit-database/SKILL.md +45 -0
  113. package/src/templates/skills/agentkit-database/templates/appointments.schema.sql +15 -0
  114. package/src/templates/skills/agentkit-database/templates/leads.schema.sql +17 -0
  115. package/src/templates/skills/agentkit-deploy/SKILL.md +50 -0
  116. package/src/templates/skills/agentkit-evals/SKILL.md +109 -0
  117. package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +29 -0
  118. package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +18 -0
  119. package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +18 -0
  120. package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +27 -0
  121. package/src/templates/skills/agentkit-improve/SKILL.md +86 -0
  122. package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
  123. package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
  124. package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
  125. package/src/templates/skills/agentkit-integrations/SKILL.md +76 -0
  126. package/src/templates/skills/agentkit-knowledge/SKILL.md +43 -0
  127. package/src/templates/skills/agentkit-knowledge/templates/faq.md +14 -0
  128. package/src/templates/skills/agentkit-knowledge/templates/policies.md +14 -0
  129. package/src/templates/skills/agentkit-knowledge/templates/prices.csv +3 -0
  130. package/src/templates/skills/agentkit-prompts/SKILL.md +47 -0
  131. package/src/templates/skills/agentkit-prompts/templates/knowledge-grounded-faq.instructions.md +11 -0
  132. package/src/templates/skills/agentkit-provider/SKILL.md +60 -0
  133. package/src/templates/skills/agentkit-security/SKILL.md +56 -0
  134. package/src/templates/skills/agentkit-tools/SKILL.md +37 -0
  135. package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +35 -0
  136. package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +37 -0
  137. package/src/templates/skills/agentkit-tools/examples/lookup-order.tool.md +46 -0
  138. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +76 -0
  139. package/src/templates/support.ts +77 -18
  140. package/docs/guides/channels-production-handoff.md +0 -99
  141. package/docs/portable-deploy-release-checklist.md +0 -41
  142. package/src/runtime/targets/cloudflare/deploy.ts +0 -5475
package/src/cli/index.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  import { spawn } from "node:child_process";
2
- import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
- import { homedir } from "node:os";
4
- import { join, resolve } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
5
 
6
6
  import { createProject } from "../create-project";
7
7
  import { buildAgentCapsule } from "../runtime/build";
8
8
  import { findAgentCapsuleRoot, loadAgentCapsule } from "../runtime/config";
9
9
  import { readLocalDeployStateIfExists, type LocalDeployState } from "../runtime/core/deploy-state";
10
- import { listTargetsWithCapability, resolveBuildTarget, resolveDeployTarget } from "../runtime/core/targets";
10
+ import { resolveBuildTarget, resolveDeployTarget } from "../runtime/core/targets";
11
11
  import { getConversationFromCwd, listConversationsFromCwd } from "../runtime/conversations";
12
+ import { getConversationTraceFromCwd } from "../runtime/traces";
12
13
  import { runAgentMessageFromCwd } from "../runtime/chat";
13
14
  import { deployBuildToAgentKitCloud, writeLocalDeployState } from "../runtime/deploy";
14
15
  import {
@@ -17,182 +18,205 @@ import {
17
18
  resetLocalDatabase,
18
19
  seedLocalDatabase,
19
20
  } from "../runtime/db-commands";
20
- import { runEvalsFromCwd } from "../runtime/evals";
21
+ import { runEvalsFromCwd, writeEvalFromConversation } from "../runtime/evals";
21
22
  import { findAgentKitDocs } from "../runtime/docs";
22
23
  import { listCapsuleEnv, setCapsuleEnv, unsetCapsuleEnv } from "../runtime/env";
23
- import { loadDeployReadinessContext, stableCloudProjectId, type DeployReadinessContext } from "../runtime/deploy-readiness";
24
+ import { collectDeployImproveBundle, collectLocalImproveBundle, generateImproveEvals, replayImproveBundle } from "../runtime/improve";
25
+ import { checkAgentSpec, initializeAgentSpec } from "../runtime/spec";
26
+ import { checkDefaultSkillStatus, syncDefaultSkillFiles } from "../runtime/skills";
27
+ import { initializeSyncScaffold, runCapsuleSync } from "../runtime/sync";
24
28
  import { runToolFromCwd } from "../runtime/tool-runner";
25
29
  import { startAgentDevServer } from "../runtime/dev-server";
26
30
  import { inspectAgentCapsule } from "../runtime/inspect";
27
31
  import { AgentKitError, isAgentKitError } from "../runtime/errors";
28
32
  import { prepareVpsDeploy } from "../runtime/targets/vps/deploy";
33
+ import { parseArgs, isHelpRequested, type ParsedArgs } from "./args";
34
+ import {
35
+ defaultAgentKitDevPort,
36
+ defaultDeployChatAccessTokenName,
37
+ defaultDeployChatAccessTokenPath,
38
+ } from "./constants";
39
+ import {
40
+ clearCloudAuth,
41
+ cloudApiRequest,
42
+ readCloudAuth,
43
+ readCloudAuthForApiUrl,
44
+ readCloudError,
45
+ resolveCloudApiUrl,
46
+ verifyCloudLogin,
47
+ writeCloudAuth,
48
+ type AccountApiTokenCreateResponse,
49
+ type AccountApiTokenListResponse,
50
+ type BillingCheckoutIntentResponse,
51
+ type BillingCheckoutResponse,
52
+ type BillingClaimResponse,
53
+ type BillingPortalResponse,
54
+ type CloudMeResponse,
55
+ type DeployAccessTokenCreateResponse,
56
+ type DeployAccessTokenListResponse,
57
+ } from "./cloud-client";
58
+ import {
59
+ checkDeployReadiness,
60
+ formatReadinessError,
61
+ printDeployReadinessReport,
62
+ resolveProjectIdForSecretCommand,
63
+ syncHostedSecretsFromLocal,
64
+ } from "./deploy-readiness";
65
+ import { readDeployAccessTokenForUi, startDeployChatUiServer } from "./deploy-chat-ui";
66
+ import {
67
+ parseCloudApiUrl,
68
+ parseOptionalStringFlag,
69
+ parsePort,
70
+ parseRequiredStringFlag,
71
+ parseTargetFlag,
72
+ resolveSecretInputValue,
73
+ } from "./flags";
74
+ import { renderCommandHelp, renderCommandReference, renderHelp } from "./help";
75
+ import { runInheritedCommand, waitForShutdown } from "./process";
76
+ import { handleChannelsCommand } from "./commands/channels";
77
+ import { handleFeedbackCommand } from "./commands/feedback";
78
+ import { handleKnowledgeCommand } from "./commands/knowledge";
79
+ import { handleTranscribeCommand } from "./commands/transcribe";
80
+
81
+ const hostedDeployEntitlement = "agentkit_cloud_deploy";
29
82
 
30
- const defaultAgentKitCloudApiUrl = "https://agentkit-cloud.aibuilders.com.br";
31
-
32
- function renderHelp(): string {
33
- return `AgentKit
34
-
35
- Usage:
36
- agentkit new <name> [--template blank|support]
37
- agentkit dev
38
- agentkit chat --message <text>
39
- agentkit tool <name> [--input <path-or-json>]
40
- agentkit db migrate
41
- agentkit db reset --yes
42
- agentkit db shell
43
- agentkit db seed [--file <path>]
44
- agentkit eval run
45
- agentkit conversations list
46
- agentkit conversations show <conversation-id>
47
- agentkit channels list
48
- agentkit channels add <website|telegram|whatsapp> <name> [--provider zapster|meta] [--api <url>]
49
- agentkit channels setup <name> [--apply] [--api <url>]
50
- agentkit channels status <name> [--api <url>]
51
- agentkit channels test <name> [--message <text>] [--fixture <path>] [--api <url>]
52
- agentkit channels deliveries list <name> [--api <url>]
53
- agentkit channels deliveries show <delivery-id> [--api <url>]
54
- agentkit inspect
55
- agentkit build [--target cloudflare]
56
- agentkit login --token <token> [--api <url>]
57
- agentkit logout
58
- agentkit deploy [--target cloudflare|vps] [--host <host>] [--api <url>] [--dry-run] [--anonymous] [--local-wrangler]
59
- agentkit deploy doctor [--api <url>] [--anonymous]
60
- agentkit deploy status [--api <url>]
61
- agentkit deploy pause [--api <url>]
62
- agentkit deploy resume [--api <url>]
63
- agentkit secret set <NAME> <VALUE> [--api <url>]
64
- agentkit secret list [--api <url>]
65
- agentkit secret unset <NAME> [--api <url>]
66
- agentkit access token create <name> [--api <url>]
67
- agentkit access token list [--api <url>]
68
- agentkit access token revoke <token-id> [--api <url>]
69
- agentkit env set <NAME> <VALUE>
70
- agentkit env list
71
- agentkit env unset <NAME>
72
- agentkit docs path
73
- agentkit docs llms
74
- agentkit docs full
75
- agentkit handoff codex [goal]
76
- agentkit --help
77
-
78
- Commands:
79
- new Create a new Agent Capsule
80
- dev Run the Agent Capsule locally
81
- chat Send one message to the local Agent Capsule
82
- tool Run one registered tool directly
83
- db Manage the local SQLite database
84
- eval Run local eval files
85
- conversations List and inspect stored local conversations
86
- channels Manage hosted channel resources
87
- inspect Print machine-readable Agent Capsule state
88
- build Build a hosted deploy artifact
89
- login Store an AgentKit Cloud alpha token outside the capsule
90
- logout Remove the stored AgentKit Cloud alpha token
91
- deploy Build and send a hosted artifact to AgentKit Cloud
92
- secret Manage hosted AgentKit Cloud secrets without readback
93
- access Manage hosted deploy access tokens
94
- env Manage local .env values without opening code
95
- docs Print AgentKit docs locations
96
- handoff Print a ready-to-paste prompt for another coding agent
97
-
98
- Examples:
99
- agentkit new support-agent --template support
100
- cd support-agent
101
- agentkit dev
102
- agentkit tool lookup_order --input '{"orderId":"A100"}'
103
- agentkit eval run
104
- `;
105
- }
83
+ async function main() {
84
+ const args = parseArgs(process.argv.slice(2));
106
85
 
107
- type ParsedArgs = {
108
- command?: string;
109
- positional: string[];
110
- flags: Record<string, string | boolean>;
111
- };
86
+ if (!args.command || args.command === "--help" || args.command === "-h") {
87
+ console.log(renderHelp());
88
+ return;
89
+ }
112
90
 
113
- function parseArgs(argv: string[]): ParsedArgs {
114
- const [command, ...rest] = argv;
115
- const positional: string[] = [];
116
- const flags: Record<string, string | boolean> = {};
91
+ if (isHelpRequested(args)) {
92
+ console.log(renderCommandHelp(args.command));
93
+ return;
94
+ }
117
95
 
118
- for (let index = 0; index < rest.length; index += 1) {
119
- const value = rest[index];
96
+ if (args.command === "help") {
97
+ const [topic] = args.positional;
120
98
 
121
- if (!value.startsWith("--")) {
122
- positional.push(value);
123
- continue;
99
+ if (!topic) {
100
+ console.log(renderHelp());
101
+ return;
124
102
  }
125
103
 
126
- const name = value.slice(2);
127
- const next = rest[index + 1];
128
-
129
- if (next && !next.startsWith("--")) {
130
- flags[name] = next;
131
- index += 1;
132
- } else {
133
- flags[name] = true;
104
+ if (topic === "commands") {
105
+ console.log(renderCommandReference());
106
+ return;
134
107
  }
135
- }
136
-
137
- return { command, positional, flags };
138
- }
139
108
 
140
- async function main() {
141
- const args = parseArgs(process.argv.slice(2));
142
-
143
- if (!args.command || args.command === "--help" || args.command === "-h") {
144
- console.log(renderHelp());
145
- return;
109
+ throw new Error("Usage: agentkit help [commands]");
146
110
  }
147
111
 
148
112
  if (args.command === "new") {
149
113
  const [name] = args.positional;
150
114
  const template = String(args.flags.template ?? "blank");
115
+ const shouldInstall = args.flags["no-install"] !== true;
151
116
 
152
117
  if (!name) {
153
- throw new Error("Missing project name. Usage: agentkit new <name> [--template blank|support]");
118
+ throw new Error("Missing project name. Usage: agentkit new <name> [--template blank|support|dentista] [--no-install]");
154
119
  }
155
120
 
156
121
  const result = await createProject({ name, template, cwd: process.cwd() });
157
122
  console.log(`Created ${result.name} at ${result.path}`);
123
+
124
+ if (shouldInstall) {
125
+ console.log("");
126
+ console.log("Installing dependencies...");
127
+ await installProjectDependencies(result.path);
128
+ }
129
+
158
130
  console.log("");
159
131
  console.log("Next:");
160
132
  if (resolve(result.path) !== resolve(process.cwd())) {
161
133
  console.log(` cd ${result.name}`);
162
134
  }
163
- console.log(" npm install");
135
+ if (!shouldInstall) {
136
+ console.log(" npm install");
137
+ }
164
138
  console.log(" open this folder in Codex or Claude Code");
139
+ console.log(" coding agents start at skills/agentkit-capsule/SKILL.md");
165
140
  console.log(' ask: "Develop an appointment and intake agent for an ophthalmology office"');
166
141
  console.log(' npm run chat -- --message "hello"');
167
142
  console.log(" npm run dev");
168
143
  console.log(" npm run agentkit -- deploy");
169
144
  console.log("");
170
- console.log("Optional: manage local secrets with `npm run agentkit -- env set <NAME> <VALUE>`.");
145
+ console.log("Optional: manage local secrets with `printf %s \"$VALUE\" | npm run agentkit -- env set <NAME> --stdin`.");
171
146
  return;
172
147
  }
173
148
 
174
149
  if (args.command === "dev") {
175
- const port = parsePort(args.flags.port);
176
- const devServer = await startAgentDevServer(process.cwd(), { port });
150
+ const port = parsePort(args.flags.port, "agentkit dev --port <number>");
151
+ const devServer = await startAgentDevServer(process.cwd(), { port, basePort: localDevBasePort() });
177
152
 
178
153
  console.log("Agent Capsule running");
179
154
  console.log("");
155
+ if (devServer.portConflict) {
156
+ const owner = devServer.portConflict.agent ? ` by ${devServer.portConflict.agent}` : "";
157
+ console.log(
158
+ `Port ${devServer.portConflict.port} is already used${owner}. Started ${devServer.capsule.config.name} at ${devServer.urls.chat}.`,
159
+ );
160
+ console.log("");
161
+ }
180
162
  console.log(`Chat: ${devServer.urls.chat}`);
181
163
  console.log(`API: ${devServer.urls.api}`);
182
164
  console.log(`Inspect: ${devServer.urls.inspect}`);
183
165
  console.log(`Storage: ${devServer.storage ?? "managed"}`);
166
+ console.log("Reload: prompts/config reload per request; restart after changing tool modules or dependencies");
184
167
  await waitForShutdown(devServer.stop);
185
168
  return;
186
169
  }
187
170
 
171
+ if (args.command === "open") {
172
+ const server = await findRunningLocalDevServer(process.cwd());
173
+
174
+ if (!server) {
175
+ console.log("No running AgentKit dev server found for this capsule.");
176
+ console.log("Run: npm run dev");
177
+ return;
178
+ }
179
+
180
+ await openLocalUrl(server.url);
181
+ console.log(`Opened ${server.url}`);
182
+ return;
183
+ }
184
+
185
+ if (args.command === "chat-ui") {
186
+ if (!args.flags.deploy) {
187
+ throw new Error("Usage: agentkit chat-ui --deploy [--port <number>] [--token-file <path>]");
188
+ }
189
+
190
+ const port = parsePort(args.flags.port, "agentkit chat-ui --deploy --port <number>") ?? 4124;
191
+ const tokenFile = parseOptionalStringFlag(
192
+ args.flags["token-file"],
193
+ "token-file",
194
+ "agentkit chat-ui --deploy --token-file <path>",
195
+ );
196
+ const server = await startDeployChatUiServer(process.cwd(), { port, tokenFile });
197
+
198
+ console.log("AgentKit hosted chat UI running");
199
+ console.log("");
200
+ console.log(`Chat: ${server.url}`);
201
+ console.log(`Production: ${server.deployUrl}`);
202
+ console.log(`Token: ${server.tokenSource}`);
203
+ await waitForShutdown(server.stop);
204
+ return;
205
+ }
206
+
188
207
  if (args.command === "chat") {
189
208
  const message = args.flags.message;
209
+ const conversationId = parseOptionalStringFlag(
210
+ args.flags["conversation-id"] ?? args.flags.conversation,
211
+ "conversation id",
212
+ 'agentkit chat --message "hello" [--conversation-id <id>]',
213
+ );
190
214
 
191
215
  if (typeof message !== "string") {
192
- throw new Error('Missing message. Usage: agentkit chat --message "hello"');
216
+ throw new Error('Missing message. Usage: agentkit chat --message "hello" [--conversation-id <id>]');
193
217
  }
194
218
 
195
- const result = await runAgentMessageFromCwd(process.cwd(), { message });
219
+ const result = await runAgentMessageFromCwd(process.cwd(), { message, conversationId });
196
220
  console.log(result.message.content);
197
221
  return;
198
222
  }
@@ -217,6 +241,8 @@ async function main() {
217
241
  const result = await migrateLocalDatabase(process.cwd());
218
242
  console.log("Local database migrated");
219
243
  console.log(`Database: ${result.databasePath}`);
244
+ console.log(`Migrations: ${result.migrationsPath ?? "none"}`);
245
+ console.log(`Applied: ${result.migrationsApplied.length > 0 ? result.migrationsApplied.join(", ") : "none"}`);
220
246
  console.log(`Schema: ${result.schemaPath ?? "none"}`);
221
247
  return;
222
248
  }
@@ -230,6 +256,8 @@ async function main() {
230
256
  console.log("Local database reset");
231
257
  console.log(`Deleted: ${result.resetPath}`);
232
258
  console.log(`Database: ${result.databasePath}`);
259
+ console.log(`Migrations: ${result.migrationsPath ?? "none"}`);
260
+ console.log(`Applied: ${result.migrationsApplied.length > 0 ? result.migrationsApplied.join(", ") : "none"}`);
233
261
  console.log(`Schema: ${result.schemaPath ?? "none"}`);
234
262
  return;
235
263
  }
@@ -252,6 +280,8 @@ async function main() {
252
280
  const result = await seedLocalDatabase(process.cwd(), seedFile);
253
281
  console.log("Local database seeded");
254
282
  console.log(`Database: ${result.databasePath}`);
283
+ console.log(`Migrations: ${result.migrationsPath ?? "none"}`);
284
+ console.log(`Applied: ${result.migrationsApplied.length > 0 ? result.migrationsApplied.join(", ") : "none"}`);
255
285
  console.log(`Schema: ${result.schemaPath ?? "none"}`);
256
286
  console.log(`Seed: ${result.seedPath}`);
257
287
  return;
@@ -261,10 +291,39 @@ async function main() {
261
291
  }
262
292
 
263
293
  if (args.command === "eval") {
264
- const [subcommand] = args.positional;
294
+ const [subcommand, conversationId] = args.positional;
295
+
296
+ if (subcommand === "from-conversation") {
297
+ if (!conversationId) {
298
+ throw new Error("Missing conversation id. Usage: agentkit eval from-conversation <conversation-id> [--out <path>] [--force]");
299
+ }
300
+
301
+ const out = parseOptionalStringFlag(
302
+ args.flags.out,
303
+ "out",
304
+ "agentkit eval from-conversation <conversation-id> --out <path>",
305
+ );
306
+ const name = parseOptionalStringFlag(
307
+ args.flags.name,
308
+ "name",
309
+ "agentkit eval from-conversation <conversation-id> --name <name>",
310
+ );
311
+ const result = await writeEvalFromConversation(process.cwd(), {
312
+ conversationId,
313
+ out,
314
+ name,
315
+ force: args.flags.force === true,
316
+ });
317
+
318
+ console.log("Eval created from conversation");
319
+ console.log(`Conversation: ${result.conversationId}`);
320
+ console.log(`File: ${relativePath(result.root, result.file)}`);
321
+ console.log(`Turns: ${result.turns}`);
322
+ return;
323
+ }
265
324
 
266
325
  if (subcommand !== "run") {
267
- throw new Error("Usage: agentkit eval run");
326
+ throw new Error("Usage: agentkit eval run | agentkit eval from-conversation <conversation-id> [--out <path>] [--force]");
268
327
  }
269
328
 
270
329
  const summary = await runEvalsFromCwd(process.cwd());
@@ -287,10 +346,132 @@ async function main() {
287
346
  return;
288
347
  }
289
348
 
349
+ if (args.command === "improve") {
350
+ const [subcommand, bundlePath] = args.positional;
351
+
352
+ if (subcommand === "collect") {
353
+ const since = parseOptionalStringFlag(args.flags.since, "since", 'agentkit improve collect --since "24h"');
354
+ const conversationId = parseOptionalStringFlag(
355
+ args.flags["conversation-id"] ?? args.flags.conversation,
356
+ "conversation id",
357
+ "agentkit improve collect --conversation-id <id>",
358
+ );
359
+ const out = parseOptionalStringFlag(args.flags.out, "out", "agentkit improve collect --out <directory>");
360
+ const deployState = args.flags.deploy === true ? await requireLocalDeployState() : null;
361
+ const apiUrl = args.flags.deploy === true ? await resolveCloudApiUrl(args.flags.api) : null;
362
+ const cloudAuth = apiUrl ? await readCloudAuthForApiUrl(apiUrl) : null;
363
+ const result = args.flags.deploy === true
364
+ ? await collectDeployImproveBundle(process.cwd(), {
365
+ deployId: deployState?.deploy_id ?? "",
366
+ since,
367
+ conversationId,
368
+ out,
369
+ collector: {
370
+ ...(cloudAuth && apiUrl && deployState
371
+ ? {
372
+ exportEvidence: (input: { deployId: string; since?: string; conversationId?: string }) =>
373
+ fetchDeployImproveEvidence(apiUrl, input),
374
+ }
375
+ : {}),
376
+ listConversations: () => fetchDeployConversationJson(process.cwd(), "/v1/conversations"),
377
+ getTrace: (id: string) =>
378
+ fetchDeployConversationJson(process.cwd(), `/v1/conversations/${encodeURIComponent(id)}/trace`),
379
+ },
380
+ })
381
+ : await collectLocalImproveBundle(process.cwd(), {
382
+ since,
383
+ conversationId,
384
+ out,
385
+ });
386
+
387
+ console.log("Improve bundle collected");
388
+ console.log(`Source: ${result.source}`);
389
+ console.log(`Directory: ${relativePath(result.root, result.directory)}`);
390
+ console.log(`Bundle: ${relativePath(result.root, result.bundlePath)}`);
391
+ console.log(`Report: ${relativePath(result.root, result.reportPath)}`);
392
+ console.log(`Traces: ${result.traceCount}`);
393
+ console.log("");
394
+ console.log(`Next: agentkit improve evals ${relativePath(result.root, result.directory)}`);
395
+ console.log(`Replay: agentkit replay ${relativePath(result.root, result.directory)} --against local`);
396
+ return;
397
+ }
398
+
399
+ if (subcommand === "evals") {
400
+ if (!bundlePath) {
401
+ throw new Error("Missing bundle path. Usage: agentkit improve evals <bundle-dir-or-json> [--force]");
402
+ }
403
+
404
+ const result = await generateImproveEvals(process.cwd(), {
405
+ bundle: bundlePath,
406
+ force: args.flags.force === true,
407
+ });
408
+
409
+ console.log("Improve evals generated");
410
+ console.log(`Bundle: ${relativePath(result.root, result.bundlePath)}`);
411
+ console.log(`Files: ${result.files.length}`);
412
+ for (const file of result.files) {
413
+ console.log(` ${relativePath(result.root, file)}`);
414
+ }
415
+ if (result.skipped.length > 0) {
416
+ console.log(`Skipped: ${result.skipped.length}`);
417
+ for (const skipped of result.skipped) {
418
+ console.log(` ${skipped.trace_id}: ${skipped.reason}`);
419
+ }
420
+ }
421
+ return;
422
+ }
423
+
424
+ throw new Error("Usage: agentkit improve collect [--deploy] [--since <duration|iso>] [--conversation-id <id>] [--out <directory>] | agentkit improve evals <bundle-dir-or-json> [--force]");
425
+ }
426
+
427
+ if (args.command === "replay") {
428
+ const [bundlePath] = args.positional;
429
+
430
+ if (!bundlePath) {
431
+ throw new Error("Missing bundle path. Usage: agentkit replay <bundle-dir-or-json> --against local");
432
+ }
433
+
434
+ const against = parseOptionalStringFlag(args.flags.against, "against", "agentkit replay <bundle> --against local") ?? "local";
435
+
436
+ if (against !== "local") {
437
+ throw new Error("Usage: agentkit replay <bundle-dir-or-json> --against local");
438
+ }
439
+
440
+ const result = await replayImproveBundle(process.cwd(), {
441
+ bundle: bundlePath,
442
+ against,
443
+ });
444
+
445
+ for (const item of result.results) {
446
+ console.log(`${item.status.toUpperCase()} ${item.conversation_id} (${item.turns} turns)`);
447
+ if (item.error) {
448
+ console.log(` - ${item.error}`);
449
+ }
450
+ }
451
+
452
+ console.log("");
453
+ console.log(`Replay passed: ${result.passed}`);
454
+ console.log(`Replay failed: ${result.failed}`);
455
+ console.log(`Replay skipped: ${result.skipped}`);
456
+
457
+ if (result.failed > 0) {
458
+ process.exitCode = 1;
459
+ }
460
+
461
+ return;
462
+ }
463
+
290
464
  if (args.command === "conversations") {
291
465
  const [subcommand, conversationId] = args.positional;
466
+ const fromDeploy = args.flags.deploy === true;
292
467
 
293
468
  if (subcommand === "list") {
469
+ if (fromDeploy) {
470
+ const payload = await fetchDeployConversationJson(process.cwd(), "/v1/conversations");
471
+ printJson(payload);
472
+ return;
473
+ }
474
+
294
475
  const conversations = await listConversationsFromCwd(process.cwd());
295
476
 
296
477
  if (conversations.length === 0) {
@@ -316,7 +497,16 @@ async function main() {
316
497
 
317
498
  if (subcommand === "show") {
318
499
  if (!conversationId) {
319
- throw new Error("Missing conversation id. Usage: agentkit conversations show <conversation-id>");
500
+ throw new Error("Missing conversation id. Usage: agentkit conversations show <conversation-id> [--deploy]");
501
+ }
502
+
503
+ if (fromDeploy) {
504
+ const payload = await fetchDeployConversationJson(
505
+ process.cwd(),
506
+ `/v1/conversations/${encodeURIComponent(conversationId)}`,
507
+ );
508
+ printJson(payload);
509
+ return;
320
510
  }
321
511
 
322
512
  const conversation = await getConversationFromCwd(process.cwd(), conversationId);
@@ -333,7 +523,107 @@ async function main() {
333
523
  return;
334
524
  }
335
525
 
336
- throw new Error("Usage: agentkit conversations list | agentkit conversations show <conversation-id>");
526
+ if (subcommand === "trace") {
527
+ if (!conversationId) {
528
+ throw new Error("Missing conversation id. Usage: agentkit conversations trace <conversation-id> [--deploy]");
529
+ }
530
+
531
+ if (fromDeploy) {
532
+ const trace = await fetchDeployConversationJson(
533
+ process.cwd(),
534
+ `/v1/conversations/${encodeURIComponent(conversationId)}/trace`,
535
+ );
536
+ printJson(trace);
537
+ return;
538
+ }
539
+
540
+ const trace = await getConversationTraceFromCwd(process.cwd(), conversationId);
541
+ console.log(JSON.stringify(trace, null, 2));
542
+ return;
543
+ }
544
+
545
+ throw new Error("Usage: agentkit conversations list [--deploy] | agentkit conversations show <conversation-id> [--deploy] | agentkit conversations trace <conversation-id> [--deploy]");
546
+ }
547
+
548
+ if (args.command === "knowledge") {
549
+ await handleKnowledgeCommand(args);
550
+ return;
551
+ }
552
+
553
+ if (args.command === "feedback") {
554
+ await handleFeedbackCommand(args);
555
+ return;
556
+ }
557
+
558
+ if (args.command === "sync") {
559
+ const [subcommand] = args.positional;
560
+
561
+ if (subcommand === "init") {
562
+ const result = await initializeSyncScaffold(process.cwd(), {
563
+ force: args.flags.force === true,
564
+ });
565
+
566
+ console.log("Sync scaffold ready");
567
+ if (result.files.length === 0) {
568
+ console.log("Files: none written; existing files kept");
569
+ } else {
570
+ console.log(`Files: ${result.files.join(", ")}`);
571
+ }
572
+ return;
573
+ }
574
+
575
+ if (subcommand === "run") {
576
+ const file = parseOptionalStringFlag(args.flags.file, "file", "agentkit sync run --file <path>");
577
+ const result = await runCapsuleSync(process.cwd(), { file });
578
+
579
+ console.log("Sync completed");
580
+ console.log(`File: ${relativePath(result.root, result.file)}`);
581
+ console.log(`Output: ${JSON.stringify(result.output)}`);
582
+ return;
583
+ }
584
+
585
+ throw new Error("Usage: agentkit sync init [--force] | agentkit sync run [--file <path>]");
586
+ }
587
+
588
+ if (args.command === "spec") {
589
+ const [subcommand] = args.positional;
590
+
591
+ if (subcommand === "init") {
592
+ const brief = parseRequiredStringFlag(
593
+ args.flags.brief,
594
+ "brief",
595
+ 'agentkit spec init --brief "agent for a real estate brokerage"',
596
+ );
597
+ const out = parseOptionalStringFlag(args.flags.out, "out", "agentkit spec init --out <path>");
598
+ const result = await initializeAgentSpec(process.cwd(), {
599
+ brief,
600
+ out,
601
+ force: args.flags.force === true,
602
+ });
603
+
604
+ console.log("Agent spec created");
605
+ console.log(`File: ${relativePath(result.root, result.file)}`);
606
+ return;
607
+ }
608
+
609
+ if (subcommand === "check") {
610
+ const file = parseOptionalStringFlag(args.flags.file, "file", "agentkit spec check --file <path>");
611
+ const result = await checkAgentSpec(process.cwd(), file);
612
+
613
+ if (result.ok) {
614
+ console.log(`Agent spec ok: ${relativePath(result.root, result.file)}`);
615
+ return;
616
+ }
617
+
618
+ console.log(`Agent spec incomplete: ${relativePath(result.root, result.file)}`);
619
+ for (const heading of result.missingHeadings) {
620
+ console.log(` - missing ## ${heading}`);
621
+ }
622
+ process.exitCode = 1;
623
+ return;
624
+ }
625
+
626
+ throw new Error('Usage: agentkit spec init --brief "<owner request>" [--out <path>] [--force] | agentkit spec check [--file <path>]');
337
627
  }
338
628
 
339
629
  if (args.command === "inspect") {
@@ -351,8 +641,27 @@ async function main() {
351
641
 
352
642
  if (args.command === "login") {
353
643
  const token = parseRequiredStringFlag(args.flags.token, "token", "agentkit login --token <token> [--api <url>]");
354
- await writeCloudAuth({ apiUrl: parseCloudApiUrl(args.flags.api), token });
355
- console.log("AgentKit Cloud token stored.");
644
+ const apiUrl = parseCloudApiUrl(args.flags.api);
645
+ const verified = await verifyCloudLogin(apiUrl, token);
646
+ await writeCloudAuth({ apiUrl, token });
647
+ const email = verified.me.account?.email;
648
+ console.log(email ? `Logged in to AgentKit Cloud as ${email}.` : "AgentKit Cloud token verified and stored.");
649
+
650
+ const hasHostedDeploy = (verified.capabilities.capabilities ?? []).some(
651
+ (capability) =>
652
+ (capability.name === "cloudflare_deploy_alpha" || capability.name === hostedDeployEntitlement) &&
653
+ capability.status === "active",
654
+ );
655
+ console.log(
656
+ hasHostedDeploy
657
+ ? "Entitlement: hosted deploy active."
658
+ : `Warning: cloudflare_deploy_alpha or ${hostedDeployEntitlement} entitlement was not found for this account.`,
659
+ );
660
+
661
+ if (process.env.AGENTKIT_CLOUD_API_TOKEN && process.env.AGENTKIT_CLOUD_API_TOKEN !== token) {
662
+ console.log("Warning: AGENTKIT_CLOUD_API_TOKEN is set and will override this saved login in the current shell.");
663
+ }
664
+
356
665
  return;
357
666
  }
358
667
 
@@ -362,94 +671,317 @@ async function main() {
362
671
  return;
363
672
  }
364
673
 
365
- if (args.command === "deploy") {
366
- const [deploySubcommand] = args.positional;
674
+ if (args.command === "account") {
675
+ const [noun, verb, value] = args.positional;
367
676
 
368
- if (deploySubcommand === "doctor") {
369
- const report = await checkDeployReadiness({
370
- apiUrl: await resolveCloudApiUrl(args.flags.api),
371
- requireLogin: !args.flags.anonymous,
372
- });
373
- printDeployReadinessReport(report);
677
+ if (noun !== "token") {
678
+ throw new Error("Usage: agentkit account token create <name> [--use] [--out <path>] | list | revoke <token-id> [--api <url>]");
679
+ }
374
680
 
375
- if (!report.ok) {
376
- process.exitCode = 1;
377
- }
681
+ const apiUrl = await resolveCloudApiUrl(args.flags.api);
378
682
 
379
- return;
380
- }
683
+ if (verb === "create" && value) {
684
+ const payload = (await cloudApiRequest(apiUrl, "/v1/account/tokens", {
685
+ method: "POST",
686
+ body: JSON.stringify({ name: value }),
687
+ })) as AccountApiTokenCreateResponse;
688
+ const token = payload.token;
381
689
 
382
- if (deploySubcommand === "status" || deploySubcommand === "pause" || deploySubcommand === "resume") {
383
- const state = await requireLocalDeployState();
384
- const apiUrl = await resolveCloudApiUrl(args.flags.api);
690
+ if (!token?.token || !token.id) {
691
+ throw new AgentKitError("account_token_invalid", "AgentKit Cloud returned an invalid account token payload.");
692
+ }
385
693
 
386
- if (deploySubcommand === "status") {
387
- const payload = await cloudApiRequest(apiUrl, `/v1/deploys/${encodeURIComponent(state.deploy_id)}`, {
388
- method: "GET",
389
- });
694
+ const outPath = parseOptionalStringFlag(
695
+ args.flags.out,
696
+ "out",
697
+ "agentkit account token create <name> [--api <url>] [--use] [--out <path>]",
698
+ );
699
+
700
+ if (outPath) {
701
+ const absolutePath = resolve(process.cwd(), outPath);
702
+ await writeSecretJson(absolutePath, payload);
703
+ console.log(`Account token written to ${absolutePath}`);
704
+ } else {
390
705
  printJson(payload);
391
- return;
392
706
  }
393
707
 
394
- const payload = await cloudApiRequest(apiUrl, `/v1/deploys/${encodeURIComponent(state.deploy_id)}/${deploySubcommand}`, {
708
+ if (args.flags.use === true) {
709
+ await writeCloudAuth({ apiUrl, token: token.token });
710
+ console.log(`Saved ${token.id} as the current AgentKit Cloud login.`);
711
+ }
712
+
713
+ return;
714
+ }
715
+
716
+ if (verb === "list" && !value) {
717
+ const payload = (await cloudApiRequest(apiUrl, "/v1/account/tokens", {
718
+ method: "GET",
719
+ })) as AccountApiTokenListResponse;
720
+ printJson(payload);
721
+ return;
722
+ }
723
+
724
+ if (verb === "revoke" && value) {
725
+ const payload = await cloudApiRequest(apiUrl, `/v1/account/tokens/${encodeURIComponent(value)}/revoke`, {
395
726
  method: "POST",
396
727
  });
397
728
  printJson(payload);
398
729
  return;
399
730
  }
400
731
 
401
- const target = resolveDeployTarget(parseTargetFlag(args.flags.target, "deploy"));
402
- const anonymous = Boolean(args.flags.anonymous);
732
+ throw new Error("Usage: agentkit account token create <name> [--use] [--out <path>] | list | revoke <token-id> [--api <url>]");
733
+ }
403
734
 
404
- if (target.id === "vps") {
405
- const host = parseRequiredStringFlag(args.flags.host, "host", "agentkit deploy --target vps --host <host>");
406
- const container = await buildAgentCapsule(process.cwd(), { target: "container" });
407
- const plan = await prepareVpsDeploy(container, { host, dryRun: Boolean(args.flags["dry-run"]) });
408
- printBuildResult(container);
409
- printVpsDeployPlan(plan);
410
- return;
411
- }
735
+ if (args.command === "billing") {
736
+ const [subcommand, intentId] = args.positional;
737
+ const apiUrl = await resolveCloudApiUrl(args.flags.api);
412
738
 
413
- if (!args.flags["dry-run"] && !args.flags["local-wrangler"]) {
414
- const report = await checkDeployReadiness({
415
- apiUrl: await resolveCloudApiUrl(args.flags.api),
416
- requireLogin: !anonymous,
417
- });
739
+ if (subcommand === "checkout") {
740
+ const slots = parsePositiveIntegerFlag(
741
+ args.flags.slots,
742
+ "slots",
743
+ "agentkit billing checkout --slots <count> [--email <email>] [--api <url>]",
744
+ );
745
+ const email = parseOptionalStringFlag(
746
+ args.flags.email,
747
+ "email",
748
+ "agentkit billing checkout --slots <count> --email <email>",
749
+ );
750
+ const payload = (await cloudApiRequest(apiUrl, "/v1/billing/checkout", {
751
+ method: "POST",
752
+ body: JSON.stringify({
753
+ slots,
754
+ ...(email ? { email } : {}),
755
+ }),
756
+ })) as BillingCheckoutResponse;
757
+ const checkoutUrl = payload.checkout?.url;
758
+ const billingIntentId = payload.billing_intent?.id;
759
+ const secret = payload.billing_intent?.secret;
760
+
761
+ if (!checkoutUrl || !billingIntentId || !secret) {
762
+ throw new AgentKitError("billing_checkout_invalid", "AgentKit Cloud returned an invalid billing checkout payload.");
763
+ }
418
764
 
419
- if (!report.ok) {
420
- printDeployReadinessReport(report);
421
- throw new AgentKitError(
422
- "deploy_readiness_failed",
423
- "Fix deploy readiness failures before running agentkit deploy.",
424
- );
765
+ if (args.flags.json === true) {
766
+ printJson(payload);
767
+ } else {
768
+ console.log("AgentKit Cloud checkout");
769
+ console.log("");
770
+ console.log(`URL: ${checkoutUrl}`);
771
+ console.log(`Intent: ${billingIntentId}`);
772
+ console.log(`Secret: ${secret}`);
773
+ console.log("");
774
+ console.log(`After payment: agentkit billing claim ${billingIntentId} --secret ${secret} --api ${apiUrl}`);
425
775
  }
426
- }
427
776
 
428
- const result = await buildAgentCapsule(process.cwd(), { target: target.id });
429
- if (result.target !== "cloudflare") {
430
- throw new Error(`Unsupported AgentKit Cloud deploy target: ${result.target}`);
431
- }
432
- printBuildResult(result);
777
+ if (args.flags.open === true) {
778
+ await openLocalUrl(checkoutUrl);
779
+ }
433
780
 
434
- if (args.flags["dry-run"]) {
435
- console.log("");
436
- console.log("Dry run only. Artifact is ready for AgentKit Cloud.");
437
781
  return;
438
782
  }
439
783
 
440
- if (args.flags["local-wrangler"]) {
441
- await deployCloudflare(result.wranglerPath, result.buildDir);
784
+ if (subcommand === "status" && intentId) {
785
+ const secret = parseRequiredStringFlag(
786
+ args.flags.secret,
787
+ "secret",
788
+ "agentkit billing status <billing-intent-id> --secret <secret> [--api <url>]",
789
+ );
790
+ const payload = (await cloudApiRequest(
791
+ apiUrl,
792
+ `/v1/billing/checkout-intents/${encodeURIComponent(intentId)}?secret=${encodeURIComponent(secret)}`,
793
+ { method: "GET" },
794
+ )) as BillingCheckoutIntentResponse;
795
+ printJson(payload);
442
796
  return;
443
797
  }
444
798
 
445
- const apiUrl = await resolveCloudApiUrl(args.flags.api);
446
- const auth = await readCloudAuth();
447
- const deploy = await deployBuildToAgentKitCloud(result, {
448
- apiUrl,
449
- apiToken: auth?.token,
450
- anonymous: anonymous || !auth?.token,
451
- });
799
+ if (subcommand === "claim" && intentId) {
800
+ const secret = parseRequiredStringFlag(
801
+ args.flags.secret,
802
+ "secret",
803
+ "agentkit billing claim <billing-intent-id> --secret <secret> [--token-name <name>] [--api <url>]",
804
+ );
805
+ const tokenName = parseOptionalStringFlag(
806
+ args.flags["token-name"],
807
+ "token-name",
808
+ "agentkit billing claim <billing-intent-id> --secret <secret> --token-name <name>",
809
+ );
810
+ const payload = (await cloudApiRequest(apiUrl, `/v1/billing/checkout-intents/${encodeURIComponent(intentId)}/claim`, {
811
+ method: "POST",
812
+ body: JSON.stringify({
813
+ secret,
814
+ ...(tokenName ? { token_name: tokenName } : {}),
815
+ }),
816
+ })) as BillingClaimResponse;
817
+ const token = payload.token;
818
+
819
+ if (!token?.token || !token.id) {
820
+ throw new AgentKitError("billing_claim_invalid", "AgentKit Cloud returned an invalid billing claim payload.");
821
+ }
822
+
823
+ await writeCloudAuth({ apiUrl, token: token.token });
824
+
825
+ const outPath = parseOptionalStringFlag(
826
+ args.flags.out,
827
+ "out",
828
+ "agentkit billing claim <billing-intent-id> --secret <secret> [--out <path>]",
829
+ );
830
+
831
+ if (outPath) {
832
+ const absolutePath = resolve(process.cwd(), outPath);
833
+ await writeSecretJson(absolutePath, payload);
834
+ console.log(`Account token written to ${absolutePath}`);
835
+ } else if (args.flags.json === true) {
836
+ printJson(payload);
837
+ } else {
838
+ console.log("AgentKit Cloud account token claimed and saved.");
839
+ console.log(`Token: ${token.id}`);
840
+ console.log(`API: ${apiUrl}`);
841
+ }
842
+
843
+ return;
844
+ }
845
+
846
+ if (subcommand === "portal") {
847
+ const payload = (await cloudApiRequest(apiUrl, "/v1/billing/portal", {
848
+ method: "POST",
849
+ body: JSON.stringify({}),
850
+ })) as BillingPortalResponse;
851
+ const portalUrl = payload.portal?.url;
852
+
853
+ if (!portalUrl) {
854
+ throw new AgentKitError("billing_portal_invalid", "AgentKit Cloud returned an invalid billing portal payload.");
855
+ }
856
+
857
+ if (args.flags.json === true) {
858
+ printJson(payload);
859
+ } else {
860
+ console.log("AgentKit Cloud billing portal");
861
+ console.log(`URL: ${portalUrl}`);
862
+ }
863
+
864
+ if (args.flags.open === true) {
865
+ await openLocalUrl(portalUrl);
866
+ }
867
+
868
+ return;
869
+ }
870
+
871
+ throw new Error(
872
+ "Usage: agentkit billing checkout --slots <count> [--email <email>] | status <intent-id> --secret <secret> | claim <intent-id> --secret <secret> | portal [--api <url>]",
873
+ );
874
+ }
875
+
876
+ if (args.command === "deploy") {
877
+ const [deploySubcommand] = args.positional;
878
+
879
+ if (deploySubcommand === "smoke") {
880
+ const message =
881
+ parseOptionalStringFlag(args.flags.message ?? args.flags.smoke, "message", 'agentkit deploy smoke --message "hello"') ??
882
+ "hello";
883
+ const state = await requireLocalDeployState();
884
+ const token = await readDeployAccessTokenForHostedRequest(process.cwd());
885
+ await runDeploySmoke({
886
+ deployUrl: requireDeployUrl(state),
887
+ message,
888
+ token,
889
+ });
890
+ return;
891
+ }
892
+
893
+ if (deploySubcommand === "doctor") {
894
+ const report = await checkDeployReadiness({
895
+ apiUrl: await resolveCloudApiUrl(args.flags.api),
896
+ requireLogin: !args.flags.anonymous,
897
+ });
898
+ printDeployReadinessReport(report);
899
+
900
+ if (!report.ok) {
901
+ process.exitCode = 1;
902
+ }
903
+
904
+ return;
905
+ }
906
+
907
+ if (deploySubcommand === "status" || deploySubcommand === "pause" || deploySubcommand === "resume") {
908
+ const state = await requireLocalDeployState();
909
+ const apiUrl = await resolveCloudApiUrl(args.flags.api);
910
+
911
+ if (deploySubcommand === "status") {
912
+ const payload = await cloudApiRequest(apiUrl, `/v1/deploys/${encodeURIComponent(state.deploy_id)}`, {
913
+ method: "GET",
914
+ });
915
+ printJson(payload);
916
+ return;
917
+ }
918
+
919
+ const payload = await cloudApiRequest(apiUrl, `/v1/deploys/${encodeURIComponent(state.deploy_id)}/${deploySubcommand}`, {
920
+ method: "POST",
921
+ });
922
+ printJson(payload);
923
+ return;
924
+ }
925
+
926
+ const target = resolveDeployTarget(parseTargetFlag(args.flags.target, "deploy"));
927
+ const anonymous = Boolean(args.flags.anonymous);
928
+ const smokeMessage = parseOptionalStringFlag(args.flags.smoke, "smoke", 'agentkit deploy --smoke "hello"');
929
+
930
+ if (target.id === "vps") {
931
+ const host = parseRequiredStringFlag(args.flags.host, "host", "agentkit deploy --target vps --host <host>");
932
+ const container = await buildAgentCapsule(process.cwd(), { target: "container" });
933
+ const plan = await prepareVpsDeploy(container, { host, dryRun: Boolean(args.flags["dry-run"]) });
934
+ printBuildResult(container);
935
+ printVpsDeployPlan(plan);
936
+ return;
937
+ }
938
+
939
+ if (!args.flags["dry-run"] && !args.flags["local-wrangler"]) {
940
+ const report = await checkDeployReadiness({
941
+ apiUrl: await resolveCloudApiUrl(args.flags.api),
942
+ requireLogin: !anonymous,
943
+ });
944
+
945
+ if (!report.ok) {
946
+ printDeployReadinessReport(report);
947
+ throw new AgentKitError(
948
+ "deploy_readiness_failed",
949
+ "Fix deploy readiness failures before running agentkit deploy.",
950
+ );
951
+ }
952
+ }
953
+
954
+ const result = await buildAgentCapsule(process.cwd(), { target: target.id });
955
+ if (result.target !== "cloudflare") {
956
+ throw new Error(`Unsupported AgentKit Cloud deploy target: ${result.target}`);
957
+ }
958
+ printBuildResult(result);
959
+
960
+ if (args.flags["dry-run"]) {
961
+ console.log("");
962
+ console.log("Dry run only. Artifact is ready for AgentKit Cloud.");
963
+ return;
964
+ }
965
+
966
+ if (args.flags["local-wrangler"]) {
967
+ await deployCloudflare(result.wranglerPath, result.buildDir);
968
+ return;
969
+ }
970
+
971
+ const apiUrl = await resolveCloudApiUrl(args.flags.api);
972
+ const auth = anonymous ? null : await readCloudAuthForApiUrl(apiUrl);
973
+ const deploy = await deployBuildToAgentKitCloud(result, {
974
+ apiUrl,
975
+ apiToken: anonymous ? null : auth?.token,
976
+ anonymous: anonymous || !auth?.token,
977
+ });
452
978
  const statePath = await writeLocalDeployState(result.root, deploy);
979
+ const chatAccess = await ensureDeployChatAccessToken({
980
+ apiUrl,
981
+ root: result.root,
982
+ deployId: deploy.deploy.id,
983
+ skip: anonymous || !auth?.token,
984
+ });
453
985
 
454
986
  console.log("");
455
987
  console.log("Deploy ready");
@@ -457,13 +989,40 @@ async function main() {
457
989
  console.log(`Project: ${deploy.project.id}`);
458
990
  console.log(`Deploy: ${deploy.deploy.id}`);
459
991
  console.log(`State: ${statePath}`);
992
+
993
+ if (chatAccess?.status === "ready") {
994
+ console.log(`Chat auth: ${relativePath(process.cwd(), chatAccess.path)}`);
995
+ } else if (chatAccess?.status === "skipped") {
996
+ console.log("Chat auth: anonymous deploy; no local chat token was created.");
997
+ } else if (chatAccess?.status === "failed") {
998
+ console.log(`Chat auth: could not create local chat token. ${formatReadinessError(chatAccess.error)}`);
999
+ console.log(`Run: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`);
1000
+ }
1001
+
1002
+ if (smokeMessage) {
1003
+ if (chatAccess?.status === "failed") {
1004
+ throw new AgentKitError("deploy_smoke_failed", "Could not run deploy smoke because chat access token creation failed.");
1005
+ }
1006
+
1007
+ await runDeploySmoke({
1008
+ deployUrl: requireDeployUrl({
1009
+ deploy_id: deploy.deploy.id,
1010
+ url: deploy.deploy.url,
1011
+ }),
1012
+ message: smokeMessage,
1013
+ token:
1014
+ chatAccess?.status === "ready"
1015
+ ? await readDeployAccessTokenForUi(chatAccess.path)
1016
+ : undefined,
1017
+ });
1018
+ }
460
1019
  return;
461
1020
  }
462
1021
 
463
1022
  if (args.command === "secret") {
464
1023
  const [subcommand, name, ...valueParts] = args.positional;
465
- const projectId = await resolveProjectIdForSecretCommand(process.cwd());
466
1024
  const apiUrl = await resolveCloudApiUrl(args.flags.api);
1025
+ const projectId = await resolveProjectIdForSecretCommand(process.cwd(), apiUrl);
467
1026
 
468
1027
  if (subcommand === "list") {
469
1028
  const payload = await cloudApiRequest(apiUrl, `/v1/projects/${encodeURIComponent(projectId)}/secrets`, {
@@ -474,10 +1033,19 @@ async function main() {
474
1033
  }
475
1034
 
476
1035
  if (subcommand === "set") {
477
- const value = valueParts.join(" ");
1036
+ const value = await resolveSecretInputValue({
1037
+ name,
1038
+ valueParts,
1039
+ stdinFlag: args.flags.stdin,
1040
+ fromEnvFlag: args.flags["from-env"],
1041
+ fromLocalEnvFlag: args.flags["from-local-env"],
1042
+ localEnvRoot: args.flags["from-local-env"] !== undefined ? await findAgentCapsuleRoot(process.cwd()) : undefined,
1043
+ usage: "agentkit secret set <NAME> <VALUE> | agentkit secret set <NAME> --stdin | agentkit secret set <NAME> --from-env [ENV_NAME] | agentkit secret set <NAME> --from-local-env [--api <url>]",
1044
+ inlineSensitiveHelp: "Prefer --stdin, --from-env, or --from-local-env so the value does not enter shell history.",
1045
+ });
478
1046
 
479
- if (!name || value.length === 0) {
480
- throw new Error("Usage: agentkit secret set <NAME> <VALUE> [--api <url>]");
1047
+ if (!name) {
1048
+ throw new Error("Usage: agentkit secret set <NAME> <VALUE> | agentkit secret set <NAME> --stdin | agentkit secret set <NAME> --from-env [ENV_NAME] | agentkit secret set <NAME> --from-local-env [--api <url>]");
481
1049
  }
482
1050
 
483
1051
  const payload = await cloudApiRequest(apiUrl, `/v1/projects/${encodeURIComponent(projectId)}/secrets/${encodeURIComponent(name)}`, {
@@ -488,6 +1056,15 @@ async function main() {
488
1056
  return;
489
1057
  }
490
1058
 
1059
+ if (subcommand === "sync") {
1060
+ if (args.flags["from-local"] !== true) {
1061
+ throw new Error("Usage: agentkit secret sync --from-local [--api <url>]");
1062
+ }
1063
+
1064
+ await syncHostedSecretsFromLocal({ apiUrl, projectId });
1065
+ return;
1066
+ }
1067
+
491
1068
  if (subcommand === "unset") {
492
1069
  if (!name) {
493
1070
  throw new Error("Usage: agentkit secret unset <NAME> [--api <url>]");
@@ -500,14 +1077,14 @@ async function main() {
500
1077
  return;
501
1078
  }
502
1079
 
503
- throw new Error("Usage: agentkit secret set <NAME> <VALUE> | agentkit secret list | agentkit secret unset <NAME>");
1080
+ throw new Error("Usage: agentkit secret set <NAME> <VALUE> | agentkit secret set <NAME> --stdin | agentkit secret set <NAME> --from-env [ENV_NAME] | agentkit secret set <NAME> --from-local-env | agentkit secret sync --from-local | agentkit secret list | agentkit secret unset <NAME>");
504
1081
  }
505
1082
 
506
1083
  if (args.command === "access") {
507
1084
  const [subcommand, noun, name] = args.positional;
508
1085
 
509
1086
  if (subcommand !== "token") {
510
- throw new Error("Usage: agentkit access token create <name> | list | revoke <token-id> [--api <url>]");
1087
+ throw new Error("Usage: agentkit access token create <name> [--out <path>] | list | revoke <token-id> [--api <url>]");
511
1088
  }
512
1089
 
513
1090
  const state = await requireLocalDeployState();
@@ -518,7 +1095,7 @@ async function main() {
518
1095
  method: "POST",
519
1096
  body: JSON.stringify({ name }),
520
1097
  });
521
- printJson(payload);
1098
+ await printJsonOrWrite(payload, args.flags.out, "agentkit access token create <name> [--api <url>] [--out <path>]");
522
1099
  return;
523
1100
  }
524
1101
 
@@ -540,7 +1117,7 @@ async function main() {
540
1117
  return;
541
1118
  }
542
1119
 
543
- throw new Error("Usage: agentkit access token create <name> | list | revoke <token-id> [--api <url>]");
1120
+ throw new Error("Usage: agentkit access token create <name> [--out <path>] | list | revoke <token-id> [--api <url>]");
544
1121
  }
545
1122
 
546
1123
  if (args.command === "channels") {
@@ -548,6 +1125,121 @@ async function main() {
548
1125
  return;
549
1126
  }
550
1127
 
1128
+ if (args.command === "transcribe") {
1129
+ await handleTranscribeCommand(args);
1130
+ return;
1131
+ }
1132
+
1133
+ if (args.command === "skills") {
1134
+ const [subcommand] = args.positional;
1135
+ const root = await findAgentCapsuleRoot(process.cwd());
1136
+
1137
+ if (subcommand === "status") {
1138
+ const status = await checkDefaultSkillStatus(root);
1139
+ printSkillStatus(status);
1140
+ if (status.status !== "current") {
1141
+ process.exitCode = 1;
1142
+ }
1143
+ return;
1144
+ }
1145
+
1146
+ if (subcommand === "sync") {
1147
+ const result = await syncDefaultSkillFiles(root);
1148
+ console.log(`Synced ${result.files.length} AgentKit skill files.`);
1149
+ console.log("Status: current");
1150
+ return;
1151
+ }
1152
+
1153
+ throw new Error("Usage: agentkit skills status | agentkit skills sync");
1154
+ }
1155
+
1156
+ if (args.command === "integrations") {
1157
+ const [subcommand, provider] = args.positional;
1158
+
1159
+ if (provider !== undefined && provider !== "composio") {
1160
+ throw new Error("Usage: agentkit integrations status | connect composio [--toolkit <slug>] [--callback-url <url>] [--api <url>]");
1161
+ }
1162
+
1163
+ const state = await requireLocalDeployState();
1164
+ const apiUrl = await resolveCloudApiUrl(args.flags.api);
1165
+
1166
+ if (subcommand === "status") {
1167
+ const toolkit = parseOptionalStringFlag(
1168
+ args.flags.toolkit,
1169
+ "toolkit",
1170
+ "agentkit integrations status --toolkit <slug>",
1171
+ );
1172
+ const payload = await cloudApiRequest(
1173
+ apiUrl,
1174
+ `/v1/deploys/${encodeURIComponent(state.deploy_id)}/integrations/composio/status${
1175
+ toolkit ? `?toolkit=${encodeURIComponent(toolkit)}` : ""
1176
+ }`,
1177
+ { method: "GET" },
1178
+ );
1179
+ printJson(payload);
1180
+ return;
1181
+ }
1182
+
1183
+ if (subcommand === "connect" && provider === "composio") {
1184
+ const toolkit = parseOptionalStringFlag(
1185
+ args.flags.toolkit,
1186
+ "toolkit",
1187
+ "agentkit integrations connect composio --toolkit <slug>",
1188
+ );
1189
+ const callbackUrl = parseOptionalStringFlag(
1190
+ args.flags["callback-url"],
1191
+ "callback-url",
1192
+ "agentkit integrations connect composio --callback-url <url>",
1193
+ );
1194
+ const payload = (await cloudApiRequest(
1195
+ apiUrl,
1196
+ `/v1/deploys/${encodeURIComponent(state.deploy_id)}/integrations/composio/connect-link`,
1197
+ {
1198
+ method: "POST",
1199
+ body: JSON.stringify({
1200
+ ...(toolkit ? { toolkit } : {}),
1201
+ ...(callbackUrl ? { callback_url: callbackUrl } : {}),
1202
+ }),
1203
+ },
1204
+ )) as {
1205
+ connect_link?: {
1206
+ toolkit?: string;
1207
+ user_id?: string;
1208
+ redirect_url?: string;
1209
+ expires_at?: string;
1210
+ connected_account_id?: string;
1211
+ auth_config_source?: string;
1212
+ };
1213
+ };
1214
+
1215
+ const link = payload.connect_link;
1216
+
1217
+ if (!link?.redirect_url) {
1218
+ throw new AgentKitError("integration_response_invalid", "AgentKit Cloud returned an invalid Composio connect-link payload.");
1219
+ }
1220
+
1221
+ console.log("Composio connect link");
1222
+ console.log(`Toolkit: ${link.toolkit ?? toolkit ?? "unknown"}`);
1223
+ console.log(`User: ${link.user_id ?? "unknown"}`);
1224
+ if (link.auth_config_source) {
1225
+ console.log(`Auth: ${link.auth_config_source}`);
1226
+ }
1227
+ console.log(`URL: ${link.redirect_url}`);
1228
+
1229
+ if (link.expires_at) {
1230
+ console.log(`Expires: ${link.expires_at}`);
1231
+ }
1232
+
1233
+ if (link.connected_account_id) {
1234
+ console.log(`Account: ${link.connected_account_id}`);
1235
+ }
1236
+
1237
+ return;
1238
+ }
1239
+
1240
+ throw new Error("Usage: agentkit integrations status | connect composio [--toolkit <slug>] [--callback-url <url>] [--api <url>]");
1241
+ }
1242
+
551
1243
  if (args.command === "env") {
552
1244
  const [subcommand, name, ...valueParts] = args.positional;
553
1245
  const root = await findAgentCapsuleRoot(process.cwd());
@@ -568,10 +1260,18 @@ async function main() {
568
1260
  }
569
1261
 
570
1262
  if (subcommand === "set") {
571
- const value = valueParts.join(" ");
1263
+ const value = await resolveSecretInputValue({
1264
+ name,
1265
+ valueParts,
1266
+ stdinFlag: args.flags.stdin,
1267
+ fromEnvFlag: args.flags["from-env"],
1268
+ fromLocalEnvFlag: undefined,
1269
+ usage: "agentkit env set <NAME> <VALUE> | agentkit env set <NAME> --stdin | agentkit env set <NAME> --from-env [ENV_NAME]",
1270
+ inlineSensitiveHelp: "Prefer --stdin or --from-env so the value does not enter shell history.",
1271
+ });
572
1272
 
573
- if (!name || value.length === 0) {
574
- throw new Error("Usage: agentkit env set <NAME> <VALUE>");
1273
+ if (!name) {
1274
+ throw new Error("Usage: agentkit env set <NAME> <VALUE> | agentkit env set <NAME> --stdin | agentkit env set <NAME> --from-env [ENV_NAME]");
575
1275
  }
576
1276
 
577
1277
  await setCapsuleEnv(root, name, value);
@@ -589,7 +1289,7 @@ async function main() {
589
1289
  return;
590
1290
  }
591
1291
 
592
- throw new Error("Usage: agentkit env list | agentkit env set <NAME> <VALUE> | agentkit env unset <NAME>");
1292
+ throw new Error("Usage: agentkit env list | agentkit env set <NAME> <VALUE> | agentkit env set <NAME> --stdin | agentkit env set <NAME> --from-env [ENV_NAME] | agentkit env unset <NAME>");
593
1293
  }
594
1294
 
595
1295
  if (args.command === "docs") {
@@ -623,15 +1323,17 @@ async function main() {
623
1323
 
624
1324
  const root = await findAgentCapsuleRoot(process.cwd());
625
1325
  const docs = findAgentKitDocs();
626
- console.log(renderHandoffPrompt({ target, root, docsFull: docs.llmsFull, goal: goalParts.join(" ").trim() }));
1326
+ console.log(renderHandoffPrompt({ target, root, docsLlms: docs.llms, goal: goalParts.join(" ").trim() }));
627
1327
  return;
628
1328
  }
629
1329
 
630
1330
  throw new Error(`Unknown command: ${args.command}`);
631
1331
  }
632
1332
 
633
- function renderHandoffPrompt(input: { target: string; root: string; docsFull: string; goal: string }): string {
1333
+ function renderHandoffPrompt(input: { target: string; root: string; docsLlms: string; goal: string }): string {
634
1334
  const agentName = input.target === "claude" ? "Claude Code" : "Codex";
1335
+ const skillRouter = join(input.root, "skills/agentkit-capsule/SKILL.md");
1336
+ const skillLine = existsSync(skillRouter) ? `- ${skillRouter}\n` : "";
635
1337
  const goal = input.goal
636
1338
  ? `Owner request:
637
1339
  ${input.goal}`
@@ -645,17 +1347,22 @@ ${input.root}
645
1347
  Read these files first:
646
1348
 
647
1349
  - ${join(input.root, "AGENTKIT.md")}
648
- - ${input.docsFull}
1350
+ ${skillLine}- ${input.docsLlms}
649
1351
 
650
1352
  ${goal}
651
1353
 
652
1354
  Rules:
653
- - Follow the AgentKit contract in llms-full.txt.
1355
+ - Start with the AgentKit capsule skill when it exists.
1356
+ - Use llms.txt as the docs router.
1357
+ - Read llms-full.txt only when a skill or ambiguous framework behavior requires the complete contract.
654
1358
  - Treat the owner's request as the brief.
655
1359
  - Build or update the agent inside this capsule as far as you can without asking for obvious restatements.
1360
+ - Do not wait for a wizard or recipe; implement directly in the capsule files.
656
1361
  - Edit agent behavior in prompts/instructions.md.
657
1362
  - Edit the agent contract in agentkit.config.ts.
658
1363
  - Add TypeScript tools under tools/.
1364
+ - Keep the first useful version runnable with test/fake unless the owner has already chosen a real provider.
1365
+ - Before testing real conversation behavior, ask the owner to choose OpenRouter, OpenAI, Anthropic, or another supported provider. Do not choose for them.
659
1366
  - Keep local secrets in .env only.
660
1367
  - Do not commit or print secret values.
661
1368
  - Do not edit .agentkit/ directly.
@@ -666,7 +1373,6 @@ Verification:
666
1373
  Run these before you finish:
667
1374
 
668
1375
  \`\`\`sh
669
- npm install
670
1376
  npm run typecheck
671
1377
  npm run agentkit -- inspect
672
1378
  npm run chat -- --message "hello"
@@ -678,883 +1384,282 @@ If you add a tool, also run a tool smoke test with the fake provider, for exampl
678
1384
  npm run agentkit -- tool tool_name --input '{}'
679
1385
  \`\`\`
680
1386
 
1387
+ UI testing:
1388
+ - For local UI testing, run \`npm run dev\`, open the printed \`Chat:\` URL, and tell the owner the exact URL.
1389
+ - For hosted UI testing after deploy, run \`npm run agentkit -- chat-ui --deploy\`, open the printed \`Chat:\` URL, and tell the owner it is connected to the hosted deploy.
1390
+ - If the provider is still \`test/fake\`, say the UI was tested only with the deterministic fake provider.
1391
+
681
1392
  Final response:
682
1393
  - Summarize what changed.
683
1394
  - List files changed.
684
1395
  - Paste the verification commands and results.
1396
+ - State whether testing used \`test/fake\` or a real provider selected by the owner.
685
1397
  - Call out any missing user details or blocked checks.
686
1398
  `;
687
1399
  }
688
1400
 
689
- type DeployReadinessStatus = "pass" | "fail" | "warn" | "info";
690
-
691
- type DeployReadinessCheck = {
692
- status: DeployReadinessStatus;
693
- title: string;
694
- message: string;
695
- action?: string;
696
- };
1401
+ function isRecord(value: unknown): value is Record<string, unknown> {
1402
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1403
+ }
697
1404
 
698
- type DeployReadinessReport = {
699
- ok: boolean;
700
- apiUrl: string;
701
- context: DeployReadinessContext;
702
- checks: DeployReadinessCheck[];
703
- };
704
-
705
- type CloudMeResponse = {
706
- account?: {
707
- email?: string;
708
- status?: string;
709
- };
710
- };
711
-
712
- type CloudCapabilitiesResponse = {
713
- capabilities?: Array<{
714
- name?: string;
715
- status?: string;
716
- }>;
717
- };
718
-
719
- type CloudSecretsResponse = {
720
- secrets?: Array<{
721
- name?: string;
722
- status?: string;
723
- updated_at?: string;
724
- }>;
725
- };
726
-
727
- async function checkDeployReadiness(options: {
1405
+ async function ensureDeployChatAccessToken(options: {
728
1406
  apiUrl: string;
729
- requireLogin: boolean;
730
- }): Promise<DeployReadinessReport> {
731
- const context = await loadDeployReadinessContext(process.cwd(), process.env);
732
- const auth = await readCloudAuth();
733
- const checks: DeployReadinessCheck[] = [];
734
- let cloudAuthUsable = false;
735
-
736
- if (context.runtime !== "edge") {
737
- checks.push({
738
- status: "fail",
739
- title: "Target runtime",
740
- message: 'Cloudflare deploy requires runtime: "edge" in agentkit.config.ts.',
741
- action: "set runtime: \"edge\" before deploying to AgentKit Cloud",
742
- });
743
- } else {
744
- checks.push({
745
- status: "pass",
746
- title: "Target runtime",
747
- message: "Capsule is configured for edge deploy.",
748
- });
1407
+ root: string;
1408
+ deployId: string;
1409
+ skip: boolean;
1410
+ }): Promise<
1411
+ | { status: "ready"; path: string; tokenId: string; created: boolean }
1412
+ | { status: "skipped" }
1413
+ | { status: "failed"; error: unknown }
1414
+ > {
1415
+ if (options.skip) {
1416
+ return { status: "skipped" };
749
1417
  }
750
1418
 
751
- if (!auth?.token) {
752
- checks.push({
753
- status: options.requireLogin ? "fail" : "warn",
754
- title: "Login",
755
- message: options.requireLogin
756
- ? "No AgentKit Cloud token was found for hosted production deploy."
757
- : "No AgentKit Cloud token was found; deploy will request anonymous mode.",
758
- action: "agentkit login --token <token>",
759
- });
760
- } else {
761
- try {
762
- const me = (await cloudApiRequest(options.apiUrl, "/v1/me", { method: "GET" })) as CloudMeResponse;
763
- const email = me.account?.email;
764
- checks.push({
765
- status: "pass",
766
- title: "Login",
767
- message: email ? `Authenticated as ${email}.` : "AgentKit Cloud token is accepted.",
768
- });
769
- cloudAuthUsable = true;
770
- } catch (error) {
771
- checks.push({
772
- status: "fail",
773
- title: "Login",
774
- message: `AgentKit Cloud rejected the stored token. ${formatReadinessError(error)}`,
775
- action: "agentkit login --token <token>",
776
- });
777
- }
778
- }
1419
+ try {
1420
+ const path = join(options.root, defaultDeployChatAccessTokenPath);
1421
+ const existing = await readDeployAccessTokenFile(path);
779
1422
 
780
- if (cloudAuthUsable) {
781
- try {
782
- const payload = (await cloudApiRequest(options.apiUrl, "/v1/me/capabilities", {
783
- method: "GET",
784
- })) as CloudCapabilitiesResponse;
785
- const capabilities = payload.capabilities ?? [];
786
- const hasCloudflareAlpha = capabilities.some(
787
- (capability) => capability.name === "cloudflare_deploy_alpha" && capability.status === "active",
1423
+ if (existing?.deploy_id === options.deployId && existing.id) {
1424
+ const listed = (await cloudApiRequest(
1425
+ options.apiUrl,
1426
+ `/v1/deploys/${encodeURIComponent(options.deployId)}/access-tokens`,
1427
+ { method: "GET" },
1428
+ )) as DeployAccessTokenListResponse;
1429
+ const stillUsable = (listed.access_tokens ?? []).some(
1430
+ (token) => token.id === existing.id && deployAccessTokenMetadataIsUsable(token),
788
1431
  );
789
1432
 
790
- checks.push({
791
- status: hasCloudflareAlpha ? "pass" : "fail",
792
- title: "Deploy entitlement",
793
- message: hasCloudflareAlpha
794
- ? "cloudflare_deploy_alpha is active."
795
- : "This account is missing the cloudflare_deploy_alpha entitlement.",
796
- ...(hasCloudflareAlpha ? {} : { action: "log in with an invited AgentKit Cloud alpha token" }),
797
- });
798
- } catch (error) {
799
- checks.push({
800
- status: "fail",
801
- title: "Deploy entitlement",
802
- message: `Could not verify AgentKit Cloud capabilities. ${formatReadinessError(error)}`,
803
- });
1433
+ if (stillUsable) {
1434
+ await chmod(path, 0o600);
1435
+ return { status: "ready", path, tokenId: existing.id, created: false };
1436
+ }
804
1437
  }
805
- }
806
-
807
- if (context.agentKitManagedSecrets.length > 0) {
808
- checks.push({
809
- status: "info",
810
- title: "AgentKit-managed secrets",
811
- message: `${context.agentKitManagedSecrets.join(", ")} ${
812
- context.agentKitManagedSecrets.length === 1 ? "is" : "are"
813
- } provisioned by AgentKit Cloud during deploy.`,
814
- });
815
- }
816
1438
 
817
- if (context.accessMode === "private") {
818
- checks.push({
819
- status: "info",
820
- title: "Private access",
821
- message: "AGENTKIT_PRIVATE_TOKEN is required at runtime; AgentKit Cloud generates it and saves it in .agentkit/deploy.json.",
822
- });
823
- }
1439
+ const created = (await cloudApiRequest(
1440
+ options.apiUrl,
1441
+ `/v1/deploys/${encodeURIComponent(options.deployId)}/access-tokens`,
1442
+ {
1443
+ method: "POST",
1444
+ body: JSON.stringify({ name: defaultDeployChatAccessTokenName }),
1445
+ },
1446
+ )) as DeployAccessTokenCreateResponse;
1447
+ const token = created.access_token;
824
1448
 
825
- if (context.userManagedSecrets.length === 0) {
826
- checks.push({
827
- status: "pass",
828
- title: "Hosted secrets",
829
- message: "No user-managed hosted secrets are required by this capsule.",
830
- });
831
- } else if (!cloudAuthUsable) {
832
- checks.push({
833
- status: options.requireLogin ? "fail" : "warn",
834
- title: "Hosted secrets",
835
- message: `Could not check hosted secrets without a valid AgentKit Cloud login. Required: ${context.userManagedSecrets.join(", ")}.`,
836
- action: "agentkit secret list",
837
- });
838
- } else {
839
- try {
840
- const payload = (await cloudApiRequest(
841
- options.apiUrl,
842
- `/v1/projects/${encodeURIComponent(context.projectId)}/secrets`,
843
- { method: "GET" },
844
- )) as CloudSecretsResponse;
845
- const hostedSecretNames = new Set((payload.secrets ?? []).map((secret) => secret.name).filter(isString));
846
- const missing = context.userManagedSecrets.filter((name) => !hostedSecretNames.has(name));
847
-
848
- if (missing.length === 0) {
849
- checks.push({
850
- status: "pass",
851
- title: "Hosted secrets",
852
- message: "All user-managed hosted secrets are set.",
853
- });
854
- } else {
855
- for (const secret of missing) {
856
- const localSource = context.localSecrets[secret];
857
- checks.push({
858
- status: "fail",
859
- title: `Hosted secret ${secret}`,
860
- message: localSource
861
- ? `${secret} is present locally but has not been set in AgentKit Cloud.`
862
- : `${secret} is required but missing from AgentKit Cloud.`,
863
- action: secretSetCommand(secret, localSource),
864
- });
865
- }
866
- }
867
- } catch (error) {
868
- checks.push({
869
- status: "fail",
870
- title: "Hosted secrets",
871
- message: `Could not list hosted secrets for project ${context.projectId}. ${formatReadinessError(error)}`,
872
- action: "agentkit secret list",
873
- });
1449
+ if (!token?.id || token.deploy_id !== options.deployId || !token.token) {
1450
+ throw new AgentKitError("access_token_invalid", "AgentKit Cloud returned an invalid deploy access token payload.");
874
1451
  }
875
- }
876
1452
 
877
- return {
878
- ok: checks.every((check) => check.status !== "fail"),
879
- apiUrl: options.apiUrl,
880
- context,
881
- checks,
882
- };
1453
+ await writeSecretJson(path, created);
1454
+ return { status: "ready", path, tokenId: token.id, created: true };
1455
+ } catch (error) {
1456
+ return { status: "failed", error };
1457
+ }
883
1458
  }
884
1459
 
885
- function printDeployReadinessReport(report: DeployReadinessReport): void {
886
- console.log("AgentKit deploy readiness");
887
- console.log(`Project: ${report.context.projectName}`);
888
- console.log(`Project ID: ${report.context.projectId}`);
889
- console.log(`Target: ${report.context.target}`);
890
- console.log(`API: ${report.apiUrl}`);
891
- console.log("");
1460
+ function deployAccessTokenMetadataIsUsable(token: { expires_at?: string }): boolean {
1461
+ if (!token.expires_at) {
1462
+ return true;
1463
+ }
892
1464
 
893
- for (const check of report.checks) {
894
- console.log(`[${check.status}] ${check.title}: ${check.message}`);
1465
+ const expiresAtMs = Date.parse(token.expires_at);
1466
+ return Number.isFinite(expiresAtMs) && expiresAtMs > Date.now();
1467
+ }
895
1468
 
896
- if (check.action) {
897
- console.log(` Run: ${check.action}`);
898
- }
1469
+ async function readDeployAccessTokenFile(path: string): Promise<{ id?: string; deploy_id?: string } | null> {
1470
+ try {
1471
+ const parsed = JSON.parse(await readFile(path, "utf8")) as DeployAccessTokenCreateResponse;
1472
+ return parsed.access_token ?? null;
1473
+ } catch {
1474
+ return null;
899
1475
  }
900
1476
  }
901
1477
 
902
- function secretSetCommand(secret: string, source: DeployReadinessContext["localSecrets"][string] | undefined): string {
903
- if (source?.dotenv) {
904
- return `agentkit secret set ${secret} <value-from-.env>`;
905
- }
1478
+ async function writeSecretJson(path: string, value: unknown): Promise<void> {
1479
+ await mkdir(dirname(path), { recursive: true });
1480
+ await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
1481
+ await chmod(path, 0o600);
1482
+ }
1483
+
1484
+ async function requireLocalDeployState(): Promise<LocalDeployState> {
1485
+ const state = await readLocalDeployStateIfExists(process.cwd());
906
1486
 
907
- if (source?.environment) {
908
- return `agentkit secret set ${secret} "$${secret}"`;
1487
+ if (!state) {
1488
+ throw new Error("No .agentkit/deploy.json found. Run agentkit deploy first.");
909
1489
  }
910
1490
 
911
- return `agentkit secret set ${secret} <value>`;
1491
+ return state;
912
1492
  }
913
1493
 
914
- function formatReadinessError(error: unknown): string {
915
- if (isAgentKitError(error)) {
916
- return `${error.code}: ${error.message}`;
1494
+ function requireDeployUrl(state: Pick<LocalDeployState, "deploy_id" | "url">): string {
1495
+ if (!state.url) {
1496
+ throw new Error(`Deploy ${state.deploy_id} does not have a URL. Run agentkit deploy again.`);
917
1497
  }
918
1498
 
919
- return error instanceof Error ? error.message : String(error);
1499
+ return state.url;
920
1500
  }
921
1501
 
922
- function isString(value: unknown): value is string {
923
- return typeof value === "string";
1502
+ async function readDeployAccessTokenForHostedRequest(cwd: string): Promise<string> {
1503
+ const root = await findAgentCapsuleRoot(cwd);
1504
+ return readDeployAccessTokenForUi(join(root, defaultDeployChatAccessTokenPath));
924
1505
  }
925
1506
 
926
- async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
927
- const [subcommand, first, second, third] = args.positional;
928
-
929
- if (subcommand === "list") {
930
- const deployState = await readDeployStateIfExists(process.cwd());
931
-
932
- if (deployState) {
933
- const channels = await cloudGet<{ channels: CloudChannel[] }>(
934
- parseCloudApiUrl(args.flags.api),
935
- `/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
936
- );
937
- printChannelsTable(channels.channels);
938
- return;
939
- }
1507
+ async function runDeploySmoke(input: { deployUrl: string; message: string; token?: string }): Promise<void> {
1508
+ const headers = new Headers({ "Content-Type": "application/json" });
940
1509
 
941
- const capsule = await loadAgentCapsule(process.cwd());
942
- printChannelsTable(
943
- (capsule.config.channels ?? []).map((channel) => ({
944
- id: "",
945
- name: channel.name,
946
- type: channel.type,
947
- provider: channel.provider,
948
- status: "local_config",
949
- webhook_url: "",
950
- required_secrets: channel.secrets.map((name) => ({ name, status: "missing" })),
951
- })),
952
- );
953
- return;
954
- }
955
-
956
- if (subcommand === "add") {
957
- const type = first;
958
- const name = second;
959
-
960
- if (!type || !name || (type !== "website" && type !== "telegram" && type !== "whatsapp")) {
961
- throw new Error("Usage: agentkit channels add <website|telegram|whatsapp> <name> [--provider zapster|meta]");
962
- }
963
-
964
- const deployState = await readDeployStateRequired(process.cwd());
965
- const provider = channelProviderForCli(type, args.flags.provider);
966
- const payload = {
967
- name,
968
- type,
969
- provider,
970
- secrets: defaultChannelSecretsForCli(type, provider),
971
- };
972
- const response = await cloudPost<{ channel: CloudChannel }>(
973
- parseCloudApiUrl(args.flags.api),
974
- `/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
975
- payload,
976
- );
977
- printChannelStatus(response.channel);
978
- return;
979
- }
980
-
981
- if (subcommand === "setup") {
982
- if (!first) {
983
- throw new Error("Usage: agentkit channels setup <name> [--apply]");
984
- }
985
-
986
- const channel = await resolveCloudChannelByName(first, args.flags.api);
987
- printChannelStatus(channel);
988
- if (channel.type === "telegram") {
989
- if (args.flags.apply) {
990
- await applyTelegramWebhook(channel);
991
- console.log("Telegram webhook updated.");
992
- } else {
993
- console.log(`Setup: call Telegram setWebhook with ${channel.webhook_url}`);
994
- console.log("Use --apply to mutate Telegram webhook settings.");
995
- }
996
- } else if (channel.type === "whatsapp") {
997
- console.log(`Next step: paste this URL into ${channel.provider}'s webhook settings.`);
998
- } else {
999
- console.log(`Website endpoint: ${channel.webhook_url}`);
1000
- console.log("Use this endpoint from the AgentKit website client or a signed server-to-server test.");
1001
- }
1002
- return;
1003
- }
1004
-
1005
- if (subcommand === "status") {
1006
- if (!first) {
1007
- throw new Error("Usage: agentkit channels status <name>");
1008
- }
1009
-
1010
- const channel = await resolveCloudChannelByName(first, args.flags.api);
1011
- printChannelStatus(channel);
1012
- await printChannelDeliverySummary(channel, args.flags.api);
1013
- return;
1014
- }
1015
-
1016
- if (subcommand === "test") {
1017
- if (!first) {
1018
- throw new Error('Usage: agentkit channels test <name> [--message "hello"] [--fixture <path>]');
1019
- }
1020
-
1021
- const channel = await resolveCloudChannelByName(first, args.flags.api);
1022
- const apiUrl = parseCloudApiUrl(args.flags.api);
1023
- const body = await channelTestBody(channel, args.flags.message, args.flags.fixture);
1024
- const path = new URL(channel.webhook_url).pathname;
1025
- const headers = channelTestHeaders(channel);
1026
- const response = await cloudFetch(apiUrl, path, {
1027
- method: channel.type === "whatsapp" && channel.provider === "meta" ? "POST" : "POST",
1028
- headers,
1029
- body,
1030
- });
1031
- const payload = await response.json();
1032
-
1033
- console.log(JSON.stringify(payload, null, 2));
1034
- if (!response.ok) {
1035
- process.exitCode = 1;
1036
- }
1037
- return;
1038
- }
1039
-
1040
- if (subcommand === "deliveries") {
1041
- if (first === "list") {
1042
- if (!second) {
1043
- throw new Error("Usage: agentkit channels deliveries list <name>");
1044
- }
1045
-
1046
- const channel = await resolveCloudChannelByName(second, args.flags.api);
1047
- const response = await cloudGet<{ deliveries: CloudDelivery[] }>(
1048
- parseCloudApiUrl(args.flags.api),
1049
- `/v1/channels/${encodeURIComponent(channel.id)}/deliveries`,
1050
- );
1051
-
1052
- if (response.deliveries.length === 0) {
1053
- console.log("No channel deliveries found.");
1054
- return;
1055
- }
1056
-
1057
- console.log("ID\tCHANNEL\tDIRECTION\tSTATUS\tPROVIDER_EVENT\tRECEIVED");
1058
- for (const delivery of response.deliveries) {
1059
- console.log(
1060
- [
1061
- delivery.id,
1062
- delivery.channel_id,
1063
- delivery.direction,
1064
- delivery.status,
1065
- delivery.provider.event_id ?? "",
1066
- delivery.received_at,
1067
- ].join("\t"),
1068
- );
1069
- }
1070
- return;
1071
- }
1072
-
1073
- if (first === "show") {
1074
- if (!second) {
1075
- throw new Error("Usage: agentkit channels deliveries show <delivery-id>");
1076
- }
1077
-
1078
- const response = await cloudGet<{ delivery: CloudDelivery }>(
1079
- parseCloudApiUrl(args.flags.api),
1080
- `/v1/channel-deliveries/${encodeURIComponent(second)}`,
1081
- );
1082
- console.log(JSON.stringify(response.delivery, null, 2));
1083
- return;
1084
- }
1085
- }
1086
-
1087
- throw new Error(
1088
- "Usage: agentkit channels list | add | setup | status | test | deliveries list | deliveries show",
1089
- );
1090
- }
1091
-
1092
- type DeployState = LocalDeployState;
1093
-
1094
- type CloudChannel = {
1095
- id: string;
1096
- name: string;
1097
- type: "website" | "telegram" | "whatsapp";
1098
- provider: "agentkit" | "telegram" | "zapster" | "meta";
1099
- status: string;
1100
- webhook_url: string;
1101
- required_secrets: Array<{ name: string; status: string }>;
1102
- };
1103
-
1104
- type CloudDelivery = {
1105
- id: string;
1106
- channel_id: string;
1107
- direction: string;
1108
- status: string;
1109
- provider: {
1110
- event_id?: string;
1111
- };
1112
- received_at: string;
1113
- };
1114
-
1115
- async function applyTelegramWebhook(channel: CloudChannel): Promise<void> {
1116
- const botToken = process.env.TELEGRAM_BOT_TOKEN;
1117
- const secretToken = process.env.TELEGRAM_WEBHOOK_SECRET;
1118
-
1119
- if (!botToken || !secretToken) {
1120
- throw new Error("TELEGRAM_BOT_TOKEN and TELEGRAM_WEBHOOK_SECRET must be set to apply Telegram setup.");
1510
+ if (input.token) {
1511
+ headers.set("Authorization", `Bearer ${input.token}`);
1121
1512
  }
1122
1513
 
1123
1514
  let response: Response;
1124
1515
 
1125
1516
  try {
1126
- response = await fetch(`https://api.telegram.org/bot${botToken}/setWebhook`, {
1517
+ response = await fetch(new URL("/v1/chat", input.deployUrl).href, {
1127
1518
  method: "POST",
1128
- headers: { "Content-Type": "application/json" },
1129
- body: JSON.stringify({
1130
- url: channel.webhook_url,
1131
- secret_token: secretToken,
1132
- allowed_updates: ["message"],
1133
- }),
1519
+ headers,
1520
+ body: JSON.stringify({ message: input.message }),
1134
1521
  });
1135
- } catch {
1136
- throw new Error("Telegram setWebhook request failed before Telegram returned a response.");
1137
- }
1138
-
1139
- const payload = await response.json().catch(() => ({}));
1140
-
1141
- if (!response.ok || !isTelegramOk(payload)) {
1142
- throw new Error(readTelegramSetupError(payload, response.status));
1143
- }
1144
- }
1145
-
1146
- function isTelegramOk(payload: unknown): payload is { ok: true } {
1147
- return Boolean(payload && typeof payload === "object" && "ok" in payload && payload.ok === true);
1148
- }
1149
-
1150
- function readTelegramSetupError(payload: unknown, status: number): string {
1151
- if (payload && typeof payload === "object" && "description" in payload && typeof payload.description === "string") {
1152
- return `Telegram setWebhook failed: ${payload.description}`;
1153
- }
1154
-
1155
- return `Telegram setWebhook failed with HTTP ${status}.`;
1156
- }
1157
-
1158
- async function readDeployStateIfExists(cwd: string): Promise<DeployState | null> {
1159
- return readLocalDeployStateIfExists(cwd);
1160
- }
1161
-
1162
- async function readDeployStateRequired(cwd: string): Promise<DeployState> {
1163
- const state = await readDeployStateIfExists(cwd);
1164
-
1165
- if (!state?.deploy_id) {
1166
- throw new Error("No .agentkit/deploy.json found. Run agentkit deploy before creating hosted channels.");
1167
- }
1168
-
1169
- return state;
1170
- }
1171
-
1172
- function printChannelsTable(channels: CloudChannel[]): void {
1173
- if (channels.length === 0) {
1174
- console.log("No channels found.");
1175
- return;
1176
- }
1177
-
1178
- console.log("NAME\tTYPE\tPROVIDER\tSTATUS\tLAST_EVENT");
1179
- for (const channel of channels) {
1180
- console.log([channel.name, channel.type, channel.provider, channel.status, ""].join("\t"));
1181
- }
1182
- }
1183
-
1184
- function printChannelStatus(channel: CloudChannel): void {
1185
- console.log(`Channel: ${channel.name}`);
1186
- if (channel.id) {
1187
- console.log(`ID: ${channel.id}`);
1188
- }
1189
- console.log(`Type: ${channel.type}`);
1190
- console.log(`Provider: ${channel.provider}`);
1191
- console.log(`Status: ${channel.status}`);
1192
- if (channel.webhook_url) {
1193
- console.log(`Webhook URL: ${channel.webhook_url}`);
1194
- }
1195
- console.log("Required secrets:");
1196
- for (const secret of channel.required_secrets) {
1197
- console.log(` ${secret.name}: ${secret.status}`);
1198
- }
1199
- }
1200
-
1201
- async function printChannelDeliverySummary(
1202
- channel: CloudChannel,
1203
- apiFlag: string | boolean | undefined,
1204
- ): Promise<void> {
1205
- const response = await cloudGet<{ deliveries: CloudDelivery[] }>(
1206
- parseCloudApiUrl(apiFlag),
1207
- `/v1/channels/${encodeURIComponent(channel.id)}/deliveries`,
1208
- );
1209
- const inbound = [...response.deliveries].reverse().find((delivery) => delivery.direction === "inbound");
1210
- const outbound = [...response.deliveries].reverse().find((delivery) => delivery.direction === "outbound");
1211
-
1212
- console.log("Recent delivery:");
1213
- console.log(` Last inbound: ${inbound ? `${inbound.status} ${inbound.received_at}` : "none"}`);
1214
- console.log(` Last outbound: ${outbound ? `${outbound.status} ${outbound.received_at}` : "none"}`);
1215
- console.log("Provider health: not_checked");
1216
- }
1217
-
1218
- async function resolveCloudChannelByName(name: string, apiFlag: string | boolean | undefined): Promise<CloudChannel> {
1219
- const deployState = await readDeployStateRequired(process.cwd());
1220
- const response = await cloudGet<{ channels: CloudChannel[] }>(
1221
- parseCloudApiUrl(apiFlag),
1222
- `/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
1223
- );
1224
- const channel = response.channels.find((candidate) => candidate.name === name);
1225
-
1226
- if (!channel) {
1227
- throw new Error(`Channel "${name}" was not found for deploy ${deployState.deploy_id}.`);
1522
+ } catch (error) {
1523
+ throw new AgentKitError(
1524
+ "deploy_smoke_failed",
1525
+ `Deploy smoke request failed before a response: ${error instanceof Error ? error.message : String(error)}`,
1526
+ );
1228
1527
  }
1229
1528
 
1230
- return channel;
1231
- }
1232
-
1233
- function channelProviderForCli(type: string, providerFlag: string | boolean | undefined): string {
1234
- if (type === "website") {
1235
- return "agentkit";
1236
- }
1529
+ const payload = await response.json().catch(() => null);
1237
1530
 
1238
- if (type === "telegram") {
1239
- return "telegram";
1531
+ if (!response.ok) {
1532
+ throw new AgentKitError("deploy_smoke_failed", readCloudError(payload, response.status));
1240
1533
  }
1241
1534
 
1242
- const provider = providerFlag ?? "zapster";
1535
+ const content =
1536
+ payload && typeof payload === "object" && "message" in payload && isRecord((payload as { message?: unknown }).message)
1537
+ ? (payload as { message: { content?: unknown } }).message.content
1538
+ : undefined;
1243
1539
 
1244
- if (provider !== "zapster" && provider !== "meta") {
1245
- throw new Error('WhatsApp provider must be "zapster" or "meta".');
1540
+ console.log("");
1541
+ console.log("Deploy smoke verified");
1542
+ console.log(`URL: ${input.deployUrl}`);
1543
+ console.log(`Message: ${input.message}`);
1544
+ if (typeof content === "string") {
1545
+ console.log(`Response: ${content}`);
1246
1546
  }
1247
-
1248
- return provider;
1249
1547
  }
1250
1548
 
1251
- function defaultChannelSecretsForCli(type: string, provider: string): string[] {
1252
- if (type === "website") {
1253
- return ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"];
1254
- }
1549
+ async function fetchDeployConversationJson(cwd: string, path: string): Promise<unknown> {
1550
+ const state = await requireLocalDeployState();
1551
+ const deployUrl = requireDeployUrl(state);
1552
+ const token = await readDeployAccessTokenForHostedRequest(cwd);
1553
+ const headers = new Headers();
1255
1554
 
1256
- if (type === "telegram") {
1257
- return ["TELEGRAM_BOT_TOKEN", "TELEGRAM_WEBHOOK_SECRET"];
1258
- }
1555
+ headers.set("Authorization", `Bearer ${token}`);
1259
1556
 
1260
- if (type === "whatsapp" && provider === "zapster") {
1261
- return ["ZAPSTER_API_KEY", "ZAPSTER_WEBHOOK_SECRET"];
1262
- }
1263
-
1264
- if (type === "whatsapp" && provider === "meta") {
1265
- return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
1266
- }
1267
-
1268
- return [];
1269
- }
1270
-
1271
- async function channelTestBody(
1272
- channel: CloudChannel,
1273
- messageFlag: string | boolean | undefined,
1274
- fixtureFlag: string | boolean | undefined,
1275
- ): Promise<string> {
1276
- if (typeof fixtureFlag === "string") {
1277
- return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
1278
- }
1279
-
1280
- const message = typeof messageFlag === "string" ? messageFlag : "hello";
1281
-
1282
- if (channel.type === "telegram") {
1283
- return JSON.stringify({
1284
- update_id: Date.now(),
1285
- message: {
1286
- message_id: 1,
1287
- chat: { id: 456, type: "private" },
1288
- from: { id: 123, first_name: "AgentKit" },
1289
- text: message,
1290
- },
1291
- });
1292
- }
1557
+ let response: Response;
1293
1558
 
1294
- if (channel.type === "whatsapp") {
1295
- return JSON.stringify({
1296
- event_id: `evt_${Date.now()}`,
1297
- instance_id: "local",
1298
- message: {
1299
- id: `msg_${Date.now()}`,
1300
- from: "5511999999999",
1301
- type: "text",
1302
- text: { body: message },
1303
- },
1559
+ try {
1560
+ response = await fetch(new URL(path, deployUrl).href, {
1561
+ method: "GET",
1562
+ headers,
1304
1563
  });
1564
+ } catch (error) {
1565
+ throw new AgentKitError(
1566
+ "deploy_conversation_request_failed",
1567
+ `Could not read hosted conversation data: ${error instanceof Error ? error.message : String(error)}`,
1568
+ );
1305
1569
  }
1306
1570
 
1307
- return JSON.stringify({
1308
- event_id: `web_${Date.now()}`,
1309
- visitor_id: "visitor_test",
1310
- message: {
1311
- id: `msg_${Date.now()}`,
1312
- text: message,
1313
- },
1314
- });
1315
- }
1316
-
1317
- function channelTestHeaders(channel: CloudChannel): Headers {
1318
- const headers = new Headers({ "Content-Type": "application/json" });
1319
-
1320
- if (channel.type === "website") {
1321
- const token = process.env.AGENTKIT_WEBSITE_CHANNEL_TOKEN;
1322
-
1323
- if (token) {
1324
- headers.set("Authorization", `Bearer ${token}`);
1325
- }
1326
- }
1327
-
1328
- if (channel.type === "telegram") {
1329
- headers.set("X-Telegram-Bot-Api-Secret-Token", process.env.TELEGRAM_WEBHOOK_SECRET ?? "");
1330
- }
1331
-
1332
- if (channel.type === "whatsapp" && channel.provider === "zapster") {
1333
- headers.set("X-Zapster-Webhook-Secret", process.env.ZAPSTER_WEBHOOK_SECRET ?? "");
1334
- }
1335
-
1336
- return headers;
1337
- }
1338
-
1339
- async function cloudGet<T>(apiUrl: string, path: string): Promise<T> {
1340
- const response = await cloudFetch(apiUrl, path);
1341
- const payload = await response.json();
1342
-
1343
- if (!response.ok) {
1344
- throw new Error(readCloudError(payload, response.status));
1345
- }
1346
-
1347
- return payload as T;
1348
- }
1349
-
1350
- async function cloudPost<T>(apiUrl: string, path: string, body: unknown): Promise<T> {
1351
- const response = await cloudFetch(apiUrl, path, {
1352
- method: "POST",
1353
- headers: { "Content-Type": "application/json" },
1354
- body: JSON.stringify(body),
1355
- });
1356
- const payload = await response.json();
1571
+ const payload = await response.json().catch(() => null);
1357
1572
 
1358
1573
  if (!response.ok) {
1359
- throw new Error(readCloudError(payload, response.status));
1574
+ throw new AgentKitError("deploy_conversation_request_failed", readCloudError(payload, response.status));
1360
1575
  }
1361
1576
 
1362
- return payload as T;
1363
- }
1364
-
1365
- function cloudFetch(apiUrl: string, path: string, init: RequestInit = {}): Promise<Response> {
1366
- const headers = new Headers(init.headers);
1367
- const apiToken = process.env.AGENTKIT_CLOUD_API_TOKEN;
1368
-
1369
- if (apiToken && !headers.has("Authorization")) {
1370
- headers.set("Authorization", `Bearer ${apiToken}`);
1371
- }
1372
-
1373
- return fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
1374
- ...init,
1375
- headers,
1376
- });
1377
- }
1378
-
1379
- type CloudAuthConfig = {
1380
- apiUrl: string;
1381
- token: string;
1382
- };
1383
-
1384
- function cloudAuthPath(): string {
1385
- return join(homedir(), ".agentkit", "cloud.json");
1577
+ return payload;
1386
1578
  }
1387
1579
 
1388
- async function readCloudAuth(): Promise<CloudAuthConfig | null> {
1389
- if (process.env.AGENTKIT_CLOUD_API_TOKEN) {
1390
- return {
1391
- apiUrl: process.env.AGENTKIT_CLOUD_API_URL ?? defaultAgentKitCloudApiUrl,
1392
- token: process.env.AGENTKIT_CLOUD_API_TOKEN,
1393
- };
1394
- }
1395
-
1580
+ async function fetchDeployImproveEvidence(
1581
+ apiUrl: string,
1582
+ input: { deployId: string; since?: string; conversationId?: string },
1583
+ ): Promise<unknown | null> {
1396
1584
  try {
1397
- const parsed = JSON.parse(await readFile(cloudAuthPath(), "utf8"));
1398
-
1399
- if (parsed && typeof parsed === "object" && typeof parsed.token === "string") {
1400
- return {
1401
- apiUrl: typeof parsed.apiUrl === "string" ? parsed.apiUrl : defaultAgentKitCloudApiUrl,
1402
- token: parsed.token,
1403
- };
1585
+ return await cloudApiRequest(apiUrl, `/v1/deploys/${encodeURIComponent(input.deployId)}/improve/export`, {
1586
+ method: "POST",
1587
+ body: JSON.stringify({
1588
+ status: "all",
1589
+ ...(input.since ? { since: input.since } : {}),
1590
+ ...(input.conversationId ? { conversation_id: input.conversationId } : {}),
1591
+ }),
1592
+ });
1593
+ } catch (error) {
1594
+ if (
1595
+ isAgentKitError(error) &&
1596
+ (error.code === "improve_evidence_store_not_configured" || error.code === "not_found")
1597
+ ) {
1598
+ return null;
1404
1599
  }
1405
- } catch {
1406
- return null;
1407
- }
1408
-
1409
- return null;
1410
- }
1411
-
1412
- async function writeCloudAuth(config: CloudAuthConfig): Promise<void> {
1413
- await mkdir(join(homedir(), ".agentkit"), { recursive: true });
1414
- await writeFile(cloudAuthPath(), `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
1415
- }
1416
-
1417
- async function clearCloudAuth(): Promise<void> {
1418
- await rm(cloudAuthPath(), { force: true });
1419
- }
1420
-
1421
- async function resolveCloudApiUrl(flag: string | boolean | undefined): Promise<string> {
1422
- if (flag !== undefined) {
1423
- return parseCloudApiUrl(flag);
1424
- }
1425
-
1426
- const auth = await readCloudAuth();
1427
- return auth?.apiUrl ?? parseCloudApiUrl(undefined);
1428
- }
1429
-
1430
- async function requireLocalDeployState(): Promise<LocalDeployState> {
1431
- const state = await readLocalDeployStateIfExists(process.cwd());
1432
-
1433
- if (!state) {
1434
- throw new Error("No .agentkit/deploy.json found. Run agentkit deploy first.");
1435
- }
1436
-
1437
- return state;
1438
- }
1439
-
1440
- async function resolveProjectIdForSecretCommand(cwd: string): Promise<string> {
1441
- const state = await readLocalDeployStateIfExists(cwd);
1442
1600
 
1443
- if (state?.project_id) {
1444
- return state.project_id;
1601
+ throw error;
1445
1602
  }
1446
-
1447
- const root = await findAgentCapsuleRoot(cwd);
1448
- const capsule = await loadAgentCapsule(root);
1449
- return stableCloudProjectId(capsule.config.name);
1450
- }
1451
-
1452
- async function cloudApiRequest(apiUrl: string, path: string, init: RequestInit): Promise<unknown> {
1453
- const auth = await readCloudAuth();
1454
- const headers = new Headers(init.headers);
1455
-
1456
- if (!headers.has("Content-Type") && init.body !== undefined) {
1457
- headers.set("Content-Type", "application/json");
1458
- }
1459
-
1460
- if (auth?.token && !headers.has("Authorization")) {
1461
- headers.set("Authorization", `Bearer ${auth.token}`);
1462
- }
1463
-
1464
- const response = await fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
1465
- ...init,
1466
- headers,
1467
- });
1468
- const payload = await response.json().catch(() => null);
1469
-
1470
- if (!response.ok) {
1471
- const code =
1472
- payload && typeof payload === "object" && "error" in payload
1473
- ? String((payload as { error?: { code?: unknown } }).error?.code ?? "cloud_request_failed")
1474
- : "cloud_request_failed";
1475
- throw new AgentKitError(code, readCloudError(payload, response.status));
1476
- }
1477
-
1478
- return payload;
1479
1603
  }
1480
1604
 
1481
1605
  function printJson(value: unknown): void {
1482
1606
  console.log(JSON.stringify(value, null, 2));
1483
1607
  }
1484
1608
 
1485
- function readCloudError(payload: unknown, status: number): string {
1486
- if (payload && typeof payload === "object" && "error" in payload) {
1487
- const error = (payload as { error?: { message?: unknown } }).error;
1488
- if (typeof error?.message === "string") {
1489
- return error.message;
1609
+ function printSkillStatus(status: Awaited<ReturnType<typeof checkDefaultSkillStatus>>): void {
1610
+ console.log("AgentKit skills");
1611
+ console.log(`Status: ${status.status}`);
1612
+ console.log(`Checked: ${status.checked}`);
1613
+
1614
+ if (status.missing.length > 0) {
1615
+ console.log(`Missing: ${status.missing.length}`);
1616
+ for (const file of status.missing.slice(0, 10)) {
1617
+ console.log(` ${file}`);
1490
1618
  }
1491
1619
  }
1492
1620
 
1493
- return `AgentKit Cloud request failed with HTTP ${status}.`;
1494
- }
1495
-
1496
- function parsePort(value: string | boolean | undefined): number | undefined {
1497
- if (value === undefined) {
1498
- return undefined;
1621
+ if (status.outdated.length > 0) {
1622
+ console.log(`Outdated: ${status.outdated.length}`);
1623
+ for (const file of status.outdated.slice(0, 10)) {
1624
+ console.log(` ${file}`);
1625
+ }
1499
1626
  }
1500
1627
 
1501
- if (typeof value !== "string") {
1502
- throw new Error("Missing port. Usage: agentkit dev --port <number>");
1628
+ if (status.status !== "current") {
1629
+ console.log("Run: npm run agentkit -- skills sync");
1503
1630
  }
1504
-
1505
- const port = Number(value);
1506
-
1507
- if (!Number.isInteger(port) || port <= 0 || port > 65535) {
1508
- throw new Error("Invalid port. Usage: agentkit dev --port <number>");
1509
- }
1510
-
1511
- return port;
1512
1631
  }
1513
1632
 
1514
- function parseTargetFlag(value: string | boolean | undefined, capability: "build" | "deploy"): string {
1515
- if (value === undefined) {
1516
- return "cloudflare";
1517
- }
1633
+ async function printJsonOrWrite(value: unknown, outFlag: string | boolean | undefined, usage: string): Promise<void> {
1634
+ const outPath = parseOptionalStringFlag(outFlag, "out", usage);
1518
1635
 
1519
- if (typeof value !== "string") {
1520
- throw new Error(
1521
- `Missing target. Usage: agentkit ${capability} --target ${listTargetsWithCapability(capability).join("|")}`,
1522
- );
1636
+ if (!outPath) {
1637
+ printJson(value);
1638
+ return;
1523
1639
  }
1524
1640
 
1525
- return value;
1641
+ const absolutePath = resolve(process.cwd(), outPath);
1642
+ await writeSecretJson(absolutePath, value);
1643
+ console.log(`Deploy access token written to ${absolutePath}`);
1526
1644
  }
1527
1645
 
1528
- function parseCloudApiUrl(value: string | boolean | undefined): string {
1529
- if (value === undefined) {
1530
- return process.env.AGENTKIT_CLOUD_API_URL ?? defaultAgentKitCloudApiUrl;
1531
- }
1532
-
1646
+ function parsePositiveIntegerFlag(value: string | boolean | undefined, name: string, usage: string): number {
1533
1647
  if (typeof value !== "string") {
1534
- throw new Error("Missing API URL. Usage: agentkit deploy --api <url>");
1535
- }
1536
-
1537
- return value;
1538
- }
1539
-
1540
- function parseRequiredStringFlag(value: string | boolean | undefined, name: string, usage: string): string {
1541
- if (typeof value !== "string" || value.trim().length === 0) {
1542
1648
  throw new Error(`Missing ${name}. Usage: ${usage}`);
1543
1649
  }
1544
1650
 
1545
- return value.trim();
1546
- }
1651
+ const parsed = Number(value);
1547
1652
 
1548
- function parseOptionalStringFlag(value: string | boolean | undefined, name: string, usage: string): string | undefined {
1549
- if (value === undefined) {
1550
- return undefined;
1653
+ if (!Number.isInteger(parsed) || parsed <= 0) {
1654
+ throw new Error(`Invalid ${name}. Usage: ${usage}`);
1551
1655
  }
1552
1656
 
1553
- if (typeof value !== "string" || value.trim().length === 0) {
1554
- throw new Error(`Missing ${name}. Usage: ${usage}`);
1555
- }
1657
+ return parsed;
1658
+ }
1556
1659
 
1557
- return value.trim();
1660
+ function relativePath(root: string, path: string): string {
1661
+ const output = relative(root, path);
1662
+ return output.startsWith("..") ? path : output || ".";
1558
1663
  }
1559
1664
 
1560
1665
  function printBuildResult(result: Awaited<ReturnType<typeof buildAgentCapsule>>): void {
@@ -1614,16 +1719,12 @@ async function deployCloudflare(wranglerPath: string, buildDir: string): Promise
1614
1719
  }
1615
1720
  }
1616
1721
 
1617
- function runInheritedCommand(command: string, args: string[], cwd: string): Promise<number> {
1618
- return new Promise((resolvePromise, reject) => {
1619
- const child = spawn(command, args, {
1620
- cwd,
1621
- stdio: "inherit",
1622
- });
1722
+ async function installProjectDependencies(projectPath: string): Promise<void> {
1723
+ const exitCode = await runInheritedCommand("npm", ["install"], projectPath);
1623
1724
 
1624
- child.once("error", reject);
1625
- child.once("close", (code) => resolvePromise(code ?? 1));
1626
- });
1725
+ if (exitCode !== 0) {
1726
+ throw new Error(`Dependency install failed with exit code ${exitCode}. Run \`npm install\` in ${projectPath}.`);
1727
+ }
1627
1728
  }
1628
1729
 
1629
1730
  async function parseToolInput(
@@ -1669,21 +1770,81 @@ function parseJsonInput(source: string, label: string): unknown {
1669
1770
  }
1670
1771
  }
1671
1772
 
1672
- async function waitForShutdown(stop: () => Promise<void>): Promise<void> {
1673
- await new Promise<void>((resolve) => {
1674
- let stopped = false;
1773
+ async function findRunningLocalDevServer(cwd: string): Promise<{ url: string; agent: string } | null> {
1774
+ const capsule = await loadAgentCapsule(cwd);
1775
+ const basePort = localDevBasePort();
1675
1776
 
1676
- const shutdown = () => {
1677
- if (stopped) {
1678
- return;
1679
- }
1777
+ for (let port = basePort; port <= basePort + 50; port += 1) {
1778
+ const state = await fetchLocalAgentKitState(`http://localhost:${port}/_agentkit`);
1680
1779
 
1681
- stopped = true;
1682
- void stop().finally(resolve);
1683
- };
1780
+ if (state?.agent === capsule.config.name) {
1781
+ return {
1782
+ url: `http://localhost:${port}`,
1783
+ agent: state.agent,
1784
+ };
1785
+ }
1786
+ }
1684
1787
 
1685
- process.once("SIGINT", shutdown);
1686
- process.once("SIGTERM", shutdown);
1788
+ return null;
1789
+ }
1790
+
1791
+ function localDevBasePort(): number {
1792
+ const value = process.env.AGENTKIT_DEV_BASE_PORT;
1793
+
1794
+ if (!value) {
1795
+ return defaultAgentKitDevPort;
1796
+ }
1797
+
1798
+ const parsed = Number(value);
1799
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : defaultAgentKitDevPort;
1800
+ }
1801
+
1802
+ async function fetchLocalAgentKitState(url: string): Promise<{ agent?: string } | null> {
1803
+ const controller = new AbortController();
1804
+ const timeout = setTimeout(() => controller.abort(), 300);
1805
+
1806
+ try {
1807
+ const response = await fetch(url, { signal: controller.signal });
1808
+ const payload = await response.json().catch(() => null);
1809
+
1810
+ if (payload && typeof payload === "object") {
1811
+ return {
1812
+ agent: typeof (payload as { agent?: unknown }).agent === "string" ? (payload as { agent: string }).agent : undefined,
1813
+ };
1814
+ }
1815
+ } catch {
1816
+ return null;
1817
+ } finally {
1818
+ clearTimeout(timeout);
1819
+ }
1820
+
1821
+ return null;
1822
+ }
1823
+
1824
+ async function openLocalUrl(url: string): Promise<void> {
1825
+ if (process.env.AGENTKIT_OPEN_COMMAND === "none") {
1826
+ return;
1827
+ }
1828
+
1829
+ const command =
1830
+ process.env.AGENTKIT_OPEN_COMMAND ??
1831
+ (process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open");
1832
+ const args = process.env.AGENTKIT_OPEN_COMMAND
1833
+ ? [url]
1834
+ : process.platform === "win32"
1835
+ ? ["/c", "start", "", url]
1836
+ : [url];
1837
+
1838
+ await new Promise<void>((resolvePromise, reject) => {
1839
+ const child = spawn(command, args, {
1840
+ detached: true,
1841
+ stdio: "ignore",
1842
+ });
1843
+ child.once("error", reject);
1844
+ child.once("spawn", () => {
1845
+ child.unref();
1846
+ resolvePromise();
1847
+ });
1687
1848
  });
1688
1849
  }
1689
1850
 
@@ -1691,5 +1852,6 @@ main().catch((error: unknown) => {
1691
1852
  const message = error instanceof Error ? error.message : String(error);
1692
1853
  const prefix = isAgentKitError(error) ? `agentkit ${error.code}` : "agentkit";
1693
1854
  console.error(`${prefix}: ${message}`);
1855
+ console.error("Feedback draft: agentkit feedback create --about last-run --kind bug --summary <text>");
1694
1856
  process.exitCode = 1;
1695
1857
  });