@an-sdk/cli 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +179 -61
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/login.ts
4
- import { createInterface } from "readline/promises";
4
+ import * as p from "@clack/prompts";
5
5
 
6
6
  // src/config.ts
7
7
  import { readFileSync, writeFileSync, mkdirSync } from "fs";
@@ -11,6 +11,7 @@ var AN_DIR = join(homedir(), ".an");
11
11
  var CREDENTIALS_PATH = join(AN_DIR, "credentials");
12
12
  var PROJECT_PATH = join(process.cwd(), ".an", "project.json");
13
13
  function getApiKey() {
14
+ if (process.env.AN_API_KEY) return process.env.AN_API_KEY;
14
15
  try {
15
16
  const data = JSON.parse(readFileSync(CREDENTIALS_PATH, "utf-8"));
16
17
  return data.apiKey || null;
@@ -24,7 +25,10 @@ function saveApiKey(apiKey) {
24
25
  }
25
26
  function getProject() {
26
27
  try {
27
- return JSON.parse(readFileSync(PROJECT_PATH, "utf-8"));
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;
28
32
  } catch {
29
33
  return null;
30
34
  }
@@ -37,51 +41,71 @@ function saveProject(data) {
37
41
  // src/login.ts
38
42
  var API_BASE = process.env.AN_API_URL || "https://an.dev/api/v1";
39
43
  async function login() {
44
+ p.intro("an login");
40
45
  const existing = getApiKey();
41
46
  if (existing) {
42
- console.log(
43
- "Already logged in. Run `an login` again to re-authenticate.\n"
44
- );
47
+ p.log.info("Already logged in. Continuing will re-authenticate.");
45
48
  }
46
- const rl = createInterface({ input: process.stdin, output: process.stdout });
47
- const apiKey = await rl.question("Enter your API key: ");
48
- rl.close();
49
- if (!apiKey.trim()) {
50
- console.error("Error: API key cannot be empty");
51
- process.exit(1);
49
+ const apiKey = await p.text({
50
+ message: "Enter your API key",
51
+ validate: (val) => {
52
+ if (!val.trim()) return "API key cannot be empty";
53
+ }
54
+ });
55
+ if (p.isCancel(apiKey)) {
56
+ p.cancel("Login cancelled.");
57
+ process.exit(0);
52
58
  }
59
+ const s = p.spinner();
60
+ s.start("Verifying API key...");
53
61
  const res = await fetch(`${API_BASE}/me`, {
54
62
  headers: { Authorization: `Bearer ${apiKey.trim()}` }
55
63
  });
56
64
  if (!res.ok) {
57
- console.error("Error: Invalid API key");
65
+ s.stop("Invalid API key");
66
+ p.log.error("The API key could not be verified. Check it and try again.");
58
67
  process.exit(1);
59
68
  }
60
69
  const { user, team } = await res.json();
61
70
  saveApiKey(apiKey.trim());
62
- console.log(
63
- `
64
- \u2713 Authenticated as ${user.displayName || user.email} (team: ${team.name})`
65
- );
66
- console.log(" Key saved to ~/.an/credentials\n");
71
+ s.stop("Verified");
72
+ p.log.success(`Authenticated as ${user.displayName || user.email} (team: ${team.name})`);
73
+ p.log.info("Key saved to ~/.an/credentials");
74
+ p.outro("Done");
67
75
  }
68
76
 
69
77
  // src/bundler.ts
70
78
  import esbuild from "esbuild";
71
- var ENTRY_CANDIDATES = [
72
- "src/agent.ts",
73
- "src/index.ts",
74
- "agent.ts",
75
- "index.ts"
76
- ];
77
- async function findEntryPoint() {
78
- const { existsSync } = await import("fs");
79
- for (const candidate of ENTRY_CANDIDATES) {
80
- if (existsSync(candidate)) return candidate;
81
- }
82
- throw new Error(
83
- "Cannot find agent entry point. Expected one of:\n" + ENTRY_CANDIDATES.map((c) => ` ${c}`).join("\n")
84
- );
79
+ async function findAgentEntryPoints() {
80
+ const { existsSync, readdirSync, statSync } = await import("fs");
81
+ const { join: join2, basename: basename2, extname } = await import("path");
82
+ if (!existsSync("agents") || !statSync("agents").isDirectory()) {
83
+ throw new Error("No agents found. Create agents in the `agents/` directory.");
84
+ }
85
+ const entries = [];
86
+ const items = readdirSync("agents");
87
+ for (const item of items) {
88
+ const fullPath = join2("agents", item);
89
+ const stat = statSync(fullPath);
90
+ if (stat.isDirectory()) {
91
+ for (const indexFile of ["index.ts", "index.js"]) {
92
+ const indexPath = join2(fullPath, indexFile);
93
+ if (existsSync(indexPath)) {
94
+ entries.push({ slug: item, entryPoint: indexPath });
95
+ break;
96
+ }
97
+ }
98
+ } else if (stat.isFile()) {
99
+ const ext = extname(item);
100
+ if (ext === ".ts" || ext === ".js") {
101
+ entries.push({ slug: basename2(item, ext), entryPoint: fullPath });
102
+ }
103
+ }
104
+ }
105
+ if (entries.length === 0) {
106
+ throw new Error("No agents found. Create agents in the `agents/` directory.");
107
+ }
108
+ return entries;
85
109
  }
86
110
  async function bundleAgent(entryPoint) {
87
111
  const result = await esbuild.build({
@@ -106,44 +130,138 @@ ${result.errors.map((e) => e.text).join("\n")}`
106
130
 
107
131
  // src/deploy.ts
108
132
  import { basename } from "path";
133
+ import * as p2 from "@clack/prompts";
134
+ function slugify(name) {
135
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
136
+ }
109
137
  var API_BASE2 = process.env.AN_API_URL || "https://an.dev/api/v1";
138
+ var AN_BASE = process.env.AN_URL || "https://an.dev";
139
+ async function fetchProjects(apiKey) {
140
+ const res = await fetch(`${API_BASE2}/projects`, {
141
+ headers: { Authorization: `Bearer ${apiKey}` }
142
+ });
143
+ if (!res.ok) return [];
144
+ const data = await res.json();
145
+ return data.projects ?? [];
146
+ }
147
+ async function linkProject(apiKey) {
148
+ const projects = await fetchProjects(apiKey);
149
+ if (projects.length === 0) {
150
+ const name = await p2.text({
151
+ message: "Project name",
152
+ defaultValue: basename(process.cwd()),
153
+ placeholder: basename(process.cwd())
154
+ });
155
+ if (p2.isCancel(name)) {
156
+ p2.cancel("Deploy cancelled.");
157
+ process.exit(0);
158
+ }
159
+ return { projectId: "", projectSlug: slugify(name) };
160
+ }
161
+ const options = [
162
+ { value: "__new__", label: "Create a new project" },
163
+ ...projects.map((proj) => ({ value: proj.slug, label: proj.name || proj.slug }))
164
+ ];
165
+ const choice = await p2.select({
166
+ message: "Link to a project",
167
+ options
168
+ });
169
+ if (p2.isCancel(choice)) {
170
+ p2.cancel("Deploy cancelled.");
171
+ process.exit(0);
172
+ }
173
+ if (choice === "__new__") {
174
+ const name = await p2.text({
175
+ message: "Project name",
176
+ defaultValue: basename(process.cwd()),
177
+ placeholder: basename(process.cwd())
178
+ });
179
+ if (p2.isCancel(name)) {
180
+ p2.cancel("Deploy cancelled.");
181
+ process.exit(0);
182
+ }
183
+ return { projectId: "", projectSlug: slugify(name) };
184
+ }
185
+ const selected = projects.find((proj) => proj.slug === choice);
186
+ return { projectId: selected.id, projectSlug: selected.slug };
187
+ }
110
188
  async function deploy() {
189
+ p2.intro("an deploy");
111
190
  const apiKey = getApiKey();
112
191
  if (!apiKey) {
113
- console.error("Not logged in. Run `an login` first.");
192
+ p2.log.error("Not logged in. Run `an login` first, or set AN_API_KEY env var.");
114
193
  process.exit(1);
115
194
  }
116
- const entryPoint = await findEntryPoint();
117
- console.log(` Bundling ${entryPoint}...`);
118
- const bundle = await bundleAgent(entryPoint);
119
- console.log(` Bundled (${(bundle.length / 1024).toFixed(1)}kb)`);
120
- const project = getProject();
121
- const slug = project?.slug || basename(process.cwd());
122
- console.log(` Deploying ${slug}...`);
123
- const res = await fetch(`${API_BASE2}/agents/deploy`, {
124
- method: "POST",
125
- headers: {
126
- Authorization: `Bearer ${apiKey}`,
127
- "Content-Type": "application/json"
128
- },
129
- body: JSON.stringify({
130
- slug,
131
- bundle: bundle.toString("base64")
132
- })
133
- });
134
- if (!res.ok) {
135
- const err = await res.json().catch(() => ({}));
136
- console.error(`
137
- Error: ${err.message || "Deploy failed"}`);
195
+ let agents;
196
+ try {
197
+ agents = await findAgentEntryPoints();
198
+ } catch (err) {
199
+ p2.log.error(err.message);
138
200
  process.exit(1);
139
201
  }
140
- const result = await res.json();
141
- saveProject({ agentId: result.agentId, slug: result.slug });
142
- console.log(`
143
- \u2713 ${result.url}
144
- `);
145
- console.log(` Agent: ${result.slug}`);
146
- console.log(` Sandbox: ${result.sandboxId}`);
202
+ p2.log.info(`Found ${agents.length} agent${agents.length > 1 ? "s" : ""}`);
203
+ let project = getProject();
204
+ let projectSlug;
205
+ let projectId;
206
+ if (project) {
207
+ projectSlug = project.projectSlug;
208
+ projectId = project.projectId;
209
+ } else if (process.stdin.isTTY) {
210
+ const linked = await linkProject(apiKey);
211
+ projectSlug = linked.projectSlug;
212
+ projectId = linked.projectId;
213
+ } else {
214
+ projectSlug = slugify(basename(process.cwd()));
215
+ projectId = "";
216
+ }
217
+ const deployed = [];
218
+ for (const agent of agents) {
219
+ const s = p2.spinner();
220
+ s.start(`Bundling ${agent.slug}...`);
221
+ const bundle = await bundleAgent(agent.entryPoint);
222
+ s.stop(`Bundled ${agent.slug} (${(bundle.length / 1024).toFixed(1)}kb)`);
223
+ const s2 = p2.spinner();
224
+ s2.start(`Deploying ${agent.slug}...`);
225
+ const res = await fetch(`${API_BASE2}/agents/deploy`, {
226
+ method: "POST",
227
+ headers: {
228
+ Authorization: `Bearer ${apiKey}`,
229
+ "Content-Type": "application/json"
230
+ },
231
+ body: JSON.stringify({
232
+ projectSlug,
233
+ slug: agent.slug,
234
+ bundle: bundle.toString("base64")
235
+ })
236
+ });
237
+ if (!res.ok) {
238
+ const err = await res.json().catch(() => ({}));
239
+ s2.stop(`Failed to deploy ${agent.slug}`);
240
+ p2.log.error(err.message || "Deploy failed");
241
+ if (deployed.length > 0) {
242
+ p2.log.info(`
243
+ Deployed before failure:`);
244
+ for (const a of deployed) {
245
+ p2.log.info(` ${a.slug} \u2192 ${AN_BASE}/a/${projectSlug}/${a.slug}`);
246
+ }
247
+ }
248
+ process.exit(1);
249
+ }
250
+ const result = await res.json();
251
+ projectId = result.projectId;
252
+ projectSlug = result.projectSlug;
253
+ deployed.push({ slug: agent.slug });
254
+ s2.stop(`${agent.slug} deployed`);
255
+ }
256
+ saveProject({ projectId, projectSlug });
257
+ p2.log.success(`Deployed ${deployed.length} agent${deployed.length > 1 ? "s" : ""} to ${projectSlug}`);
258
+ console.log();
259
+ for (const agent of deployed) {
260
+ console.log(` ${agent.slug} \u2192 ${AN_BASE}/a/${projectSlug}/${agent.slug}`);
261
+ }
262
+ console.log();
263
+ console.log(` Dashboard \u2192 ${AN_BASE}/an/deployments`);
264
+ p2.outro("Done");
147
265
  }
148
266
 
149
267
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@an-sdk/cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "AN CLI — deploy AI agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "dev": "tsx src/index.ts"
16
16
  },
17
17
  "dependencies": {
18
+ "@clack/prompts": "^1.0.1",
18
19
  "esbuild": "^0.24.0"
19
20
  },
20
21
  "devDependencies": {