@an-sdk/cli 0.0.2 → 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.
- package/README.md +96 -0
- package/dist/index.js +179 -61
- package/package.json +4 -2
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @an-sdk/cli
|
|
2
|
+
|
|
3
|
+
Deploy AI agents to [AN](https://an.dev) from your terminal.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Login with your API key (get one at an.dev)
|
|
9
|
+
npx @an-sdk/cli login
|
|
10
|
+
|
|
11
|
+
# 2. Create your agent
|
|
12
|
+
npm init -y && npm install @an-sdk/agent zod
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Create `src/agent.ts`:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { agent, tool } from "@an-sdk/agent"
|
|
19
|
+
import { z } from "zod"
|
|
20
|
+
|
|
21
|
+
export default agent({
|
|
22
|
+
model: "claude-sonnet-4-6",
|
|
23
|
+
systemPrompt: "You are a helpful assistant.",
|
|
24
|
+
tools: {
|
|
25
|
+
add: tool({
|
|
26
|
+
description: "Add two numbers",
|
|
27
|
+
inputSchema: z.object({ a: z.number(), b: z.number() }),
|
|
28
|
+
execute: async ({ a, b }) => ({
|
|
29
|
+
content: [{ type: "text", text: `${a + b}` }],
|
|
30
|
+
}),
|
|
31
|
+
}),
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 3. Deploy
|
|
38
|
+
npx @an-sdk/cli deploy
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's it. Your agent is live.
|
|
42
|
+
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
### `an login`
|
|
46
|
+
|
|
47
|
+
Authenticate with the AN platform.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx @an-sdk/cli login
|
|
51
|
+
# Enter your API key: an_sk_...
|
|
52
|
+
# Authenticated as John (team: my-team)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Your key is saved to `~/.an/credentials`.
|
|
56
|
+
|
|
57
|
+
### `an deploy`
|
|
58
|
+
|
|
59
|
+
Bundle and deploy your agent.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx @an-sdk/cli deploy
|
|
63
|
+
# Bundling src/agent.ts...
|
|
64
|
+
# Bundled (12.3kb)
|
|
65
|
+
# Deploying my-agent...
|
|
66
|
+
# https://api.an.dev/v1/chat/my-agent
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The CLI:
|
|
70
|
+
1. Finds your entry point (`src/agent.ts`, `src/index.ts`, `agent.ts`, or `index.ts`)
|
|
71
|
+
2. Bundles your code + dependencies with esbuild
|
|
72
|
+
3. Deploys to a secure cloud sandbox
|
|
73
|
+
4. Returns your agent's URL
|
|
74
|
+
|
|
75
|
+
## How It Works
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Your code --> an deploy --> Cloud Sandbox --> Clients
|
|
79
|
+
(E2B + Claude)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- Your agent runs in an isolated cloud sandbox with Node.js, git, and system tools
|
|
83
|
+
- The Claude Agent SDK executes your agent with the model and tools you defined
|
|
84
|
+
- Clients connect via SSE streaming at the returned URL
|
|
85
|
+
|
|
86
|
+
## Project Linking
|
|
87
|
+
|
|
88
|
+
After first deploy, the CLI saves `.an/project.json` in your project directory. Subsequent deploys to the same project update the existing agent.
|
|
89
|
+
|
|
90
|
+
## Environment Variables
|
|
91
|
+
|
|
92
|
+
`AN_API_URL` — Override the API endpoint (default: `https://an.dev/api/v1`)
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT
|
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
|
}
|
|
@@ -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
|
-
|
|
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";
|
|
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
|
-
|
|
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,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@an-sdk/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "AN CLI — deploy AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"an": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
14
|
"build": "tsup",
|
|
14
15
|
"dev": "tsx src/index.ts"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
18
|
+
"@clack/prompts": "^1.0.1",
|
|
17
19
|
"esbuild": "^0.24.0"
|
|
18
20
|
},
|
|
19
21
|
"devDependencies": {
|