@andreprado/agentkit 0.1.0-alpha.9 → 0.1.1

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 (122) hide show
  1. package/README.md +18 -1
  2. package/docs/guides/add-channel.md +251 -7
  3. package/docs/guides/add-knowledge.md +10 -0
  4. package/docs/guides/add-managed-composio.md +165 -0
  5. package/docs/guides/add-tool.md +10 -3
  6. package/docs/guides/channel-security.md +162 -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 +61 -1
  10. package/docs/guides/connect-whatsapp-evolution.md +121 -0
  11. package/docs/guides/connect-whatsapp-uazapi.md +139 -0
  12. package/docs/guides/connect-whatsapp-zapster.md +119 -16
  13. package/docs/guides/create-agent.md +31 -4
  14. package/docs/guides/debug-channel.md +159 -0
  15. package/docs/guides/improve-from-production.md +151 -0
  16. package/docs/guides/prepare-deploy.md +32 -14
  17. package/docs/guides/replay-production-traces.md +72 -0
  18. package/docs/guides/run-evals.md +95 -25
  19. package/docs/guides/security-rules.md +9 -5
  20. package/docs/guides/send-feedback.md +135 -0
  21. package/docs/guides/use-jev.md +67 -0
  22. package/docs/guides/use-provider.md +70 -3
  23. package/docs/llms-full.txt +295 -25
  24. package/docs/llms.txt +54 -7
  25. package/package.json +3 -7
  26. package/src/cli/args.ts +23 -2
  27. package/src/cli/cloud-client.ts +121 -9
  28. package/src/cli/commands/channels.ts +856 -36
  29. package/src/cli/commands/feedback.ts +438 -0
  30. package/src/cli/commands/provider.ts +47 -0
  31. package/src/cli/commands/transcribe.ts +171 -0
  32. package/src/cli/deploy-chat-ui.ts +232 -18
  33. package/src/cli/deploy-readiness.ts +227 -14
  34. package/src/cli/help.ts +67 -9
  35. package/src/cli/index.ts +740 -35
  36. package/src/cli/new-command.ts +41 -0
  37. package/src/cloud/client.ts +4 -3
  38. package/src/cloud/contracts.ts +1 -1
  39. package/src/create-project.ts +18 -35
  40. package/src/index.ts +565 -11
  41. package/src/providers/codex-auth.ts +111 -0
  42. package/src/providers/pi.ts +88 -19
  43. package/src/providers/test.ts +36 -0
  44. package/src/providers/types.ts +8 -0
  45. package/src/runtime/channel-test-harness.ts +21 -1
  46. package/src/runtime/channels/discord.ts +904 -0
  47. package/src/runtime/channels/generic-webhook.ts +682 -0
  48. package/src/runtime/channels/net-guard.ts +480 -0
  49. package/src/runtime/channels/provider-fetch.ts +54 -0
  50. package/src/runtime/channels/slack.ts +652 -0
  51. package/src/runtime/channels/telegram.ts +379 -15
  52. package/src/runtime/channels/whatsapp-evolution.ts +1330 -0
  53. package/src/runtime/channels/whatsapp-meta.ts +9 -0
  54. package/src/runtime/channels/whatsapp-uazapi.ts +1192 -0
  55. package/src/runtime/channels/whatsapp-zapster.ts +702 -40
  56. package/src/runtime/channels.ts +83 -3
  57. package/src/runtime/chat.ts +70 -44
  58. package/src/runtime/config.ts +512 -20
  59. package/src/runtime/core/manifest.ts +75 -5
  60. package/src/runtime/core/targets.ts +5 -5
  61. package/src/runtime/deploy-readiness.ts +34 -4
  62. package/src/runtime/dev-server.ts +639 -39
  63. package/src/runtime/env.ts +8 -3
  64. package/src/runtime/evals.ts +445 -74
  65. package/src/runtime/improve.ts +868 -0
  66. package/src/runtime/inspect.ts +173 -4
  67. package/src/runtime/integrations/composio.ts +425 -0
  68. package/src/runtime/knowledge/embeddings.ts +45 -7
  69. package/src/runtime/knowledge/ingest.ts +69 -6
  70. package/src/runtime/knowledge/retrieve.ts +25 -5
  71. package/src/runtime/knowledge/schema.ts +45 -1
  72. package/src/runtime/knowledge/vector.ts +30 -30
  73. package/src/runtime/prompt-context.ts +141 -0
  74. package/src/runtime/runtime-contract.ts +71 -7
  75. package/src/runtime/skills.ts +95 -0
  76. package/src/runtime/targets/cloudflare/build.ts +1010 -208
  77. package/src/runtime/targets/container/server.ts +1 -1
  78. package/src/runtime/targets/vps/deploy.ts +26 -9
  79. package/src/runtime/tool-runner.ts +9 -1
  80. package/src/runtime/tools.ts +26 -2
  81. package/src/runtime/transcription.ts +483 -0
  82. package/src/storage/sqlite.ts +7 -2
  83. package/src/templates/blank.ts +37 -9
  84. package/src/templates/dentista.ts +40 -14
  85. package/src/templates/skills/agentkit-build-agent/SKILL.md +34 -5
  86. package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +2 -1
  87. package/src/templates/skills/agentkit-capsule/SKILL.md +32 -3
  88. package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -2
  89. package/src/templates/skills/agentkit-channels/SKILL.md +66 -1
  90. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +8 -1
  91. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +28 -3
  92. package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
  93. package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
  94. package/src/templates/skills/agentkit-channels/references/telegram.md +34 -0
  95. package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +57 -0
  96. package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +54 -0
  97. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +42 -8
  98. package/src/templates/skills/agentkit-database/SKILL.md +11 -0
  99. package/src/templates/skills/agentkit-deploy/SKILL.md +9 -1
  100. package/src/templates/skills/agentkit-evals/SKILL.md +77 -13
  101. package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +13 -6
  102. package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +8 -4
  103. package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +8 -4
  104. package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +16 -7
  105. package/src/templates/skills/agentkit-improve/SKILL.md +96 -0
  106. package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
  107. package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
  108. package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
  109. package/src/templates/skills/agentkit-integrations/SKILL.md +98 -0
  110. package/src/templates/skills/agentkit-knowledge/SKILL.md +4 -1
  111. package/src/templates/skills/agentkit-prompts/SKILL.md +3 -1
  112. package/src/templates/skills/agentkit-provider/SKILL.md +29 -4
  113. package/src/templates/skills/agentkit-security/SKILL.md +5 -2
  114. package/src/templates/skills/agentkit-tools/SKILL.md +8 -1
  115. package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +8 -8
  116. package/src/templates/skills/agentkit-tools/examples/jev-service-fit.tool.md +110 -0
  117. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +25 -1
  118. package/src/templates/support.ts +42 -12
  119. package/docs/guides/agentkit-skills-architecture.md +0 -471
  120. package/docs/guides/channels-implementation-map.md +0 -243
  121. package/docs/guides/channels-production-handoff.md +0 -101
  122. package/docs/portable-deploy-release-checklist.md +0 -41
@@ -0,0 +1,438 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { chmod, mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, extname, join, relative, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import { AgentKitError } from "../../runtime/errors";
7
+ import { findAgentCapsuleRoot, loadAgentCapsule } from "../../runtime/config";
8
+ import { readLocalDeployStateIfExists } from "../../runtime/core/deploy-state";
9
+ import type { ParsedArgs } from "../args";
10
+ import {
11
+ cloudApiRequest,
12
+ readCloudAuthForApiUrl,
13
+ resolveCloudApiUrl,
14
+ type FeedbackSendResponse,
15
+ } from "../cloud-client";
16
+ import { parseOptionalStringFlag } from "../flags";
17
+
18
+ type FeedbackKind =
19
+ | "bug"
20
+ | "confusion"
21
+ | "missing_docs"
22
+ | "feature_request"
23
+ | "deploy_issue"
24
+ | "runtime_issue"
25
+ | "other";
26
+
27
+ type FeedbackAbout = "last-run" | "deploy" | "manual";
28
+
29
+ type FeedbackDraft = {
30
+ schema_version: "agentkit.feedback.v1";
31
+ client_report_id: string;
32
+ kind: FeedbackKind;
33
+ summary: string;
34
+ message?: string;
35
+ source: Record<string, unknown>;
36
+ context: Record<string, unknown>;
37
+ diagnostics: Record<string, unknown>;
38
+ };
39
+
40
+ const feedbackKinds = new Set<FeedbackKind>([
41
+ "bug",
42
+ "confusion",
43
+ "missing_docs",
44
+ "feature_request",
45
+ "deploy_issue",
46
+ "runtime_issue",
47
+ "other",
48
+ ]);
49
+
50
+ const feedbackAboutValues = new Set<FeedbackAbout>(["last-run", "deploy", "manual"]);
51
+
52
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
53
+
54
+ export async function handleFeedbackCommand(args: ParsedArgs): Promise<void> {
55
+ const [subcommand, draftPath] = args.positional;
56
+
57
+ if (subcommand === "create") {
58
+ const draft = await buildFeedbackDraft(process.cwd(), args);
59
+ const written = await writeFeedbackDraft(process.cwd(), draft, args.flags.out);
60
+
61
+ console.log("Feedback draft created");
62
+ console.log(`JSON: ${relativePath(process.cwd(), written.jsonPath)}`);
63
+ console.log(`Markdown: ${relativePath(process.cwd(), written.markdownPath)}`);
64
+ console.log(`Send: agentkit feedback send ${relativePath(process.cwd(), written.jsonPath)}`);
65
+ return;
66
+ }
67
+
68
+ if (subcommand === "preview") {
69
+ const draft = draftPath
70
+ ? await readFeedbackDraft(resolve(process.cwd(), draftPath))
71
+ : await buildFeedbackDraft(process.cwd(), args);
72
+ console.log(JSON.stringify(draft, null, 2));
73
+ return;
74
+ }
75
+
76
+ if (subcommand === "send") {
77
+ const apiUrl = await resolveCloudApiUrl(args.flags.api);
78
+ const auth = await readCloudAuthForApiUrl(apiUrl);
79
+
80
+ if (!auth) {
81
+ throw new AgentKitError(
82
+ "auth_required",
83
+ `Log in before sending feedback: agentkit login --token <token> --api ${apiUrl}`,
84
+ );
85
+ }
86
+
87
+ const draft = draftPath
88
+ ? await readFeedbackDraft(resolve(process.cwd(), draftPath))
89
+ : await buildFeedbackDraft(process.cwd(), args);
90
+
91
+ if (!draftPath && args.flags.save === true) {
92
+ const written = await writeFeedbackDraft(process.cwd(), draft, args.flags.out);
93
+ console.log(`Feedback draft saved: ${relativePath(process.cwd(), written.jsonPath)}`);
94
+ }
95
+
96
+ const payload = (await cloudApiRequest(apiUrl, "/v1/feedback", {
97
+ method: "POST",
98
+ headers: {
99
+ "Idempotency-Key": draft.client_report_id,
100
+ },
101
+ body: JSON.stringify(feedbackDraftToSubmission(draft)),
102
+ })) as FeedbackSendResponse;
103
+
104
+ const feedback = payload.feedback;
105
+
106
+ if (!feedback?.id) {
107
+ throw new AgentKitError("feedback_response_invalid", "AgentKit Cloud returned an invalid feedback response.");
108
+ }
109
+
110
+ console.log("Feedback sent");
111
+ console.log(`ID: ${feedback.id}`);
112
+ console.log(`Kind: ${feedback.kind ?? draft.kind}`);
113
+ console.log(`Status: ${feedback.status ?? "new"}`);
114
+ return;
115
+ }
116
+
117
+ throw new Error(
118
+ "Usage: agentkit feedback create|preview|send [draft.json] [--about last-run|deploy|manual] [--kind bug|confusion|missing_docs|feature_request|deploy_issue|runtime_issue|other] [--summary <text>] [--message <text>] [--api <url>]",
119
+ );
120
+ }
121
+
122
+ async function buildFeedbackDraft(cwd: string, args: ParsedArgs): Promise<FeedbackDraft> {
123
+ const about = parseFeedbackAbout(args.flags.about);
124
+ const kind = parseFeedbackKind(args.flags.kind, about === "deploy" ? "deploy_issue" : "other");
125
+ const message = parseOptionalStringFlag(args.flags.message, "message", "agentkit feedback create --message <text>");
126
+ const summary =
127
+ parseOptionalStringFlag(args.flags.summary, "summary", "agentkit feedback create --summary <text>") ??
128
+ summarizeMessage(message);
129
+
130
+ if (!summary) {
131
+ throw new Error("Missing summary. Usage: agentkit feedback create --summary <text> [--message <text>]");
132
+ }
133
+
134
+ const now = new Date().toISOString();
135
+ const context = await collectFeedbackContext(cwd);
136
+ const draft: FeedbackDraft = {
137
+ schema_version: "agentkit.feedback.v1",
138
+ client_report_id: `fbdraft_${Date.now().toString(36)}_${randomBytes(6).toString("hex")}`,
139
+ kind,
140
+ summary: redactText(summary).slice(0, 200),
141
+ ...(message ? { message: redactText(message).slice(0, 5_000) } : {}),
142
+ source: {
143
+ channel: "cli",
144
+ about,
145
+ created_at: now,
146
+ agentkit_version: await readAgentKitVersion(),
147
+ },
148
+ context,
149
+ diagnostics: {
150
+ redaction: "agentkit-cli-redaction-v1",
151
+ note:
152
+ about === "last-run"
153
+ ? "AgentKit does not persist a last-command transcript yet; this draft includes current capsule and deploy context."
154
+ : "This draft includes current capsule and deploy context.",
155
+ },
156
+ };
157
+
158
+ return redactJson(draft) as FeedbackDraft;
159
+ }
160
+
161
+ async function collectFeedbackContext(cwd: string): Promise<Record<string, unknown>> {
162
+ const context: Record<string, unknown> = {
163
+ platform: {
164
+ node: process.version,
165
+ bun: (globalThis as { Bun?: { version?: string } }).Bun?.version,
166
+ platform: process.platform,
167
+ arch: process.arch,
168
+ package_manager: await detectPackageManager(cwd),
169
+ },
170
+ };
171
+
172
+ const root = await findAgentCapsuleRoot(cwd).catch(() => null);
173
+
174
+ if (!root) {
175
+ context.capsule = {
176
+ detected: false,
177
+ cwd_name: basename(cwd),
178
+ };
179
+ return redactJson(context) as Record<string, unknown>;
180
+ }
181
+
182
+ const capsule = await loadAgentCapsule(root).catch((error: unknown) => ({
183
+ error: error instanceof Error ? error.message : String(error),
184
+ }));
185
+ const deployState = await readLocalDeployStateIfExists(root).catch(() => null);
186
+
187
+ if ("config" in capsule) {
188
+ context.capsule = {
189
+ detected: true,
190
+ root_name: basename(root),
191
+ name: capsule.config.name,
192
+ runtime: capsule.config.runtime,
193
+ provider: capsule.config.provider,
194
+ access: capsule.config.access,
195
+ storage: capsule.config.storage,
196
+ secret_names: capsule.config.secrets,
197
+ tool_count: capsule.config.tools?.length ?? 0,
198
+ channel_count: capsule.config.channels?.length ?? 0,
199
+ };
200
+ } else {
201
+ context.capsule = {
202
+ detected: true,
203
+ root_name: basename(root),
204
+ load_error: capsule.error,
205
+ };
206
+ }
207
+
208
+ if (deployState) {
209
+ context.deploy = {
210
+ project_id: deployState.project_id,
211
+ deploy_id: deployState.deploy_id,
212
+ target: deployState.target,
213
+ runtime: deployState.runtime,
214
+ owner_state: deployState.owner_state,
215
+ access: deployState.access,
216
+ url: deployState.url,
217
+ created_at: deployState.created_at,
218
+ };
219
+ }
220
+
221
+ return redactJson(context) as Record<string, unknown>;
222
+ }
223
+
224
+ async function detectPackageManager(cwd: string): Promise<string> {
225
+ const candidates = [
226
+ ["bun.lock", "bun"],
227
+ ["bun.lockb", "bun"],
228
+ ["pnpm-lock.yaml", "pnpm"],
229
+ ["yarn.lock", "yarn"],
230
+ ["package-lock.json", "npm"],
231
+ ] as const;
232
+
233
+ for (const [file, name] of candidates) {
234
+ if (await pathExists(join(cwd, file))) {
235
+ return name;
236
+ }
237
+ }
238
+
239
+ return "unknown";
240
+ }
241
+
242
+ async function writeFeedbackDraft(
243
+ cwd: string,
244
+ draft: FeedbackDraft,
245
+ outFlag: string | boolean | undefined,
246
+ ): Promise<{ jsonPath: string; markdownPath: string }> {
247
+ const out = parseOptionalStringFlag(outFlag, "out", "agentkit feedback create --out <path>");
248
+ const defaultDir = join(cwd, ".agentkit", "feedback");
249
+ const slug = slugify(draft.summary);
250
+ const filename = `${new Date().toISOString().replace(/[:.]/g, "-")}-${slug}`;
251
+ const jsonPath = out && extname(out) === ".json" ? resolve(cwd, out) : join(out ? resolve(cwd, out) : defaultDir, `${filename}.json`);
252
+ const markdownPath = jsonPath.replace(/\.json$/, ".md");
253
+
254
+ await mkdir(dirname(jsonPath), { recursive: true });
255
+ await writeFile(jsonPath, `${JSON.stringify(draft, null, 2)}\n`, { mode: 0o600 });
256
+ await chmod(jsonPath, 0o600);
257
+ await writeFile(markdownPath, renderFeedbackMarkdown(draft), { mode: 0o600 });
258
+ await chmod(markdownPath, 0o600);
259
+
260
+ return { jsonPath, markdownPath };
261
+ }
262
+
263
+ async function readFeedbackDraft(path: string): Promise<FeedbackDraft> {
264
+ const parsed = JSON.parse(await readFile(path, "utf8")) as unknown;
265
+ const draft = validateFeedbackDraft(parsed);
266
+
267
+ if (!draft) {
268
+ throw new AgentKitError("feedback_draft_invalid", "Feedback draft must be an agentkit.feedback.v1 JSON object.");
269
+ }
270
+
271
+ return redactJson(draft) as FeedbackDraft;
272
+ }
273
+
274
+ function validateFeedbackDraft(value: unknown): FeedbackDraft | null {
275
+ if (!isRecord(value) || value.schema_version !== "agentkit.feedback.v1") {
276
+ return null;
277
+ }
278
+
279
+ if (typeof value.client_report_id !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(value.client_report_id)) {
280
+ return null;
281
+ }
282
+
283
+ if (!isFeedbackKind(value.kind) || typeof value.summary !== "string" || !isRecord(value.source)) {
284
+ return null;
285
+ }
286
+
287
+ return {
288
+ schema_version: "agentkit.feedback.v1",
289
+ client_report_id: value.client_report_id,
290
+ kind: value.kind,
291
+ summary: redactText(value.summary).slice(0, 200),
292
+ ...(typeof value.message === "string" ? { message: redactText(value.message).slice(0, 5_000) } : {}),
293
+ source: redactJson(value.source) as Record<string, unknown>,
294
+ context: isRecord(value.context) ? redactJson(value.context) as Record<string, unknown> : {},
295
+ diagnostics: isRecord(value.diagnostics) ? redactJson(value.diagnostics) as Record<string, unknown> : {},
296
+ };
297
+ }
298
+
299
+ function feedbackDraftToSubmission(draft: FeedbackDraft): Record<string, unknown> {
300
+ return {
301
+ kind: draft.kind,
302
+ summary: draft.summary,
303
+ ...(draft.message ? { message: draft.message } : {}),
304
+ source: draft.source,
305
+ context: draft.context,
306
+ diagnostics: draft.diagnostics,
307
+ client_report_id: draft.client_report_id,
308
+ };
309
+ }
310
+
311
+ function renderFeedbackMarkdown(draft: FeedbackDraft): string {
312
+ const lines = [
313
+ "# AgentKit Feedback Draft",
314
+ "",
315
+ `Kind: ${draft.kind}`,
316
+ `Summary: ${draft.summary}`,
317
+ `Client report: ${draft.client_report_id}`,
318
+ "",
319
+ ];
320
+
321
+ if (draft.message) {
322
+ lines.push("## Message", "", draft.message, "");
323
+ }
324
+
325
+ lines.push("## Context", "", "```json", JSON.stringify(draft.context, null, 2), "```", "");
326
+ lines.push("Send with the JSON path printed by `agentkit feedback create`:", "", "```sh\nagentkit feedback send <draft.json>\n```", "");
327
+
328
+ return `${lines.join("\n")}\n`;
329
+ }
330
+
331
+ function parseFeedbackKind(value: string | boolean | undefined, fallback: FeedbackKind): FeedbackKind {
332
+ if (value === undefined || value === false) {
333
+ return fallback;
334
+ }
335
+
336
+ if (typeof value === "string" && isFeedbackKind(value)) {
337
+ return value;
338
+ }
339
+
340
+ throw new Error("feedback --kind must be bug, confusion, missing_docs, feature_request, deploy_issue, runtime_issue, or other.");
341
+ }
342
+
343
+ function parseFeedbackAbout(value: string | boolean | undefined): FeedbackAbout {
344
+ if (value === undefined || value === false) {
345
+ return "last-run";
346
+ }
347
+
348
+ if (typeof value === "string" && feedbackAboutValues.has(value as FeedbackAbout)) {
349
+ return value as FeedbackAbout;
350
+ }
351
+
352
+ throw new Error("feedback --about must be last-run, deploy, or manual.");
353
+ }
354
+
355
+ function summarizeMessage(message: string | undefined): string | undefined {
356
+ if (!message) {
357
+ return undefined;
358
+ }
359
+
360
+ return message.replace(/\s+/g, " ").trim().slice(0, 120);
361
+ }
362
+
363
+ function isFeedbackKind(value: unknown): value is FeedbackKind {
364
+ return typeof value === "string" && feedbackKinds.has(value as FeedbackKind);
365
+ }
366
+
367
+ function redactJson(value: unknown, key = ""): unknown {
368
+ if (Array.isArray(value)) {
369
+ return value.map((item) => redactJson(item, key));
370
+ }
371
+
372
+ if (!isRecord(value)) {
373
+ return typeof value === "string" ? redactText(value) : value;
374
+ }
375
+
376
+ const out: Record<string, unknown> = {};
377
+
378
+ for (const [nestedKey, nestedValue] of Object.entries(value)) {
379
+ if (shouldRedactKey(nestedKey)) {
380
+ out[nestedKey] = "[redacted]";
381
+ continue;
382
+ }
383
+
384
+ out[nestedKey] = redactJson(nestedValue, nestedKey);
385
+ }
386
+
387
+ return out;
388
+ }
389
+
390
+ function shouldRedactKey(key: string): boolean {
391
+ return /token|password|credential|api[_-]?key|private[_-]?key/i.test(key) && key !== "client_report_id";
392
+ }
393
+
394
+ function redactText(value: string): string {
395
+ return value
396
+ .replace(/Authorization:\s*Bearer\s+[A-Za-z0-9._:-]+/gi, "Authorization: Bearer [redacted]")
397
+ .replace(/\b(?:agk_user|agk_deploy|ak_live|bsec|whsec|sk|rk|pk)[-_A-Za-z0-9]{12,}\b/g, "[redacted-token]")
398
+ .replace(/\b[A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{24,}\b/g, "[redacted-token]");
399
+ }
400
+
401
+ function slugify(value: string): string {
402
+ const slug = value
403
+ .toLowerCase()
404
+ .replace(/[^a-z0-9]+/g, "-")
405
+ .replace(/^-+|-+$/g, "")
406
+ .slice(0, 48);
407
+
408
+ return slug || "feedback";
409
+ }
410
+
411
+ async function readAgentKitVersion(): Promise<string> {
412
+ try {
413
+ const packageJson = JSON.parse(await readFile(join(moduleDir, "..", "..", "..", "package.json"), "utf8")) as {
414
+ version?: unknown;
415
+ };
416
+ return typeof packageJson.version === "string" ? packageJson.version : "unknown";
417
+ } catch {
418
+ return "unknown";
419
+ }
420
+ }
421
+
422
+ async function pathExists(path: string): Promise<boolean> {
423
+ try {
424
+ await stat(path);
425
+ return true;
426
+ } catch {
427
+ return false;
428
+ }
429
+ }
430
+
431
+ function relativePath(root: string, path: string): string {
432
+ const output = relative(root, path);
433
+ return output.startsWith("..") ? path : output || ".";
434
+ }
435
+
436
+ function isRecord(value: unknown): value is Record<string, unknown> {
437
+ return typeof value === "object" && value !== null && !Array.isArray(value);
438
+ }
@@ -0,0 +1,47 @@
1
+ import { createInterface } from "node:readline/promises";
2
+
3
+ import { loginOpenAICodex } from "@earendil-works/pi-ai/oauth";
4
+
5
+ import { clearCodexLogin, codexLoginStatus, saveCodexLogin } from "../../providers/codex-auth";
6
+ import { AgentKitError } from "../../runtime/errors";
7
+ import type { ParsedArgs } from "../args";
8
+
9
+ export async function handleProviderCommand(args: ParsedArgs): Promise<void> {
10
+ const [action, provider] = args.positional;
11
+ if (provider !== "openai-codex" || args.positional.length !== 2 || Object.keys(args.flags).length ||
12
+ !["login", "status", "logout"].includes(action)) {
13
+ throw new Error("Usage: agentkit provider login|status|logout openai-codex");
14
+ }
15
+ if (action === "status") {
16
+ console.log(JSON.stringify(await codexLoginStatus(), null, 2));
17
+ return;
18
+ }
19
+ if (action === "logout") {
20
+ await clearCodexLogin();
21
+ console.log("Removed the local AgentKit Codex login. This does not revoke the session at OpenAI.");
22
+ return;
23
+ }
24
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
25
+ throw new AgentKitError("provider_auth_interactive_required", "Run agentkit provider login openai-codex in an interactive terminal to sign in with ChatGPT.");
26
+ }
27
+ const readline = createInterface({ input: process.stdin, output: process.stdout });
28
+ const abort = new AbortController();
29
+ readline.on("SIGINT", () => abort.abort());
30
+ try {
31
+ const credentials = await loginOpenAICodex({
32
+ onAuth({ url, instructions }) {
33
+ console.log(`Open this URL to sign in with ChatGPT:\n${url}`);
34
+ if (instructions) console.log(instructions);
35
+ },
36
+ onPrompt: ({ message }) => readline.question(`${message} `, { signal: abort.signal }),
37
+ onManualCodeInput: () => readline.question("After signing in, paste the redirect URL here if the browser callback cannot reach this terminal: ", { signal: abort.signal }),
38
+ }).catch(() => {
39
+ throw new AgentKitError("provider_auth_failed", "Codex login failed or was cancelled. Run agentkit provider login openai-codex again.");
40
+ });
41
+ await saveCodexLogin(credentials);
42
+ console.log('Signed in. Set provider: { name: "openai-codex", model: "gpt-5.4" } in agentkit.config.ts. No OPENAI_API_KEY is required.');
43
+ } finally {
44
+ abort.abort();
45
+ readline.close();
46
+ }
47
+ }
@@ -0,0 +1,171 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename, extname, resolve } from "node:path";
3
+
4
+ import type { TranscriptionProviderName } from "../../index";
5
+ import { findAgentCapsuleRoot, loadAgentCapsule } from "../../runtime/config";
6
+ import { loadCapsuleEnv } from "../../runtime/env";
7
+ import {
8
+ defaultTranscriptionSecret,
9
+ resolveTranscriptionConfig,
10
+ transcribeAudio,
11
+ type ResolvedTranscriptionConfig,
12
+ } from "../../runtime/transcription";
13
+ import type { ParsedArgs } from "../args";
14
+
15
+ type SmokeFixture = {
16
+ audio: Uint8Array;
17
+ filename: string;
18
+ mimeType: string;
19
+ source: string;
20
+ };
21
+
22
+ const defaultModels: Record<TranscriptionProviderName, string> = {
23
+ test: "fake",
24
+ openai: "gpt-4o-mini-transcribe",
25
+ groq: "whisper-large-v3-turbo",
26
+ };
27
+
28
+ // Short macOS-generated spoken "hello" sample encoded as M4A. M4A is supported by OpenAI and Groq.
29
+ const builtinSmokeM4aBase64 =
30
+ "AAAAHGZ0eXBNNEEgAAAAAE00QSBtcDQyaXNvbQAAA3ptb292AAAAbG12aGQAAAAA5kTZseZE2bEAAFYiAAA4AAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACDHRyYWsAAABcdGtoZAAAAAfmRNmx5kTZsQAAAAEAAAAAAAA4AAAAAAAAAAAAAAAAAAEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAahtZGlhAAAAIG1kaGQAAAAA5kTZseZE2bEAAFYiAAA4AAAAAAAAAAAiaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAAAAAAABXm1pbmYAAAAQc21oZAAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABInN0YmwAAAB2c3RzZAAAAAAAAAABAAAAZm1wNGEAAAAAAAAAAQAAAAAAAAAAAAIAEAAAAABWIgAAAAAAM2VzZHMAAAAAA4CAgCIAAAAEgICAFEAUABgAAAAU+AAAfQAFgICAAhOIBoCAgAECAAAAD3NidGQAAAAASTE2AAAAGHN0dHMAAAAAAAAAAQAAAA4AAAQAAAAAKHN0c2MAAAAAAAAAAgAAAAEAAAALAAAAAQAAAAIAAAADAAAAAQAAAExzdHN6AAAAAAAAAAAAAAAOAAAABAAAAbkAAADiAAAA0wAAAL8AAADLAAAAzAAAAJMAAACmAAAA5wAAALcAAACnAAAAtgAAADoAAAAYc3RjbwAAAAAAAAACAAAQAAAAGJ8AAAD6dWR0YQAAAPJtZXRhAAAAAAAAACJoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAADEaWxzdAAAALwtLS0tAAAAHG1lYW4AAAAAY29tLmFwcGxlLmlUdW5lcwAAABRuYW1lAAAAAGlUdW5TTVBCAAAAhGRhdGEAAAABAAAAACAwMDAwMDAwMCAwMDAwMDg0MCAwMDAwMDEwOCAwMDAwMDAwMDAwMDAyRUI4IDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwAAAMYmZyZWUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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";
31
+
32
+ export async function handleTranscribeCommand(args: ParsedArgs): Promise<void> {
33
+ const [subcommand] = args.positional;
34
+
35
+ if (subcommand !== "smoke") {
36
+ throw new Error("Usage: agentkit transcribe smoke [--provider test|openai|groq] [--model <model>] [--fixture <audio-file>]");
37
+ }
38
+
39
+ const root = await findAgentCapsuleRoot(process.cwd());
40
+ const capsule = await loadAgentCapsule(root);
41
+ const env = compactEnv(await loadCapsuleEnv(root, process.env));
42
+ const configured = resolveTranscriptionConfig(capsule.config.transcription);
43
+ const provider = parseProviderFlag(args.flags.provider, configured);
44
+ const model = typeof args.flags.model === "string" ? args.flags.model : configured.enabled && configured.provider === provider ? configured.model : defaultModels[provider];
45
+ const secret = typeof args.flags.secret === "string"
46
+ ? args.flags.secret
47
+ : configured.enabled && configured.provider === provider
48
+ ? configured.secret
49
+ : defaultTranscriptionSecret(provider);
50
+ const fixture = await loadSmokeFixture(provider, args.flags.fixture);
51
+
52
+ const config: ResolvedTranscriptionConfig = {
53
+ enabled: true,
54
+ provider,
55
+ model,
56
+ secret,
57
+ language: typeof args.flags.language === "string" ? args.flags.language : configured.enabled ? configured.language : null,
58
+ prompt: typeof args.flags.prompt === "string" ? args.flags.prompt : configured.enabled ? configured.prompt : null,
59
+ limits: configured.enabled ? configured.limits : {},
60
+ rawAudioTtlSeconds: configured.enabled ? configured.rawAudioTtlSeconds : 3600,
61
+ };
62
+ const result = await transcribeAudio({
63
+ config,
64
+ secrets: env,
65
+ audio: fixture.audio,
66
+ filename: fixture.filename,
67
+ mimeType: fixture.mimeType,
68
+ });
69
+
70
+ console.log("Transcription smoke");
71
+ console.log(`Provider: ${provider}`);
72
+ console.log(`Model: ${model}`);
73
+ console.log(`Fixture: ${fixture.source} (${fixture.mimeType})`);
74
+
75
+ if (result.ok) {
76
+ console.log("Status: ok");
77
+ console.log(`Transcript: ${JSON.stringify(result.text)}`);
78
+ return;
79
+ }
80
+
81
+ console.log("Status: failed");
82
+ console.log(`Code: ${result.code}`);
83
+ console.log(`Message: ${result.message}`);
84
+ process.exitCode = 1;
85
+ }
86
+
87
+ function parseProviderFlag(
88
+ value: string | boolean | undefined,
89
+ configured: ResolvedTranscriptionConfig,
90
+ ): TranscriptionProviderName {
91
+ if (value === undefined || value === false) {
92
+ return configured.enabled ? configured.provider : "groq";
93
+ }
94
+
95
+ if (value === "test" || value === "openai" || value === "groq") {
96
+ return value;
97
+ }
98
+
99
+ throw new Error("Usage: agentkit transcribe smoke [--provider test|openai|groq] [--model <model>] [--fixture <audio-file>]");
100
+ }
101
+
102
+ function compactEnv(env: Record<string, string | undefined>): Record<string, string> {
103
+ const compact: Record<string, string> = {};
104
+
105
+ for (const [name, value] of Object.entries(env)) {
106
+ if (value !== undefined) {
107
+ compact[name] = value;
108
+ }
109
+ }
110
+
111
+ return compact;
112
+ }
113
+
114
+ async function loadSmokeFixture(
115
+ provider: TranscriptionProviderName,
116
+ fixtureFlag: string | boolean | undefined,
117
+ ): Promise<SmokeFixture> {
118
+ if (typeof fixtureFlag === "string") {
119
+ const filename = resolve(process.cwd(), fixtureFlag);
120
+ return {
121
+ audio: new Uint8Array(await readFile(filename)),
122
+ filename: basename(filename),
123
+ mimeType: mimeTypeForFilename(filename),
124
+ source: fixtureFlag,
125
+ };
126
+ }
127
+
128
+ if (provider === "test") {
129
+ return {
130
+ audio: new TextEncoder().encode("hello"),
131
+ filename: "agentkit-smoke.ogg",
132
+ mimeType: "audio/ogg",
133
+ source: "built-in text fixture",
134
+ };
135
+ }
136
+
137
+ return {
138
+ audio: new Uint8Array(Buffer.from(builtinSmokeM4aBase64, "base64")),
139
+ filename: "agentkit-transcribe-smoke.m4a",
140
+ mimeType: "audio/m4a",
141
+ source: "built-in spoken hello",
142
+ };
143
+ }
144
+
145
+ function mimeTypeForFilename(filename: string): string {
146
+ const extension = extname(filename).toLowerCase();
147
+
148
+ if (extension === ".ogg" || extension === ".opus") {
149
+ return "audio/ogg";
150
+ }
151
+ if (extension === ".mp3" || extension === ".mpeg" || extension === ".mpga") {
152
+ return "audio/mpeg";
153
+ }
154
+ if (extension === ".m4a") {
155
+ return "audio/m4a";
156
+ }
157
+ if (extension === ".mp4") {
158
+ return "audio/mp4";
159
+ }
160
+ if (extension === ".wav") {
161
+ return "audio/wav";
162
+ }
163
+ if (extension === ".webm") {
164
+ return "audio/webm";
165
+ }
166
+ if (extension === ".flac") {
167
+ return "audio/flac";
168
+ }
169
+
170
+ return "application/octet-stream";
171
+ }