@an-sdk/cli 0.0.7 → 0.0.9

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/src/deploy.ts ADDED
@@ -0,0 +1,177 @@
1
+ import { getApiKey, getProject, saveProject } from "./config.js"
2
+ import { findAgentEntryPoints, bundleAgent } from "./bundler.js"
3
+ import { basename } from "path"
4
+ import * as p from "@clack/prompts"
5
+
6
+ function slugify(name: string): string {
7
+ return name
8
+ .toLowerCase()
9
+ .replace(/[^a-z0-9]+/g, "-")
10
+ .replace(/^-|-$/g, "")
11
+ }
12
+
13
+ const API_BASE = process.env.AN_API_URL || "https://an.dev/api/v1"
14
+ const AN_BASE = process.env.AN_URL || "https://an.dev"
15
+
16
+ type ProjectInfo = { id: string; slug: string; name: string }
17
+
18
+ async function fetchProjects(apiKey: string): Promise<ProjectInfo[]> {
19
+ const res = await fetch(`${API_BASE}/projects`, {
20
+ headers: { Authorization: `Bearer ${apiKey}` },
21
+ })
22
+ if (!res.ok) return []
23
+ const data = await res.json()
24
+ return data.projects ?? []
25
+ }
26
+
27
+ async function linkProject(apiKey: string): Promise<{ projectId: string; projectSlug: string }> {
28
+ const projects = await fetchProjects(apiKey)
29
+
30
+ if (projects.length === 0) {
31
+ // No existing projects — just ask for a name
32
+ const name = await p.text({
33
+ message: "Project name",
34
+ defaultValue: basename(process.cwd()),
35
+ placeholder: basename(process.cwd()),
36
+ })
37
+ if (p.isCancel(name)) {
38
+ p.cancel("Deploy cancelled.")
39
+ process.exit(0)
40
+ }
41
+ return { projectId: "", projectSlug: slugify(name) }
42
+ }
43
+
44
+ // Has existing projects — let them pick or create new
45
+ const options = [
46
+ { value: "__new__", label: "Create a new project" },
47
+ ...projects.map((proj) => ({ value: proj.slug, label: proj.name || proj.slug })),
48
+ ]
49
+
50
+ const choice = await p.select({
51
+ message: "Link to a project",
52
+ options,
53
+ })
54
+ if (p.isCancel(choice)) {
55
+ p.cancel("Deploy cancelled.")
56
+ process.exit(0)
57
+ }
58
+
59
+ if (choice === "__new__") {
60
+ const name = await p.text({
61
+ message: "Project name",
62
+ defaultValue: basename(process.cwd()),
63
+ placeholder: basename(process.cwd()),
64
+ })
65
+ if (p.isCancel(name)) {
66
+ p.cancel("Deploy cancelled.")
67
+ process.exit(0)
68
+ }
69
+ return { projectId: "", projectSlug: slugify(name) }
70
+ }
71
+
72
+ // Linked to existing project
73
+ const selected = projects.find((proj) => proj.slug === choice)!
74
+ return { projectId: selected.id, projectSlug: selected.slug }
75
+ }
76
+
77
+ export async function deploy() {
78
+ p.intro("an deploy")
79
+
80
+ // 1. Check auth
81
+ const apiKey = getApiKey()
82
+ if (!apiKey) {
83
+ p.log.error("Not logged in. Run `an login` first, or set AN_API_KEY env var.")
84
+ process.exit(1)
85
+ }
86
+
87
+ // 2. Find agent entry points
88
+ let agents
89
+ try {
90
+ agents = await findAgentEntryPoints()
91
+ } catch (err: any) {
92
+ p.log.error(err.message)
93
+ process.exit(1)
94
+ }
95
+ p.log.info(`Found ${agents.length} agent${agents.length > 1 ? "s" : ""}`)
96
+
97
+ // 3. Determine project
98
+ let project = getProject()
99
+ let projectSlug: string
100
+ let projectId: string
101
+
102
+ if (project) {
103
+ projectSlug = project.projectSlug
104
+ projectId = project.projectId
105
+ } else if (process.stdin.isTTY) {
106
+ // Interactive linking
107
+ const linked = await linkProject(apiKey)
108
+ projectSlug = linked.projectSlug
109
+ projectId = linked.projectId
110
+ } else {
111
+ // Non-interactive: auto-create from cwd name
112
+ projectSlug = slugify(basename(process.cwd()))
113
+ projectId = ""
114
+ }
115
+
116
+ // 4. Deploy each agent
117
+ const deployed: { slug: string }[] = []
118
+
119
+ for (const agent of agents) {
120
+ const s = p.spinner()
121
+ s.start(`Bundling ${agent.slug}...`)
122
+ const bundle = await bundleAgent(agent.entryPoint)
123
+ s.stop(`Bundled ${agent.slug} (${(bundle.length / 1024).toFixed(1)}kb)`)
124
+
125
+ const s2 = p.spinner()
126
+ s2.start(`Deploying ${agent.slug}...`)
127
+ const res = await fetch(`${API_BASE}/agents/deploy`, {
128
+ method: "POST",
129
+ headers: {
130
+ Authorization: `Bearer ${apiKey}`,
131
+ "Content-Type": "application/json",
132
+ },
133
+ body: JSON.stringify({
134
+ projectSlug,
135
+ slug: agent.slug,
136
+ bundle: bundle.toString("base64"),
137
+ }),
138
+ })
139
+
140
+ if (!res.ok) {
141
+ const err = await res.json().catch(() => ({}))
142
+ s2.stop(`Failed to deploy ${agent.slug}`)
143
+ p.log.error((err as any).message || "Deploy failed")
144
+
145
+ if (deployed.length > 0) {
146
+ p.log.info(`\nDeployed before failure:`)
147
+ for (const a of deployed) {
148
+ p.log.info(` ${a.slug} → ${AN_BASE}/a/${projectSlug}/${a.slug}`)
149
+ }
150
+ }
151
+ process.exit(1)
152
+ }
153
+
154
+ const result = await res.json()
155
+ projectId = result.projectId
156
+ projectSlug = result.projectSlug
157
+ deployed.push({ slug: agent.slug })
158
+ s2.stop(`${agent.slug} deployed`)
159
+ }
160
+
161
+ // 5. Save project link
162
+ saveProject({ projectId, projectSlug })
163
+
164
+ // 6. Output
165
+ p.log.success(`Deployed ${deployed.length} agent${deployed.length > 1 ? "s" : ""} to ${projectSlug}`)
166
+ console.log()
167
+ for (const agent of deployed) {
168
+ console.log(` ${agent.slug} → ${AN_BASE}/a/${projectSlug}/${agent.slug}`)
169
+ }
170
+ console.log()
171
+ p.log.info("Next steps:")
172
+ console.log(" · Open the link above to test your agent")
173
+ console.log(" · Run `an deploy` again after changes")
174
+ console.log(` · View all deployments: ${AN_BASE}/an/deployments`)
175
+
176
+ p.outro("Done")
177
+ }
package/src/index.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { login } from "./login.js"
2
+ import { deploy } from "./deploy.js"
3
+ import { createRequire } from "module"
4
+
5
+ const require = createRequire(import.meta.url)
6
+ const { version } = require("../package.json")
7
+
8
+ const command = process.argv[2]
9
+ const args = process.argv.slice(3)
10
+ const hasFlag = (flag: string) => args.includes(flag)
11
+
12
+ function showHelp() {
13
+ console.log(`AN CLI v${version} — deploy AI agents\n`)
14
+ console.log("Usage: an <command>\n")
15
+ console.log("Commands:")
16
+ console.log(" an login Authenticate with AN platform")
17
+ console.log(" an deploy Bundle and deploy your agent")
18
+ console.log("\nOptions:")
19
+ console.log(" --help, -h Show help")
20
+ console.log(" --version, -v Show version")
21
+ console.log("\nGet started: an login")
22
+ console.log("Docs: https://an.dev/docs")
23
+ }
24
+
25
+ function showLoginHelp() {
26
+ console.log("Usage: an login\n")
27
+ console.log("Authenticate with the AN platform using an API key.")
28
+ console.log("Get your API key at https://an.dev/api-keys")
29
+ }
30
+
31
+ function showDeployHelp() {
32
+ console.log("Usage: an deploy\n")
33
+ console.log("Bundle and deploy agents from the ./agents/ directory.\n")
34
+ console.log("Agent structure:")
35
+ console.log(" agents/")
36
+ console.log(" my-agent/")
37
+ console.log(" index.ts agent entry point")
38
+ console.log(" another.ts single-file agent")
39
+ console.log("\nDocs: https://an.dev/docs")
40
+ }
41
+
42
+ if (command === "login") {
43
+ if (hasFlag("--help") || hasFlag("-h")) {
44
+ showLoginHelp()
45
+ } else {
46
+ await login()
47
+ }
48
+ } else if (command === "deploy") {
49
+ if (hasFlag("--help") || hasFlag("-h")) {
50
+ showDeployHelp()
51
+ } else {
52
+ await deploy()
53
+ }
54
+ } else if (command === "--version" || command === "-v") {
55
+ console.log(version)
56
+ } else {
57
+ showHelp()
58
+ }
package/src/login.ts ADDED
@@ -0,0 +1,48 @@
1
+ import * as p from "@clack/prompts"
2
+ import { getApiKey, saveApiKey } from "./config.js"
3
+
4
+ const API_BASE = process.env.AN_API_URL || "https://an.dev/api/v1"
5
+
6
+ export async function login() {
7
+ p.intro("an login")
8
+
9
+ const existing = getApiKey()
10
+ if (existing) {
11
+ p.log.info("Already logged in. Continuing will re-authenticate.")
12
+ }
13
+ p.log.info("Get your API key at https://an.dev/api-keys")
14
+
15
+ const apiKey = await p.text({
16
+ message: "Enter your API key",
17
+ validate: (val) => {
18
+ if (!val.trim()) return "API key cannot be empty"
19
+ },
20
+ })
21
+
22
+ if (p.isCancel(apiKey)) {
23
+ p.cancel("Login cancelled.")
24
+ process.exit(0)
25
+ }
26
+
27
+ const s = p.spinner()
28
+ s.start("Verifying API key...")
29
+
30
+ const res = await fetch(`${API_BASE}/me`, {
31
+ headers: { Authorization: `Bearer ${apiKey.trim()}` },
32
+ })
33
+
34
+ if (!res.ok) {
35
+ s.stop("Invalid API key")
36
+ p.log.error("Invalid API key. Get a new one at https://an.dev/api-keys")
37
+ process.exit(1)
38
+ }
39
+
40
+ const { user, team } = await res.json()
41
+ saveApiKey(apiKey.trim())
42
+ s.stop("Verified")
43
+
44
+ p.log.success(`Authenticated as ${user.displayName || user.email} (team: ${team.name})`)
45
+ p.log.info("Key saved to ~/.an/credentials")
46
+
47
+ p.outro("Done")
48
+ }