@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 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
- function getProject() {
27
- try {
28
- const data = JSON.parse(readFileSync(PROJECT_PATH, "utf-8"));
29
- if (data.projectId && data.projectSlug) return data;
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 saveProject(data) {
37
- mkdirSync(join(process.cwd(), ".an"), { recursive: true });
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 login() {
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
- const res = await fetch(`${API_BASE}/me`, {
63
- headers: { Authorization: `Bearer ${apiKey.trim()}` }
64
- });
65
- if (!res.ok) {
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: join2, basename: basename2, extname } = await import("path");
83
- if (!existsSync("agents") || !statSync("agents").isDirectory()) {
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 = join2("agents", item);
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 = join2(fullPath, indexFile);
94
- if (existsSync(indexPath)) {
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: basename2(item, ext), entryPoint: fullPath });
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
- // src/deploy.ts
133
- import { basename } from "path";
134
- import * as p2 from "@clack/prompts";
135
- function slugify(name) {
136
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
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
- var API_BASE2 = process.env.AN_API_URL || "https://an.dev/api/v1";
139
- var AN_BASE = process.env.AN_URL || "https://an.dev";
140
- async function fetchProjects(apiKey) {
141
- const res = await fetch(`${API_BASE2}/projects`, {
142
- headers: { Authorization: `Bearer ${apiKey}` }
143
- });
144
- if (!res.ok) return [];
145
- const data = await res.json();
146
- return data.projects ?? [];
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
- async function linkProject(apiKey) {
149
- const projects = await fetchProjects(apiKey);
150
- if (projects.length === 0) {
151
- const name = await p2.text({
152
- message: "Project name",
153
- defaultValue: basename(process.cwd()),
154
- placeholder: basename(process.cwd())
155
- });
156
- if (p2.isCancel(name)) {
157
- p2.cancel("Deploy cancelled.");
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
- return { projectId: "", projectSlug: slugify(name) };
161
- }
162
- const options = [
163
- { value: "__new__", label: "Create a new project" },
164
- ...projects.map((proj) => ({ value: proj.slug, label: proj.name || proj.slug }))
165
- ];
166
- const choice = await p2.select({
167
- message: "Link to a project",
168
- options
169
- });
170
- if (p2.isCancel(choice)) {
171
- p2.cancel("Deploy cancelled.");
172
- process.exit(0);
173
- }
174
- if (choice === "__new__") {
175
- const name = await p2.text({
176
- message: "Project name",
177
- defaultValue: basename(process.cwd()),
178
- placeholder: basename(process.cwd())
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 { projectId: "", projectSlug: slugify(name) };
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/${projectSlug}/${a.slug}`);
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
- s2.stop(`${agent.slug} deployed`);
285
+ const versionTag = result.version ? ` (v${result.version})` : "";
286
+ s2.stop(`${agent.slug} deployed${versionTag}`);
256
287
  }
257
- saveProject({ projectId, projectSlug });
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/${projectSlug}/${agent.slug}`);
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 Authenticate with AN platform");
284
- console.log(" an deploy Bundle and deploy your agent");
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 Show help");
287
- console.log(" --version, -v Show version");
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("Get your API key at https://an.dev/api-keys");
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.