@an-sdk/cli 0.0.3 → 0.0.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/dist/index.js +181 -63
- 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
|
|
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
|
-
|
|
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
|
}
|
|
@@ -35,53 +39,73 @@ function saveProject(data) {
|
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
// src/login.ts
|
|
38
|
-
var API_BASE = process.env.AN_API_URL || "https://an.dev/api/v1";
|
|
42
|
+
var API_BASE = process.env.AN_API_URL || "https://www.an.dev/api/v1";
|
|
39
43
|
async function login() {
|
|
44
|
+
p.intro("an login");
|
|
40
45
|
const existing = getApiKey();
|
|
41
46
|
if (existing) {
|
|
42
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
for (const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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";
|
|
109
|
-
|
|
133
|
+
import * as p2 from "@clack/prompts";
|
|
134
|
+
function slugify(name) {
|
|
135
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
136
|
+
}
|
|
137
|
+
var API_BASE2 = process.env.AN_API_URL || "https://www.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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
+
"version": "0.0.5",
|
|
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": {
|