@andreprado/agentkit 0.1.0-alpha.4 → 0.1.0-alpha.5
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.
- package/package.json +1 -1
- package/src/cli/args.ts +36 -0
- package/src/cli/cloud-client.ts +257 -0
- package/src/cli/commands/channels.ts +421 -0
- package/src/cli/constants.ts +4 -0
- package/src/cli/deploy-chat-ui.ts +385 -0
- package/src/cli/deploy-readiness.ts +329 -0
- package/src/cli/flags.ts +162 -0
- package/src/cli/help.ts +158 -0
- package/src/cli/index.ts +203 -1908
- package/src/cli/process.ts +31 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { ParsedArgs } from "../args";
|
|
5
|
+
import { cloudFetch, cloudGet, cloudPost } from "../cloud-client";
|
|
6
|
+
import { parseCloudApiUrl } from "../flags";
|
|
7
|
+
import { loadAgentCapsule } from "../../runtime/config";
|
|
8
|
+
import { readLocalDeployStateIfExists, type LocalDeployState } from "../../runtime/core/deploy-state";
|
|
9
|
+
|
|
10
|
+
type DeployState = LocalDeployState;
|
|
11
|
+
|
|
12
|
+
type CloudChannel = {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
type: "website" | "telegram" | "whatsapp";
|
|
16
|
+
provider: "agentkit" | "telegram" | "zapster" | "meta";
|
|
17
|
+
status: string;
|
|
18
|
+
webhook_url: string;
|
|
19
|
+
required_secrets: Array<{ name: string; status: string }>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type CloudDelivery = {
|
|
23
|
+
id: string;
|
|
24
|
+
channel_id: string;
|
|
25
|
+
direction: string;
|
|
26
|
+
status: string;
|
|
27
|
+
provider: {
|
|
28
|
+
event_id?: string;
|
|
29
|
+
};
|
|
30
|
+
received_at: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
34
|
+
const [subcommand, first, second] = args.positional;
|
|
35
|
+
|
|
36
|
+
if (subcommand === "list") {
|
|
37
|
+
const deployState = await readDeployStateIfExists(process.cwd());
|
|
38
|
+
|
|
39
|
+
if (deployState) {
|
|
40
|
+
const channels = await cloudGet<{ channels: CloudChannel[] }>(
|
|
41
|
+
parseCloudApiUrl(args.flags.api),
|
|
42
|
+
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
43
|
+
);
|
|
44
|
+
printChannelsTable(channels.channels);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const capsule = await loadAgentCapsule(process.cwd());
|
|
49
|
+
printChannelsTable(
|
|
50
|
+
(capsule.config.channels ?? []).map((channel) => ({
|
|
51
|
+
id: "",
|
|
52
|
+
name: channel.name,
|
|
53
|
+
type: channel.type,
|
|
54
|
+
provider: channel.provider,
|
|
55
|
+
status: "local_config",
|
|
56
|
+
webhook_url: "",
|
|
57
|
+
required_secrets: channel.secrets.map((name) => ({ name, status: "missing" })),
|
|
58
|
+
})),
|
|
59
|
+
);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (subcommand === "add") {
|
|
64
|
+
const type = first;
|
|
65
|
+
const name = second;
|
|
66
|
+
|
|
67
|
+
if (!type || !name || (type !== "website" && type !== "telegram" && type !== "whatsapp")) {
|
|
68
|
+
throw new Error("Usage: agentkit channels add <website|telegram|whatsapp> <name> [--provider zapster|meta]");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const deployState = await readDeployStateRequired(process.cwd());
|
|
72
|
+
const provider = channelProviderForCli(type, args.flags.provider);
|
|
73
|
+
const payload = {
|
|
74
|
+
name,
|
|
75
|
+
type,
|
|
76
|
+
provider,
|
|
77
|
+
secrets: defaultChannelSecretsForCli(type, provider),
|
|
78
|
+
};
|
|
79
|
+
const response = await cloudPost<{ channel: CloudChannel }>(
|
|
80
|
+
parseCloudApiUrl(args.flags.api),
|
|
81
|
+
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
82
|
+
payload,
|
|
83
|
+
);
|
|
84
|
+
printChannelStatus(response.channel);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (subcommand === "setup") {
|
|
89
|
+
if (!first) {
|
|
90
|
+
throw new Error("Usage: agentkit channels setup <name> [--apply]");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const channel = await resolveCloudChannelByName(first, args.flags.api);
|
|
94
|
+
printChannelStatus(channel);
|
|
95
|
+
if (channel.type === "telegram") {
|
|
96
|
+
if (args.flags.apply) {
|
|
97
|
+
await applyTelegramWebhook(channel);
|
|
98
|
+
console.log("Telegram webhook updated.");
|
|
99
|
+
} else {
|
|
100
|
+
console.log(`Setup: call Telegram setWebhook with ${channel.webhook_url}`);
|
|
101
|
+
console.log("Use --apply to mutate Telegram webhook settings.");
|
|
102
|
+
}
|
|
103
|
+
} else if (channel.type === "whatsapp") {
|
|
104
|
+
console.log(`Next step: paste this URL into ${channel.provider}'s webhook settings.`);
|
|
105
|
+
} else {
|
|
106
|
+
console.log(`Website endpoint: ${channel.webhook_url}`);
|
|
107
|
+
console.log("Use this endpoint from the AgentKit website client or a signed server-to-server test.");
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (subcommand === "status") {
|
|
113
|
+
if (!first) {
|
|
114
|
+
throw new Error("Usage: agentkit channels status <name>");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const channel = await resolveCloudChannelByName(first, args.flags.api);
|
|
118
|
+
printChannelStatus(channel);
|
|
119
|
+
await printChannelDeliverySummary(channel, args.flags.api);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (subcommand === "test") {
|
|
124
|
+
if (!first) {
|
|
125
|
+
throw new Error('Usage: agentkit channels test <name> [--message "hello"] [--fixture <path>]');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const channel = await resolveCloudChannelByName(first, args.flags.api);
|
|
129
|
+
const apiUrl = parseCloudApiUrl(args.flags.api);
|
|
130
|
+
const body = await channelTestBody(channel, args.flags.message, args.flags.fixture);
|
|
131
|
+
const path = new URL(channel.webhook_url).pathname;
|
|
132
|
+
const headers = channelTestHeaders(channel);
|
|
133
|
+
const response = await cloudFetch(apiUrl, path, {
|
|
134
|
+
method: channel.type === "whatsapp" && channel.provider === "meta" ? "POST" : "POST",
|
|
135
|
+
headers,
|
|
136
|
+
body,
|
|
137
|
+
});
|
|
138
|
+
const payload = await response.json();
|
|
139
|
+
|
|
140
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (subcommand === "deliveries") {
|
|
148
|
+
if (first === "list") {
|
|
149
|
+
if (!second) {
|
|
150
|
+
throw new Error("Usage: agentkit channels deliveries list <name>");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const channel = await resolveCloudChannelByName(second, args.flags.api);
|
|
154
|
+
const response = await cloudGet<{ deliveries: CloudDelivery[] }>(
|
|
155
|
+
parseCloudApiUrl(args.flags.api),
|
|
156
|
+
`/v1/channels/${encodeURIComponent(channel.id)}/deliveries`,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (response.deliveries.length === 0) {
|
|
160
|
+
console.log("No channel deliveries found.");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log("ID\tCHANNEL\tDIRECTION\tSTATUS\tPROVIDER_EVENT\tRECEIVED");
|
|
165
|
+
for (const delivery of response.deliveries) {
|
|
166
|
+
console.log(
|
|
167
|
+
[
|
|
168
|
+
delivery.id,
|
|
169
|
+
delivery.channel_id,
|
|
170
|
+
delivery.direction,
|
|
171
|
+
delivery.status,
|
|
172
|
+
delivery.provider.event_id ?? "",
|
|
173
|
+
delivery.received_at,
|
|
174
|
+
].join("\t"),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (first === "show") {
|
|
181
|
+
if (!second) {
|
|
182
|
+
throw new Error("Usage: agentkit channels deliveries show <delivery-id>");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const response = await cloudGet<{ delivery: CloudDelivery }>(
|
|
186
|
+
parseCloudApiUrl(args.flags.api),
|
|
187
|
+
`/v1/channel-deliveries/${encodeURIComponent(second)}`,
|
|
188
|
+
);
|
|
189
|
+
console.log(JSON.stringify(response.delivery, null, 2));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
throw new Error(
|
|
195
|
+
"Usage: agentkit channels list | add | setup | status | test | deliveries list | deliveries show",
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function applyTelegramWebhook(channel: CloudChannel): Promise<void> {
|
|
200
|
+
const botToken = process.env.TELEGRAM_BOT_TOKEN;
|
|
201
|
+
const secretToken = process.env.TELEGRAM_WEBHOOK_SECRET;
|
|
202
|
+
|
|
203
|
+
if (!botToken || !secretToken) {
|
|
204
|
+
throw new Error("TELEGRAM_BOT_TOKEN and TELEGRAM_WEBHOOK_SECRET must be set to apply Telegram setup.");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let response: Response;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
response = await fetch(`https://api.telegram.org/bot${botToken}/setWebhook`, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: { "Content-Type": "application/json" },
|
|
213
|
+
body: JSON.stringify({
|
|
214
|
+
url: channel.webhook_url,
|
|
215
|
+
secret_token: secretToken,
|
|
216
|
+
allowed_updates: ["message"],
|
|
217
|
+
}),
|
|
218
|
+
});
|
|
219
|
+
} catch {
|
|
220
|
+
throw new Error("Telegram setWebhook request failed before Telegram returned a response.");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const payload = await response.json().catch(() => ({}));
|
|
224
|
+
|
|
225
|
+
if (!response.ok || !isTelegramOk(payload)) {
|
|
226
|
+
throw new Error(readTelegramSetupError(payload, response.status));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isTelegramOk(payload: unknown): payload is { ok: true } {
|
|
231
|
+
return Boolean(payload && typeof payload === "object" && "ok" in payload && payload.ok === true);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function readTelegramSetupError(payload: unknown, status: number): string {
|
|
235
|
+
if (payload && typeof payload === "object" && "description" in payload && typeof payload.description === "string") {
|
|
236
|
+
return `Telegram setWebhook failed: ${payload.description}`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return `Telegram setWebhook failed with HTTP ${status}.`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function readDeployStateIfExists(cwd: string): Promise<DeployState | null> {
|
|
243
|
+
return readLocalDeployStateIfExists(cwd);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function readDeployStateRequired(cwd: string): Promise<DeployState> {
|
|
247
|
+
const state = await readDeployStateIfExists(cwd);
|
|
248
|
+
|
|
249
|
+
if (!state?.deploy_id) {
|
|
250
|
+
throw new Error("No .agentkit/deploy.json found. Run agentkit deploy before creating hosted channels.");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return state;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function printChannelsTable(channels: CloudChannel[]): void {
|
|
257
|
+
if (channels.length === 0) {
|
|
258
|
+
console.log("No channels found.");
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.log("NAME\tTYPE\tPROVIDER\tSTATUS\tLAST_EVENT");
|
|
263
|
+
for (const channel of channels) {
|
|
264
|
+
console.log([channel.name, channel.type, channel.provider, channel.status, ""].join("\t"));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function printChannelStatus(channel: CloudChannel): void {
|
|
269
|
+
console.log(`Channel: ${channel.name}`);
|
|
270
|
+
if (channel.id) {
|
|
271
|
+
console.log(`ID: ${channel.id}`);
|
|
272
|
+
}
|
|
273
|
+
console.log(`Type: ${channel.type}`);
|
|
274
|
+
console.log(`Provider: ${channel.provider}`);
|
|
275
|
+
console.log(`Status: ${channel.status}`);
|
|
276
|
+
if (channel.webhook_url) {
|
|
277
|
+
console.log(`Webhook URL: ${channel.webhook_url}`);
|
|
278
|
+
}
|
|
279
|
+
console.log("Required secrets:");
|
|
280
|
+
for (const secret of channel.required_secrets) {
|
|
281
|
+
console.log(` ${secret.name}: ${secret.status}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function printChannelDeliverySummary(
|
|
286
|
+
channel: CloudChannel,
|
|
287
|
+
apiFlag: string | boolean | undefined,
|
|
288
|
+
): Promise<void> {
|
|
289
|
+
const response = await cloudGet<{ deliveries: CloudDelivery[] }>(
|
|
290
|
+
parseCloudApiUrl(apiFlag),
|
|
291
|
+
`/v1/channels/${encodeURIComponent(channel.id)}/deliveries`,
|
|
292
|
+
);
|
|
293
|
+
const inbound = [...response.deliveries].reverse().find((delivery) => delivery.direction === "inbound");
|
|
294
|
+
const outbound = [...response.deliveries].reverse().find((delivery) => delivery.direction === "outbound");
|
|
295
|
+
|
|
296
|
+
console.log("Recent delivery:");
|
|
297
|
+
console.log(` Last inbound: ${inbound ? `${inbound.status} ${inbound.received_at}` : "none"}`);
|
|
298
|
+
console.log(` Last outbound: ${outbound ? `${outbound.status} ${outbound.received_at}` : "none"}`);
|
|
299
|
+
console.log("Provider health: not_checked");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function resolveCloudChannelByName(name: string, apiFlag: string | boolean | undefined): Promise<CloudChannel> {
|
|
303
|
+
const deployState = await readDeployStateRequired(process.cwd());
|
|
304
|
+
const response = await cloudGet<{ channels: CloudChannel[] }>(
|
|
305
|
+
parseCloudApiUrl(apiFlag),
|
|
306
|
+
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
307
|
+
);
|
|
308
|
+
const channel = response.channels.find((candidate) => candidate.name === name);
|
|
309
|
+
|
|
310
|
+
if (!channel) {
|
|
311
|
+
throw new Error(`Channel "${name}" was not found for deploy ${deployState.deploy_id}.`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return channel;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function channelProviderForCli(type: string, providerFlag: string | boolean | undefined): string {
|
|
318
|
+
if (type === "website") {
|
|
319
|
+
return "agentkit";
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (type === "telegram") {
|
|
323
|
+
return "telegram";
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const provider = providerFlag ?? "zapster";
|
|
327
|
+
|
|
328
|
+
if (provider !== "zapster" && provider !== "meta") {
|
|
329
|
+
throw new Error('WhatsApp provider must be "zapster" or "meta".');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return provider;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function defaultChannelSecretsForCli(type: string, provider: string): string[] {
|
|
336
|
+
if (type === "website") {
|
|
337
|
+
return ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (type === "telegram") {
|
|
341
|
+
return ["TELEGRAM_BOT_TOKEN", "TELEGRAM_WEBHOOK_SECRET"];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (type === "whatsapp" && provider === "zapster") {
|
|
345
|
+
return ["ZAPSTER_API_KEY", "ZAPSTER_WEBHOOK_SECRET"];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (type === "whatsapp" && provider === "meta") {
|
|
349
|
+
return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function channelTestBody(
|
|
356
|
+
channel: CloudChannel,
|
|
357
|
+
messageFlag: string | boolean | undefined,
|
|
358
|
+
fixtureFlag: string | boolean | undefined,
|
|
359
|
+
): Promise<string> {
|
|
360
|
+
if (typeof fixtureFlag === "string") {
|
|
361
|
+
return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const message = typeof messageFlag === "string" ? messageFlag : "hello";
|
|
365
|
+
|
|
366
|
+
if (channel.type === "telegram") {
|
|
367
|
+
return JSON.stringify({
|
|
368
|
+
update_id: Date.now(),
|
|
369
|
+
message: {
|
|
370
|
+
message_id: 1,
|
|
371
|
+
chat: { id: 456, type: "private" },
|
|
372
|
+
from: { id: 123, first_name: "AgentKit" },
|
|
373
|
+
text: message,
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (channel.type === "whatsapp") {
|
|
379
|
+
return JSON.stringify({
|
|
380
|
+
event_id: `evt_${Date.now()}`,
|
|
381
|
+
instance_id: "local",
|
|
382
|
+
message: {
|
|
383
|
+
id: `msg_${Date.now()}`,
|
|
384
|
+
from: "5511999999999",
|
|
385
|
+
type: "text",
|
|
386
|
+
text: { body: message },
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return JSON.stringify({
|
|
392
|
+
event_id: `web_${Date.now()}`,
|
|
393
|
+
visitor_id: "visitor_test",
|
|
394
|
+
message: {
|
|
395
|
+
id: `msg_${Date.now()}`,
|
|
396
|
+
text: message,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function channelTestHeaders(channel: CloudChannel): Headers {
|
|
402
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
403
|
+
|
|
404
|
+
if (channel.type === "website") {
|
|
405
|
+
const token = process.env.AGENTKIT_WEBSITE_CHANNEL_TOKEN;
|
|
406
|
+
|
|
407
|
+
if (token) {
|
|
408
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (channel.type === "telegram") {
|
|
413
|
+
headers.set("X-Telegram-Bot-Api-Secret-Token", process.env.TELEGRAM_WEBHOOK_SECRET ?? "");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (channel.type === "whatsapp" && channel.provider === "zapster") {
|
|
417
|
+
headers.set("X-Zapster-Webhook-Secret", process.env.ZAPSTER_WEBHOOK_SECRET ?? "");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return headers;
|
|
421
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const defaultAgentKitCloudApiUrl = "https://agentkit-cloud.aibuilders.com.br";
|
|
2
|
+
export const defaultAgentKitDevPort = 4123;
|
|
3
|
+
export const defaultDeployChatAccessTokenName = "agentkit-chat-ui";
|
|
4
|
+
export const defaultDeployChatAccessTokenPath = ".agentkit/chat-access-token.json";
|