@animaapp/cli 0.8.0 → 0.9.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/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 Command21 } from "commander";
4
+ import { Command as Command23 } from "commander";
5
5
 
6
6
  // src/commands/create.ts
7
7
  import { Command } from "commander";
@@ -176,7 +176,19 @@ function validationError(message, detail) {
176
176
  );
177
177
  }
178
178
  function apiError(message, detail) {
179
- return new CliError("API_ERROR", message, EXIT_CODES.API_ERROR, detail);
179
+ const text = message.replace(/^Error: /, "");
180
+ return new CliError("API_ERROR", withWorkspaceHint(text), EXIT_CODES.API_ERROR, detail);
181
+ }
182
+ function withWorkspaceHint(message) {
183
+ if (/\bworkspaceId\b/.test(message)) {
184
+ return `${message}
185
+ Pass it with --workspace <id>; \`anima workspaces\` lists the ids.`;
186
+ }
187
+ if (message === "Workspace not found") {
188
+ return `${message}
189
+ \`anima workspaces\` lists the workspaces you can reach, with their ids.`;
190
+ }
191
+ return message;
180
192
  }
181
193
  function timeoutError() {
182
194
  return new CliError(
@@ -423,9 +435,16 @@ import { CallToolResultSchema, ErrorCode } from "@modelcontextprotocol/sdk/types
423
435
  import { createRequire } from "module";
424
436
  var { version } = createRequire(import.meta.url)("../package.json");
425
437
 
438
+ // src/clientIdentity.ts
439
+ var ANIMA_CLIENT_IDENTITY = {
440
+ type: "cli",
441
+ name: "anima-cli",
442
+ version
443
+ };
444
+
426
445
  // src/api/mcp.ts
427
446
  var MCP_PATH = "/v1/mcp";
428
- var CLIENT_INFO = { name: "anima-cli", version };
447
+ var CLIENT_INFO = { name: ANIMA_CLIENT_IDENTITY.name, version: ANIMA_CLIENT_IDENTITY.version };
429
448
  var DEFAULT_TIMEOUT_MS = 6e5;
430
449
  var AnimaMcpClient = class {
431
450
  #client;
@@ -433,7 +452,10 @@ var AnimaMcpClient = class {
433
452
  #url;
434
453
  constructor(config) {
435
454
  const headers = {
436
- Authorization: `Bearer ${config.token}`
455
+ Authorization: `Bearer ${config.token}`,
456
+ "X-Anima-Client-Type": ANIMA_CLIENT_IDENTITY.type,
457
+ "X-Anima-Client-Name": ANIMA_CLIENT_IDENTITY.name,
458
+ "X-Anima-Client-Version": ANIMA_CLIENT_IDENTITY.version
437
459
  };
438
460
  if (config.teamId) headers["X-Team-Id"] = config.teamId;
439
461
  if (config.figmaToken) headers["X-Figma-Token"] = config.figmaToken;
@@ -475,6 +497,11 @@ var AnimaMcpClient = class {
475
497
  if (err?.code === ErrorCode.RequestTimeout) {
476
498
  throw timeoutError();
477
499
  }
500
+ if (err?.code === ErrorCode.InvalidParams && /\bnot found\b/.test(err.message)) {
501
+ throw apiError(
502
+ `${name} is not available to this credential. An agent is offered it only once a human has granted it the access it needs in a workspace of this team (read to list, write to create).`
503
+ );
504
+ }
478
505
  throw err;
479
506
  }
480
507
  logEvent("mcp_tool_result", { tool: name, isError: result.isError ?? false });
@@ -827,6 +854,7 @@ async function createAnonymousArtifact(request, fetchFn = fetch) {
827
854
  const url = `${request.apiUrl.replace(/\/$/, "")}/v1/generationSessions`;
828
855
  const body = {
829
856
  type: "import",
857
+ clientType: ANIMA_CLIENT_IDENTITY.type,
830
858
  ...request.files ? { files: request.files } : {},
831
859
  ...request.zipUploadId ? { zipUploadId: request.zipUploadId } : {},
832
860
  ...request.framework ? { framework: request.framework } : {},
@@ -867,6 +895,14 @@ async function getAnonymousZipUploadUrl(apiUrl, fetchFn = fetch) {
867
895
  return { zipUploadId: payload.zipUploadId, uploadUrl: payload.uploadUrl };
868
896
  }
869
897
 
898
+ // src/utils/workspaceFlag.ts
899
+ function workspaceFlag(value) {
900
+ if (value !== void 0 && value.trim() === "") {
901
+ throw validationError("--workspace needs a workspace id; `anima workspaces` lists them");
902
+ }
903
+ return value;
904
+ }
905
+
870
906
  // src/commands/create.ts
871
907
  var createCommand = new Command("create").summary("Create an artifact: your code, an empty repo, or AI generation").description(
872
908
  "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)."
@@ -879,7 +915,10 @@ var createCommand = new Command("create").summary("Create an artifact: your code
879
915
  ).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(
880
916
  "--artifact-type <kind>",
881
917
  "app | markdown \u2014 what the artifact IS. app (default) needs an index.html; markdown needs .md files"
882
- ).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(
918
+ ).option("--name <name>", "Project name (empty/import only)").option(
919
+ "-w, --workspace <id>",
920
+ "The workspace to create it in (id from `anima workspaces`). Optional when you can create in only one workspace; otherwise required, since there is no default"
921
+ ).option("--from <path>", "Folder or .zip with your project (import only) \u2014 becomes the first commit").option(
883
922
  "--anonymous",
884
923
  "Create without an account (import only). Returns a claim link to send a human, and a handoff token to log in with once they claim it"
885
924
  ).option(
@@ -946,7 +985,7 @@ ${grant.nextSteps}` : message, grant);
946
985
  return { created, status: status2 };
947
986
  }).then(({ created, status: status2 }) => ({ result: created, settled: status2 }));
948
987
  if (!result.success || !result.sessionId) {
949
- const message = result.reason || result.error || "Generation failed";
988
+ const message = result.message || result.reason || result.error || "Generation failed";
950
989
  throw apiError(result.nextSteps ? `${message}
951
990
  ${result.nextSteps}` : message, result);
952
991
  }
@@ -1152,6 +1191,12 @@ Log in first for the other types: anima login`
1152
1191
  if (type !== "import" && opts.from) {
1153
1192
  throw validationError("--from is only valid with -t import");
1154
1193
  }
1194
+ workspaceFlag(opts.workspace);
1195
+ if (opts.workspace !== void 0 && opts.anonymous) {
1196
+ throw validationError(
1197
+ "--workspace does not apply to --anonymous creates \u2014 they belong to no team until a human claims them"
1198
+ );
1199
+ }
1155
1200
  }
1156
1201
  function resolveFigmaToken(opts, stored) {
1157
1202
  const figmaToken = opts.figmaToken || process.env.FIGMA_TOKEN || stored;
@@ -1170,7 +1215,8 @@ function buildCreateArgs(type, opts) {
1170
1215
  // import is what made markdown artifacts render as empty apps.
1171
1216
  ...opts.framework && opts.artifactType !== "markdown" ? { framework: opts.framework } : {},
1172
1217
  ...opts.artifactType ? { artifactType: opts.artifactType } : {},
1173
- ...opts.name ? { name: opts.name } : {}
1218
+ ...opts.name ? { name: opts.name } : {},
1219
+ ...opts.workspace !== void 0 ? { workspaceId: opts.workspace } : {}
1174
1220
  };
1175
1221
  }
1176
1222
  const framework = opts.framework ?? "html";
@@ -1185,6 +1231,7 @@ function buildCreateArgs(type, opts) {
1185
1231
  };
1186
1232
  if (language) args.language = language;
1187
1233
  if (opts.uiLibrary) args.uiLibrary = opts.uiLibrary;
1234
+ if (opts.workspace !== void 0) args.workspaceId = opts.workspace;
1188
1235
  switch (type) {
1189
1236
  case "p2c":
1190
1237
  args.prompt = opts.prompt;
@@ -1209,22 +1256,107 @@ function buildCreateArgs(type, opts) {
1209
1256
  return args;
1210
1257
  }
1211
1258
 
1212
- // src/commands/list.ts
1259
+ // src/commands/createKnowledge.ts
1213
1260
  import { Command as Command2 } from "commander";
1261
+
1262
+ // src/commands/runner.ts
1263
+ async function withCommandErrors(mode, fn) {
1264
+ try {
1265
+ await fn();
1266
+ } catch (err) {
1267
+ if (err instanceof CliError) {
1268
+ if (mode === "pretty") {
1269
+ printError(err.message);
1270
+ } else {
1271
+ outputError(mode, { code: err.code, message: err.message });
1272
+ }
1273
+ process.exit(err.exitCode);
1274
+ }
1275
+ throw err;
1276
+ }
1277
+ }
1278
+
1279
+ // src/commands/createKnowledge.ts
1280
+ function buildCreateKnowledgeArgs(name, workspaceId) {
1281
+ if (name !== void 0 && name.length > 120) {
1282
+ throw validationError("--name must be at most 120 characters");
1283
+ }
1284
+ return {
1285
+ ...name === void 0 ? {} : { name },
1286
+ ...workspaceFlag(workspaceId) === void 0 ? {} : { workspaceId }
1287
+ };
1288
+ }
1289
+ function makeCreateKnowledgeCommand() {
1290
+ return new Command2("create-knowledge").summary("Create a knowledge artifact from Anima's template").description(
1291
+ "Create a ready knowledge artifact from Anima's server-provided template. The template supplies the files and framework; only an optional display name is accepted."
1292
+ ).option("--name <name>", 'Display name (defaults to "Untitled project"; max 120 characters)').option(
1293
+ "-w, --workspace <id>",
1294
+ "The workspace to create it in (id from `anima workspaces`). Optional when you can create in only one workspace; otherwise required, since there is no default"
1295
+ ).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 (opts) => {
1296
+ const mode = detectOutputMode(opts);
1297
+ initLogger(opts.logFile);
1298
+ const progress = new ProgressReporter(mode, opts.verbose);
1299
+ await withCommandErrors(mode, async () => {
1300
+ try {
1301
+ await run();
1302
+ } catch (err) {
1303
+ progress.fail("Failed");
1304
+ throw err;
1305
+ }
1306
+ });
1307
+ async function run() {
1308
+ const args = buildCreateKnowledgeArgs(opts.name, opts.workspace);
1309
+ const auth = await resolveAuth(opts.apiUrl);
1310
+ progress.start("Creating knowledge artifact from template...");
1311
+ const result = await withMcp(
1312
+ { apiBaseUrl: resolveApiUrl(opts.apiUrl), token: auth.token, teamId: auth.teamId },
1313
+ (client) => client.callTool("artifact-create_knowledge", args)
1314
+ );
1315
+ if (!result.success || !result.sessionId) {
1316
+ throw apiError(result.message || result.reason || result.error || "Knowledge artifact creation failed", result);
1317
+ }
1318
+ progress.succeed("Knowledge artifact created");
1319
+ if (mode === "pretty") {
1320
+ printSessionResult({
1321
+ sessionId: result.sessionId,
1322
+ artifactUrl: result.artifactUrl,
1323
+ playgroundUrl: result.playgroundUrl
1324
+ });
1325
+ if (result.fileCount !== void 0) {
1326
+ printKeyValue("Template files", String(result.fileCount));
1327
+ }
1328
+ console.log();
1329
+ } else {
1330
+ outputResult(mode, result);
1331
+ }
1332
+ }
1333
+ });
1334
+ }
1335
+ var createKnowledgeCommand = makeCreateKnowledgeCommand();
1336
+
1337
+ // src/commands/list.ts
1338
+ import { Command as Command3 } from "commander";
1214
1339
  import chalk3 from "chalk";
1215
- var listCommand = new Command2("list").summary("List your team's artifacts, most-recently-updated first").aliases(["ls", "artifacts"]).description(
1216
- "List your team's artifacts (most-recently-updated first) so you can resume existing work."
1340
+ var listCommand = new Command3("list").summary("List the artifacts you can read, most-recently-updated first").aliases(["ls", "artifacts"]).description(
1341
+ "List the artifacts you can read, across every workspace you can reach or in one (most-recently-updated first), so you can resume existing work."
1342
+ ).option(
1343
+ "-w, --workspace <id>",
1344
+ "Only this workspace (id from `anima workspaces`); omit to span every workspace you can read"
1217
1345
  ).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").action(async (opts) => {
1218
1346
  const mode = detectOutputMode(opts);
1219
1347
  initLogger(opts.logFile);
1220
1348
  try {
1221
1349
  const auth = await resolveAuth(opts.apiUrl);
1350
+ const workspaceId = workspaceFlag(opts.workspace);
1222
1351
  const result = await withMcp(
1223
1352
  { apiBaseUrl: resolveApiUrl(opts.apiUrl), token: auth.token, teamId: auth.teamId },
1224
- (client) => client.callTool("workspace-list_artifacts", {})
1353
+ (client) => client.callTool(
1354
+ "workspace-list_artifacts",
1355
+ workspaceId !== void 0 ? { workspaceId } : {}
1356
+ )
1225
1357
  );
1226
1358
  if (!result.success) {
1227
- const message = result.reason || result.error || "Could not list artifacts";
1359
+ const message = result.message || result.reason || result.error || "Could not list artifacts";
1228
1360
  throw apiError(message, result);
1229
1361
  }
1230
1362
  const artifacts = result.artifacts ?? [];
@@ -1233,20 +1365,19 @@ var listCommand = new Command2("list").summary("List your team's artifacts, most
1233
1365
  if (artifacts.length === 0) {
1234
1366
  console.log();
1235
1367
  console.log(
1236
- " No artifacts yet \u2014 create one with `create -t empty` or `create -t import`."
1368
+ workspaceId !== void 0 ? " Nothing you can read in that workspace." : " No artifacts you can read \u2014 create one with `create -t empty` or `create -t import`."
1237
1369
  );
1238
1370
  console.log();
1239
1371
  } else {
1240
1372
  console.log();
1241
1373
  for (const a of artifacts) {
1242
1374
  console.log(` ${chalk3.bold(a.name)} ${chalk3.gray(`(${a.type})`)}`);
1243
- if (a.sessionId) {
1244
- console.log(` ${chalk3.gray("session:")} ${a.sessionId}`);
1375
+ console.log(` ${chalk3.gray("session:")} ${a.sessionId}`);
1376
+ if (a.workspaceName) {
1377
+ console.log(` ${chalk3.gray("workspace:")} ${a.workspaceName}`);
1245
1378
  }
1246
1379
  console.log(` ${chalk3.gray("updated:")} ${formatUpdatedAt(a.updatedAt)}`);
1247
- if (a.sessionId) {
1248
- console.log(` ${chalk3.cyan.underline(artifactUrlFor(a.sessionId))}`);
1249
- }
1380
+ console.log(` ${chalk3.cyan.underline(artifactUrlFor(a.sessionId))}`);
1250
1381
  console.log();
1251
1382
  }
1252
1383
  if (result.truncated) {
@@ -1259,13 +1390,9 @@ var listCommand = new Command2("list").summary("List your team's artifacts, most
1259
1390
  } else {
1260
1391
  outputResult(mode, {
1261
1392
  success: true,
1262
- workspaceId: result.workspaceId,
1263
1393
  // The listing tool returns no URL; deriving it here saves every caller
1264
1394
  // the same string concatenation, and keeps one definition of the link.
1265
- artifacts: artifacts.map((a) => ({
1266
- ...a,
1267
- ...a.sessionId ? { artifactUrl: artifactUrlFor(a.sessionId) } : {}
1268
- })),
1395
+ artifacts: artifacts.map((a) => ({ ...a, artifactUrl: artifactUrlFor(a.sessionId) })),
1269
1396
  truncated: result.truncated ?? false
1270
1397
  });
1271
1398
  }
@@ -1289,10 +1416,65 @@ function formatUpdatedAt(iso) {
1289
1416
  return `${relative2} (${iso})`;
1290
1417
  }
1291
1418
 
1292
- // src/commands/status.ts
1293
- import { Command as Command3 } from "commander";
1419
+ // src/commands/workspaces.ts
1420
+ import { Command as Command4 } from "commander";
1294
1421
  import chalk4 from "chalk";
1295
- var statusCommand = new Command3("status").summary("Check whether an artifact finished generating").description(
1422
+ var workspacesCommand = new Command4("workspaces").summary("List the workspaces you can reach, and what you may do in each").aliases(["ws"]).description(
1423
+ "List the workspaces you can read in your team, with the capabilities you hold in each. Use a workspace id with `list --workspace` or `create --workspace`."
1424
+ ).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").action(async (opts) => {
1425
+ const mode = detectOutputMode(opts);
1426
+ initLogger(opts.logFile);
1427
+ try {
1428
+ const auth = await resolveAuth(opts.apiUrl);
1429
+ const result = await withMcp(
1430
+ { apiBaseUrl: resolveApiUrl(opts.apiUrl), token: auth.token, teamId: auth.teamId },
1431
+ (client) => client.callTool("workspace-list_workspaces", {})
1432
+ );
1433
+ if (!result.success) {
1434
+ const message = result.message || result.reason || result.error || "Could not list workspaces";
1435
+ throw apiError(message, result);
1436
+ }
1437
+ const workspaces = result.workspaces ?? [];
1438
+ logEvent("workspaces_listed", { count: workspaces.length });
1439
+ if (mode === "pretty") {
1440
+ console.log();
1441
+ if (workspaces.length === 0) {
1442
+ console.log(" You cannot reach any workspace in this team.");
1443
+ console.log(
1444
+ chalk4.gray(" Someone who manages the team can grant you access to one.")
1445
+ );
1446
+ console.log();
1447
+ } else {
1448
+ for (const workspace of workspaces) {
1449
+ const label = workspace.isGeneral && workspace.name !== "General" ? chalk4.gray(" (General)") : "";
1450
+ console.log(` ${chalk4.bold(workspace.name)}${label}`);
1451
+ console.log(` ${chalk4.gray("id:")} ${workspace.workspaceId}`);
1452
+ console.log(
1453
+ ` ${chalk4.gray("you can:")} ${workspace.capabilities.join(", ") || chalk4.gray("nothing")}`
1454
+ );
1455
+ console.log();
1456
+ }
1457
+ }
1458
+ } else {
1459
+ outputResult(mode, { success: true, workspaces });
1460
+ }
1461
+ } catch (err) {
1462
+ if (err instanceof CliError) {
1463
+ if (mode === "pretty") {
1464
+ console.error(err.message);
1465
+ } else {
1466
+ outputError(mode, { code: err.code, message: err.message });
1467
+ }
1468
+ process.exit(err.exitCode);
1469
+ }
1470
+ throw err;
1471
+ }
1472
+ });
1473
+
1474
+ // src/commands/status.ts
1475
+ import { Command as Command5 } from "commander";
1476
+ import chalk5 from "chalk";
1477
+ var statusCommand = new Command5("status").summary("Check whether an artifact finished generating").description(
1296
1478
  "Check whether an artifact finished generating. --wait blocks until it is ready or failed."
1297
1479
  ).argument("<artifactUrl>", "Artifact URL or session ID").option("--wait", "Block until generation finishes instead of returning a snapshot").option("--json", "Output as JSON").option("--timeout <ms>", "How long --wait may block", "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 (artifactUrl, opts) => {
1298
1480
  const mode = detectOutputMode(opts);
@@ -1348,10 +1530,10 @@ function printArtifactStatus(sessionId, result) {
1348
1530
  printKeyValue("Progress", `${result.progress}%`);
1349
1531
  }
1350
1532
  if (result.artifactUrl) {
1351
- printKeyValue("Your artifact", chalk4.cyan.underline(result.artifactUrl));
1533
+ printKeyValue("Your artifact", chalk5.cyan.underline(result.artifactUrl));
1352
1534
  }
1353
1535
  if (result.playgroundUrl) {
1354
- printKeyValue("Edit in Playground", chalk4.cyan.underline(result.playgroundUrl));
1536
+ printKeyValue("Edit in Playground", chalk5.cyan.underline(result.playgroundUrl));
1355
1537
  }
1356
1538
  if (result.nextStep) {
1357
1539
  console.log();
@@ -1361,8 +1543,8 @@ function printArtifactStatus(sessionId, result) {
1361
1543
  }
1362
1544
 
1363
1545
  // src/commands/publish.ts
1364
- import { Command as Command4 } from "commander";
1365
- var publishCommand = new Command4("publish").summary("Deploy to a public live URL (only when asked)").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) => {
1546
+ import { Command as Command6 } from "commander";
1547
+ var publishCommand = new Command6("publish").summary("Deploy to a public live URL (only when asked)").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) => {
1366
1548
  const mode = detectOutputMode(opts);
1367
1549
  initLogger(opts.logFile);
1368
1550
  const progress = new ProgressReporter(mode, opts.verbose);
@@ -1408,7 +1590,7 @@ var publishCommand = new Command4("publish").summary("Deploy to a public live UR
1408
1590
  });
1409
1591
 
1410
1592
  // src/commands/codegen.ts
1411
- import { Command as Command5 } from "commander";
1593
+ import { Command as Command7 } from "commander";
1412
1594
 
1413
1595
  // src/utils/files.ts
1414
1596
  import fs4 from "fs";
@@ -1436,7 +1618,7 @@ function writeFilesToDisk(files, outputDir) {
1436
1618
  }
1437
1619
 
1438
1620
  // src/commands/codegen.ts
1439
- var codegenCommand = new Command5("codegen").summary("Convert a Figma design to local code files").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) => {
1621
+ var codegenCommand = new Command7("codegen").summary("Convert a Figma design to local code files").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) => {
1440
1622
  const mode = detectOutputMode(opts);
1441
1623
  initLogger(opts.logFile);
1442
1624
  const progress = new ProgressReporter(mode, opts.verbose);
@@ -1508,8 +1690,8 @@ var codegenCommand = new Command5("codegen").summary("Convert a Figma design to
1508
1690
  });
1509
1691
 
1510
1692
  // src/commands/getGitToken.ts
1511
- import { Command as Command6 } from "commander";
1512
- import chalk5 from "chalk";
1693
+ import { Command as Command8 } from "commander";
1694
+ import chalk6 from "chalk";
1513
1695
 
1514
1696
  // src/utils/gitToken.ts
1515
1697
  function preview(payload) {
@@ -1555,7 +1737,7 @@ function gitConfigCommands(commitAuthor) {
1555
1737
  }
1556
1738
 
1557
1739
  // src/commands/getGitToken.ts
1558
- var getGitTokenCommand = new Command6("get-git-token").summary("Mint git access to an artifact's repository").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) => {
1740
+ var getGitTokenCommand = new Command8("get-git-token").summary("Mint git access to an artifact's repository").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) => {
1559
1741
  const mode = detectOutputMode(opts);
1560
1742
  initLogger(opts.logFile);
1561
1743
  const progress = new ProgressReporter(mode, opts.verbose);
@@ -1572,12 +1754,12 @@ var getGitTokenCommand = new Command6("get-git-token").summary("Mint git access
1572
1754
  progress.succeed(`Git access granted (${grant.access === "rw" ? "read-write" : "read-only"})`);
1573
1755
  if (mode === "pretty") {
1574
1756
  console.log();
1575
- console.log(` ${chalk5.cyan(`git clone ${grant.gitRemoteUrl}`)}`);
1757
+ console.log(` ${chalk6.cyan(`git clone ${grant.gitRemoteUrl}`)}`);
1576
1758
  console.log();
1577
1759
  if (grant.access === "rw") {
1578
1760
  if (grant.commitAuthor) {
1579
1761
  console.log(" In the clone, set who your commits are from:");
1580
- console.log(` ${chalk5.cyan(gitConfigCommands(grant.commitAuthor))}`);
1762
+ console.log(` ${chalk6.cyan(gitConfigCommands(grant.commitAuthor))}`);
1581
1763
  console.log();
1582
1764
  }
1583
1765
  console.log(" Commit and push to update the live artifact.");
@@ -1587,7 +1769,7 @@ var getGitTokenCommand = new Command6("get-git-token").summary("Mint git access
1587
1769
  if (grant.expiresAt) {
1588
1770
  console.log(
1589
1771
  ` The URL embeds a token that expires at ${grant.expiresAt} and cannot be renewed;
1590
- re-run this command and ${chalk5.cyan("git remote set-url origin <new url>")} to keep pushing.`
1772
+ re-run this command and ${chalk6.cyan("git remote set-url origin <new url>")} to keep pushing.`
1591
1773
  );
1592
1774
  }
1593
1775
  console.log(" Treat the URL as a secret \u2014 it grants access to this artifact.");
@@ -1620,11 +1802,11 @@ var getGitTokenCommand = new Command6("get-git-token").summary("Mint git access
1620
1802
  });
1621
1803
 
1622
1804
  // src/commands/mcpConfig.ts
1623
- import { Command as Command8 } from "commander";
1805
+ import { Command as Command10 } from "commander";
1624
1806
 
1625
1807
  // src/commands/login.ts
1626
- import { Command as Command7 } from "commander";
1627
- import chalk6 from "chalk";
1808
+ import { Command as Command9 } from "commander";
1809
+ import chalk7 from "chalk";
1628
1810
  import open from "open";
1629
1811
 
1630
1812
  // src/auth/device.ts
@@ -1814,7 +1996,7 @@ function resolveDeadline(claimableUntil, now) {
1814
1996
  }
1815
1997
 
1816
1998
  // src/commands/login.ts
1817
- var loginCommand = new Command7("login").summary("Connect this machine as a scoped agent identity").description("Log in via the device flow, redeem an invite with --invite, or finish an anonymous create with --handoff").option("--invite <url-or-code>", "Redeem an invite link (\u2026/invite/<code>.md) or bare code \u2014 no approval step").option(
1999
+ var loginCommand = new Command9("login").summary("Connect this machine as a scoped agent identity").description("Log in via the device flow, redeem an invite with --invite, or finish an anonymous create with --handoff").option("--invite <url-or-code>", "Redeem an invite link (\u2026/invite/<code>.md) or bare code \u2014 no approval step").option(
1818
2000
  "--handoff [token]",
1819
2001
  "Log in with the handoff token from an anonymous create. Waits until a human claims the artifact. Defaults to your last one"
1820
2002
  ).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(
@@ -1884,9 +2066,9 @@ var loginCommand = new Command7("login").summary("Connect this machine as a scop
1884
2066
  console.log();
1885
2067
  printInfo("To connect, open the verification page and enter this code:");
1886
2068
  console.log();
1887
- console.log(` ${chalk6.cyan.underline(info.verificationUri)}`);
2069
+ console.log(` ${chalk7.cyan.underline(info.verificationUri)}`);
1888
2070
  console.log();
1889
- console.log(` ${chalk6.bold.white(info.userCode)}`);
2071
+ console.log(` ${chalk7.bold.white(info.userCode)}`);
1890
2072
  console.log();
1891
2073
  printInfo(`Waiting for approval\u2026 (code expires in ${Math.round(info.expiresInSeconds / 60)} minutes)`);
1892
2074
  } else {
@@ -1963,7 +2145,7 @@ async function runHandoffLogin(opts, mode) {
1963
2145
  printInfo("Waiting for a human to claim the artifact:");
1964
2146
  if (pending.artifactUrl) {
1965
2147
  console.log();
1966
- console.log(` ${chalk6.cyan.underline(pending.artifactUrl)}`);
2148
+ console.log(` ${chalk7.cyan.underline(pending.artifactUrl)}`);
1967
2149
  }
1968
2150
  console.log();
1969
2151
  printInfo(
@@ -2038,7 +2220,7 @@ function buildMcpConfig(apiUrl) {
2038
2220
  }
2039
2221
 
2040
2222
  // src/commands/mcpConfig.ts
2041
- var mcpConfigCommand = new Command8("mcp-config").summary("Print an MCP server config for your MCP client").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) => {
2223
+ var mcpConfigCommand = new Command10("mcp-config").summary("Print an MCP server config for your MCP client").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) => {
2042
2224
  const mode = detectOutputMode(opts);
2043
2225
  try {
2044
2226
  const mcpConfig = buildMcpConfig(resolveApiUrl(opts.apiUrl));
@@ -2065,8 +2247,8 @@ var mcpConfigCommand = new Command8("mcp-config").summary("Print an MCP server c
2065
2247
  });
2066
2248
 
2067
2249
  // src/commands/auth.ts
2068
- import { Command as Command9 } from "commander";
2069
- import chalk7 from "chalk";
2250
+ import { Command as Command11 } from "commander";
2251
+ import chalk8 from "chalk";
2070
2252
  var DEFAULT_API_URL2 = "https://api.agentgrid.io";
2071
2253
  function describePendingHandoff(pending, state) {
2072
2254
  return {
@@ -2084,10 +2266,10 @@ function printPendingHandoff(pending, state) {
2084
2266
  return;
2085
2267
  }
2086
2268
  printInfo(`Anonymous artifact ${pending.sessionId} is awaiting a claim (until ${pending.claimableUntil}):`);
2087
- console.log(` ${chalk7.cyan.underline(pending.artifactUrl)}`);
2088
- console.log(` Run ${chalk7.cyan("anima login --handoff")} to wait for it.`);
2269
+ console.log(` ${chalk8.cyan.underline(pending.artifactUrl)}`);
2270
+ console.log(` Run ${chalk8.cyan("anima login --handoff")} to wait for it.`);
2089
2271
  }
2090
- var authCommand = new Command9("auth").summary("Inspect or manage stored credentials").description("Manage authentication").option("--token <token>", "Store AgentGrid API token").option("--figma-token <token>", "Store Figma personal access token").option("--team-id <teamId>", "Team ID to associate with token").option("--status", "Show current auth status").option("--logout", "Clear stored credentials (alias for `anima logout`)").option("--json", "Output as JSON").option("--api-url <url>", "API base URL", DEFAULT_API_URL2).action(async (opts) => {
2272
+ var authCommand = new Command11("auth").summary("Inspect or manage stored credentials").description("Manage authentication").option("--token <token>", "Store AgentGrid API token").option("--figma-token <token>", "Store Figma personal access token").option("--team-id <teamId>", "Team ID to associate with token").option("--status", "Show current auth status").option("--logout", "Clear stored credentials (alias for `anima logout`)").option("--json", "Output as JSON").option("--api-url <url>", "API base URL", DEFAULT_API_URL2).action(async (opts) => {
2091
2273
  const mode = detectOutputMode(opts);
2092
2274
  try {
2093
2275
  if (opts.token || opts.figmaToken) {
@@ -2187,13 +2369,13 @@ var authCommand = new Command9("auth").summary("Inspect or manage stored credent
2187
2369
  if (mode === "pretty") {
2188
2370
  console.log();
2189
2371
  printInfo("Auth commands:");
2190
- console.log(` ${chalk7.cyan("anima login")} Log in (device flow \u2014 approve on any browser)`);
2191
- console.log(` ${chalk7.cyan("anima login --handoff")} Wait for a human to claim your anonymous artifact`);
2192
- console.log(` ${chalk7.cyan("anima logout")} Clear all credentials`);
2193
- console.log(` ${chalk7.cyan("anima auth --status")} Check auth status`);
2194
- console.log(` ${chalk7.cyan("anima auth --figma-token <T>")} Save Figma token (needed for codegen/f2c)`);
2372
+ console.log(` ${chalk8.cyan("anima login")} Log in (device flow \u2014 approve on any browser)`);
2373
+ console.log(` ${chalk8.cyan("anima login --handoff")} Wait for a human to claim your anonymous artifact`);
2374
+ console.log(` ${chalk8.cyan("anima logout")} Clear all credentials`);
2375
+ console.log(` ${chalk8.cyan("anima auth --status")} Check auth status`);
2376
+ console.log(` ${chalk8.cyan("anima auth --figma-token <T>")} Save Figma token (needed for codegen/f2c)`);
2195
2377
  console.log();
2196
- console.log(` Figma token: ${chalk7.cyan.underline("https://www.figma.com")} > Settings > Personal access tokens`);
2378
+ console.log(` Figma token: ${chalk8.cyan.underline("https://www.figma.com")} > Settings > Personal access tokens`);
2197
2379
  console.log();
2198
2380
  } else {
2199
2381
  outputResult(mode, {
@@ -2215,8 +2397,8 @@ var authCommand = new Command9("auth").summary("Inspect or manage stored credent
2215
2397
  });
2216
2398
 
2217
2399
  // src/commands/logout.ts
2218
- import { Command as Command10 } from "commander";
2219
- var logoutCommand = new Command10("logout").summary("Disconnect: revoke and clear every stored credential").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) => {
2400
+ import { Command as Command12 } from "commander";
2401
+ var logoutCommand = new Command12("logout").summary("Disconnect: revoke and clear every stored credential").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) => {
2220
2402
  const mode = detectOutputMode(opts);
2221
2403
  const revoked = await revokeStoredRefreshToken(resolveApiUrl(opts.apiUrl));
2222
2404
  const hadCredentials = clearStorage();
@@ -2234,8 +2416,8 @@ var logoutCommand = new Command10("logout").summary("Disconnect: revoke and clea
2234
2416
  });
2235
2417
 
2236
2418
  // src/commands/unpublish.ts
2237
- import { Command as Command11 } from "commander";
2238
- var unpublishCommand = new Command11("unpublish").summary("Take a published artifact offline").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) => {
2419
+ import { Command as Command13 } from "commander";
2420
+ var unpublishCommand = new Command13("unpublish").summary("Take a published artifact offline").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) => {
2239
2421
  const mode = detectOutputMode(opts);
2240
2422
  initLogger(opts.logFile);
2241
2423
  const progress = new ProgressReporter(mode, opts.verbose);
@@ -2270,8 +2452,8 @@ var unpublishCommand = new Command11("unpublish").summary("Take a published arti
2270
2452
  });
2271
2453
 
2272
2454
  // src/commands/update.ts
2273
- import { Command as Command12 } from "commander";
2274
- var updateCommand = new Command12("update").summary("Rename an artifact or change its visibility").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) => {
2455
+ import { Command as Command14 } from "commander";
2456
+ var updateCommand = new Command14("update").summary("Rename an artifact or change its visibility").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) => {
2275
2457
  const mode = detectOutputMode(opts);
2276
2458
  initLogger(opts.logFile);
2277
2459
  const progress = new ProgressReporter(mode, opts.verbose);
@@ -2316,8 +2498,8 @@ var updateCommand = new Command12("update").summary("Rename an artifact or chang
2316
2498
  });
2317
2499
 
2318
2500
  // src/commands/config.ts
2319
- import { Command as Command13 } from "commander";
2320
- var configCommand = new Command13("config").summary("Store CLI preferences (e.g. a default API URL)").description(
2501
+ import { Command as Command15 } from "commander";
2502
+ var configCommand = new Command15("config").summary("Store CLI preferences (e.g. a default API URL)").description(
2321
2503
  `Store CLI preferences (keys: ${configKeys().join(", ")})`
2322
2504
  );
2323
2505
  configCommand.command("set <key> <value>").description("Set a config value").option("--json", "Output as JSON").action((key, value, opts) => {
@@ -2383,9 +2565,9 @@ function assertUrl(value) {
2383
2565
  }
2384
2566
 
2385
2567
  // src/commands/duplicate.ts
2386
- import { Command as Command14 } from "commander";
2387
- import chalk8 from "chalk";
2388
- function buildDuplicateArgs(artifactUrl, name) {
2568
+ import { Command as Command16 } from "commander";
2569
+ import chalk9 from "chalk";
2570
+ function buildDuplicateArgs(artifactUrl, name, workspaceId) {
2389
2571
  const sessionId = parseArtifactUrl(artifactUrl);
2390
2572
  if (!sessionId) {
2391
2573
  throw validationError("A source artifact URL or session ID is required");
@@ -2393,17 +2575,24 @@ function buildDuplicateArgs(artifactUrl, name) {
2393
2575
  if (name !== void 0 && (name.length === 0 || name.length > 120)) {
2394
2576
  throw validationError("--name must be between 1 and 120 characters");
2395
2577
  }
2396
- return { sessionId, ...name !== void 0 ? { name } : {} };
2578
+ return {
2579
+ sessionId,
2580
+ ...name !== void 0 ? { name } : {},
2581
+ ...workspaceFlag(workspaceId) !== void 0 ? { workspaceId } : {}
2582
+ };
2397
2583
  }
2398
2584
  function createDuplicateCommand() {
2399
- return new Command14("duplicate").summary("Copy an artifact into a new, independent one").description(
2585
+ return new Command16("duplicate").summary("Copy an artifact into a new, independent one").description(
2400
2586
  "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."
2401
- ).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) => {
2587
+ ).argument("<artifactUrl>", "Source artifact URL or session ID").option("--name <name>", "Display name for the duplicate (defaults to '<source name> (Copy)')").option(
2588
+ "-w, --workspace <id>",
2589
+ "The workspace to put the copy in (id from `anima workspaces`). Optional when you can create in only one workspace; otherwise required, since there is no default"
2590
+ ).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) => {
2402
2591
  const mode = detectOutputMode(opts);
2403
2592
  initLogger(opts.logFile);
2404
2593
  const progress = new ProgressReporter(mode, opts.verbose);
2405
2594
  try {
2406
- const args = buildDuplicateArgs(artifactUrl, opts.name);
2595
+ const args = buildDuplicateArgs(artifactUrl, opts.name, opts.workspace);
2407
2596
  const auth = await resolveAuth(opts.apiUrl);
2408
2597
  progress.start("Duplicating artifact...");
2409
2598
  const result = await withMcp(
@@ -2411,7 +2600,7 @@ function createDuplicateCommand() {
2411
2600
  (client) => client.callTool("artifact-duplicate", args)
2412
2601
  );
2413
2602
  if (!result.success) {
2414
- throw apiError(result.reason || result.error || "Artifact duplication failed", result);
2603
+ throw apiError(result.message || result.reason || result.error || "Artifact duplication failed", result);
2415
2604
  }
2416
2605
  progress.succeed("Artifact duplicated");
2417
2606
  if (mode === "pretty") {
@@ -2420,12 +2609,12 @@ function createDuplicateCommand() {
2420
2609
  printKeyValue("Name", result.name);
2421
2610
  printKeyValue("Session ID", result.sessionId);
2422
2611
  printKeyValue("Source session", result.sourceSessionId);
2423
- printKeyValue("Artifact", chalk8.cyan.underline(result.artifactUrl));
2612
+ printKeyValue("Artifact", chalk9.cyan.underline(result.artifactUrl));
2424
2613
  if (result.playgroundUrl) {
2425
- printKeyValue("Edit in Playground", chalk8.cyan.underline(result.playgroundUrl));
2614
+ printKeyValue("Edit in Playground", chalk9.cyan.underline(result.playgroundUrl));
2426
2615
  }
2427
2616
  if (result.previewUrl) {
2428
- printKeyValue("Preview", chalk8.cyan.underline(result.previewUrl));
2617
+ printKeyValue("Preview", chalk9.cyan.underline(result.previewUrl));
2429
2618
  }
2430
2619
  printKeyValue("Next steps", result.nextSteps);
2431
2620
  console.log();
@@ -2449,8 +2638,8 @@ function createDuplicateCommand() {
2449
2638
  var duplicateCommand = createDuplicateCommand();
2450
2639
 
2451
2640
  // src/commands/explore.ts
2452
- import { Command as Command15 } from "commander";
2453
- import chalk9 from "chalk";
2641
+ import { Command as Command17 } from "commander";
2642
+ import chalk10 from "chalk";
2454
2643
 
2455
2644
  // src/api/tool.ts
2456
2645
  async function runTool(toolName, args, options = {}) {
@@ -2476,25 +2665,8 @@ function describeFailure(result) {
2476
2665
  ${nextSteps}` : base;
2477
2666
  }
2478
2667
 
2479
- // src/commands/runner.ts
2480
- async function withCommandErrors(mode, fn) {
2481
- try {
2482
- await fn();
2483
- } catch (err) {
2484
- if (err instanceof CliError) {
2485
- if (mode === "pretty") {
2486
- printError(err.message);
2487
- } else {
2488
- outputError(mode, { code: err.code, message: err.message });
2489
- }
2490
- process.exit(err.exitCode);
2491
- }
2492
- throw err;
2493
- }
2494
- }
2495
-
2496
2668
  // src/commands/explore.ts
2497
- var exploreCommand = new Command15("explore").summary("Read an artifact's files: list, search, read, history").description(
2669
+ var exploreCommand = new Command17("explore").summary("Read an artifact's files: list, search, read, history").description(
2498
2670
  "Inspect an artifact's files without git: list them (--tree), search them (--search), read them (--read), or show commits (--history)."
2499
2671
  ).argument("<artifactUrl>", "Artifact URL or session ID").option("--tree", "List file paths").option("--search <query>", "Find text inside files \u2014 start here when you don't know the file").option("--read <paths...>", "Return the contents of these files").option("--history", "List commits, newest first").option("--path <prefix>", "tree/search: a literal directory prefix (not a glob)").option("--range <start,end>", "read: line window, 1-based inclusive, for a single file").option("--revision <rev>", "Look at the artifact as it was at this commit").option("--regex", "search: treat the query as a JavaScript regular expression").option("--case-sensitive", "search: match case exactly").option("--include-excluded", "tree/search: also look inside node_modules, dist and build").option("--limit <n>", "tree/search/history: maximum rows to return").option("--cursor <cursor>", "history: continue after a previous response's nextCursor").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").action(async (artifactUrl, opts) => {
2500
2672
  const mode = detectOutputMode(opts);
@@ -2567,42 +2739,42 @@ function printExplore(action, result) {
2567
2739
  if (action === "tree") {
2568
2740
  for (const file of result.files ?? []) {
2569
2741
  const kind = result.unwritable?.[file];
2570
- console.log(` ${file}${kind ? chalk9.gray(` (${kind} \u2014 git only)`) : ""}`);
2742
+ console.log(` ${file}${kind ? chalk10.gray(` (${kind} \u2014 git only)`) : ""}`);
2571
2743
  }
2572
2744
  if (result.matchedFiles !== void 0) {
2573
2745
  console.log();
2574
- console.log(chalk9.gray(` ${(result.files ?? []).length} of ${result.matchedFiles} shown`));
2746
+ console.log(chalk10.gray(` ${(result.files ?? []).length} of ${result.matchedFiles} shown`));
2575
2747
  }
2576
2748
  } else if (action === "search") {
2577
2749
  for (const match of result.matches ?? []) {
2578
- console.log(` ${chalk9.cyan(match.path)}:${match.line} ${match.lineText.trim()}`);
2750
+ console.log(` ${chalk10.cyan(match.path)}:${match.line} ${match.lineText.trim()}`);
2579
2751
  }
2580
2752
  if (!result.matches?.length) {
2581
2753
  console.log(" No matches.");
2582
2754
  const skipped = result.notSearched;
2583
2755
  if (skipped?.excluded) {
2584
2756
  console.log(
2585
- chalk9.yellow(
2757
+ chalk10.yellow(
2586
2758
  ` ${skipped.excluded} files were skipped (node_modules, dist, build) \u2014 retry with --include-excluded.`
2587
2759
  )
2588
2760
  );
2589
2761
  }
2590
2762
  if (skipped?.assets) {
2591
- console.log(chalk9.gray(` ${skipped.assets} binary files cannot match a text query.`));
2763
+ console.log(chalk10.gray(` ${skipped.assets} binary files cannot match a text query.`));
2592
2764
  }
2593
2765
  }
2594
2766
  } else if (action === "read") {
2595
2767
  for (const file of result.files ?? []) {
2596
- console.log(chalk9.bold(` ${file.path}`));
2768
+ console.log(chalk10.bold(` ${file.path}`));
2597
2769
  if (file.asset) {
2598
- console.log(chalk9.gray(` binary \u2014 ${file.size ?? "?"} bytes, ${file.mime ?? "unknown type"}`));
2770
+ console.log(chalk10.gray(` binary \u2014 ${file.size ?? "?"} bytes, ${file.mime ?? "unknown type"}`));
2599
2771
  } else if (file.deferred) {
2600
- console.log(chalk9.yellow(" not returned: the response ran out of budget \u2014 read it on its own"));
2772
+ console.log(chalk10.yellow(" not returned: the response ran out of budget \u2014 read it on its own"));
2601
2773
  } else {
2602
2774
  console.log(file.content ?? "");
2603
2775
  if (file.truncated) {
2604
2776
  console.log(
2605
- chalk9.yellow(
2777
+ chalk10.yellow(
2606
2778
  ` lines ${file.startLine}-${file.endLine} of ${file.totalLines} \u2014 read the rest with --range`
2607
2779
  )
2608
2780
  );
@@ -2612,33 +2784,33 @@ function printExplore(action, result) {
2612
2784
  }
2613
2785
  for (const missing of result.missingPaths ?? []) {
2614
2786
  const near = result.suggestions?.[missing];
2615
- console.log(chalk9.yellow(` Not found: ${missing}${near?.length ? ` \u2014 did you mean ${near.join(", ")}?` : ""}`));
2787
+ console.log(chalk10.yellow(` Not found: ${missing}${near?.length ? ` \u2014 did you mean ${near.join(", ")}?` : ""}`));
2616
2788
  }
2617
2789
  } else {
2618
2790
  const commits = result.commit ? [result.commit] : result.commits ?? [];
2619
2791
  for (const commit of commits) {
2620
- console.log(` ${chalk9.yellow(commit.revision.slice(0, 10))} ${commit.message.split("\n")[0]}`);
2621
- console.log(chalk9.gray(` ${commit.author} \u2014 ${commit.date}`));
2792
+ console.log(` ${chalk10.yellow(commit.revision.slice(0, 10))} ${commit.message.split("\n")[0]}`);
2793
+ console.log(chalk10.gray(` ${commit.author} \u2014 ${commit.date}`));
2622
2794
  }
2623
2795
  for (const changed of result.commit?.changedFiles ?? []) {
2624
- console.log(chalk9.gray(` ${changed.change.padEnd(8)} ${changed.path}`));
2796
+ console.log(chalk10.gray(` ${changed.change.padEnd(8)} ${changed.path}`));
2625
2797
  }
2626
2798
  }
2627
2799
  if (result.truncated) {
2628
- console.log(chalk9.yellow(" The list was capped \u2014 raise --limit to see more."));
2800
+ console.log(chalk10.yellow(" The list was capped \u2014 raise --limit to see more."));
2629
2801
  }
2630
2802
  if (result.revision) {
2631
2803
  console.log();
2632
- console.log(chalk9.gray(` revision: ${result.revision} (pass to \`anima edit --base-revision\`)`));
2804
+ console.log(chalk10.gray(` revision: ${result.revision} (pass to \`anima edit --base-revision\`)`));
2633
2805
  }
2634
2806
  console.log();
2635
2807
  }
2636
2808
 
2637
2809
  // src/commands/edit.ts
2638
- import { Command as Command16 } from "commander";
2810
+ import { Command as Command18 } from "commander";
2639
2811
  import fs5 from "fs";
2640
- import chalk10 from "chalk";
2641
- var editCommand = new Command16("edit").summary("Change an artifact's files and commit them").description(
2812
+ import chalk11 from "chalk";
2813
+ var editCommand = new Command18("edit").summary("Change an artifact's files and commit them").description(
2642
2814
  "Change an artifact's files and commit them, without git. All changes land as one commit."
2643
2815
  ).argument("<artifactUrl>", "Artifact URL or session ID").requiredOption("-m, --message <message>", "Commit message").option(
2644
2816
  "--base-revision <rev>",
@@ -2660,7 +2832,7 @@ var editCommand = new Command16("edit").summary("Change an artifact's files and
2660
2832
  printSuccess("Committed.");
2661
2833
  for (const changed of result.changedFiles ?? []) {
2662
2834
  const detail = changed.op === "move" ? `${changed.fromPath} \u2192 ${changed.path}` : changed.replacements !== void 0 ? `${changed.path} (${changed.replacements} replaced)` : changed.path;
2663
- console.log(` ${chalk10.gray(changed.op.padEnd(12))} ${detail}`);
2835
+ console.log(` ${chalk11.gray(changed.op.padEnd(12))} ${detail}`);
2664
2836
  }
2665
2837
  console.log();
2666
2838
  printKeyValue("Revision", result.revision ?? "unknown");
@@ -2758,11 +2930,11 @@ function readFile(path7) {
2758
2930
  }
2759
2931
 
2760
2932
  // src/commands/uploadAsset.ts
2761
- import { Command as Command17 } from "commander";
2933
+ import { Command as Command19 } from "commander";
2762
2934
  import fs6 from "fs";
2763
2935
  import path5 from "path";
2764
- import chalk11 from "chalk";
2765
- var uploadAssetCommand = new Command17("upload-asset").summary("Add an image, font or media file over 256 KB").description(
2936
+ import chalk12 from "chalk";
2937
+ var uploadAssetCommand = new Command19("upload-asset").summary("Add an image, font or media file over 256 KB").description(
2766
2938
  "Add a large image, font or media file (over 256 KB) to an artifact: stage it, upload it, and commit it at --path."
2767
2939
  ).argument("<artifactUrl>", "Artifact URL or session ID").argument("<file>", "Local file to upload").option(
2768
2940
  "--path <path>",
@@ -2799,7 +2971,7 @@ var uploadAssetCommand = new Command17("upload-asset").summary("Add an image, fo
2799
2971
  console.log();
2800
2972
  console.log(" Commit it where it belongs with:");
2801
2973
  console.log(
2802
- ` ${chalk11.cyan(
2974
+ ` ${chalk12.cyan(
2803
2975
  `anima edit ${sessionId} -m "Add asset" --changes '[{"op":"write","path":"public/${path5.basename(file)}","assetUploadId":"${grant.assetUploadId}"}]'`
2804
2976
  )}`
2805
2977
  );
@@ -2876,8 +3048,8 @@ function readAsset(file) {
2876
3048
  }
2877
3049
 
2878
3050
  // src/commands/delete.ts
2879
- import { Command as Command18 } from "commander";
2880
- var deleteCommand = new Command18("delete").summary("Delete an artifact (reversible: hidden, not erased)").description("Soft-delete an artifact only when explicitly requested (unpublish it first; content is retained)").argument("<artifactUrl>", "Artifact URL or session ID to soft-delete").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) => {
3051
+ import { Command as Command20 } from "commander";
3052
+ var deleteCommand = new Command20("delete").summary("Delete an artifact (reversible: hidden, not erased)").description("Soft-delete an artifact only when explicitly requested (unpublish it first; content is retained)").argument("<artifactUrl>", "Artifact URL or session ID to soft-delete").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) => {
2881
3053
  const mode = detectOutputMode(opts);
2882
3054
  initLogger(opts.logFile);
2883
3055
  const progress = new ProgressReporter(mode, opts.verbose);
@@ -2918,9 +3090,9 @@ var deleteCommand = new Command18("delete").summary("Delete an artifact (reversi
2918
3090
  });
2919
3091
 
2920
3092
  // src/commands/review.ts
2921
- import { Command as Command19 } from "commander";
2922
- import chalk12 from "chalk";
2923
- var listSubcommand = new Command19("list").summary("Read the comments addressed to you").description("Read the review comments addressed to you, newest artifact activity first").argument("[artifactUrl]", "Limit to one artifact; omit for every review addressed to you").option("--limit <n>", "Maximum reviews to return (1-50, default 20)").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").action(async (artifactUrl, opts) => {
3093
+ import { Command as Command21 } from "commander";
3094
+ import chalk13 from "chalk";
3095
+ var listSubcommand = new Command21("list").summary("Read the comments addressed to you").description("Read the review comments addressed to you, newest artifact activity first").argument("[artifactUrl]", "Limit to one artifact; omit for every review addressed to you").option("--limit <n>", "Maximum reviews to return (1-50, default 20)").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").action(async (artifactUrl, opts) => {
2924
3096
  const mode = detectOutputMode(opts);
2925
3097
  initLogger(opts.logFile);
2926
3098
  await withCommandErrors(mode, async () => {
@@ -2935,7 +3107,7 @@ var listSubcommand = new Command19("list").summary("Read the comments addressed
2935
3107
  }
2936
3108
  });
2937
3109
  });
2938
- var resolveSubcommand = new Command19("resolve").summary("Close one comment without a commit").description("Close one comment without a commit \u2014 for a question answered, or a change you decided against").argument("<commentId>", "Comment to close, from `anima review list`").option("--note <text>", "What to tell the reviewer as the closing message").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").action(async (commentId, opts) => {
3110
+ var resolveSubcommand = new Command21("resolve").summary("Close one comment without a commit").description("Close one comment without a commit \u2014 for a question answered, or a change you decided against").argument("<commentId>", "Comment to close, from `anima review list`").option("--note <text>", "What to tell the reviewer as the closing message").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").action(async (commentId, opts) => {
2939
3111
  const mode = detectOutputMode(opts);
2940
3112
  initLogger(opts.logFile);
2941
3113
  await withCommandErrors(mode, async () => {
@@ -2954,7 +3126,7 @@ var resolveSubcommand = new Command19("resolve").summary("Close one comment with
2954
3126
  }
2955
3127
  });
2956
3128
  });
2957
- var replySubcommand = new Command19("reply").summary("Answer one comment, leaving it open").description("Say something back on one comment and LEAVE IT OPEN \u2014 to ask, report, or say you are blocked").argument("<commentId>", "Comment to reply to, from `anima review list`").requiredOption("-m, --message <message>", "What to say to the reviewer").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").action(async (commentId, opts) => {
3129
+ var replySubcommand = new Command21("reply").summary("Answer one comment, leaving it open").description("Say something back on one comment and LEAVE IT OPEN \u2014 to ask, report, or say you are blocked").argument("<commentId>", "Comment to reply to, from `anima review list`").requiredOption("-m, --message <message>", "What to say to the reviewer").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").action(async (commentId, opts) => {
2958
3130
  const mode = detectOutputMode(opts);
2959
3131
  initLogger(opts.logFile);
2960
3132
  await withCommandErrors(mode, async () => {
@@ -2973,7 +3145,7 @@ var replySubcommand = new Command19("reply").summary("Answer one comment, leavin
2973
3145
  }
2974
3146
  });
2975
3147
  });
2976
- var reviewCommand = new Command19("review").summary("Read and answer the comments humans addressed to you").description("Read and answer the review comments humans addressed to you (list / resolve / reply)").addCommand(listSubcommand).addCommand(resolveSubcommand).addCommand(replySubcommand);
3148
+ var reviewCommand = new Command21("review").summary("Read and answer the comments humans addressed to you").description("Read and answer the review comments humans addressed to you (list / resolve / reply)").addCommand(listSubcommand).addCommand(resolveSubcommand).addCommand(replySubcommand);
2977
3149
  function parseLimit2(value) {
2978
3150
  const limit = Number(value);
2979
3151
  if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
@@ -2990,29 +3162,29 @@ function printReviews(result) {
2990
3162
  return;
2991
3163
  }
2992
3164
  for (const review of reviews) {
2993
- console.log(` ${chalk12.bold(`Review ${review.reviewId}`)} ${chalk12.gray(`by ${review.author.name}`)}`);
2994
- console.log(` ${chalk12.cyan.underline(review.artifactUrl)}`);
3165
+ console.log(` ${chalk13.bold(`Review ${review.reviewId}`)} ${chalk13.gray(`by ${review.author.name}`)}`);
3166
+ console.log(` ${chalk13.cyan.underline(review.artifactUrl)}`);
2995
3167
  if (!review.isLatest) {
2996
3168
  console.log(
2997
- chalk12.yellow(" The artifact moved since this was written \u2014 check the comments still apply.")
3169
+ chalk13.yellow(" The artifact moved since this was written \u2014 check the comments still apply.")
2998
3170
  );
2999
3171
  }
3000
3172
  console.log();
3001
3173
  for (const comment of review.comments) {
3002
3174
  const marks = [
3003
- comment.status !== "open" ? chalk12.gray(comment.status) : null,
3004
- comment.unassigned ? chalk12.yellow("unassigned") : null
3175
+ comment.status !== "open" ? chalk13.gray(comment.status) : null,
3176
+ comment.unassigned ? chalk13.yellow("unassigned") : null
3005
3177
  ].filter(Boolean).join(" ");
3006
- console.log(` ${chalk12.bold(`#${comment.index}`)} ${comment.body}${marks ? ` ${marks}` : ""}`);
3007
- console.log(chalk12.gray(` ${comment.commentId}${describeAnchor(comment)}`));
3178
+ console.log(` ${chalk13.bold(`#${comment.index}`)} ${comment.body}${marks ? ` ${marks}` : ""}`);
3179
+ console.log(chalk13.gray(` ${comment.commentId}${describeAnchor(comment)}`));
3008
3180
  for (const reply of comment.replies ?? []) {
3009
- console.log(chalk12.gray(` \u21B3 ${reply.author.name}: ${reply.body}`));
3181
+ console.log(chalk13.gray(` \u21B3 ${reply.author.name}: ${reply.body}`));
3010
3182
  }
3011
3183
  console.log();
3012
3184
  }
3013
3185
  printKeyValue("To close these in a commit", "");
3014
3186
  for (const line of review.resolveTrailer.split("\n")) {
3015
- console.log(` ${chalk12.gray(line)}`);
3187
+ console.log(` ${chalk13.gray(line)}`);
3016
3188
  }
3017
3189
  console.log();
3018
3190
  }
@@ -3033,13 +3205,13 @@ function describeAnchor(comment) {
3033
3205
  }
3034
3206
 
3035
3207
  // src/commands/skill.ts
3036
- import { Command as Command20 } from "commander";
3208
+ import { Command as Command22 } from "commander";
3037
3209
  import fs7 from "fs";
3038
3210
  import path6 from "path";
3039
- import chalk13 from "chalk";
3211
+ import chalk14 from "chalk";
3040
3212
  var GUIDE_PATH = "/guide.md";
3041
3213
  function createSkillCommand() {
3042
- return new Command20("skill").summary("Install the AgentGrid guide as a skill your agent can read").description(
3214
+ return new Command22("skill").summary("Install the AgentGrid guide as a skill your agent can read").description(
3043
3215
  "Fetch the AgentGrid agent guide and write it as a SKILL.md your runtime can load, or print it to stdout. The guide comes from the API, so it stays current; the command reference is generated from this CLI's own commands."
3044
3216
  ).argument(
3045
3217
  "[path]",
@@ -3066,8 +3238,8 @@ ${body}
3066
3238
  logEvent("skill_installed", { path: target, bytes: document.length });
3067
3239
  if (mode === "pretty") {
3068
3240
  console.log();
3069
- printSuccess(`Skill written to ${chalk13.cyan(target)}`);
3070
- console.log(` ${chalk13.gray(`${document.length} bytes, from ${apiUrl}${GUIDE_PATH}`)}`);
3241
+ printSuccess(`Skill written to ${chalk14.cyan(target)}`);
3242
+ console.log(` ${chalk14.gray(`${document.length} bytes, from ${apiUrl}${GUIDE_PATH}`)}`);
3071
3243
  console.log();
3072
3244
  } else {
3073
3245
  outputResult(mode, {
@@ -3196,7 +3368,7 @@ function writeDocument(target, document) {
3196
3368
  }
3197
3369
 
3198
3370
  // src/index.ts
3199
- var program = new Command21().name("anima").description(
3371
+ var program = new Command23().name("anima").description(
3200
3372
  `AgentGrid CLI \u2014 create, change, publish and share artifacts on AgentGrid.
3201
3373
  An artifact is a real git repository, and every command below runs one
3202
3374
  AgentGrid MCP tool for you. The command list is at the bottom; run
@@ -3206,6 +3378,7 @@ THE MAIN FLOW (build something and put it on AgentGrid):
3206
3378
  1. anima login # once; a human approves
3207
3379
  2. anima create -t import --from ./my-project # your code \u2192 a live app
3208
3380
  (or: create -t empty --framework html \u2014 an empty repo you push to)
3381
+ (can create in several workspaces? add --workspace <id>; see "workspaces")
3209
3382
  3. Change it. Two doors, same git repository:
3210
3383
  - no clone: "explore" to read the files, "edit" to commit them. Needs no
3211
3384
  shell, no git binary and no network of its own \u2014 prefer it.
@@ -3249,8 +3422,10 @@ OUTPUT: pretty text in a terminal; a single JSON object on stdout when piped
3249
3422
  ).version(version, "-v, --version");
3250
3423
  program.addCommand(loginCommand);
3251
3424
  program.addCommand(logoutCommand);
3425
+ program.addCommand(workspacesCommand);
3252
3426
  program.addCommand(listCommand);
3253
3427
  program.addCommand(createCommand);
3428
+ program.addCommand(createKnowledgeCommand);
3254
3429
  program.addCommand(statusCommand);
3255
3430
  program.addCommand(exploreCommand);
3256
3431
  program.addCommand(editCommand);