@animaapp/cli 0.5.0 → 0.6.0
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 +142 -82
- package/dist/index.js +408 -269
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command14 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/create.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -13,29 +13,57 @@ import path from "path";
|
|
|
13
13
|
import os from "os";
|
|
14
14
|
var CONFIG_DIR = path.join(os.homedir(), ".config", "anima");
|
|
15
15
|
var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
|
|
16
|
-
function
|
|
16
|
+
function classifyCredentials() {
|
|
17
|
+
let parsed;
|
|
17
18
|
try {
|
|
18
|
-
if (!fs.existsSync(CREDENTIALS_FILE)) return
|
|
19
|
-
|
|
20
|
-
const parsed = JSON.parse(content);
|
|
21
|
-
if (!parsed.token) return null;
|
|
22
|
-
return parsed;
|
|
19
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) return { kind: "absent" };
|
|
20
|
+
parsed = JSON.parse(fs.readFileSync(CREDENTIALS_FILE, "utf-8"));
|
|
23
21
|
} catch {
|
|
24
|
-
return
|
|
22
|
+
return { kind: "malformed" };
|
|
23
|
+
}
|
|
24
|
+
const figmaToken = typeof parsed.figmaToken === "string" ? parsed.figmaToken : void 0;
|
|
25
|
+
if (typeof parsed.token !== "string") {
|
|
26
|
+
return figmaToken ? { kind: "figma-only", figmaToken } : { kind: "absent" };
|
|
27
|
+
}
|
|
28
|
+
if (parsed.tokenType === "agent") {
|
|
29
|
+
const required2 = ["teamId", "agentId", "expiresAt", "refreshToken", "refreshExpiresAt"];
|
|
30
|
+
if (required2.some((field) => typeof parsed[field] !== "string")) {
|
|
31
|
+
return { kind: "malformed" };
|
|
32
|
+
}
|
|
33
|
+
return { kind: "agent", credential: parsed, figmaToken };
|
|
25
34
|
}
|
|
35
|
+
return {
|
|
36
|
+
kind: "account",
|
|
37
|
+
credential: {
|
|
38
|
+
tokenType: "account",
|
|
39
|
+
token: parsed.token,
|
|
40
|
+
teamId: typeof parsed.teamId === "string" ? parsed.teamId : void 0
|
|
41
|
+
},
|
|
42
|
+
figmaToken
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function readCredentials() {
|
|
46
|
+
const stored = classifyCredentials();
|
|
47
|
+
if (stored.kind !== "agent" && stored.kind !== "account") return null;
|
|
48
|
+
return { ...stored.credential, ...stored.figmaToken ? { figmaToken: stored.figmaToken } : {} };
|
|
26
49
|
}
|
|
27
|
-
function
|
|
28
|
-
const
|
|
29
|
-
|
|
50
|
+
function saveCredential(credential) {
|
|
51
|
+
const figmaToken = readCredentials()?.figmaToken;
|
|
52
|
+
write({ ...credential, ...figmaToken ? { figmaToken } : {} });
|
|
53
|
+
}
|
|
54
|
+
function saveFigmaToken(figmaToken) {
|
|
55
|
+
const existing = readCredentials();
|
|
56
|
+
write({ ...existing ?? {}, figmaToken });
|
|
57
|
+
}
|
|
58
|
+
function write(credentials) {
|
|
59
|
+
const merged = { ...credentials };
|
|
30
60
|
for (const key of Object.keys(merged)) {
|
|
31
61
|
if (merged[key] === void 0) delete merged[key];
|
|
32
62
|
}
|
|
33
63
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{ mode: 384 }
|
|
38
|
-
);
|
|
64
|
+
const tmp = `${CREDENTIALS_FILE}.${process.pid}.tmp`;
|
|
65
|
+
fs.writeFileSync(tmp, JSON.stringify(merged, null, 2) + "\n", { mode: 384 });
|
|
66
|
+
fs.renameSync(tmp, CREDENTIALS_FILE);
|
|
39
67
|
}
|
|
40
68
|
function deleteCredentials() {
|
|
41
69
|
try {
|
|
@@ -74,6 +102,17 @@ function extractTeamIdFromJWT(token) {
|
|
|
74
102
|
}
|
|
75
103
|
}
|
|
76
104
|
|
|
105
|
+
// src/auth/jwt.ts
|
|
106
|
+
function decodeJwtPayload(token) {
|
|
107
|
+
try {
|
|
108
|
+
const parts = token.split(".");
|
|
109
|
+
if (parts.length !== 3) return void 0;
|
|
110
|
+
return JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
|
|
111
|
+
} catch {
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
77
116
|
// src/api/errors.ts
|
|
78
117
|
var EXIT_CODES = {
|
|
79
118
|
SUCCESS: 0,
|
|
@@ -116,37 +155,39 @@ function timeoutError() {
|
|
|
116
155
|
);
|
|
117
156
|
}
|
|
118
157
|
|
|
119
|
-
// src/auth/
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
token: envToken,
|
|
126
|
-
teamId: process.env.ANIMA_TEAM_ID,
|
|
127
|
-
figmaToken: process.env.FIGMA_TOKEN || creds?.figmaToken,
|
|
128
|
-
source: "env"
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
if (creds) {
|
|
132
|
-
if (creds.tokenType === "agent" && creds.expiresAt && new Date(creds.expiresAt) <= /* @__PURE__ */ new Date()) {
|
|
133
|
-
throw authError("Your agent token has expired. Run: anima login");
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
token: creds.token,
|
|
137
|
-
teamId: creds.teamId,
|
|
138
|
-
figmaToken: process.env.FIGMA_TOKEN || creds.figmaToken,
|
|
139
|
-
source: "file",
|
|
140
|
-
tokenType: creds.tokenType
|
|
141
|
-
};
|
|
158
|
+
// src/auth/agentSession.ts
|
|
159
|
+
function required(value, field) {
|
|
160
|
+
if (value === void 0) {
|
|
161
|
+
throw authError(
|
|
162
|
+
`The server's token response is missing \`${field}\`. Check --api-url points at an Anima API that issues agent credentials.`
|
|
163
|
+
);
|
|
142
164
|
}
|
|
143
|
-
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
function agentSessionFromTokenResponse(body, carriedOver) {
|
|
168
|
+
const token = typeof body.access_token === "string" ? body.access_token : void 0;
|
|
169
|
+
const claims = decodeJwtPayload(required(token, "access_token"));
|
|
170
|
+
return {
|
|
171
|
+
token,
|
|
172
|
+
teamId: required(typeof claims?.team_id === "string" ? claims.team_id : void 0, "team_id"),
|
|
173
|
+
agentId: required(typeof claims?.agent_id === "string" ? claims.agent_id : void 0, "agent_id"),
|
|
174
|
+
expiresAt: required(
|
|
175
|
+
typeof claims?.exp === "number" ? new Date(claims.exp * 1e3).toISOString() : void 0,
|
|
176
|
+
"exp"
|
|
177
|
+
),
|
|
178
|
+
refreshToken: required(
|
|
179
|
+
typeof body.refresh_token === "string" ? body.refresh_token : carriedOver?.refreshToken,
|
|
180
|
+
"refresh_token"
|
|
181
|
+
),
|
|
182
|
+
refreshExpiresAt: required(
|
|
183
|
+
typeof body.refresh_expires_in === "number" ? new Date(Date.now() + body.refresh_expires_in * 1e3).toISOString() : carriedOver?.refreshExpiresAt,
|
|
184
|
+
"refresh_expires_in"
|
|
185
|
+
)
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function persistAgentSession(session) {
|
|
189
|
+
saveCredential({ tokenType: "agent", ...session });
|
|
144
190
|
}
|
|
145
|
-
|
|
146
|
-
// src/api/mcp.ts
|
|
147
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
148
|
-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
149
|
-
import { CallToolResultSchema, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
150
191
|
|
|
151
192
|
// src/output/log.ts
|
|
152
193
|
import fs2 from "fs";
|
|
@@ -180,6 +221,173 @@ function redact(value) {
|
|
|
180
221
|
return out;
|
|
181
222
|
}
|
|
182
223
|
|
|
224
|
+
// src/auth/refresh.ts
|
|
225
|
+
var REFRESH_GRANT_TYPE = "refresh_token";
|
|
226
|
+
function readFailure(body) {
|
|
227
|
+
const raw = body.error;
|
|
228
|
+
const nested = typeof raw === "object" && raw !== null ? raw : null;
|
|
229
|
+
const code = typeof raw === "string" ? raw : typeof nested?.code === "string" ? nested.code : typeof nested?.error === "string" ? nested.error : null;
|
|
230
|
+
const described = typeof body.error_description === "string" ? body.error_description : null;
|
|
231
|
+
const message = described ?? (typeof raw === "string" ? raw : raw == null ? null : JSON.stringify(raw));
|
|
232
|
+
return { code, message };
|
|
233
|
+
}
|
|
234
|
+
async function refreshAccessToken(apiUrl, carriedOver, fetchFn = fetch) {
|
|
235
|
+
const base = apiUrl.replace(/\/$/, "");
|
|
236
|
+
const response = await fetchFn(`${base}/oauth/token`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
239
|
+
body: new URLSearchParams({
|
|
240
|
+
grant_type: REFRESH_GRANT_TYPE,
|
|
241
|
+
refresh_token: carriedOver.refreshToken
|
|
242
|
+
}).toString()
|
|
243
|
+
});
|
|
244
|
+
const body = await response.json().catch(() => ({}));
|
|
245
|
+
const failure = readFailure(body);
|
|
246
|
+
logEvent("refresh_token_response", { status: response.status, error: failure.code });
|
|
247
|
+
if (!response.ok || typeof body.access_token !== "string") {
|
|
248
|
+
if (failure.code === "invalid_grant") {
|
|
249
|
+
clearStorage();
|
|
250
|
+
throw authError(`${failure.message ?? "Your credentials are no longer valid"}
|
|
251
|
+
Run: anima login`);
|
|
252
|
+
}
|
|
253
|
+
throw apiError(
|
|
254
|
+
`Could not refresh credentials (HTTP ${response.status})${failure.message ? `: ${failure.message}` : ""}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const session = agentSessionFromTokenResponse(body, carriedOver);
|
|
258
|
+
saveCredential({ tokenType: "agent", ...session });
|
|
259
|
+
return session;
|
|
260
|
+
}
|
|
261
|
+
async function revokeStoredRefreshToken(apiUrl, fetchFn = fetch) {
|
|
262
|
+
const creds = readCredentials();
|
|
263
|
+
if (creds?.tokenType !== "agent") return false;
|
|
264
|
+
try {
|
|
265
|
+
const response = await fetchFn(`${apiUrl.replace(/\/$/, "")}/oauth/revoke`, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
268
|
+
body: new URLSearchParams({ token: creds.refreshToken, token_type_hint: "refresh_token" }).toString()
|
|
269
|
+
});
|
|
270
|
+
logEvent("revoke_response", { status: response.status });
|
|
271
|
+
return response.ok;
|
|
272
|
+
} catch (err) {
|
|
273
|
+
logEvent("revoke_failed", { error: err instanceof Error ? err.message : String(err) });
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/config/store.ts
|
|
279
|
+
import fs3 from "fs";
|
|
280
|
+
import path3 from "path";
|
|
281
|
+
import os2 from "os";
|
|
282
|
+
var CONFIG_DIR2 = path3.join(os2.homedir(), ".config", "anima");
|
|
283
|
+
var CONFIG_FILE = path3.join(CONFIG_DIR2, "config.json");
|
|
284
|
+
var DEFAULT_API_URL = "https://api.agentgrid.io";
|
|
285
|
+
var CONFIG_KEYS = {
|
|
286
|
+
"api-url": "apiUrl"
|
|
287
|
+
};
|
|
288
|
+
function isConfigKey(key) {
|
|
289
|
+
return key in CONFIG_KEYS;
|
|
290
|
+
}
|
|
291
|
+
function configKeys() {
|
|
292
|
+
return Object.keys(CONFIG_KEYS);
|
|
293
|
+
}
|
|
294
|
+
function readConfig() {
|
|
295
|
+
try {
|
|
296
|
+
if (!fs3.existsSync(CONFIG_FILE)) return {};
|
|
297
|
+
const parsed = JSON.parse(fs3.readFileSync(CONFIG_FILE, "utf-8"));
|
|
298
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
299
|
+
} catch {
|
|
300
|
+
return {};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function setConfig(key, value) {
|
|
304
|
+
const merged = { ...readConfig(), [CONFIG_KEYS[key]]: value };
|
|
305
|
+
fs3.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
306
|
+
fs3.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n");
|
|
307
|
+
}
|
|
308
|
+
function deleteConfig() {
|
|
309
|
+
try {
|
|
310
|
+
if (!fs3.existsSync(CONFIG_FILE)) return false;
|
|
311
|
+
fs3.unlinkSync(CONFIG_FILE);
|
|
312
|
+
return true;
|
|
313
|
+
} catch {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function unsetConfig(key) {
|
|
318
|
+
const config = readConfig();
|
|
319
|
+
const field = CONFIG_KEYS[key];
|
|
320
|
+
if (!(field in config)) return false;
|
|
321
|
+
delete config[field];
|
|
322
|
+
fs3.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
323
|
+
fs3.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
function getConfigPath() {
|
|
327
|
+
return CONFIG_FILE;
|
|
328
|
+
}
|
|
329
|
+
function resolveApiUrl(flagValue) {
|
|
330
|
+
return flagValue || process.env.ANIMA_API_URL || readConfig().apiUrl || DEFAULT_API_URL;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/auth/token.ts
|
|
334
|
+
var RENEW_BEFORE_EXPIRY_MS = 6e4;
|
|
335
|
+
function needsRenewal(expiresAt) {
|
|
336
|
+
if (!expiresAt) return false;
|
|
337
|
+
const expiry = new Date(expiresAt).getTime();
|
|
338
|
+
return Number.isNaN(expiry) || expiry - Date.now() <= RENEW_BEFORE_EXPIRY_MS;
|
|
339
|
+
}
|
|
340
|
+
async function resolveAuth(apiUrlOverride) {
|
|
341
|
+
const stored = classifyCredentials();
|
|
342
|
+
const envToken = process.env.ANIMA_API_TOKEN;
|
|
343
|
+
if (envToken) {
|
|
344
|
+
return {
|
|
345
|
+
token: envToken,
|
|
346
|
+
teamId: process.env.ANIMA_TEAM_ID,
|
|
347
|
+
figmaToken: process.env.FIGMA_TOKEN || ("figmaToken" in stored ? stored.figmaToken : void 0),
|
|
348
|
+
source: "env"
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const figmaToken = process.env.FIGMA_TOKEN || ("figmaToken" in stored ? stored.figmaToken : void 0);
|
|
352
|
+
if (stored.kind === "agent") {
|
|
353
|
+
const { credential } = stored;
|
|
354
|
+
if (needsRenewal(credential.expiresAt)) {
|
|
355
|
+
const renewed = await refreshAccessToken(resolveApiUrl(apiUrlOverride), {
|
|
356
|
+
refreshToken: credential.refreshToken,
|
|
357
|
+
refreshExpiresAt: credential.refreshExpiresAt
|
|
358
|
+
});
|
|
359
|
+
return { token: renewed.token, teamId: renewed.teamId, figmaToken, source: "file", tokenType: "agent" };
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
token: credential.token,
|
|
363
|
+
teamId: credential.teamId,
|
|
364
|
+
figmaToken,
|
|
365
|
+
source: "file",
|
|
366
|
+
tokenType: "agent"
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (stored.kind === "account") {
|
|
370
|
+
return {
|
|
371
|
+
token: stored.credential.token,
|
|
372
|
+
teamId: stored.credential.teamId,
|
|
373
|
+
figmaToken,
|
|
374
|
+
source: "file",
|
|
375
|
+
tokenType: "account"
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (stored.kind === "malformed") {
|
|
379
|
+
throw authError(
|
|
380
|
+
`Your credentials file is damaged or incomplete (${getCredentialsPath()}). Run: anima login`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
throw authError("No credentials found. Run: anima login\n(or set the ANIMA_API_TOKEN env var)");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/api/mcp.ts
|
|
387
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
388
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
389
|
+
import { CallToolResultSchema, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
390
|
+
|
|
183
391
|
// src/version.ts
|
|
184
392
|
import { createRequire } from "module";
|
|
185
393
|
var { version } = createRequire(import.meta.url)("../package.json");
|
|
@@ -367,10 +575,10 @@ function printKeyValue(key, value) {
|
|
|
367
575
|
}
|
|
368
576
|
function printSessionResult(result) {
|
|
369
577
|
console.log();
|
|
370
|
-
printSuccess("
|
|
578
|
+
printSuccess("Artifact created successfully!");
|
|
371
579
|
printKeyValue("Session ID", result.sessionId);
|
|
372
580
|
if (result.playgroundUrl) {
|
|
373
|
-
printKeyValue("Your
|
|
581
|
+
printKeyValue("Your artifact", chalk.cyan.underline(result.playgroundUrl));
|
|
374
582
|
}
|
|
375
583
|
if (result.tokenUsage) {
|
|
376
584
|
printKeyValue("Token usage", result.tokenUsage.toLocaleString());
|
|
@@ -383,13 +591,6 @@ function printDeployResult(result) {
|
|
|
383
591
|
if (result.url) {
|
|
384
592
|
printKeyValue("Live URL", chalk.cyan.underline(result.url));
|
|
385
593
|
}
|
|
386
|
-
if (result.packageName) {
|
|
387
|
-
printKeyValue("Package", result.packageName);
|
|
388
|
-
printKeyValue("Version", result.packageVersion ?? "");
|
|
389
|
-
if (result.packageUrl) {
|
|
390
|
-
printKeyValue("URL", chalk.cyan.underline(result.packageUrl));
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
594
|
console.log();
|
|
394
595
|
}
|
|
395
596
|
function printFilesWritten(outputDir, count) {
|
|
@@ -533,78 +734,26 @@ function parsePlaygroundUrl(input) {
|
|
|
533
734
|
}
|
|
534
735
|
}
|
|
535
736
|
|
|
536
|
-
// src/config/store.ts
|
|
537
|
-
import fs3 from "fs";
|
|
538
|
-
import path3 from "path";
|
|
539
|
-
import os2 from "os";
|
|
540
|
-
var CONFIG_DIR2 = path3.join(os2.homedir(), ".config", "anima");
|
|
541
|
-
var CONFIG_FILE = path3.join(CONFIG_DIR2, "config.json");
|
|
542
|
-
var DEFAULT_API_URL = "https://api.agentgrid.io";
|
|
543
|
-
var CONFIG_KEYS = {
|
|
544
|
-
"api-url": "apiUrl"
|
|
545
|
-
};
|
|
546
|
-
function isConfigKey(key) {
|
|
547
|
-
return key in CONFIG_KEYS;
|
|
548
|
-
}
|
|
549
|
-
function configKeys() {
|
|
550
|
-
return Object.keys(CONFIG_KEYS);
|
|
551
|
-
}
|
|
552
|
-
function readConfig() {
|
|
553
|
-
try {
|
|
554
|
-
if (!fs3.existsSync(CONFIG_FILE)) return {};
|
|
555
|
-
const parsed = JSON.parse(fs3.readFileSync(CONFIG_FILE, "utf-8"));
|
|
556
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
557
|
-
} catch {
|
|
558
|
-
return {};
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
function setConfig(key, value) {
|
|
562
|
-
const merged = { ...readConfig(), [CONFIG_KEYS[key]]: value };
|
|
563
|
-
fs3.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
564
|
-
fs3.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n");
|
|
565
|
-
}
|
|
566
|
-
function deleteConfig() {
|
|
567
|
-
try {
|
|
568
|
-
if (!fs3.existsSync(CONFIG_FILE)) return false;
|
|
569
|
-
fs3.unlinkSync(CONFIG_FILE);
|
|
570
|
-
return true;
|
|
571
|
-
} catch {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
function unsetConfig(key) {
|
|
576
|
-
const config = readConfig();
|
|
577
|
-
const field = CONFIG_KEYS[key];
|
|
578
|
-
if (!(field in config)) return false;
|
|
579
|
-
delete config[field];
|
|
580
|
-
fs3.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
581
|
-
fs3.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
582
|
-
return true;
|
|
583
|
-
}
|
|
584
|
-
function getConfigPath() {
|
|
585
|
-
return CONFIG_FILE;
|
|
586
|
-
}
|
|
587
|
-
function resolveApiUrl(flagValue) {
|
|
588
|
-
return flagValue || process.env.ANIMA_API_URL || readConfig().apiUrl || DEFAULT_API_URL;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
737
|
// src/commands/create.ts
|
|
592
738
|
var createCommand = new Command("create").description(
|
|
593
|
-
"Create
|
|
739
|
+
"Create an artifact from your own code: empty for code you'll push (instant) or import your existing code (instant). Also supports AI generation: p2c/l2c/f2c (3-7 min)."
|
|
594
740
|
).requiredOption(
|
|
595
741
|
"-t, --type <type>",
|
|
596
742
|
"empty (empty repo you push to), import (create FROM your code \u2014 use --from), or AI-generated: p2c, l2c, f2c"
|
|
597
743
|
).option("-p, --prompt <prompt>", "Text prompt (p2c)").option("-u, --url <url>", "Website URL (l2c)").option("--file-key <key>", "Figma file key or URL (f2c)").option("--nodes <nodes>", "Figma node IDs, comma-separated (f2c)").option("--figma-token <token>", "Figma personal access token (f2c)").option(
|
|
598
744
|
"--framework <fw>",
|
|
599
745
|
"html | react \u2014 the only supported frameworks. REQUIRED for empty (declare react if you will push React code); detected from package.json for import"
|
|
600
|
-
).option("--styling <style>", "tailwind | css | plain_css | css_modules | inline_styles", "tailwind").option("--language <lang>", "typescript | javascript").option("--ui-library <lib>", "mui | antd | shadcn | clean_react").option("--guidelines <text>", "Additional constraints (p2c only)").option(
|
|
746
|
+
).option("--styling <style>", "tailwind | css | plain_css | css_modules | inline_styles", "tailwind").option("--language <lang>", "typescript | javascript").option("--ui-library <lib>", "mui | antd | shadcn | clean_react").option("--guidelines <text>", "Additional constraints (p2c only)").option(
|
|
747
|
+
"--artifact-type <kind>",
|
|
748
|
+
"app | markdown \u2014 what the artifact IS. app (default) needs an index.html; markdown needs .md files"
|
|
749
|
+
).option("--name <name>", "Project name (empty/import only)").option("--from <path>", "Folder or .zip with your project (import only) \u2014 becomes the first commit").option("--json", "Output as JSON").option("--timeout <ms>", "Timeout in milliseconds", "600000").option("--verbose", "Show progress on stderr").option("--api-url <url>", "API base URL (overrides config / ANIMA_API_URL)").option("--log-file <path>", "Append a JSON debug log of each step to this file").action(async (opts) => {
|
|
601
750
|
const mode = detectOutputMode(opts);
|
|
602
751
|
initLogger(opts.logFile);
|
|
603
752
|
const progress = new ProgressReporter(mode, opts.verbose);
|
|
604
753
|
try {
|
|
605
754
|
const type = opts.type;
|
|
606
755
|
validateCreateOptions(type, opts);
|
|
607
|
-
const auth = resolveAuth();
|
|
756
|
+
const auth = await resolveAuth(opts.apiUrl);
|
|
608
757
|
const figmaToken = type === "f2c" ? resolveFigmaToken(opts, auth.figmaToken) : void 0;
|
|
609
758
|
const args = buildCreateArgs(type, opts);
|
|
610
759
|
const mcpConfig = { apiBaseUrl: resolveApiUrl(opts.apiUrl), token: auth.token, teamId: auth.teamId, figmaToken };
|
|
@@ -617,7 +766,7 @@ var createCommand = new Command("create").description(
|
|
|
617
766
|
progress.update(`Uploading project zip (${source.reason})...`);
|
|
618
767
|
const grant = await withMcp(
|
|
619
768
|
mcpConfig,
|
|
620
|
-
(client) => client.callTool("
|
|
769
|
+
(client) => client.callTool("artifact-get_zip_upload_url", {})
|
|
621
770
|
);
|
|
622
771
|
if (!grant.success || !grant.uploadUrl || !grant.zipUploadId) {
|
|
623
772
|
const message = grant.reason || grant.error || "Could not get a zip upload URL";
|
|
@@ -635,11 +784,11 @@ ${grant.nextSteps}` : message, grant);
|
|
|
635
784
|
}
|
|
636
785
|
}
|
|
637
786
|
progress.start(
|
|
638
|
-
type === "empty" ? "Creating empty
|
|
787
|
+
type === "empty" ? "Creating empty artifact..." : type === "import" ? "Importing your project..." : `Creating ${type.toUpperCase()} artifact...`
|
|
639
788
|
);
|
|
640
789
|
const result = await withMcp(
|
|
641
790
|
mcpConfig,
|
|
642
|
-
(client) => client.callTool("
|
|
791
|
+
(client) => client.callTool("artifact-create", args, {
|
|
643
792
|
timeoutMs: parseInt(opts.timeout, 10),
|
|
644
793
|
onProgress: (p) => progress.update(p.message ?? `Generating (${Math.round(p.progress)}%)...`)
|
|
645
794
|
})
|
|
@@ -650,20 +799,20 @@ ${grant.nextSteps}` : message, grant);
|
|
|
650
799
|
${result.nextSteps}` : message, result);
|
|
651
800
|
}
|
|
652
801
|
const playgroundUrl = result.playgroundUrl || `${PLAYGROUND_BASE}/${result.sessionId}`;
|
|
653
|
-
progress.succeed("
|
|
802
|
+
progress.succeed("Artifact created!");
|
|
654
803
|
if (mode === "pretty") {
|
|
655
804
|
printSessionResult({ sessionId: result.sessionId, playgroundUrl });
|
|
656
805
|
if (result.gitRemoteUrl) {
|
|
657
806
|
console.log(` ${chalk2.cyan(`git clone ${result.gitRemoteUrl}`)}`);
|
|
658
807
|
console.log();
|
|
659
|
-
console.log(" Commit and push to update the live
|
|
808
|
+
console.log(" Commit and push to update the live artifact.");
|
|
660
809
|
if (result.expiresAt) {
|
|
661
810
|
console.log(
|
|
662
811
|
` The URL embeds a token that expires at ${result.expiresAt} and cannot be renewed;
|
|
663
812
|
run ${chalk2.cyan(`anima get-git-token ${result.sessionId}`)} and ${chalk2.cyan("git remote set-url origin <new url>")} to keep pushing.`
|
|
664
813
|
);
|
|
665
814
|
}
|
|
666
|
-
console.log(" Treat the URL as a secret \u2014 it grants access to this
|
|
815
|
+
console.log(" Treat the URL as a secret \u2014 it grants access to this artifact.");
|
|
667
816
|
console.log();
|
|
668
817
|
} else if (type === "empty") {
|
|
669
818
|
console.log(
|
|
@@ -751,9 +900,12 @@ function buildCreateArgs(type, opts) {
|
|
|
751
900
|
}
|
|
752
901
|
const framework = opts.framework ?? "html";
|
|
753
902
|
const language = opts.language ?? (framework === "react" ? "typescript" : void 0);
|
|
903
|
+
const artifactType = opts.artifactType;
|
|
754
904
|
const args = {
|
|
755
905
|
type,
|
|
756
|
-
framework
|
|
906
|
+
// framework describes how to build an app; a document has none.
|
|
907
|
+
...artifactType === "markdown" ? {} : { framework },
|
|
908
|
+
...artifactType ? { artifactType } : {},
|
|
757
909
|
styling: opts.styling
|
|
758
910
|
};
|
|
759
911
|
if (language) args.language = language;
|
|
@@ -791,7 +943,7 @@ var listCommand = new Command2("list").aliases(["ls", "artifacts"]).description(
|
|
|
791
943
|
const mode = detectOutputMode(opts);
|
|
792
944
|
initLogger(opts.logFile);
|
|
793
945
|
try {
|
|
794
|
-
const auth = resolveAuth();
|
|
946
|
+
const auth = await resolveAuth(opts.apiUrl);
|
|
795
947
|
const result = await withMcp(
|
|
796
948
|
{ apiBaseUrl: resolveApiUrl(opts.apiUrl), token: auth.token, teamId: auth.teamId },
|
|
797
949
|
(client) => client.callTool("workspace-list_artifacts", {})
|
|
@@ -861,30 +1013,16 @@ function formatUpdatedAt(iso) {
|
|
|
861
1013
|
|
|
862
1014
|
// src/commands/publish.ts
|
|
863
1015
|
import { Command as Command3 } from "commander";
|
|
864
|
-
var publishCommand = new Command3("publish").description("Deploy
|
|
1016
|
+
var publishCommand = new Command3("publish").description("Deploy an artifact's app to a live public URL (1-3 min)").argument("<sessionId>", "Session ID from create command").option("--json", "Output as JSON").option("--verbose", "Show progress on stderr").option("--api-url <url>", "API base URL (overrides config / ANIMA_API_URL)").option("--log-file <path>", "Append a JSON debug log of each step to this file").action(async (sessionId, opts) => {
|
|
865
1017
|
const mode = detectOutputMode(opts);
|
|
866
1018
|
initLogger(opts.logFile);
|
|
867
1019
|
const progress = new ProgressReporter(mode, opts.verbose);
|
|
868
1020
|
try {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
`Invalid mode "${opts.mode}". Must be webapp or designSystem.`
|
|
872
|
-
);
|
|
873
|
-
}
|
|
874
|
-
if (opts.mode === "designSystem" && !opts.packageName) {
|
|
875
|
-
throw validationError("--package-name is required for designSystem mode");
|
|
876
|
-
}
|
|
877
|
-
const auth = resolveAuth();
|
|
878
|
-
const args = {
|
|
879
|
-
sessionId,
|
|
880
|
-
mode: opts.mode
|
|
881
|
-
};
|
|
882
|
-
if (opts.packageName) args.packageName = opts.packageName;
|
|
883
|
-
if (opts.packageVersion) args.packageVersion = opts.packageVersion;
|
|
884
|
-
progress.start(`Publishing as ${opts.mode}...`);
|
|
1021
|
+
const auth = await resolveAuth(opts.apiUrl);
|
|
1022
|
+
progress.start("Publishing...");
|
|
885
1023
|
const result = await withMcp(
|
|
886
1024
|
{ apiBaseUrl: resolveApiUrl(opts.apiUrl), token: auth.token, teamId: auth.teamId },
|
|
887
|
-
(client) => client.callTool("
|
|
1025
|
+
(client) => client.callTool("artifact-publish", { sessionId }, {
|
|
888
1026
|
onProgress: (p) => progress.update(p.message ?? "Publishing...")
|
|
889
1027
|
})
|
|
890
1028
|
);
|
|
@@ -898,22 +1036,12 @@ var publishCommand = new Command3("publish").description("Deploy playground to l
|
|
|
898
1036
|
}
|
|
899
1037
|
progress.succeed("Published!");
|
|
900
1038
|
if (mode === "pretty") {
|
|
901
|
-
printDeployResult({
|
|
902
|
-
url: result.liveUrl,
|
|
903
|
-
packageUrl: result.packageUrl,
|
|
904
|
-
packageName: result.packageName,
|
|
905
|
-
packageVersion: result.packageVersion
|
|
906
|
-
});
|
|
1039
|
+
printDeployResult({ url: result.liveUrl });
|
|
907
1040
|
} else {
|
|
908
1041
|
outputResult(mode, {
|
|
909
1042
|
success: true,
|
|
910
1043
|
sessionId,
|
|
911
|
-
...result.liveUrl ? { liveUrl: result.liveUrl, subdomain: result.subdomain } : {}
|
|
912
|
-
...result.packageName ? {
|
|
913
|
-
packageName: result.packageName,
|
|
914
|
-
packageVersion: result.packageVersion,
|
|
915
|
-
packageUrl: result.packageUrl
|
|
916
|
-
} : {}
|
|
1044
|
+
...result.liveUrl ? { liveUrl: result.liveUrl, subdomain: result.subdomain } : {}
|
|
917
1045
|
});
|
|
918
1046
|
}
|
|
919
1047
|
} catch (err) {
|
|
@@ -959,7 +1087,7 @@ function writeFilesToDisk(files, outputDir) {
|
|
|
959
1087
|
}
|
|
960
1088
|
|
|
961
1089
|
// src/commands/codegen.ts
|
|
962
|
-
var codegenCommand = new Command4("codegen").description("Convert Figma design to code files (no
|
|
1090
|
+
var codegenCommand = new Command4("codegen").description("Convert Figma design to code files (no artifact)").requiredOption("--file-key <key>", "Figma file key or URL").option("--nodes <nodes>", "Figma node IDs, comma-separated").option("--figma-token <token>", "Figma personal access token (or set FIGMA_TOKEN env var)").option("-o, --output <dir>", "Output directory", "./generated").option("--framework <fw>", "react | html", "react").option("--styling <style>", "tailwind | plain_css", "tailwind").option("--language <lang>", "typescript | javascript").option("--ui-library <lib>", "mui | antd | shadcn | clean_react").option("--json", "Output as JSON").option("--timeout <ms>", "Timeout in milliseconds", "600000").option("--verbose", "Show progress on stderr").option("--api-url <url>", "API base URL (overrides config / ANIMA_API_URL)").option("--log-file <path>", "Append a JSON debug log of each step to this file").action(async (opts) => {
|
|
963
1091
|
const mode = detectOutputMode(opts);
|
|
964
1092
|
initLogger(opts.logFile);
|
|
965
1093
|
const progress = new ProgressReporter(mode, opts.verbose);
|
|
@@ -976,7 +1104,7 @@ var codegenCommand = new Command4("codegen").description("Convert Figma design t
|
|
|
976
1104
|
"No node IDs provided. Use --nodes or include node-id in the Figma URL."
|
|
977
1105
|
);
|
|
978
1106
|
}
|
|
979
|
-
const auth = resolveAuth();
|
|
1107
|
+
const auth = await resolveAuth(opts.apiUrl);
|
|
980
1108
|
const figmaToken = opts.figmaToken || process.env.FIGMA_TOKEN || auth.figmaToken;
|
|
981
1109
|
if (!figmaToken) {
|
|
982
1110
|
throw validationError(
|
|
@@ -1045,18 +1173,18 @@ function parseGitTokenResponse(payload) {
|
|
|
1045
1173
|
try {
|
|
1046
1174
|
value = JSON.parse(value);
|
|
1047
1175
|
} catch {
|
|
1048
|
-
throw apiError(`Unexpected response from
|
|
1176
|
+
throw apiError(`Unexpected response from artifact-get_git_token: ${preview(payload)}`);
|
|
1049
1177
|
}
|
|
1050
1178
|
}
|
|
1051
1179
|
if (value === null || typeof value !== "object") {
|
|
1052
|
-
throw apiError(`Unexpected response from
|
|
1180
|
+
throw apiError(`Unexpected response from artifact-get_git_token: ${preview(payload)}`);
|
|
1053
1181
|
}
|
|
1054
1182
|
const body = value;
|
|
1055
1183
|
if (body.success === false) {
|
|
1056
1184
|
throw apiError(`Git access ${body.error ?? "denied"}: ${(body.reason ?? "").trim()}`);
|
|
1057
1185
|
}
|
|
1058
1186
|
if (!body.gitRemoteUrl) {
|
|
1059
|
-
throw apiError(`Unexpected response from
|
|
1187
|
+
throw apiError(`Unexpected response from artifact-get_git_token: ${preview(payload)}`);
|
|
1060
1188
|
}
|
|
1061
1189
|
return {
|
|
1062
1190
|
gitRemoteUrl: body.gitRemoteUrl,
|
|
@@ -1066,17 +1194,17 @@ function parseGitTokenResponse(payload) {
|
|
|
1066
1194
|
}
|
|
1067
1195
|
|
|
1068
1196
|
// src/commands/getGitToken.ts
|
|
1069
|
-
var getGitTokenCommand = new Command5("get-git-token").description("Mint a short-lived git remote URL for
|
|
1197
|
+
var getGitTokenCommand = new Command5("get-git-token").description("Mint a short-lived git remote URL for an artifact (clone/push with git)").argument("<artifactUrl>", "Artifact URL or session ID").option("--json", "Output as JSON").option("--verbose", "Show progress on stderr").option("--api-url <url>", "API base URL (overrides config / ANIMA_API_URL)").option("--log-file <path>", "Append a JSON debug log of each step to this file").action(async (artifactUrl, opts) => {
|
|
1070
1198
|
const mode = detectOutputMode(opts);
|
|
1071
1199
|
initLogger(opts.logFile);
|
|
1072
1200
|
const progress = new ProgressReporter(mode, opts.verbose);
|
|
1073
1201
|
try {
|
|
1074
|
-
const sessionId = parsePlaygroundUrl(
|
|
1075
|
-
const auth = resolveAuth();
|
|
1202
|
+
const sessionId = parsePlaygroundUrl(artifactUrl);
|
|
1203
|
+
const auth = await resolveAuth(opts.apiUrl);
|
|
1076
1204
|
progress.start("Requesting git access...");
|
|
1077
1205
|
const response = await withMcp(
|
|
1078
1206
|
{ apiBaseUrl: resolveApiUrl(opts.apiUrl), token: auth.token, teamId: auth.teamId },
|
|
1079
|
-
(client) => client.callTool("
|
|
1207
|
+
(client) => client.callTool("artifact-get_git_token", { sessionId })
|
|
1080
1208
|
);
|
|
1081
1209
|
const grant = parseGitTokenResponse(response);
|
|
1082
1210
|
logEvent("git_token_granted", { sessionId, access: grant.access, expiresAt: grant.expiresAt });
|
|
@@ -1086,7 +1214,7 @@ var getGitTokenCommand = new Command5("get-git-token").description("Mint a short
|
|
|
1086
1214
|
console.log(` ${chalk4.cyan(`git clone ${grant.gitRemoteUrl}`)}`);
|
|
1087
1215
|
console.log();
|
|
1088
1216
|
if (grant.access === "rw") {
|
|
1089
|
-
console.log(" Commit and push to update the live
|
|
1217
|
+
console.log(" Commit and push to update the live artifact.");
|
|
1090
1218
|
} else {
|
|
1091
1219
|
console.log(" Read-only: cloning works, pushing will be rejected.");
|
|
1092
1220
|
}
|
|
@@ -1096,7 +1224,7 @@ var getGitTokenCommand = new Command5("get-git-token").description("Mint a short
|
|
|
1096
1224
|
re-run this command and ${chalk4.cyan("git remote set-url origin <new url>")} to keep pushing.`
|
|
1097
1225
|
);
|
|
1098
1226
|
}
|
|
1099
|
-
console.log(" Treat the URL as a secret \u2014 it grants access to this
|
|
1227
|
+
console.log(" Treat the URL as a secret \u2014 it grants access to this artifact.");
|
|
1100
1228
|
console.log();
|
|
1101
1229
|
} else {
|
|
1102
1230
|
outputResult(mode, {
|
|
@@ -1133,15 +1261,6 @@ import open from "open";
|
|
|
1133
1261
|
var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
1134
1262
|
var DEFAULT_CLIENT_NAME = "AgentGrid CLI";
|
|
1135
1263
|
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1136
|
-
function decodeJwtPayload(token) {
|
|
1137
|
-
try {
|
|
1138
|
-
const parts = token.split(".");
|
|
1139
|
-
if (parts.length !== 3) return void 0;
|
|
1140
|
-
return JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
|
|
1141
|
-
} catch {
|
|
1142
|
-
return void 0;
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
1264
|
async function loginWithDeviceFlow(options) {
|
|
1146
1265
|
const fetchFn = options.fetchFn ?? fetch;
|
|
1147
1266
|
const sleep = options.sleepFn ?? defaultSleep;
|
|
@@ -1186,22 +1305,9 @@ async function loginWithDeviceFlow(options) {
|
|
|
1186
1305
|
const body = await pollResponse.json();
|
|
1187
1306
|
logEvent("device_token_poll", { status: pollResponse.status, error: body.error });
|
|
1188
1307
|
if (pollResponse.ok && typeof body.access_token === "string") {
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
token,
|
|
1193
|
-
teamId: typeof claims?.team_id === "string" ? claims.team_id : void 0,
|
|
1194
|
-
agentId: typeof claims?.agent_id === "string" ? claims.agent_id : void 0,
|
|
1195
|
-
expiresAt: typeof claims?.exp === "number" ? new Date(claims.exp * 1e3).toISOString() : void 0
|
|
1196
|
-
};
|
|
1197
|
-
writeCredentials({
|
|
1198
|
-
token: result.token,
|
|
1199
|
-
teamId: result.teamId,
|
|
1200
|
-
tokenType: "agent",
|
|
1201
|
-
agentId: result.agentId,
|
|
1202
|
-
expiresAt: result.expiresAt
|
|
1203
|
-
});
|
|
1204
|
-
return result;
|
|
1308
|
+
const session = agentSessionFromTokenResponse(body);
|
|
1309
|
+
persistAgentSession(session);
|
|
1310
|
+
return session;
|
|
1205
1311
|
}
|
|
1206
1312
|
switch (body.error) {
|
|
1207
1313
|
case "authorization_pending":
|
|
@@ -1248,15 +1354,6 @@ function parseInvite(input) {
|
|
|
1248
1354
|
}
|
|
1249
1355
|
return { code: trimmed.replace(/\.md$/, "") };
|
|
1250
1356
|
}
|
|
1251
|
-
function decodeJwtPayload2(token) {
|
|
1252
|
-
try {
|
|
1253
|
-
const parts = token.split(".");
|
|
1254
|
-
if (parts.length !== 3) return void 0;
|
|
1255
|
-
return JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
|
|
1256
|
-
} catch {
|
|
1257
|
-
return void 0;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
1357
|
async function loginWithInvite(options) {
|
|
1261
1358
|
const fetchFn = options.fetchFn ?? fetch;
|
|
1262
1359
|
const { code } = parseInvite(options.invite);
|
|
@@ -1277,28 +1374,15 @@ async function loginWithInvite(options) {
|
|
|
1277
1374
|
const description = typeof body.error_description === "string" ? body.error_description : `Invite redemption failed (HTTP ${response.status})`;
|
|
1278
1375
|
throw new CliError("AUTH_INVALID", description, EXIT_CODES.AUTH_ERROR);
|
|
1279
1376
|
}
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
token,
|
|
1284
|
-
teamId: typeof claims?.team_id === "string" ? claims.team_id : void 0,
|
|
1285
|
-
agentId: typeof claims?.agent_id === "string" ? claims.agent_id : void 0,
|
|
1286
|
-
expiresAt: typeof claims?.exp === "number" ? new Date(claims.exp * 1e3).toISOString() : void 0
|
|
1287
|
-
};
|
|
1288
|
-
writeCredentials({
|
|
1289
|
-
token: result.token,
|
|
1290
|
-
teamId: result.teamId,
|
|
1291
|
-
tokenType: "agent",
|
|
1292
|
-
agentId: result.agentId,
|
|
1293
|
-
expiresAt: result.expiresAt
|
|
1294
|
-
});
|
|
1295
|
-
return result;
|
|
1377
|
+
const session = agentSessionFromTokenResponse(body);
|
|
1378
|
+
persistAgentSession(session);
|
|
1379
|
+
return session;
|
|
1296
1380
|
}
|
|
1297
1381
|
|
|
1298
1382
|
// src/commands/login.ts
|
|
1299
1383
|
var loginCommand = new Command6("login").description("Log in via the device flow, or redeem a pre-approved invite with --invite").option("--invite <url-or-code>", "Redeem an invite link (\u2026/invite/<code>.md) or bare code \u2014 no approval step").option("--client-name <name>", "How this CLI appears on the consent screen", "AgentGrid CLI").option("--no-open", "Do not try to open the verification page in a browser").option("--json", "Output as JSON").option("--api-url <url>", "API base URL (overrides config / ANIMA_API_URL)").option("--log-file <path>", "Append a JSON debug log of each step to this file").option(
|
|
1300
1384
|
"--print-mcp-config",
|
|
1301
|
-
"After login, also print an MCP server config (
|
|
1385
|
+
"After login, also print an MCP server config (URL only \u2014 the client authorizes itself) to paste into an MCP-capable agent"
|
|
1302
1386
|
).action(async (opts) => {
|
|
1303
1387
|
const mode = detectOutputMode(opts);
|
|
1304
1388
|
initLogger(opts.logFile);
|
|
@@ -1310,17 +1394,17 @@ var loginCommand = new Command6("login").description("Log in via the device flow
|
|
|
1310
1394
|
if (inviteOrigin && readConfig().apiUrl !== apiUrl2) {
|
|
1311
1395
|
setConfig("api-url", apiUrl2);
|
|
1312
1396
|
}
|
|
1313
|
-
const mcpConfig = opts.printMcpConfig ? buildMcpConfig(apiUrl2
|
|
1397
|
+
const mcpConfig = opts.printMcpConfig ? buildMcpConfig(apiUrl2) : void 0;
|
|
1314
1398
|
if (mode === "pretty") {
|
|
1315
1399
|
printSuccess("Connected!");
|
|
1316
1400
|
printKeyValue("Credentials", getCredentialsPath());
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1401
|
+
printKeyValue("Agent", result.agentId);
|
|
1402
|
+
printKeyValue("Team ID", result.teamId);
|
|
1403
|
+
printKeyValue("Token expires", result.expiresAt);
|
|
1320
1404
|
printInfo("This is a scoped agent token \u2014 it acts within what was approved, and the team can revoke it.");
|
|
1321
1405
|
if (mcpConfig) {
|
|
1322
1406
|
console.log();
|
|
1323
|
-
printInfo("MCP server config
|
|
1407
|
+
printInfo("MCP server config \u2014 your client will prompt to authorize on first use:");
|
|
1324
1408
|
console.log();
|
|
1325
1409
|
console.log(JSON.stringify(mcpConfig, null, 2));
|
|
1326
1410
|
}
|
|
@@ -1381,17 +1465,17 @@ var loginCommand = new Command6("login").description("Log in via the device flow
|
|
|
1381
1465
|
}
|
|
1382
1466
|
}
|
|
1383
1467
|
});
|
|
1384
|
-
const mcpConfig = opts.printMcpConfig ? buildMcpConfig(apiUrl
|
|
1468
|
+
const mcpConfig = opts.printMcpConfig ? buildMcpConfig(apiUrl) : void 0;
|
|
1385
1469
|
if (mode === "pretty") {
|
|
1386
1470
|
printSuccess("Connected!");
|
|
1387
1471
|
printKeyValue("Credentials", getCredentialsPath());
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1472
|
+
printKeyValue("Agent", result.agentId);
|
|
1473
|
+
printKeyValue("Team ID", result.teamId);
|
|
1474
|
+
printKeyValue("Token expires", result.expiresAt);
|
|
1391
1475
|
printInfo("This is a scoped agent token \u2014 it acts within what you approved, and your team can revoke it.");
|
|
1392
1476
|
if (mcpConfig) {
|
|
1393
1477
|
console.log();
|
|
1394
|
-
printInfo("MCP server config
|
|
1478
|
+
printInfo("MCP server config \u2014 your client will prompt to authorize on first use:");
|
|
1395
1479
|
console.log();
|
|
1396
1480
|
console.log(JSON.stringify(mcpConfig, null, 2));
|
|
1397
1481
|
}
|
|
@@ -1418,41 +1502,30 @@ var loginCommand = new Command6("login").description("Log in via the device flow
|
|
|
1418
1502
|
throw err;
|
|
1419
1503
|
}
|
|
1420
1504
|
});
|
|
1421
|
-
function buildMcpConfig(apiUrl
|
|
1505
|
+
function buildMcpConfig(apiUrl) {
|
|
1422
1506
|
return {
|
|
1423
1507
|
mcpServers: {
|
|
1424
1508
|
anima: {
|
|
1425
1509
|
type: "http",
|
|
1426
|
-
url: `${apiUrl.replace(/\/$/, "")}/v1/mcp
|
|
1427
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
1510
|
+
url: `${apiUrl.replace(/\/$/, "")}/v1/mcp`
|
|
1428
1511
|
}
|
|
1429
1512
|
}
|
|
1430
1513
|
};
|
|
1431
1514
|
}
|
|
1432
1515
|
|
|
1433
1516
|
// src/commands/mcpConfig.ts
|
|
1434
|
-
var mcpConfigCommand = new Command7("mcp-config").description("Print an MCP server config
|
|
1517
|
+
var mcpConfigCommand = new Command7("mcp-config").description("Print an MCP server config for your MCP client").option("--json", "Output as JSON").option("--api-url <url>", "API base URL (overrides config / ANIMA_API_URL)").action(async (opts) => {
|
|
1435
1518
|
const mode = detectOutputMode(opts);
|
|
1436
1519
|
try {
|
|
1437
|
-
const
|
|
1438
|
-
const creds = readCredentials();
|
|
1439
|
-
const expiresAt = auth.source === "env" ? void 0 : creds?.expiresAt;
|
|
1440
|
-
const mcpConfig = buildMcpConfig(resolveApiUrl(opts.apiUrl), auth.token);
|
|
1520
|
+
const mcpConfig = buildMcpConfig(resolveApiUrl(opts.apiUrl));
|
|
1441
1521
|
if (mode === "pretty") {
|
|
1442
|
-
printInfo("MCP server config (add to your MCP client
|
|
1522
|
+
printInfo("MCP server config (add to your MCP client):");
|
|
1443
1523
|
console.log();
|
|
1444
1524
|
console.log(JSON.stringify(mcpConfig, null, 2));
|
|
1445
1525
|
console.log();
|
|
1446
|
-
|
|
1447
|
-
printInfo(`Token expires ${expiresAt} \u2014 run \`anima login\` then \`anima mcp-config\` to refresh.`);
|
|
1448
|
-
}
|
|
1526
|
+
printInfo("Your client will prompt you to authorize on first use.");
|
|
1449
1527
|
} else {
|
|
1450
|
-
outputResult(mode, {
|
|
1451
|
-
success: true,
|
|
1452
|
-
mcpConfig,
|
|
1453
|
-
...expiresAt ? { expiresAt } : {},
|
|
1454
|
-
...creds?.agentId && auth.source !== "env" ? { agentId: creds.agentId } : {}
|
|
1455
|
-
});
|
|
1528
|
+
outputResult(mode, { success: true, mcpConfig });
|
|
1456
1529
|
}
|
|
1457
1530
|
} catch (err) {
|
|
1458
1531
|
if (err instanceof CliError) {
|
|
@@ -1477,13 +1550,12 @@ var authCommand = new Command8("auth").description("Manage authentication").opti
|
|
|
1477
1550
|
if (opts.token || opts.figmaToken) {
|
|
1478
1551
|
const toSave = {};
|
|
1479
1552
|
if (opts.token) {
|
|
1480
|
-
toSave.
|
|
1481
|
-
|
|
1553
|
+
toSave.teamId = opts.teamId ?? extractTeamIdFromJWT(opts.token);
|
|
1554
|
+
saveCredential({ tokenType: "account", token: opts.token, teamId: toSave.teamId });
|
|
1482
1555
|
}
|
|
1483
1556
|
if (opts.figmaToken) {
|
|
1484
|
-
|
|
1557
|
+
saveFigmaToken(opts.figmaToken);
|
|
1485
1558
|
}
|
|
1486
|
-
writeCredentials(toSave);
|
|
1487
1559
|
if (mode === "pretty") {
|
|
1488
1560
|
printSuccess("Credentials saved to " + getCredentialsPath());
|
|
1489
1561
|
if (opts.token) {
|
|
@@ -1505,6 +1577,7 @@ var authCommand = new Command8("auth").description("Manage authentication").opti
|
|
|
1505
1577
|
return;
|
|
1506
1578
|
}
|
|
1507
1579
|
if (opts.logout) {
|
|
1580
|
+
const revoked = await revokeStoredRefreshToken(resolveApiUrl(opts.apiUrl));
|
|
1508
1581
|
const deleted = clearStorage();
|
|
1509
1582
|
if (mode === "pretty") {
|
|
1510
1583
|
if (deleted) {
|
|
@@ -1513,13 +1586,13 @@ var authCommand = new Command8("auth").description("Manage authentication").opti
|
|
|
1513
1586
|
printInfo("No stored credentials found");
|
|
1514
1587
|
}
|
|
1515
1588
|
} else {
|
|
1516
|
-
outputResult(mode, { success: true, action: "logged_out", hadCredentials: deleted });
|
|
1589
|
+
outputResult(mode, { success: true, action: "logged_out", hadCredentials: deleted, revoked });
|
|
1517
1590
|
}
|
|
1518
1591
|
return;
|
|
1519
1592
|
}
|
|
1520
1593
|
if (opts.status) {
|
|
1521
1594
|
try {
|
|
1522
|
-
const auth = resolveAuth();
|
|
1595
|
+
const auth = await resolveAuth(opts.apiUrl);
|
|
1523
1596
|
const creds = readCredentials();
|
|
1524
1597
|
const sourceName = auth.source === "env" ? "ANIMA_API_TOKEN env var" : getCredentialsPath();
|
|
1525
1598
|
const hasFigma = !!(process.env.FIGMA_TOKEN || creds?.figmaToken);
|
|
@@ -1527,7 +1600,7 @@ var authCommand = new Command8("auth").description("Manage authentication").opti
|
|
|
1527
1600
|
printSuccess("Token configured");
|
|
1528
1601
|
printKeyValue("Source", sourceName);
|
|
1529
1602
|
printKeyValue("AgentGrid token", auth.token.slice(0, 12) + "..." + auth.token.slice(-4));
|
|
1530
|
-
printKeyValue("Token type", auth.tokenType === "agent" ? "agent (scoped, revocable)" : "personal
|
|
1603
|
+
printKeyValue("Token type", auth.tokenType === "agent" ? "agent (scoped, revocable)" : "account (personal API token)");
|
|
1531
1604
|
if (creds?.tokenType === "agent" && creds.expiresAt) printKeyValue("Expires", creds.expiresAt);
|
|
1532
1605
|
if (auth.teamId) printKeyValue("Team ID", auth.teamId);
|
|
1533
1606
|
printKeyValue("Figma token", hasFigma ? "configured" : "not set (needed for codegen/f2c)");
|
|
@@ -1535,7 +1608,7 @@ var authCommand = new Command8("auth").description("Manage authentication").opti
|
|
|
1535
1608
|
outputResult(mode, {
|
|
1536
1609
|
success: true,
|
|
1537
1610
|
source: auth.source,
|
|
1538
|
-
tokenType: auth.tokenType ?? "
|
|
1611
|
+
tokenType: auth.tokenType ?? "account",
|
|
1539
1612
|
tokenPreview: auth.token.slice(0, 12) + "..." + auth.token.slice(-4),
|
|
1540
1613
|
teamId: auth.teamId,
|
|
1541
1614
|
expiresAt: creds?.tokenType === "agent" ? creds.expiresAt : void 0,
|
|
@@ -1586,40 +1659,42 @@ var authCommand = new Command8("auth").description("Manage authentication").opti
|
|
|
1586
1659
|
|
|
1587
1660
|
// src/commands/logout.ts
|
|
1588
1661
|
import { Command as Command9 } from "commander";
|
|
1589
|
-
var logoutCommand = new Command9("logout").description("Clear all stored credentials (AgentGrid + Figma tokens) from this machine").option("--json", "Output as JSON").action((opts) => {
|
|
1662
|
+
var logoutCommand = new Command9("logout").description("Clear all stored credentials (AgentGrid + Figma tokens) from this machine").option("--json", "Output as JSON").option("--api-url <url>", "API base URL (overrides config / ANIMA_API_URL)").action(async (opts) => {
|
|
1590
1663
|
const mode = detectOutputMode(opts);
|
|
1664
|
+
const revoked = await revokeStoredRefreshToken(resolveApiUrl(opts.apiUrl));
|
|
1591
1665
|
const hadCredentials = clearStorage();
|
|
1592
1666
|
deleteConfig();
|
|
1593
1667
|
if (mode === "pretty") {
|
|
1594
1668
|
if (hadCredentials) {
|
|
1595
1669
|
printSuccess("Logged out \u2014 all credentials cleared from " + getCredentialsPath());
|
|
1670
|
+
if (revoked) printInfo("Agent credentials revoked server-side");
|
|
1596
1671
|
} else {
|
|
1597
1672
|
printInfo("No stored credentials found");
|
|
1598
1673
|
}
|
|
1599
1674
|
} else {
|
|
1600
|
-
outputResult(mode, { success: true, action: "logged_out", hadCredentials });
|
|
1675
|
+
outputResult(mode, { success: true, action: "logged_out", hadCredentials, revoked });
|
|
1601
1676
|
}
|
|
1602
1677
|
});
|
|
1603
1678
|
|
|
1604
1679
|
// src/commands/unpublish.ts
|
|
1605
1680
|
import { Command as Command10 } from "commander";
|
|
1606
|
-
var unpublishCommand = new Command10("unpublish").description("Take a published
|
|
1681
|
+
var unpublishCommand = new Command10("unpublish").description("Take a published artifact offline (clears its live URL; code and artifact stay intact)").argument("<sessionId>", "Session ID of the published artifact").option("--json", "Output as JSON").option("--verbose", "Show progress on stderr").option("--api-url <url>", "API base URL (overrides config / ANIMA_API_URL)").option("--log-file <path>", "Append a JSON debug log of each step to this file").action(async (sessionId, opts) => {
|
|
1607
1682
|
const mode = detectOutputMode(opts);
|
|
1608
1683
|
initLogger(opts.logFile);
|
|
1609
1684
|
const progress = new ProgressReporter(mode, opts.verbose);
|
|
1610
1685
|
try {
|
|
1611
|
-
const auth = resolveAuth();
|
|
1686
|
+
const auth = await resolveAuth(opts.apiUrl);
|
|
1612
1687
|
progress.start("Unpublishing...");
|
|
1613
1688
|
const result = await withMcp(
|
|
1614
1689
|
{ apiBaseUrl: resolveApiUrl(opts.apiUrl), token: auth.token, teamId: auth.teamId },
|
|
1615
|
-
(client) => client.callTool("
|
|
1690
|
+
(client) => client.callTool("artifact-unpublish", { sessionId })
|
|
1616
1691
|
);
|
|
1617
1692
|
if (!result.success) {
|
|
1618
1693
|
throw apiError(result.reason || result.error || "Unpublish failed", result);
|
|
1619
1694
|
}
|
|
1620
1695
|
progress.succeed("Unpublished");
|
|
1621
1696
|
if (mode === "pretty") {
|
|
1622
|
-
printSuccess(result.message || `
|
|
1697
|
+
printSuccess(result.message || `Artifact ${sessionId} is no longer publicly reachable.`);
|
|
1623
1698
|
} else {
|
|
1624
1699
|
outputResult(mode, { success: true, sessionId, ...result.message ? { message: result.message } : {} });
|
|
1625
1700
|
}
|
|
@@ -1639,7 +1714,7 @@ var unpublishCommand = new Command10("unpublish").description("Take a published
|
|
|
1639
1714
|
|
|
1640
1715
|
// src/commands/update.ts
|
|
1641
1716
|
import { Command as Command11 } from "commander";
|
|
1642
|
-
var updateCommand = new Command11("update").description("Update
|
|
1717
|
+
var updateCommand = new Command11("update").description("Update an artifact's metadata: display name and/or visibility (never touches code)").argument("<sessionId>", "Session ID of the artifact").option("--name <name>", "New display name").option("--privacy <privacy>", "public (anyone with the link) | private (team only)").option("--json", "Output as JSON").option("--verbose", "Show progress on stderr").option("--api-url <url>", "API base URL (overrides config / ANIMA_API_URL)").option("--log-file <path>", "Append a JSON debug log of each step to this file").action(async (sessionId, opts) => {
|
|
1643
1718
|
const mode = detectOutputMode(opts);
|
|
1644
1719
|
initLogger(opts.logFile);
|
|
1645
1720
|
const progress = new ProgressReporter(mode, opts.verbose);
|
|
@@ -1650,11 +1725,11 @@ var updateCommand = new Command11("update").description("Update a playground's m
|
|
|
1650
1725
|
if (opts.privacy && !["public", "private"].includes(opts.privacy)) {
|
|
1651
1726
|
throw validationError(`Invalid privacy "${opts.privacy}". Must be public or private.`);
|
|
1652
1727
|
}
|
|
1653
|
-
const auth = resolveAuth();
|
|
1728
|
+
const auth = await resolveAuth(opts.apiUrl);
|
|
1654
1729
|
progress.start("Updating...");
|
|
1655
1730
|
const result = await withMcp(
|
|
1656
1731
|
{ apiBaseUrl: resolveApiUrl(opts.apiUrl), token: auth.token, teamId: auth.teamId },
|
|
1657
|
-
(client) => client.callTool("
|
|
1732
|
+
(client) => client.callTool("artifact-update_metadata", {
|
|
1658
1733
|
sessionId,
|
|
1659
1734
|
...opts.name ? { name: opts.name } : {},
|
|
1660
1735
|
...opts.privacy ? { privacy: opts.privacy } : {}
|
|
@@ -1665,7 +1740,7 @@ var updateCommand = new Command11("update").description("Update a playground's m
|
|
|
1665
1740
|
}
|
|
1666
1741
|
progress.succeed("Updated");
|
|
1667
1742
|
if (mode === "pretty") {
|
|
1668
|
-
printSuccess(result.message || `
|
|
1743
|
+
printSuccess(result.message || `Artifact ${sessionId} updated.`);
|
|
1669
1744
|
} else {
|
|
1670
1745
|
outputResult(mode, { success: true, sessionId, ...result.message ? { message: result.message } : {} });
|
|
1671
1746
|
}
|
|
@@ -1750,18 +1825,79 @@ function assertUrl(value) {
|
|
|
1750
1825
|
}
|
|
1751
1826
|
}
|
|
1752
1827
|
|
|
1828
|
+
// src/commands/duplicate.ts
|
|
1829
|
+
import { Command as Command13 } from "commander";
|
|
1830
|
+
import chalk7 from "chalk";
|
|
1831
|
+
function buildDuplicateArgs(artifactUrl, name) {
|
|
1832
|
+
const sessionId = parsePlaygroundUrl(artifactUrl);
|
|
1833
|
+
if (!sessionId) {
|
|
1834
|
+
throw validationError("A source artifact URL or session ID is required");
|
|
1835
|
+
}
|
|
1836
|
+
if (name !== void 0 && (name.length === 0 || name.length > 120)) {
|
|
1837
|
+
throw validationError("--name must be between 1 and 120 characters");
|
|
1838
|
+
}
|
|
1839
|
+
return { sessionId, ...name !== void 0 ? { name } : {} };
|
|
1840
|
+
}
|
|
1841
|
+
function createDuplicateCommand() {
|
|
1842
|
+
return new Command13("duplicate").description(
|
|
1843
|
+
"Duplicate an artifact into a new, independent artifact. Copies code, assets, and supported database content; chat and custom domains are not copied. Duplication is non-idempotent: if a request times out or its response is lost, list recent artifacts before retrying because the first request may already have created the copy."
|
|
1844
|
+
).argument("<artifactUrl>", "Source artifact URL or session ID").option("--name <name>", "Display name for the duplicate (defaults to '<source name> (Copy)')").option("--json", "Output as JSON").option("--verbose", "Show progress on stderr").option("--api-url <url>", "API base URL (overrides config / ANIMA_API_URL)").option("--log-file <path>", "Append a JSON debug log of each step to this file").action(async (artifactUrl, opts) => {
|
|
1845
|
+
const mode = detectOutputMode(opts);
|
|
1846
|
+
initLogger(opts.logFile);
|
|
1847
|
+
const progress = new ProgressReporter(mode, opts.verbose);
|
|
1848
|
+
try {
|
|
1849
|
+
const args = buildDuplicateArgs(artifactUrl, opts.name);
|
|
1850
|
+
const auth = await resolveAuth(opts.apiUrl);
|
|
1851
|
+
progress.start("Duplicating artifact...");
|
|
1852
|
+
const result = await withMcp(
|
|
1853
|
+
{ apiBaseUrl: resolveApiUrl(opts.apiUrl), token: auth.token, teamId: auth.teamId },
|
|
1854
|
+
(client) => client.callTool("artifact-duplicate", args)
|
|
1855
|
+
);
|
|
1856
|
+
if (!result.success) {
|
|
1857
|
+
throw apiError(result.reason || result.error || "Artifact duplication failed", result);
|
|
1858
|
+
}
|
|
1859
|
+
progress.succeed("Artifact duplicated");
|
|
1860
|
+
if (mode === "pretty") {
|
|
1861
|
+
console.log();
|
|
1862
|
+
printSuccess("Artifact duplicated successfully!");
|
|
1863
|
+
printKeyValue("Name", result.name);
|
|
1864
|
+
printKeyValue("Session ID", result.sessionId);
|
|
1865
|
+
printKeyValue("Source session", result.sourceSessionId);
|
|
1866
|
+
printKeyValue("Artifact", chalk7.cyan.underline(result.playgroundUrl));
|
|
1867
|
+
printKeyValue("Preview", chalk7.cyan.underline(result.previewUrl));
|
|
1868
|
+
printKeyValue("Next steps", result.nextSteps);
|
|
1869
|
+
console.log();
|
|
1870
|
+
} else {
|
|
1871
|
+
outputResult(mode, result);
|
|
1872
|
+
}
|
|
1873
|
+
} catch (err) {
|
|
1874
|
+
progress.fail("Failed");
|
|
1875
|
+
if (err instanceof CliError) {
|
|
1876
|
+
if (mode === "pretty") {
|
|
1877
|
+
printError(err.message);
|
|
1878
|
+
} else {
|
|
1879
|
+
outputError(mode, { code: err.code, message: err.message });
|
|
1880
|
+
}
|
|
1881
|
+
process.exit(err.exitCode);
|
|
1882
|
+
}
|
|
1883
|
+
throw err;
|
|
1884
|
+
}
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
var duplicateCommand = createDuplicateCommand();
|
|
1888
|
+
|
|
1753
1889
|
// src/index.ts
|
|
1754
|
-
var program = new
|
|
1755
|
-
`AgentGrid CLI \u2014 host, edit, and publish your work on AgentGrid. Every
|
|
1890
|
+
var program = new Command14().name("anima").description(
|
|
1891
|
+
`AgentGrid CLI \u2014 host, edit, and publish your work on AgentGrid. Every artifact is a
|
|
1756
1892
|
real git repository; each command runs an AgentGrid tool for you.
|
|
1757
1893
|
|
|
1758
1894
|
THE MAIN FLOW (build something and put it on AgentGrid):
|
|
1759
1895
|
1. npx @animaapp/cli login # once; a human approves
|
|
1760
|
-
2. npx @animaapp/cli create -t import --from ./my-project # your code \u2192 a live
|
|
1896
|
+
2. npx @animaapp/cli create -t import --from ./my-project # your code \u2192 a live app
|
|
1761
1897
|
(or: create -t empty --framework html \u2014 an empty repo you push to)
|
|
1762
1898
|
3. The response includes a rw gitRemoteUrl \u2014 git clone, edit, commit, push:
|
|
1763
|
-
every push updates the live
|
|
1764
|
-
4. Share the playgroundUrl with your human. That IS sharing \u2014 the
|
|
1899
|
+
every push updates the live artifact.
|
|
1900
|
+
4. Share the playgroundUrl with your human. That IS sharing \u2014 the artifact
|
|
1765
1901
|
is visible at its URL. Do NOT run publish unless the human explicitly
|
|
1766
1902
|
asked for a public site; offer it as a follow-up question instead.
|
|
1767
1903
|
|
|
@@ -1774,19 +1910,20 @@ TIMING (important for AI agents):
|
|
|
1774
1910
|
create -t empty|import \u2192 seconds
|
|
1775
1911
|
create -t p2c|l2c|f2c \u2192 3-7 minutes AI generation (keep the default 600000ms timeout)
|
|
1776
1912
|
publish/unpublish \u2192 1-3 minutes
|
|
1777
|
-
get-git-token / update \u2192 seconds
|
|
1913
|
+
duplicate / get-git-token / update \u2192 seconds
|
|
1778
1914
|
|
|
1779
1915
|
COMMANDS:
|
|
1780
1916
|
login Connect (device flow, or --invite to redeem an invite link)
|
|
1781
1917
|
logout Full local reset \u2014 clears credentials and CLI config
|
|
1782
1918
|
list List your team's artifacts (most-recently-updated first) to resume work
|
|
1783
|
-
create Make
|
|
1784
|
-
|
|
1785
|
-
|
|
1919
|
+
create Make an artifact: import your code, empty repo, or AI-generated (p2c/l2c/f2c)
|
|
1920
|
+
duplicate Copy an artifact into a new, independent artifact
|
|
1921
|
+
get-git-token Fresh git access to an existing artifact (or after token expiry)
|
|
1922
|
+
update Rename an artifact or set visibility public/private (metadata only)
|
|
1786
1923
|
publish Deploy to a public live URL \u2014 ONLY when the human asked for it
|
|
1787
|
-
unpublish Take a published
|
|
1924
|
+
unpublish Take a published artifact offline (inverse of publish)
|
|
1788
1925
|
mcp-config Print an MCP server config from stored credentials (no new login)
|
|
1789
|
-
codegen Convert Figma design to local code files (no
|
|
1926
|
+
codegen Convert Figma design to local code files (no artifact)
|
|
1790
1927
|
config Store CLI preferences (e.g. a default API URL)
|
|
1791
1928
|
auth Inspect or manage credentials (status / figma token)
|
|
1792
1929
|
|
|
@@ -1794,6 +1931,7 @@ EXAMPLES (headless flow):
|
|
|
1794
1931
|
npx @animaapp/cli login --invite https://api.agentgrid.io/invite/<code>.md
|
|
1795
1932
|
npx @animaapp/cli create -t import --from ./my-project
|
|
1796
1933
|
npx @animaapp/cli create -t empty --framework react --name "My app"
|
|
1934
|
+
npx @animaapp/cli duplicate <artifactUrl> --name "My copy"
|
|
1797
1935
|
npx @animaapp/cli get-git-token <sessionId>
|
|
1798
1936
|
npx @animaapp/cli update <sessionId> --privacy public
|
|
1799
1937
|
npx @animaapp/cli publish <sessionId> # only on explicit request
|
|
@@ -1806,6 +1944,7 @@ program.addCommand(loginCommand);
|
|
|
1806
1944
|
program.addCommand(logoutCommand);
|
|
1807
1945
|
program.addCommand(listCommand);
|
|
1808
1946
|
program.addCommand(createCommand);
|
|
1947
|
+
program.addCommand(duplicateCommand);
|
|
1809
1948
|
program.addCommand(publishCommand);
|
|
1810
1949
|
program.addCommand(unpublishCommand);
|
|
1811
1950
|
program.addCommand(updateCommand);
|