@andreprado/agentkit 0.1.0-alpha.3 → 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/README.md +9 -3
- package/docs/guides/add-tool.md +1 -1
- package/docs/guides/channels-implementation-map.md +243 -0
- package/docs/guides/create-agent.md +22 -1
- package/docs/guides/prepare-deploy.md +17 -6
- package/docs/guides/run-evals.md +28 -2
- package/docs/guides/security-rules.md +1 -1
- package/docs/guides/use-provider.md +16 -2
- package/docs/llms-full.txt +67 -36
- package/docs/llms.txt +13 -3
- 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 +295 -1696
- package/src/cli/process.ts +31 -0
- package/src/providers/test.ts +51 -0
- package/src/runtime/chat.ts +59 -4
- package/src/runtime/dev-server.ts +154 -16
- package/src/runtime/runtime-contract.ts +16 -2
- package/src/runtime/targets/cloudflare/build.ts +79 -4
- package/src/storage/sqlite.ts +2 -2
- package/src/templates/blank.ts +41 -0
- package/src/templates/dentista.ts +988 -0
- package/src/templates/index.ts +2 -0
- package/src/templates/support.ts +39 -0
package/docs/llms-full.txt
CHANGED
|
@@ -30,55 +30,65 @@ The capsule root is the runtime boundary. Run AgentKit commands from the directo
|
|
|
30
30
|
|
|
31
31
|
## Current Command Surface
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Current commands:
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
|
-
agentkit new <name> --template blank
|
|
37
|
-
agentkit
|
|
38
|
-
agentkit
|
|
39
|
-
agentkit chat --
|
|
40
|
-
agentkit
|
|
36
|
+
agentkit new <name> [--template blank|support|dentista] [--no-install]
|
|
37
|
+
agentkit dev [--port <number>]
|
|
38
|
+
agentkit open
|
|
39
|
+
agentkit chat-ui --deploy
|
|
40
|
+
agentkit chat-ui --deploy [--port <number>] [--token-file <path>]
|
|
41
|
+
agentkit chat --message <text> [--conversation-id <id>]
|
|
42
|
+
agentkit tool <name> [--input <path-or-json>]
|
|
43
|
+
agentkit db migrate
|
|
44
|
+
agentkit db reset --yes
|
|
45
|
+
agentkit db shell
|
|
46
|
+
agentkit db seed [--file <path>]
|
|
41
47
|
agentkit eval run
|
|
42
48
|
agentkit conversations list
|
|
43
49
|
agentkit conversations show <conversation-id>
|
|
44
50
|
agentkit channels list
|
|
45
|
-
agentkit channels add website
|
|
46
|
-
agentkit channels
|
|
47
|
-
agentkit channels
|
|
48
|
-
agentkit channels
|
|
49
|
-
agentkit channels
|
|
50
|
-
agentkit channels
|
|
51
|
-
agentkit channels test <name> --message "hello"
|
|
52
|
-
agentkit channels test <name> --fixture ./fixtures/provider-event.json
|
|
53
|
-
agentkit channels deliveries list <name>
|
|
54
|
-
agentkit channels deliveries show <delivery-id>
|
|
51
|
+
agentkit channels add <website|telegram|whatsapp> <name> [--provider zapster|meta] [--api <url>]
|
|
52
|
+
agentkit channels setup <name> [--apply] [--api <url>]
|
|
53
|
+
agentkit channels status <name> [--api <url>]
|
|
54
|
+
agentkit channels test <name> [--message <text>] [--fixture <path>] [--api <url>]
|
|
55
|
+
agentkit channels deliveries list <name> [--api <url>]
|
|
56
|
+
agentkit channels deliveries show <delivery-id> [--api <url>]
|
|
55
57
|
agentkit inspect
|
|
56
|
-
agentkit
|
|
57
|
-
agentkit env list
|
|
58
|
-
agentkit env unset <NAME>
|
|
59
|
-
agentkit docs path
|
|
60
|
-
agentkit docs llms
|
|
61
|
-
agentkit docs full
|
|
62
|
-
agentkit handoff codex [goal]
|
|
63
|
-
agentkit handoff claude [goal]
|
|
64
|
-
agentkit dev
|
|
65
|
-
agentkit dev --port 4124
|
|
66
|
-
agentkit build
|
|
67
|
-
agentkit deploy
|
|
68
|
-
agentkit deploy --dry-run
|
|
58
|
+
agentkit build [--target cloudflare|container]
|
|
69
59
|
agentkit login --token <token>
|
|
70
60
|
agentkit logout
|
|
61
|
+
agentkit deploy [--target cloudflare|vps] [--host <host>] [--api <url>] [--dry-run] [--anonymous] [--local-wrangler] [--smoke <message>]
|
|
62
|
+
agentkit deploy doctor [--api <url>] [--anonymous]
|
|
63
|
+
agentkit deploy smoke [--message <text>] [--api <url>]
|
|
71
64
|
agentkit deploy status
|
|
72
65
|
agentkit deploy pause
|
|
73
66
|
agentkit deploy resume
|
|
74
67
|
agentkit secret set <NAME> <VALUE>
|
|
68
|
+
agentkit secret set <NAME> --stdin
|
|
69
|
+
agentkit secret set <NAME> --from-env [ENV_NAME]
|
|
70
|
+
agentkit secret set <NAME> --from-local-env
|
|
71
|
+
agentkit secret sync --from-local
|
|
75
72
|
agentkit secret list
|
|
76
73
|
agentkit secret unset <NAME>
|
|
77
|
-
agentkit access token create <name>
|
|
74
|
+
agentkit access token create <name> [--api <url>] [--out <path>]
|
|
78
75
|
agentkit access token list
|
|
79
76
|
agentkit access token revoke <token-id>
|
|
77
|
+
agentkit env set <NAME> <VALUE>
|
|
78
|
+
agentkit env set <NAME> --stdin
|
|
79
|
+
agentkit env set <NAME> --from-env [ENV_NAME]
|
|
80
|
+
agentkit env list
|
|
81
|
+
agentkit env unset <NAME>
|
|
82
|
+
agentkit docs path
|
|
83
|
+
agentkit docs llms
|
|
84
|
+
agentkit docs full
|
|
85
|
+
agentkit handoff codex [goal]
|
|
86
|
+
agentkit handoff claude [goal]
|
|
87
|
+
agentkit help commands
|
|
80
88
|
```
|
|
81
89
|
|
|
90
|
+
Prefer `env set --stdin` or `--from-env` for local secret values, and prefer `secret set --stdin`, `--from-env`, or `--from-local-env` for hosted secrets. Inline `<VALUE>` forms exist for simple non-sensitive values, but agents should avoid putting secrets in shell history.
|
|
91
|
+
|
|
82
92
|
Planned commands described by the contract but not implemented yet:
|
|
83
93
|
|
|
84
94
|
```sh
|
|
@@ -125,7 +135,7 @@ Then open the folder in Codex, Claude Code, or another coding agent and ask dire
|
|
|
125
135
|
Develop an appointment and intake agent for an ophthalmology office.
|
|
126
136
|
```
|
|
127
137
|
|
|
128
|
-
The coding agent should infer the first useful version, edit `prompts/instructions.md`, `agentkit.config.ts`,
|
|
138
|
+
The coding agent should infer the first useful version, edit `prompts/instructions.md`, `agentkit.config.ts`, `schema.sql`, `tools/`, and `evals/`, then run the verification commands before finishing. Do not wait for a wizard or recipe. AgentKit provides the scaffold and contract; the coding agent implements directly in the capsule.
|
|
129
139
|
|
|
130
140
|
Optional handoff shortcut:
|
|
131
141
|
|
|
@@ -136,6 +146,8 @@ npm run agentkit -- handoff claude "Develop an appointment and intake agent for
|
|
|
136
146
|
|
|
137
147
|
The command prints a ready-to-paste prompt that points the coding agent at `AGENTKIT.md` and the packaged `llms-full.txt` contract.
|
|
138
148
|
|
|
149
|
+
The generated docs and handoff prompt must make UI testing explicit. For local UI testing, run `npm run dev`, open the printed `Chat:` URL, and tell the owner the exact URL. 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.
|
|
150
|
+
|
|
139
151
|
## Agent Config
|
|
140
152
|
|
|
141
153
|
`agentkit.config.ts` is the source of truth:
|
|
@@ -202,7 +214,11 @@ provider: {
|
|
|
202
214
|
secrets: [],
|
|
203
215
|
```
|
|
204
216
|
|
|
205
|
-
|
|
217
|
+
`test/fake` is deterministic. It is useful for scaffold checks, direct tool checks, and fake-provider evals, but it does not validate natural conversation quality.
|
|
218
|
+
|
|
219
|
+
Before claiming real conversation behavior has been tested, ask the owner which provider to use: OpenRouter, OpenAI, Anthropic, or another supported provider. Do not choose for them. After the owner chooses, update `agentkit.config.ts`, `.env.schema`, local secrets, hosted secrets if deploying, then rerun chat/UI checks.
|
|
220
|
+
|
|
221
|
+
OpenAI example:
|
|
206
222
|
|
|
207
223
|
```ts
|
|
208
224
|
provider: {
|
|
@@ -539,6 +555,8 @@ Run:
|
|
|
539
555
|
agentkit dev
|
|
540
556
|
```
|
|
541
557
|
|
|
558
|
+
If the default local port is occupied, `agentkit dev` chooses the next available port and prints the actual URLs. If the occupied port is another AgentKit server, the output names the agent using it.
|
|
559
|
+
|
|
542
560
|
Expected output:
|
|
543
561
|
|
|
544
562
|
```txt
|
|
@@ -550,6 +568,12 @@ Inspect: http://localhost:4123/_agentkit
|
|
|
550
568
|
Storage: .agentkit/agentkit.db
|
|
551
569
|
```
|
|
552
570
|
|
|
571
|
+
To reopen the local UI for the current capsule after the server is already running:
|
|
572
|
+
|
|
573
|
+
```sh
|
|
574
|
+
agentkit open
|
|
575
|
+
```
|
|
576
|
+
|
|
553
577
|
Endpoints:
|
|
554
578
|
|
|
555
579
|
```txt
|
|
@@ -567,6 +591,8 @@ curl -X POST http://localhost:4123/v1/chat \
|
|
|
567
591
|
-d '{"message":{"role":"user","content":"hello"}}'
|
|
568
592
|
```
|
|
569
593
|
|
|
594
|
+
The chat response includes `conversationId`. Send the same `conversationId` on the next `POST /v1/chat` request, or pass it to `agentkit chat --conversation-id <id>`, to continue with the persisted message history.
|
|
595
|
+
|
|
570
596
|
## Conversations
|
|
571
597
|
|
|
572
598
|
After chat:
|
|
@@ -610,6 +636,8 @@ persisted_tool_call
|
|
|
610
636
|
|
|
611
637
|
`persisted_tool_call` validates the tool call saved in local SQLite `tool_calls`, not a provider-specific raw response shape. It can be a tool name string or an object with `name`, `input`, `output`, `status`, and/or `visibility`. `tool_call` remains accepted as a backwards-compatible alias.
|
|
612
638
|
|
|
639
|
+
Evals run the normal capsule tools. If a tool would write externally, delete, charge money, send email, or call a real customer system, make its `execute` implementation branch on `ctx.runtime.environment === "eval"` and return deterministic non-destructive output for eval runs. Do not invent an eval-only mock API; keep the behavior inside the registered tool contract unless AgentKit adds a first-class mock facility later.
|
|
640
|
+
|
|
613
641
|
## Security Rules
|
|
614
642
|
|
|
615
643
|
Never commit:
|
|
@@ -633,7 +661,7 @@ prompts/
|
|
|
633
661
|
docs/
|
|
634
662
|
```
|
|
635
663
|
|
|
636
|
-
Local `.env` is development only. Use `.env.schema` as the committed secret-name contract; local AgentKit commands load `.env` directly. Hosted alpha deploys use `agentkit login --token ...` and managed secrets through `agentkit secret set/list/unset`.
|
|
664
|
+
Local `.env` is development only. Use `.env.schema` as the committed secret-name contract; local AgentKit commands load `.env` directly. Hosted alpha deploys use `agentkit login --token ...` and managed secrets through `agentkit secret set/list/unset` or `agentkit secret sync --from-local`. Prefer `--stdin`, `--from-env`, `--from-local-env`, or sync from local `.env` so secret values do not appear in shell history. Inline `<VALUE>` forms exist only for compatibility and simple non-sensitive values.
|
|
637
665
|
|
|
638
666
|
Tools are a security boundary. A tool must declare every secret it needs. The runtime injects only tool-declared secrets.
|
|
639
667
|
|
|
@@ -647,18 +675,21 @@ Current flow:
|
|
|
647
675
|
agentkit deploy --dry-run
|
|
648
676
|
agentkit login --token agk_user_...
|
|
649
677
|
agentkit deploy doctor
|
|
650
|
-
agentkit deploy
|
|
678
|
+
agentkit deploy --smoke "hello"
|
|
651
679
|
agentkit deploy status
|
|
680
|
+
agentkit deploy smoke --message "hello"
|
|
681
|
+
agentkit chat-ui --deploy
|
|
652
682
|
```
|
|
653
683
|
|
|
654
|
-
`agentkit deploy doctor` checks AgentKit Cloud login, `cloudflare_deploy_alpha`, hosted secrets, local `.env` names that still need `agentkit secret set`, and private-access runtime token handling. `agentkit deploy` sends the capsule to AgentKit Cloud, runs the same readiness check automatically before building and uploading, and writes the local chat/UI deploy access token to `.agentkit/chat-access-token.json` for private hosted deploys. Production alpha deploys require an account with `cloudflare_deploy_alpha`; local commands and dry-run builds do not require login. AgentKit owns infrastructure selection, backend migration, managed secrets, and public URL creation.
|
|
684
|
+
`agentkit deploy doctor` checks AgentKit Cloud login, `cloudflare_deploy_alpha`, online deploy capacity, hosted secrets, local `.env` names that still need `agentkit secret set`, and private-access runtime token handling. `agentkit deploy` sends the capsule to AgentKit Cloud, runs the same readiness check automatically before building and uploading, and writes the local chat/UI deploy access token to `.agentkit/chat-access-token.json` for private hosted deploys. `agentkit deploy --smoke "hello"` deploys and then tests `/v1/chat` with the deploy access token. `agentkit deploy smoke --message "hello"` repeats that smoke against the last local deploy. `agentkit chat-ui --deploy` serves a local UI pointed at the hosted deploy using that token without exposing it to browser code. Production alpha deploys require an account with `cloudflare_deploy_alpha`; local commands and dry-run builds do not require login. AgentKit owns infrastructure selection, backend migration, managed secrets, and public URL creation.
|
|
655
685
|
|
|
656
686
|
The CLI defaults to the hosted AgentKit Cloud API at `https://agentkit-cloud.aibuilders.com.br`. Use `AGENTKIT_CLOUD_API_URL` or `agentkit deploy --api <url>` only for local or alternate control-plane tests.
|
|
657
687
|
|
|
658
688
|
Account/access flow:
|
|
659
689
|
|
|
660
690
|
```sh
|
|
661
|
-
agentkit secret set OPENAI_API_KEY
|
|
691
|
+
agentkit secret set OPENAI_API_KEY --from-local-env
|
|
692
|
+
agentkit secret sync --from-local
|
|
662
693
|
agentkit secret list
|
|
663
694
|
agentkit access token create website-chat --out .agentkit/website-chat-access-token.json
|
|
664
695
|
agentkit access token list
|
package/docs/llms.txt
CHANGED
|
@@ -24,9 +24,10 @@ Task guides:
|
|
|
24
24
|
Current local commands:
|
|
25
25
|
|
|
26
26
|
```sh
|
|
27
|
-
agentkit new <name> --template blank|support
|
|
27
|
+
agentkit new <name> --template blank|support|dentista
|
|
28
28
|
agentkit new <name> --template blank --no-install
|
|
29
29
|
agentkit docs full
|
|
30
|
+
agentkit env set <NAME> --stdin
|
|
30
31
|
agentkit env list
|
|
31
32
|
agentkit env unset <NAME>
|
|
32
33
|
agentkit handoff codex|claude [goal]
|
|
@@ -43,16 +44,25 @@ agentkit channels test support-telegram --message "hello"
|
|
|
43
44
|
agentkit channels deliveries list support-telegram
|
|
44
45
|
agentkit login --token agk_user_...
|
|
45
46
|
agentkit deploy doctor
|
|
46
|
-
agentkit deploy
|
|
47
|
+
agentkit deploy --smoke "hello"
|
|
47
48
|
agentkit deploy status
|
|
49
|
+
agentkit deploy smoke --message "hello"
|
|
50
|
+
agentkit chat-ui --deploy
|
|
51
|
+
agentkit secret set <NAME> --from-local-env
|
|
52
|
+
agentkit secret sync --from-local
|
|
48
53
|
agentkit secret list
|
|
49
54
|
agentkit access token create website-chat --out .agentkit/website-chat-access-token.json
|
|
50
55
|
agentkit access token list
|
|
51
56
|
agentkit inspect
|
|
52
57
|
agentkit dev
|
|
58
|
+
agentkit open
|
|
53
59
|
```
|
|
54
60
|
|
|
55
|
-
Generated capsules include `AGENTKIT.md` and `AGENTS.md` so Codex, Claude Code, or another coding agent can treat the owner's natural-language request as the brief and start building immediately. `agentkit handoff codex "Develop an ophthalmology office intake agent"` is an optional prompt-printing shortcut for users who are not already inside a coding-agent workspace.
|
|
61
|
+
Generated capsules include `AGENTKIT.md` and `AGENTS.md` so Codex, Claude Code, or another coding agent can treat the owner's natural-language request as the brief and start building immediately. `agentkit handoff codex "Develop an ophthalmology office intake agent"` is an optional prompt-printing shortcut for users who are not already inside a coding-agent workspace. There is no wizard or recipe layer: the coding agent edits the capsule directly from the scaffold and contract.
|
|
62
|
+
|
|
63
|
+
UI testing is part of the handoff. For local UI testing, run `agentkit dev`, open the printed `Chat:` URL, and tell the owner the exact URL. After hosted deploy, run `agentkit chat-ui --deploy`, open the printed `Chat:` URL, and tell the owner it is connected to the deploy.
|
|
64
|
+
|
|
65
|
+
`test/fake` is deterministic and validates scaffold, direct tool calls, and fake-provider evals. It does not validate natural conversation quality. Before claiming real conversation behavior has been tested, ask the owner which provider to use: OpenRouter, OpenAI, Anthropic, or another supported provider. Do not choose for them.
|
|
56
66
|
|
|
57
67
|
Current local endpoints from `agentkit dev`:
|
|
58
68
|
|
package/package.json
CHANGED
package/src/cli/args.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type ParsedArgs = {
|
|
2
|
+
command?: string;
|
|
3
|
+
positional: string[];
|
|
4
|
+
flags: Record<string, string | boolean>;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function parseArgs(argv: string[]): ParsedArgs {
|
|
8
|
+
const [command, ...rest] = argv;
|
|
9
|
+
const positional: string[] = [];
|
|
10
|
+
const flags: Record<string, string | boolean> = {};
|
|
11
|
+
|
|
12
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
13
|
+
const value = rest[index];
|
|
14
|
+
|
|
15
|
+
if (!value.startsWith("--")) {
|
|
16
|
+
positional.push(value);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const name = value.slice(2);
|
|
21
|
+
const next = rest[index + 1];
|
|
22
|
+
|
|
23
|
+
if (next && !next.startsWith("--")) {
|
|
24
|
+
flags[name] = next;
|
|
25
|
+
index += 1;
|
|
26
|
+
} else {
|
|
27
|
+
flags[name] = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { command, positional, flags };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isHelpRequested(args: ParsedArgs): boolean {
|
|
35
|
+
return args.flags.help === true || args.flags.h === true || args.positional.includes("--help") || args.positional.includes("-h");
|
|
36
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { AgentKitError } from "../runtime/errors";
|
|
6
|
+
import { defaultAgentKitCloudApiUrl } from "./constants";
|
|
7
|
+
import { parseCloudApiUrl } from "./flags";
|
|
8
|
+
|
|
9
|
+
export type CloudAuthConfig = {
|
|
10
|
+
apiUrl: string;
|
|
11
|
+
token: string;
|
|
12
|
+
source?: "environment" | "file";
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type CloudMeResponse = {
|
|
16
|
+
account?: {
|
|
17
|
+
email?: string;
|
|
18
|
+
status?: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type CloudCapabilitiesResponse = {
|
|
23
|
+
capabilities?: Array<{
|
|
24
|
+
name?: string;
|
|
25
|
+
status?: string;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type CloudLimitsResponse = {
|
|
30
|
+
online_agents?: {
|
|
31
|
+
account?: {
|
|
32
|
+
used?: number | null;
|
|
33
|
+
limit?: number | null;
|
|
34
|
+
};
|
|
35
|
+
global?: {
|
|
36
|
+
used?: number | null;
|
|
37
|
+
limit?: number | null;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type DeployAccessTokenCreateResponse = {
|
|
43
|
+
access_token?: {
|
|
44
|
+
id?: string;
|
|
45
|
+
deploy_id?: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
token?: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type DeployAccessTokenListResponse = {
|
|
52
|
+
access_tokens?: Array<{
|
|
53
|
+
id?: string;
|
|
54
|
+
deploy_id?: string;
|
|
55
|
+
name?: string;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type CloudSecretsResponse = {
|
|
60
|
+
secrets?: Array<{
|
|
61
|
+
name?: string;
|
|
62
|
+
status?: string;
|
|
63
|
+
updated_at?: string;
|
|
64
|
+
}>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function cloudAuthPath(): string {
|
|
68
|
+
return join(homedir(), ".agentkit", "cloud.json");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function readCloudAuth(): Promise<CloudAuthConfig | null> {
|
|
72
|
+
if (process.env.AGENTKIT_CLOUD_API_TOKEN) {
|
|
73
|
+
return {
|
|
74
|
+
apiUrl: process.env.AGENTKIT_CLOUD_API_URL ?? defaultAgentKitCloudApiUrl,
|
|
75
|
+
token: process.env.AGENTKIT_CLOUD_API_TOKEN,
|
|
76
|
+
source: "environment",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(await readFile(cloudAuthPath(), "utf8"));
|
|
82
|
+
|
|
83
|
+
if (parsed && typeof parsed === "object" && typeof parsed.token === "string") {
|
|
84
|
+
return {
|
|
85
|
+
apiUrl: typeof parsed.apiUrl === "string" ? parsed.apiUrl : defaultAgentKitCloudApiUrl,
|
|
86
|
+
token: parsed.token,
|
|
87
|
+
source: "file",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function writeCloudAuth(config: CloudAuthConfig): Promise<void> {
|
|
98
|
+
await mkdir(join(homedir(), ".agentkit"), { recursive: true });
|
|
99
|
+
await writeFile(cloudAuthPath(), `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function clearCloudAuth(): Promise<void> {
|
|
103
|
+
await rm(cloudAuthPath(), { force: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function resolveCloudApiUrl(flag: string | boolean | undefined): Promise<string> {
|
|
107
|
+
if (flag !== undefined) {
|
|
108
|
+
return parseCloudApiUrl(flag);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const auth = await readCloudAuth();
|
|
112
|
+
return auth?.apiUrl ?? parseCloudApiUrl(undefined);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function verifyCloudLogin(
|
|
116
|
+
apiUrl: string,
|
|
117
|
+
token: string,
|
|
118
|
+
): Promise<{ me: CloudMeResponse; capabilities: CloudCapabilitiesResponse }> {
|
|
119
|
+
const me = await cloudApiRequestWithToken<CloudMeResponse>(apiUrl, "/v1/me", token, { method: "GET" });
|
|
120
|
+
const capabilities = await cloudApiRequestWithToken<CloudCapabilitiesResponse>(
|
|
121
|
+
apiUrl,
|
|
122
|
+
"/v1/me/capabilities",
|
|
123
|
+
token,
|
|
124
|
+
{ method: "GET" },
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return { me, capabilities };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function cloudGet<T>(apiUrl: string, path: string): Promise<T> {
|
|
131
|
+
const response = await cloudFetch(apiUrl, path);
|
|
132
|
+
const payload = await response.json();
|
|
133
|
+
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
throw new Error(readCloudError(payload, response.status));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return payload as T;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function cloudPost<T>(apiUrl: string, path: string, body: unknown): Promise<T> {
|
|
142
|
+
const response = await cloudFetch(apiUrl, path, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: { "Content-Type": "application/json" },
|
|
145
|
+
body: JSON.stringify(body),
|
|
146
|
+
});
|
|
147
|
+
const payload = await response.json();
|
|
148
|
+
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
throw new Error(readCloudError(payload, response.status));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return payload as T;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function cloudFetch(apiUrl: string, path: string, init: RequestInit = {}): Promise<Response> {
|
|
157
|
+
const headers = new Headers(init.headers);
|
|
158
|
+
const apiToken = process.env.AGENTKIT_CLOUD_API_TOKEN;
|
|
159
|
+
|
|
160
|
+
if (apiToken && !headers.has("Authorization")) {
|
|
161
|
+
headers.set("Authorization", `Bearer ${apiToken}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
|
|
165
|
+
...init,
|
|
166
|
+
headers,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function cloudApiRequest(apiUrl: string, path: string, init: RequestInit): Promise<unknown> {
|
|
171
|
+
const auth = await readCloudAuth();
|
|
172
|
+
const headers = new Headers(init.headers);
|
|
173
|
+
|
|
174
|
+
if (!headers.has("Content-Type") && init.body !== undefined) {
|
|
175
|
+
headers.set("Content-Type", "application/json");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (auth?.token && !headers.has("Authorization")) {
|
|
179
|
+
headers.set("Authorization", `Bearer ${auth.token}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const response = await fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
|
|
183
|
+
...init,
|
|
184
|
+
headers,
|
|
185
|
+
});
|
|
186
|
+
const payload = await response.json().catch(() => null);
|
|
187
|
+
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
const code =
|
|
190
|
+
payload && typeof payload === "object" && "error" in payload
|
|
191
|
+
? String((payload as { error?: { code?: unknown } }).error?.code ?? "cloud_request_failed")
|
|
192
|
+
: "cloud_request_failed";
|
|
193
|
+
let message = readCloudError(payload, response.status);
|
|
194
|
+
|
|
195
|
+
if (code === "auth_token_invalid") {
|
|
196
|
+
message = `${message} Token source: ${auth ? formatCloudAuthSource(auth) : "none"}. Run: agentkit login --token <token>`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
throw new AgentKitError(code, message);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return payload;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function cloudApiRequestWithToken<T>(
|
|
206
|
+
apiUrl: string,
|
|
207
|
+
path: string,
|
|
208
|
+
token: string,
|
|
209
|
+
init: RequestInit,
|
|
210
|
+
): Promise<T> {
|
|
211
|
+
const headers = new Headers(init.headers);
|
|
212
|
+
|
|
213
|
+
if (!headers.has("Content-Type") && init.body !== undefined) {
|
|
214
|
+
headers.set("Content-Type", "application/json");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
218
|
+
|
|
219
|
+
const response = await fetch(new URL(path, parseCloudApiUrl(apiUrl)).href, {
|
|
220
|
+
...init,
|
|
221
|
+
headers,
|
|
222
|
+
});
|
|
223
|
+
const payload = await response.json().catch(() => null);
|
|
224
|
+
|
|
225
|
+
if (!response.ok) {
|
|
226
|
+
const code =
|
|
227
|
+
payload && typeof payload === "object" && "error" in payload
|
|
228
|
+
? String((payload as { error?: { code?: unknown } }).error?.code ?? "cloud_request_failed")
|
|
229
|
+
: "cloud_request_failed";
|
|
230
|
+
throw new AgentKitError(code, readCloudError(payload, response.status));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return payload as T;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function readCloudError(payload: unknown, status: number): string {
|
|
237
|
+
if (payload && typeof payload === "object" && "error" in payload) {
|
|
238
|
+
const error = (payload as { error?: { message?: unknown } }).error;
|
|
239
|
+
if (typeof error?.message === "string") {
|
|
240
|
+
return error.message;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return `AgentKit Cloud request failed with HTTP ${status}.`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function formatCloudAuthSource(auth: CloudAuthConfig): string {
|
|
248
|
+
if (auth.source === "environment") {
|
|
249
|
+
return "AGENTKIT_CLOUD_API_TOKEN";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (auth.source === "file") {
|
|
253
|
+
return cloudAuthPath();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return "AgentKit Cloud auth";
|
|
257
|
+
}
|