@an-sdk/cli 0.0.8 → 0.0.10
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/AGENTS.md +19 -0
- package/dist/index.js +303 -114
- package/docs/01-overview.md +61 -0
- package/docs/02-getting-started.md +110 -0
- package/docs/03-defining-agents.md +159 -0
- package/docs/04-react-ui.md +186 -0
- package/docs/05-nextjs.md +117 -0
- package/docs/06-node-sdk.md +125 -0
- package/docs/07-cli.md +88 -0
- package/docs/08-custom-tools.md +88 -0
- package/package.json +10 -1
- package/src/bundler.ts +156 -0
- package/src/config.ts +24 -0
- package/src/deploy.ts +95 -0
- package/src/detect.ts +16 -0
- package/src/env.ts +136 -0
- package/src/index.ts +100 -0
- package/src/login.ts +77 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @an-sdk/cli
|
|
2
|
+
|
|
3
|
+
CLI tool for deploying AI agents to the AN platform. Two commands: `an login` and `an deploy`.
|
|
4
|
+
|
|
5
|
+
## Docs
|
|
6
|
+
|
|
7
|
+
Full documentation: `./docs/` directory (8 guides covering the entire AN platform).
|
|
8
|
+
|
|
9
|
+
## Source
|
|
10
|
+
|
|
11
|
+
Source code: `./src/` directory.
|
|
12
|
+
|
|
13
|
+
## Key Entry Points
|
|
14
|
+
|
|
15
|
+
- `src/index.ts` — Command router (dispatches to login/deploy)
|
|
16
|
+
- `src/login.ts` — API key auth flow, saves to `~/.an/credentials`
|
|
17
|
+
- `src/deploy.ts` — Bundles agent code with esbuild, deploys to AN platform
|
|
18
|
+
- `src/config.ts` — Reads/writes credentials and project config
|
|
19
|
+
- `src/bundler.ts` — esbuild wrapper (externalizes `@an-sdk/agent`)
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,6 @@ import { join } from "path";
|
|
|
9
9
|
import { homedir } from "os";
|
|
10
10
|
var AN_DIR = join(homedir(), ".an");
|
|
11
11
|
var CREDENTIALS_PATH = join(AN_DIR, "credentials");
|
|
12
|
-
var PROJECT_PATH = join(process.cwd(), ".an", "project.json");
|
|
13
12
|
function getApiKey() {
|
|
14
13
|
if (process.env.AN_API_KEY) return process.env.AN_API_KEY;
|
|
15
14
|
try {
|
|
@@ -23,24 +22,45 @@ function saveApiKey(apiKey) {
|
|
|
23
22
|
mkdirSync(AN_DIR, { recursive: true });
|
|
24
23
|
writeFileSync(CREDENTIALS_PATH, JSON.stringify({ apiKey }, null, 2));
|
|
25
24
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (data.slug) return { projectId: data.agentId ?? "", projectSlug: data.slug };
|
|
31
|
-
return null;
|
|
32
|
-
} catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
25
|
+
|
|
26
|
+
// src/detect.ts
|
|
27
|
+
function isAgent() {
|
|
28
|
+
return !!(process.env.CLAUDE_CODE || process.env.CLAUDECODE || process.env.CURSOR_TRACE_ID || process.env.CURSOR_AGENT || process.env.CODEX_SANDBOX || process.env.GEMINI_CLI || process.env.AI_AGENT);
|
|
35
29
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
writeFileSync(PROJECT_PATH, JSON.stringify(data, null, 2));
|
|
30
|
+
function isInteractive() {
|
|
31
|
+
return !!process.stdin.isTTY && !isAgent();
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
// src/login.ts
|
|
42
35
|
var API_BASE = process.env.AN_API_URL || "https://an.dev/api/v1";
|
|
43
|
-
async function
|
|
36
|
+
async function verifyKey(apiKey) {
|
|
37
|
+
const res = await fetch(`${API_BASE}/me`, {
|
|
38
|
+
headers: { Authorization: `Bearer ${apiKey.trim()}` }
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
throw new Error("Invalid API key");
|
|
42
|
+
}
|
|
43
|
+
return res.json();
|
|
44
|
+
}
|
|
45
|
+
async function login(opts) {
|
|
46
|
+
const key = opts?.apiKey;
|
|
47
|
+
if (key) {
|
|
48
|
+
try {
|
|
49
|
+
const { user, team } = await verifyKey(key);
|
|
50
|
+
saveApiKey(key.trim());
|
|
51
|
+
console.log(`Authenticated as ${user.displayName || user.email} (team: ${team.name})`);
|
|
52
|
+
console.log("Key saved to ~/.an/credentials");
|
|
53
|
+
} catch {
|
|
54
|
+
console.error("Error: Invalid API key. Get a new one at https://an.dev/api-keys");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!isInteractive()) {
|
|
60
|
+
console.error("Error: No API key provided. Use --api-key KEY or set AN_API_KEY env var.");
|
|
61
|
+
console.error("Get your API key at https://an.dev/api-keys");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
44
64
|
p.intro("an login");
|
|
45
65
|
const existing = getApiKey();
|
|
46
66
|
if (existing) {
|
|
@@ -59,39 +79,37 @@ async function login() {
|
|
|
59
79
|
}
|
|
60
80
|
const s = p.spinner();
|
|
61
81
|
s.start("Verifying API key...");
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
try {
|
|
83
|
+
const { user, team } = await verifyKey(apiKey);
|
|
84
|
+
saveApiKey(apiKey.trim());
|
|
85
|
+
s.stop("Verified");
|
|
86
|
+
p.log.success(`Authenticated as ${user.displayName || user.email} (team: ${team.name})`);
|
|
87
|
+
p.log.info("Key saved to ~/.an/credentials");
|
|
88
|
+
p.outro("Done");
|
|
89
|
+
} catch {
|
|
66
90
|
s.stop("Invalid API key");
|
|
67
91
|
p.log.error("Invalid API key. Get a new one at https://an.dev/api-keys");
|
|
68
92
|
process.exit(1);
|
|
69
93
|
}
|
|
70
|
-
const { user, team } = await res.json();
|
|
71
|
-
saveApiKey(apiKey.trim());
|
|
72
|
-
s.stop("Verified");
|
|
73
|
-
p.log.success(`Authenticated as ${user.displayName || user.email} (team: ${team.name})`);
|
|
74
|
-
p.log.info("Key saved to ~/.an/credentials");
|
|
75
|
-
p.outro("Done");
|
|
76
94
|
}
|
|
77
95
|
|
|
78
96
|
// src/bundler.ts
|
|
79
97
|
import esbuild from "esbuild";
|
|
80
98
|
async function findAgentEntryPoints() {
|
|
81
|
-
const { existsSync, readdirSync, statSync } = await import("fs");
|
|
82
|
-
const { join:
|
|
83
|
-
if (!
|
|
99
|
+
const { existsSync: existsSync2, readdirSync, statSync } = await import("fs");
|
|
100
|
+
const { join: join3, basename, extname } = await import("path");
|
|
101
|
+
if (!existsSync2("agents") || !statSync("agents").isDirectory()) {
|
|
84
102
|
throw new Error("No agents/ directory found. See https://an.dev/docs to get started.");
|
|
85
103
|
}
|
|
86
104
|
const entries = [];
|
|
87
105
|
const items = readdirSync("agents");
|
|
88
106
|
for (const item of items) {
|
|
89
|
-
const fullPath =
|
|
107
|
+
const fullPath = join3("agents", item);
|
|
90
108
|
const stat = statSync(fullPath);
|
|
91
109
|
if (stat.isDirectory()) {
|
|
92
110
|
for (const indexFile of ["index.ts", "index.js"]) {
|
|
93
|
-
const indexPath =
|
|
94
|
-
if (
|
|
111
|
+
const indexPath = join3(fullPath, indexFile);
|
|
112
|
+
if (existsSync2(indexPath)) {
|
|
95
113
|
entries.push({ slug: item, entryPoint: indexPath });
|
|
96
114
|
break;
|
|
97
115
|
}
|
|
@@ -99,7 +117,7 @@ async function findAgentEntryPoints() {
|
|
|
99
117
|
} else if (stat.isFile()) {
|
|
100
118
|
const ext = extname(item);
|
|
101
119
|
if (ext === ".ts" || ext === ".js") {
|
|
102
|
-
entries.push({ slug:
|
|
120
|
+
entries.push({ slug: basename(item, ext), entryPoint: fullPath });
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
123
|
}
|
|
@@ -128,64 +146,86 @@ ${result.errors.map((e) => e.text).join("\n")}`
|
|
|
128
146
|
}
|
|
129
147
|
return Buffer.from(result.outputFiles[0].contents);
|
|
130
148
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
149
|
+
async function importBundle(bundle) {
|
|
150
|
+
const { writeFileSync: writeFileSync2, unlinkSync, mkdirSync: mkdirSync2 } = await import("fs");
|
|
151
|
+
const { join: join3 } = await import("path");
|
|
152
|
+
const tmpDir = join3(process.cwd(), ".an-tmp");
|
|
153
|
+
mkdirSync2(tmpDir, { recursive: true });
|
|
154
|
+
const tmpPath = join3(tmpDir, `an-bundle-${Date.now()}.mjs`);
|
|
155
|
+
try {
|
|
156
|
+
writeFileSync2(tmpPath, bundle);
|
|
157
|
+
const mod = await import(tmpPath);
|
|
158
|
+
const config = mod.default;
|
|
159
|
+
if (config?._type === "agent") return config;
|
|
160
|
+
return null;
|
|
161
|
+
} catch {
|
|
162
|
+
return null;
|
|
163
|
+
} finally {
|
|
164
|
+
try {
|
|
165
|
+
unlinkSync(tmpPath);
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const { rmdirSync } = await import("fs");
|
|
170
|
+
rmdirSync(tmpDir);
|
|
171
|
+
} catch {
|
|
172
|
+
}
|
|
173
|
+
}
|
|
137
174
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return
|
|
175
|
+
async function extractSandboxConfig(bundle) {
|
|
176
|
+
const config = await importBundle(bundle);
|
|
177
|
+
if (!config) return null;
|
|
178
|
+
const sandbox = config.sandbox;
|
|
179
|
+
if (sandbox && sandbox._type === "sandbox") {
|
|
180
|
+
const { _type, ...sandboxConfig } = sandbox;
|
|
181
|
+
return sandboxConfig;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
147
184
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
process.exit(0);
|
|
185
|
+
var HOOK_NAMES = ["onStart", "onToolCall", "onToolResult", "onStepFinish", "onFinish", "onError"];
|
|
186
|
+
async function extractAgentMetadata(bundle) {
|
|
187
|
+
try {
|
|
188
|
+
const config = await importBundle(bundle);
|
|
189
|
+
if (!config) return null;
|
|
190
|
+
const tools = [];
|
|
191
|
+
if (config.tools && typeof config.tools === "object") {
|
|
192
|
+
for (const [name, def] of Object.entries(config.tools)) {
|
|
193
|
+
tools.push({ name, description: def?.description ?? "" });
|
|
194
|
+
}
|
|
159
195
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (p2.isCancel(name)) {
|
|
181
|
-
p2.cancel("Deploy cancelled.");
|
|
182
|
-
process.exit(0);
|
|
196
|
+
const hooks = HOOK_NAMES.filter((h) => typeof config[h] === "function");
|
|
197
|
+
const metadata = {
|
|
198
|
+
model: config.model ?? "unknown",
|
|
199
|
+
permissionMode: config.permissionMode ?? "default",
|
|
200
|
+
maxTurns: config.maxTurns ?? 50,
|
|
201
|
+
tools,
|
|
202
|
+
hooks
|
|
203
|
+
};
|
|
204
|
+
if (config.systemPrompt !== void 0) {
|
|
205
|
+
metadata.systemPrompt = config.systemPrompt;
|
|
206
|
+
}
|
|
207
|
+
if (config.maxBudgetUsd !== void 0) {
|
|
208
|
+
metadata.maxBudgetUsd = config.maxBudgetUsd;
|
|
209
|
+
}
|
|
210
|
+
const sandbox = config.sandbox;
|
|
211
|
+
if (sandbox && sandbox._type === "sandbox") {
|
|
212
|
+
metadata.sandbox = {};
|
|
213
|
+
if (sandbox.apt) metadata.sandbox.apt = sandbox.apt;
|
|
214
|
+
if (sandbox.setup) metadata.sandbox.setup = sandbox.setup;
|
|
215
|
+
if (sandbox.cwd) metadata.sandbox.cwd = sandbox.cwd;
|
|
183
216
|
}
|
|
184
|
-
return
|
|
217
|
+
return metadata;
|
|
218
|
+
} catch {
|
|
219
|
+
return null;
|
|
185
220
|
}
|
|
186
|
-
const selected = projects.find((proj) => proj.slug === choice);
|
|
187
|
-
return { projectId: selected.id, projectSlug: selected.slug };
|
|
188
221
|
}
|
|
222
|
+
|
|
223
|
+
// src/deploy.ts
|
|
224
|
+
import { existsSync } from "fs";
|
|
225
|
+
import { join as join2 } from "path";
|
|
226
|
+
import * as p2 from "@clack/prompts";
|
|
227
|
+
var API_BASE2 = process.env.AN_API_URL || "https://an.dev/api/v1";
|
|
228
|
+
var AN_BASE = process.env.AN_URL || "https://an.dev";
|
|
189
229
|
async function deploy() {
|
|
190
230
|
p2.intro("an deploy");
|
|
191
231
|
const apiKey = getApiKey();
|
|
@@ -193,6 +233,9 @@ async function deploy() {
|
|
|
193
233
|
p2.log.error("Not logged in. Run `an login` first, or set AN_API_KEY env var.");
|
|
194
234
|
process.exit(1);
|
|
195
235
|
}
|
|
236
|
+
if (existsSync(join2(process.cwd(), ".an", "project.json"))) {
|
|
237
|
+
p2.log.warn(".an/project.json is no longer used and can be removed.");
|
|
238
|
+
}
|
|
196
239
|
let agents;
|
|
197
240
|
try {
|
|
198
241
|
agents = await findAgentEntryPoints();
|
|
@@ -201,25 +244,13 @@ async function deploy() {
|
|
|
201
244
|
process.exit(1);
|
|
202
245
|
}
|
|
203
246
|
p2.log.info(`Found ${agents.length} agent${agents.length > 1 ? "s" : ""}`);
|
|
204
|
-
let project = getProject();
|
|
205
|
-
let projectSlug;
|
|
206
|
-
let projectId;
|
|
207
|
-
if (project) {
|
|
208
|
-
projectSlug = project.projectSlug;
|
|
209
|
-
projectId = project.projectId;
|
|
210
|
-
} else if (process.stdin.isTTY) {
|
|
211
|
-
const linked = await linkProject(apiKey);
|
|
212
|
-
projectSlug = linked.projectSlug;
|
|
213
|
-
projectId = linked.projectId;
|
|
214
|
-
} else {
|
|
215
|
-
projectSlug = slugify(basename(process.cwd()));
|
|
216
|
-
projectId = "";
|
|
217
|
-
}
|
|
218
247
|
const deployed = [];
|
|
219
248
|
for (const agent of agents) {
|
|
220
249
|
const s = p2.spinner();
|
|
221
250
|
s.start(`Bundling ${agent.slug}...`);
|
|
222
251
|
const bundle = await bundleAgent(agent.entryPoint);
|
|
252
|
+
const sandboxConfig = await extractSandboxConfig(bundle);
|
|
253
|
+
const metadata = await extractAgentMetadata(bundle);
|
|
223
254
|
s.stop(`Bundled ${agent.slug} (${(bundle.length / 1024).toFixed(1)}kb)`);
|
|
224
255
|
const s2 = p2.spinner();
|
|
225
256
|
s2.start(`Deploying ${agent.slug}...`);
|
|
@@ -230,9 +261,10 @@ async function deploy() {
|
|
|
230
261
|
"Content-Type": "application/json"
|
|
231
262
|
},
|
|
232
263
|
body: JSON.stringify({
|
|
233
|
-
projectSlug,
|
|
234
264
|
slug: agent.slug,
|
|
235
|
-
bundle: bundle.toString("base64")
|
|
265
|
+
bundle: bundle.toString("base64"),
|
|
266
|
+
...sandboxConfig && { sandboxConfig },
|
|
267
|
+
...metadata && { metadata }
|
|
236
268
|
})
|
|
237
269
|
});
|
|
238
270
|
if (!res.ok) {
|
|
@@ -243,22 +275,20 @@ async function deploy() {
|
|
|
243
275
|
p2.log.info(`
|
|
244
276
|
Deployed before failure:`);
|
|
245
277
|
for (const a of deployed) {
|
|
246
|
-
p2.log.info(` ${a.slug} \u2192 ${AN_BASE}/a/${
|
|
278
|
+
p2.log.info(` ${a.slug} \u2192 ${AN_BASE}/a/${a.slug}`);
|
|
247
279
|
}
|
|
248
280
|
}
|
|
249
281
|
process.exit(1);
|
|
250
282
|
}
|
|
251
283
|
const result = await res.json();
|
|
252
|
-
projectId = result.projectId;
|
|
253
|
-
projectSlug = result.projectSlug;
|
|
254
284
|
deployed.push({ slug: agent.slug });
|
|
255
|
-
|
|
285
|
+
const versionTag = result.version ? ` (v${result.version})` : "";
|
|
286
|
+
s2.stop(`${agent.slug} deployed${versionTag}`);
|
|
256
287
|
}
|
|
257
|
-
|
|
258
|
-
p2.log.success(`Deployed ${deployed.length} agent${deployed.length > 1 ? "s" : ""} to ${projectSlug}`);
|
|
288
|
+
p2.log.success(`Deployed ${deployed.length} agent${deployed.length > 1 ? "s" : ""}`);
|
|
259
289
|
console.log();
|
|
260
290
|
for (const agent of deployed) {
|
|
261
|
-
console.log(` ${agent.slug} \u2192 ${AN_BASE}/a/${
|
|
291
|
+
console.log(` ${agent.slug} \u2192 ${AN_BASE}/a/${agent.slug}`);
|
|
262
292
|
}
|
|
263
293
|
console.log();
|
|
264
294
|
p2.log.info("Next steps:");
|
|
@@ -268,6 +298,125 @@ Deployed before failure:`);
|
|
|
268
298
|
p2.outro("Done");
|
|
269
299
|
}
|
|
270
300
|
|
|
301
|
+
// src/env.ts
|
|
302
|
+
import * as p3 from "@clack/prompts";
|
|
303
|
+
var API_BASE3 = process.env.AN_API_URL || "https://an.dev/api/v1";
|
|
304
|
+
function maskValue(value) {
|
|
305
|
+
if (value.length <= 4) return "\u2022".repeat(value.length);
|
|
306
|
+
return value.slice(0, 2) + "\u2022".repeat(Math.min(value.length - 4, 12)) + value.slice(-2);
|
|
307
|
+
}
|
|
308
|
+
function requireAuth() {
|
|
309
|
+
const apiKey = getApiKey();
|
|
310
|
+
if (!apiKey) {
|
|
311
|
+
p3.log.error("Not logged in. Run `an login` first, or set AN_API_KEY env var.");
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
return apiKey;
|
|
315
|
+
}
|
|
316
|
+
function requireAgentSlug(args2) {
|
|
317
|
+
const slug = args2[0];
|
|
318
|
+
if (!slug || slug.startsWith("-")) {
|
|
319
|
+
p3.log.error("Agent slug is required. Usage: an env list <agent-slug>");
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
return slug;
|
|
323
|
+
}
|
|
324
|
+
async function fetchEnvVars(apiKey, agentSlug) {
|
|
325
|
+
const res = await fetch(`${API_BASE3}/agents/${agentSlug}/env`, {
|
|
326
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
327
|
+
});
|
|
328
|
+
if (!res.ok) {
|
|
329
|
+
const err = await res.json().catch(() => ({}));
|
|
330
|
+
throw new Error(err.message || `Failed to fetch env vars (${res.status})`);
|
|
331
|
+
}
|
|
332
|
+
const data = await res.json();
|
|
333
|
+
return data.envVars ?? {};
|
|
334
|
+
}
|
|
335
|
+
async function putEnvVars(apiKey, agentSlug, envVars) {
|
|
336
|
+
const res = await fetch(`${API_BASE3}/agents/${agentSlug}/env`, {
|
|
337
|
+
method: "PUT",
|
|
338
|
+
headers: {
|
|
339
|
+
Authorization: `Bearer ${apiKey}`,
|
|
340
|
+
"Content-Type": "application/json"
|
|
341
|
+
},
|
|
342
|
+
body: JSON.stringify({ envVars })
|
|
343
|
+
});
|
|
344
|
+
if (!res.ok) {
|
|
345
|
+
const err = await res.json().catch(() => ({}));
|
|
346
|
+
throw new Error(err.message || `Failed to update env vars (${res.status})`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async function envList(args2) {
|
|
350
|
+
const apiKey = requireAuth();
|
|
351
|
+
const agentSlug = requireAgentSlug(args2);
|
|
352
|
+
const showValues = args2.includes("--show-values");
|
|
353
|
+
try {
|
|
354
|
+
const vars = await fetchEnvVars(apiKey, agentSlug);
|
|
355
|
+
const keys = Object.keys(vars);
|
|
356
|
+
if (keys.length === 0) {
|
|
357
|
+
p3.log.info("No environment variables set.");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
console.log();
|
|
361
|
+
for (const key of keys) {
|
|
362
|
+
const display = showValues ? vars[key] : maskValue(vars[key]);
|
|
363
|
+
console.log(` ${key}=${display}`);
|
|
364
|
+
}
|
|
365
|
+
console.log();
|
|
366
|
+
p3.log.info(`${keys.length} variable${keys.length !== 1 ? "s" : ""}`);
|
|
367
|
+
} catch (err) {
|
|
368
|
+
p3.log.error(err.message);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function envSet(args2) {
|
|
373
|
+
const apiKey = requireAuth();
|
|
374
|
+
const agentSlug = requireAgentSlug(args2);
|
|
375
|
+
const key = args2[1];
|
|
376
|
+
const value = args2.slice(2).join(" ");
|
|
377
|
+
if (!key || !value) {
|
|
378
|
+
p3.log.error("Usage: an env set <agent-slug> KEY VALUE");
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
if (!/^[A-Z0-9_]+$/.test(key)) {
|
|
382
|
+
p3.log.error("Invalid key. Use uppercase letters, numbers, and underscores only.");
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
const vars = await fetchEnvVars(apiKey, agentSlug);
|
|
387
|
+
const existed = key in vars;
|
|
388
|
+
vars[key] = value;
|
|
389
|
+
await putEnvVars(apiKey, agentSlug, vars);
|
|
390
|
+
p3.log.success(existed ? `Updated ${key}` : `Set ${key}`);
|
|
391
|
+
} catch (err) {
|
|
392
|
+
p3.log.error(err.message);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async function envRemove(args2) {
|
|
397
|
+
const apiKey = requireAuth();
|
|
398
|
+
const agentSlug = requireAgentSlug(args2);
|
|
399
|
+
const key = args2[1];
|
|
400
|
+
if (!key) {
|
|
401
|
+
p3.log.error("Usage: an env remove <agent-slug> KEY");
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
const vars = await fetchEnvVars(apiKey, agentSlug);
|
|
406
|
+
if (!(key in vars)) {
|
|
407
|
+
p3.log.info(`${key} not found`);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
delete vars[key];
|
|
411
|
+
const envVars = Object.keys(vars).length > 0 ? vars : null;
|
|
412
|
+
await putEnvVars(apiKey, agentSlug, envVars);
|
|
413
|
+
p3.log.success(`Removed ${key}`);
|
|
414
|
+
} catch (err) {
|
|
415
|
+
p3.log.error(err.message);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
271
420
|
// src/index.ts
|
|
272
421
|
import { createRequire } from "module";
|
|
273
422
|
var require2 = createRequire(import.meta.url);
|
|
@@ -275,23 +424,36 @@ var { version } = require2("../package.json");
|
|
|
275
424
|
var command = process.argv[2];
|
|
276
425
|
var args = process.argv.slice(3);
|
|
277
426
|
var hasFlag = (flag) => args.includes(flag);
|
|
427
|
+
function getFlagValue(flag) {
|
|
428
|
+
const idx = args.indexOf(flag);
|
|
429
|
+
if (idx === -1 || idx + 1 >= args.length) return void 0;
|
|
430
|
+
return args[idx + 1];
|
|
431
|
+
}
|
|
278
432
|
function showHelp() {
|
|
279
433
|
console.log(`AN CLI v${version} \u2014 deploy AI agents
|
|
280
434
|
`);
|
|
281
435
|
console.log("Usage: an <command>\n");
|
|
282
436
|
console.log("Commands:");
|
|
283
|
-
console.log(" an login
|
|
284
|
-
console.log(" an deploy
|
|
437
|
+
console.log(" an login Authenticate with AN platform");
|
|
438
|
+
console.log(" an deploy Bundle and deploy your agent");
|
|
439
|
+
console.log(" an env list <agent> List environment variables");
|
|
440
|
+
console.log(" an env set <agent> K V Set an environment variable");
|
|
441
|
+
console.log(" an env remove <agent> K Remove an environment variable");
|
|
285
442
|
console.log("\nOptions:");
|
|
286
|
-
console.log(" --help, -h
|
|
287
|
-
console.log(" --version, -v
|
|
443
|
+
console.log(" --help, -h Show help");
|
|
444
|
+
console.log(" --version, -v Show version");
|
|
445
|
+
console.log("\nEnvironment variables:");
|
|
446
|
+
console.log(" AN_API_KEY API key (skips login prompt)");
|
|
447
|
+
console.log(" AN_API_URL API base URL override");
|
|
288
448
|
console.log("\nGet started: an login");
|
|
289
449
|
console.log("Docs: https://an.dev/docs");
|
|
290
450
|
}
|
|
291
451
|
function showLoginHelp() {
|
|
292
|
-
console.log("Usage: an login\n");
|
|
293
|
-
console.log("Authenticate with the AN platform using an API key
|
|
294
|
-
console.log("
|
|
452
|
+
console.log("Usage: an login [options]\n");
|
|
453
|
+
console.log("Authenticate with the AN platform using an API key.\n");
|
|
454
|
+
console.log("Options:");
|
|
455
|
+
console.log(" --api-key KEY Pass API key directly (non-interactive)");
|
|
456
|
+
console.log("\nGet your API key at https://an.dev/api-keys");
|
|
295
457
|
}
|
|
296
458
|
function showDeployHelp() {
|
|
297
459
|
console.log("Usage: an deploy\n");
|
|
@@ -303,18 +465,45 @@ function showDeployHelp() {
|
|
|
303
465
|
console.log(" another.ts single-file agent");
|
|
304
466
|
console.log("\nDocs: https://an.dev/docs");
|
|
305
467
|
}
|
|
468
|
+
function showEnvHelp() {
|
|
469
|
+
console.log("Usage: an env <subcommand> <agent-slug>\n");
|
|
470
|
+
console.log("Manage environment variables for an agent.\n");
|
|
471
|
+
console.log("Subcommands:");
|
|
472
|
+
console.log(" an env list <agent> List all env vars (masked)");
|
|
473
|
+
console.log(" an env list <agent> --show-values List all env vars (plain text)");
|
|
474
|
+
console.log(" an env set <agent> KEY VALUE Set or update an env var");
|
|
475
|
+
console.log(" an env remove <agent> KEY Remove an env var");
|
|
476
|
+
}
|
|
306
477
|
if (command === "login") {
|
|
307
478
|
if (hasFlag("--help") || hasFlag("-h")) {
|
|
308
479
|
showLoginHelp();
|
|
309
480
|
} else {
|
|
310
|
-
await login();
|
|
481
|
+
await login({ apiKey: getFlagValue("--api-key") });
|
|
311
482
|
}
|
|
312
483
|
} else if (command === "deploy") {
|
|
313
484
|
if (hasFlag("--help") || hasFlag("-h")) {
|
|
314
485
|
showDeployHelp();
|
|
315
486
|
} else {
|
|
487
|
+
if (hasFlag("--project")) {
|
|
488
|
+
console.log("Warning: --project flag is no longer used and will be ignored.");
|
|
489
|
+
}
|
|
316
490
|
await deploy();
|
|
317
491
|
}
|
|
492
|
+
} else if (command === "env") {
|
|
493
|
+
const subcommand = args[0];
|
|
494
|
+
const subArgs = args.slice(1);
|
|
495
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
496
|
+
showEnvHelp();
|
|
497
|
+
} else if (subcommand === "list") {
|
|
498
|
+
await envList(subArgs);
|
|
499
|
+
} else if (subcommand === "set") {
|
|
500
|
+
await envSet(subArgs);
|
|
501
|
+
} else if (subcommand === "remove") {
|
|
502
|
+
await envRemove(subArgs);
|
|
503
|
+
} else {
|
|
504
|
+
console.log(`Unknown env subcommand: ${subcommand}`);
|
|
505
|
+
showEnvHelp();
|
|
506
|
+
}
|
|
318
507
|
} else if (command === "--version" || command === "-v") {
|
|
319
508
|
console.log(version);
|
|
320
509
|
} else {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# AN Platform Overview
|
|
2
|
+
|
|
3
|
+
AN is a platform for building, deploying, and embedding AI agents. You define an agent in TypeScript, deploy it with one command, and embed a chat UI in any React app.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Your Code (agent.ts)
|
|
9
|
+
|
|
|
10
|
+
v
|
|
11
|
+
an deploy (CLI)
|
|
12
|
+
|
|
|
13
|
+
v
|
|
14
|
+
AN Platform
|
|
15
|
+
|
|
|
16
|
+
+---> E2B Sandbox (isolated Node.js environment)
|
|
17
|
+
| |
|
|
18
|
+
| +---> Claude Agent SDK / Codex (executes your agent)
|
|
19
|
+
| |
|
|
20
|
+
| +---> Your custom tools run here
|
|
21
|
+
|
|
|
22
|
+
+---> AN Relay (relay.an.dev)
|
|
23
|
+
|
|
|
24
|
+
+---> SSE streaming to clients
|
|
25
|
+
|
|
|
26
|
+
+---> Token exchange (API key -> short-lived JWT)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Packages
|
|
30
|
+
|
|
31
|
+
| Package | Purpose |
|
|
32
|
+
|---------|---------|
|
|
33
|
+
| `@an-sdk/agent` | Define agents and tools with type inference |
|
|
34
|
+
| `@an-sdk/cli` | Deploy agents from your terminal |
|
|
35
|
+
| `@an-sdk/react` | Chat UI components (theming, tool renderers) |
|
|
36
|
+
| `@an-sdk/nextjs` | Next.js server-side token handler |
|
|
37
|
+
| `@an-sdk/node` | Server-side SDK (sandboxes, threads, tokens) |
|
|
38
|
+
|
|
39
|
+
## Key Concepts
|
|
40
|
+
|
|
41
|
+
- **Agent**: A TypeScript config defining model, system prompt, tools, and hooks. Runs in a cloud sandbox.
|
|
42
|
+
- **Tool**: A function your agent can call, with a Zod schema for input validation and full type inference.
|
|
43
|
+
- **Sandbox**: An isolated E2B cloud environment where your agent executes. Has Node.js, git, and system tools.
|
|
44
|
+
- **Thread**: A conversation within a sandbox. One sandbox can have multiple threads.
|
|
45
|
+
- **Relay**: The streaming gateway at `relay.an.dev`. Handles auth, routing, and SSE streaming.
|
|
46
|
+
|
|
47
|
+
## Runtimes
|
|
48
|
+
|
|
49
|
+
AN supports two runtimes:
|
|
50
|
+
|
|
51
|
+
- **`claude-code`** (default) — Uses the Claude Agent SDK. Full tool use, file editing, bash execution.
|
|
52
|
+
- **`codex`** — Uses OpenAI Codex via ACP provider.
|
|
53
|
+
|
|
54
|
+
## Auth Model
|
|
55
|
+
|
|
56
|
+
Two layers of authentication:
|
|
57
|
+
|
|
58
|
+
1. **Client -> Relay**: API key (`an_sk_...`) or short-lived JWT (via token exchange)
|
|
59
|
+
2. **Sandbox -> AI Provider**: Handled internally by the platform (Claude Proxy)
|
|
60
|
+
|
|
61
|
+
For web apps, use `@an-sdk/nextjs` to exchange your API key for a short-lived JWT on the server, so the key never reaches the browser.
|