artifact-cafe 0.0.12 → 0.0.13

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.
Files changed (2) hide show
  1. package/dist/index.js +374 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -180,7 +180,13 @@ var finalizeResponseSchema = z4.object({
180
180
  publishToken: z4.string().optional(),
181
181
  url: z4.string().url(),
182
182
  versionId: z4.string(),
183
- versionNumber: z4.number().int().positive()
183
+ versionNumber: z4.number().int().positive(),
184
+ // SPEC-2 §5.2 — who can open the review URL. A publish into a folder
185
+ // inherits that folder's visibility, so this is the only place the CLI
186
+ // learns that a link it just printed is members-only. Optional so a new
187
+ // CLI against an older API degrades to "unknown" instead of failing the
188
+ // publish over a field that only shapes the closing message.
189
+ visibility: z4.enum(["public", "unlisted", "workspace", "private"]).optional()
184
190
  });
185
191
  var artifactResponseSchema = z4.object({
186
192
  claimed: z4.boolean(),
@@ -445,7 +451,15 @@ var grantListResponseSchema = z8.object({
445
451
  grants: z8.array(grantSchema),
446
452
  // The artifact's current general-access value, so the share surface can
447
453
  // render the "General access" line without a second read.
448
- visibility: z8.enum(["public", "unlisted", "workspace", "private"])
454
+ visibility: z8.enum(["public", "unlisted", "workspace", "private"]),
455
+ // The owning workspace's logo, so the share modal can draw the workspace
456
+ // itself rather than a generic glyph. Null when none was uploaded.
457
+ workspaceLogo: z8.string().nullable().optional(),
458
+ // The owning workspace's display name, so "People with access" can name the
459
+ // audience `workspace` visibility actually admits ("All Acme members") rather
460
+ // than leaving an empty list to imply nobody. Null for a workspace-less
461
+ // artifact; optional so an older server still parses.
462
+ workspaceName: z8.string().nullable().optional()
449
463
  });
450
464
  var revokeGrantResponseSchema = z8.object({
451
465
  revoked: z8.literal(true)
@@ -951,6 +965,15 @@ function createApiClient(apiUrl2, publishToken) {
951
965
  (d) => agentHeartbeatResponseSchema.parse(d)
952
966
  );
953
967
  },
968
+ // SPEC-2 §6.3 — create a folder in a workspace. Account key required
969
+ // (owner/admin; in a personal workspace that's always the user).
970
+ async createFolder(workspaceId, body) {
971
+ return request(
972
+ `${base}/api/v1/workspaces/${workspaceId}/folders`,
973
+ { body: JSON.stringify(body), headers, method: "POST" },
974
+ (d) => folderSchema.parse(d)
975
+ );
976
+ },
954
977
  // SPEC-2 §6.6 — share an artifact with a person (email or user id).
955
978
  // Authorized by this client's credential: the publish token (an agent
956
979
  // sharing a private draft) or an account key/session. The invitation
@@ -1049,6 +1072,15 @@ function createApiClient(apiUrl2, publishToken) {
1049
1072
  (d) => grantListResponseSchema.parse(d)
1050
1073
  );
1051
1074
  },
1075
+ // SPEC-2 §6.4 — every artifact in a workspace (members see all, with
1076
+ // each artifact's folderId). The only folder-aware artifact listing.
1077
+ async listWorkspaceArtifacts(workspaceId) {
1078
+ return request(
1079
+ `${base}/api/v1/workspaces/${workspaceId}/artifacts`,
1080
+ { headers, method: "GET" },
1081
+ (d) => workspaceArtifactListResponseSchema.parse(d)
1082
+ );
1083
+ },
1052
1084
  async listWorkspaces() {
1053
1085
  return request(
1054
1086
  `${base}/api/v1/workspaces`,
@@ -1119,6 +1151,16 @@ function createApiClient(apiUrl2, publishToken) {
1119
1151
  { body: JSON.stringify({ mode }), headers, method: "PUT" },
1120
1152
  (d) => reviewModeResponseSchema.parse(d)
1121
1153
  );
1154
+ },
1155
+ // SPEC.md §6.11 / SPEC-2 §6.4 — update an artifact (folder placement,
1156
+ // title, slug, visibility). Workspace-member action: account key only,
1157
+ // a publish token can't re-file.
1158
+ async updateArtifact(artifactId, body) {
1159
+ return request(
1160
+ `${base}/api/v1/artifacts/${artifactId}`,
1161
+ { body: JSON.stringify(body), headers, method: "PATCH" },
1162
+ (d) => artifactResponseSchema.parse(d)
1163
+ );
1122
1164
  }
1123
1165
  };
1124
1166
  }
@@ -1927,6 +1969,7 @@ async function publish(opts) {
1927
1969
  folderId: placement.folderId ?? null,
1928
1970
  url: result.url,
1929
1971
  versionNumber: result.versionNumber,
1972
+ visibility: result.visibility ?? null,
1930
1973
  workspace: placement.workspace ?? null,
1931
1974
  workspaceId: placement.workspaceId ?? null,
1932
1975
  ...result.claimUrl ? { claimUrl: result.claimUrl } : {},
@@ -1951,6 +1994,11 @@ async function publish(opts) {
1951
1994
  console.log(`Review URL:
1952
1995
  ${result.url}
1953
1996
  `);
1997
+ const access = accessNote(result.visibility);
1998
+ if (access) {
1999
+ console.log(`${access}
2000
+ `);
2001
+ }
1954
2002
  if (result.claimUrl) {
1955
2003
  console.log(
1956
2004
  `Claim URL (shown once \u2014 claim to keep this artifact):
@@ -1979,6 +2027,11 @@ ${result.claimUrl}
1979
2027
  }
1980
2028
  console.log(`${result.url}
1981
2029
  `);
2030
+ const access = accessNote(result.visibility);
2031
+ if (access) {
2032
+ console.log(`${access}
2033
+ `);
2034
+ }
1982
2035
  if (opts.password !== void 0) {
1983
2036
  console.log("Password protection: on\n");
1984
2037
  }
@@ -1991,6 +2044,15 @@ ${result.claimUrl}
1991
2044
  openInBrowser(result.url);
1992
2045
  }
1993
2046
  }
2047
+ function accessNote(visibility) {
2048
+ if (visibility === "workspace") {
2049
+ return "Access: workspace members only \u2014 anyone else needs `artifact-cafe share <email>` to open it.";
2050
+ }
2051
+ if (visibility === "private") {
2052
+ return "Access: private \u2014 you, the workspace's admins, and anyone you `artifact-cafe share` it with.";
2053
+ }
2054
+ return null;
2055
+ }
1994
2056
  async function resolvePlacement(params) {
1995
2057
  const { apiKey, apiUrl: apiUrl2, config, isNewArtifact, opts } = params;
1996
2058
  const hasPlacementFlag = opts.workspace !== void 0 || opts.folder !== void 0;
@@ -2257,6 +2319,24 @@ function printCliError(err, json) {
2257
2319
 
2258
2320
  // src/folders.ts
2259
2321
  async function folders(opts) {
2322
+ if (opts.action !== void 0 && opts.action !== "create") {
2323
+ throw new Error(
2324
+ `Unknown folders action "${opts.action}" \u2014 run \`folders\` to list, or \`folders create <name>\` to create one.`
2325
+ );
2326
+ }
2327
+ if (opts.action === "create") {
2328
+ if (!opts.name?.trim()) {
2329
+ throw new Error(
2330
+ `Pass the folder name \u2014 e.g. \`artifact-cafe folders create "Design specs" --workspace ${opts.workspace}\`.`
2331
+ );
2332
+ }
2333
+ const visibility = opts.visibility ?? "workspace";
2334
+ if (visibility !== "workspace" && visibility !== "public") {
2335
+ throw new Error(
2336
+ `--visibility must be "workspace" or "public" (got "${opts.visibility}").`
2337
+ );
2338
+ }
2339
+ }
2260
2340
  const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
2261
2341
  /\/$/,
2262
2342
  ""
@@ -2264,7 +2344,7 @@ async function folders(opts) {
2264
2344
  const key = resolveApiKey(apiUrl2, opts.apiKey);
2265
2345
  if (!key) {
2266
2346
  throw new Error(
2267
- `Listing folders needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
2347
+ `Folder commands need an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
2268
2348
  );
2269
2349
  }
2270
2350
  const client = createApiClient(apiUrl2, key);
@@ -2277,6 +2357,10 @@ async function folders(opts) {
2277
2357
  `No workspace with handle "${opts.workspace}" belongs to this account.`
2278
2358
  );
2279
2359
  }
2360
+ if (opts.action === "create") {
2361
+ await createFolder(client, workspace, opts);
2362
+ return;
2363
+ }
2280
2364
  const data = await client.listFolders(workspace.id);
2281
2365
  if (opts.json) {
2282
2366
  console.log(JSON.stringify(data, null, 2));
@@ -2295,6 +2379,33 @@ async function folders(opts) {
2295
2379
  printFolder(folder);
2296
2380
  }
2297
2381
  }
2382
+ async function createFolder(client, workspace, opts) {
2383
+ const name = opts.name.trim();
2384
+ const visibility = opts.visibility ?? "workspace";
2385
+ const existing = await client.listFolders(workspace.id);
2386
+ const duplicate = existing.folders.find(
2387
+ (candidate) => candidate.name.toLowerCase() === name.toLowerCase()
2388
+ );
2389
+ if (duplicate) {
2390
+ throw new Error(
2391
+ `A folder named "${duplicate.name}" already exists in ${workspace.handle} (${duplicate.id}) \u2014 publish into it with --folder, or pick a different name.`
2392
+ );
2393
+ }
2394
+ const folder = await client.createFolder(workspace.id, { name, visibility });
2395
+ if (opts.json) {
2396
+ console.log(JSON.stringify(folder, null, 2));
2397
+ return;
2398
+ }
2399
+ console.log(
2400
+ `\u2713 Created folder "${folder.name}" (${folder.visibility}) in ${workspace.handle}`
2401
+ );
2402
+ console.log(` ${folder.id}`);
2403
+ if (folder.visibility === "public") {
2404
+ console.log(
2405
+ " Artifacts placed in it default to public \u2014 visible to anyone."
2406
+ );
2407
+ }
2408
+ }
2298
2409
  function printFolder(folder) {
2299
2410
  console.log(`${folder.name} (${folder.visibility})`);
2300
2411
  console.log(` ${folder.id}
@@ -2409,7 +2520,12 @@ async function list(opts) {
2409
2520
  `Listing your artifacts needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
2410
2521
  );
2411
2522
  }
2412
- const data = await createApiClient(apiUrl2, key).listArtifacts();
2523
+ const client = createApiClient(apiUrl2, key);
2524
+ if (opts.workspace !== void 0) {
2525
+ await listWorkspace(client, opts);
2526
+ return;
2527
+ }
2528
+ const data = await client.listArtifacts();
2413
2529
  if (opts.json) {
2414
2530
  console.log(JSON.stringify(data, null, 2));
2415
2531
  return;
@@ -2427,6 +2543,70 @@ async function list(opts) {
2427
2543
  printArtifact(artifact);
2428
2544
  }
2429
2545
  }
2546
+ async function listWorkspace(client, opts) {
2547
+ const workspaceData = await client.listWorkspaces();
2548
+ const workspace = workspaceData.workspaces.find(
2549
+ (candidate) => candidate.handle === opts.workspace
2550
+ );
2551
+ if (!workspace) {
2552
+ throw new Error(
2553
+ `No workspace with handle "${opts.workspace}" belongs to this account.`
2554
+ );
2555
+ }
2556
+ const [artifactData, folderData] = await Promise.all([
2557
+ client.listWorkspaceArtifacts(workspace.id),
2558
+ client.listFolders(workspace.id)
2559
+ ]);
2560
+ if (opts.json) {
2561
+ console.log(
2562
+ JSON.stringify(
2563
+ { artifacts: artifactData.artifacts, folders: folderData.folders },
2564
+ null,
2565
+ 2
2566
+ )
2567
+ );
2568
+ return;
2569
+ }
2570
+ if (artifactData.artifacts.length === 0) {
2571
+ console.log(`No artifacts in workspace ${workspace.handle}.`);
2572
+ return;
2573
+ }
2574
+ const plural = artifactData.artifacts.length === 1 ? "" : "s";
2575
+ console.log(
2576
+ `${artifactData.artifacts.length} artifact${plural} in ${workspace.handle}
2577
+ `
2578
+ );
2579
+ for (const folder of folderData.folders) {
2580
+ const inFolder = artifactData.artifacts.filter(
2581
+ (artifact) => artifact.folderId === folder.id
2582
+ );
2583
+ if (inFolder.length === 0) {
2584
+ continue;
2585
+ }
2586
+ console.log(`${folder.name} (${folder.visibility})`);
2587
+ for (const artifact of inFolder) {
2588
+ printWorkspaceArtifact(artifact);
2589
+ }
2590
+ }
2591
+ const unfiled = artifactData.artifacts.filter(
2592
+ (artifact) => artifact.folderId === null
2593
+ );
2594
+ if (unfiled.length > 0) {
2595
+ console.log("Unfiled");
2596
+ for (const artifact of unfiled) {
2597
+ printWorkspaceArtifact(artifact);
2598
+ }
2599
+ }
2600
+ }
2601
+ function printWorkspaceArtifact(a) {
2602
+ const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
2603
+ const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
2604
+ const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
2605
+ console.log(
2606
+ ` ${marker} ${a.title} \u2014 ${version} \xB7 ${open2} \xB7 ${a.visibility}`
2607
+ );
2608
+ console.log(` ${a.url}`);
2609
+ }
2430
2610
  function printArtifact(a) {
2431
2611
  const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
2432
2612
  const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
@@ -2626,14 +2806,109 @@ async function deleteMessage(opts) {
2626
2806
  console.log(`\u2713 Deleted ${messageId}.`);
2627
2807
  }
2628
2808
 
2629
- // src/open.ts
2809
+ // src/move.ts
2630
2810
  import path10 from "path";
2631
- async function openArtifact(opts) {
2811
+ async function move(opts) {
2812
+ if (Boolean(opts.folder) === Boolean(opts.unfiled)) {
2813
+ throw new Error("Pass exactly one of --folder <name-or-id> or --unfiled.");
2814
+ }
2632
2815
  const dir = path10.resolve(opts.dir);
2633
2816
  const target = resolveArtifactTarget(dir, {
2634
2817
  apiKey: opts.apiKey,
2635
2818
  artifact: opts.artifact
2636
2819
  });
2820
+ const key = resolveApiKey(target.apiUrl, opts.apiKey);
2821
+ if (!key) {
2822
+ throw new Error(
2823
+ `Moving an artifact needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(target.apiUrl)}).`
2824
+ );
2825
+ }
2826
+ const client = createApiClient(target.apiUrl, key);
2827
+ const artifact = await client.getArtifact(target.artifactId);
2828
+ if (!artifact.workspaceId) {
2829
+ throw new Error(
2830
+ "This artifact isn't in a workspace \u2014 folders live in workspaces. Claim it (or publish it while logged in) first."
2831
+ );
2832
+ }
2833
+ let folderId = null;
2834
+ let folderName = null;
2835
+ if (opts.folder) {
2836
+ const data = await client.listFolders(artifact.workspaceId);
2837
+ const matches = data.folders.filter(
2838
+ (candidate) => candidate.name === opts.folder || candidate.id === opts.folder
2839
+ );
2840
+ if (matches.length === 0) {
2841
+ throw new Error(
2842
+ `No folder named "${opts.folder}" exists in this workspace \u2014 list them with \`artifact-cafe folders --workspace <handle>\`, or create it with \`folders create\`.`
2843
+ );
2844
+ }
2845
+ if (matches.length > 1) {
2846
+ throw new Error(
2847
+ `More than one folder is named "${opts.folder}"; pass the folder id instead.`
2848
+ );
2849
+ }
2850
+ folderId = matches[0].id;
2851
+ folderName = matches[0].name;
2852
+ }
2853
+ if (artifact.folderId === folderId) {
2854
+ const where = folderName ? `folder "${folderName}"` : "no folder";
2855
+ if (opts.json) {
2856
+ console.log(
2857
+ JSON.stringify(
2858
+ {
2859
+ artifactId: artifact.id,
2860
+ folder: folderName ? { id: folderId, name: folderName } : null,
2861
+ moved: false,
2862
+ title: artifact.title,
2863
+ visibility: artifact.visibility
2864
+ },
2865
+ null,
2866
+ 2
2867
+ )
2868
+ );
2869
+ return;
2870
+ }
2871
+ console.log(`"${artifact.title}" is already in ${where} \u2014 nothing to do.`);
2872
+ return;
2873
+ }
2874
+ const updated = await client.updateArtifact(artifact.id, { folderId });
2875
+ if (opts.json) {
2876
+ console.log(
2877
+ JSON.stringify(
2878
+ {
2879
+ artifactId: updated.id,
2880
+ folder: folderName ? { id: folderId, name: folderName } : null,
2881
+ moved: true,
2882
+ previousVisibility: artifact.visibility,
2883
+ title: updated.title,
2884
+ visibility: updated.visibility
2885
+ },
2886
+ null,
2887
+ 2
2888
+ )
2889
+ );
2890
+ return;
2891
+ }
2892
+ const destination = folderName ? `"${folderName}"` : "Unfiled";
2893
+ console.log(`\u2713 Moved "${updated.title}" \u2192 ${destination}`);
2894
+ if (updated.visibility !== artifact.visibility) {
2895
+ console.log(
2896
+ ` Visibility: ${artifact.visibility} \u2192 ${updated.visibility} (folder default)`
2897
+ );
2898
+ if (updated.visibility === "public") {
2899
+ console.log(" Now public \u2014 visible to anyone.");
2900
+ }
2901
+ }
2902
+ }
2903
+
2904
+ // src/open.ts
2905
+ import path11 from "path";
2906
+ async function openArtifact(opts) {
2907
+ const dir = path11.resolve(opts.dir);
2908
+ const target = resolveArtifactTarget(dir, {
2909
+ apiKey: opts.apiKey,
2910
+ artifact: opts.artifact
2911
+ });
2637
2912
  const url = target.url ?? reviewUrl(
2638
2913
  target.apiUrl,
2639
2914
  (await createApiClient(target.apiUrl, target.credential).getArtifact(
@@ -2647,7 +2922,7 @@ async function openArtifact(opts) {
2647
2922
  }
2648
2923
 
2649
2924
  // src/protect.ts
2650
- import path11 from "path";
2925
+ import path12 from "path";
2651
2926
  async function protect(opts) {
2652
2927
  if (opts.off && opts.password !== void 0) {
2653
2928
  throw new Error("Pass either --password or --off, not both.");
@@ -2664,7 +2939,7 @@ async function protect(opts) {
2664
2939
  throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
2665
2940
  }
2666
2941
  }
2667
- const dir = path11.resolve(opts.dir);
2942
+ const dir = path12.resolve(opts.dir);
2668
2943
  const target = resolveArtifactTarget(dir, {
2669
2944
  apiKey: opts.apiKey,
2670
2945
  artifact: opts.artifact
@@ -2689,7 +2964,7 @@ async function protect(opts) {
2689
2964
  }
2690
2965
 
2691
2966
  // src/reply.ts
2692
- import path12 from "path";
2967
+ import path13 from "path";
2693
2968
  async function readStdin3() {
2694
2969
  if (process.stdin.isTTY) {
2695
2970
  return "";
@@ -2713,7 +2988,7 @@ async function replyToThread(opts) {
2713
2988
  'Nothing to post. Pass the reply with --body "\u2026", or pipe it on stdin.'
2714
2989
  );
2715
2990
  }
2716
- const dir = path12.resolve(opts.dir);
2991
+ const dir = path13.resolve(opts.dir);
2717
2992
  const target = resolveArtifactTarget(dir, {
2718
2993
  apiKey: opts.apiKey,
2719
2994
  artifact: opts.artifact
@@ -2742,7 +3017,7 @@ async function replyToThread(opts) {
2742
3017
  }
2743
3018
 
2744
3019
  // src/resolve.ts
2745
- import path13 from "path";
3020
+ import path14 from "path";
2746
3021
  async function changeThreadStatus(opts) {
2747
3022
  const threadId = opts.thread?.trim();
2748
3023
  if (!threadId) {
@@ -2750,7 +3025,7 @@ async function changeThreadStatus(opts) {
2750
3025
  `Pass the thread id to ${opts.action} \u2014 get it from \`artifact-cafe comments --json\`.`
2751
3026
  );
2752
3027
  }
2753
- const dir = path13.resolve(opts.dir);
3028
+ const dir = path14.resolve(opts.dir);
2754
3029
  const target = resolveArtifactTarget(dir, {
2755
3030
  apiKey: opts.apiKey,
2756
3031
  artifact: opts.artifact
@@ -2787,7 +3062,7 @@ async function changeThreadStatus(opts) {
2787
3062
  }
2788
3063
 
2789
3064
  // src/review.ts
2790
- import path14 from "path";
3065
+ import path15 from "path";
2791
3066
  var OUTCOME = {
2792
3067
  off: "Plain artifact \u2014 the review tools are hidden entirely",
2793
3068
  open: "Open for review \u2014 anyone who can open it can comment",
@@ -2801,7 +3076,7 @@ async function review(opts) {
2801
3076
  );
2802
3077
  }
2803
3078
  const mode = parsed.data;
2804
- const dir = path14.resolve(opts.dir);
3079
+ const dir = path15.resolve(opts.dir);
2805
3080
  const target = resolveArtifactTarget(dir, {
2806
3081
  apiKey: opts.apiKey,
2807
3082
  artifact: opts.artifact
@@ -2823,10 +3098,10 @@ async function review(opts) {
2823
3098
  }
2824
3099
 
2825
3100
  // src/share.ts
2826
- import path15 from "path";
3101
+ import path16 from "path";
2827
3102
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2828
3103
  async function share(opts) {
2829
- const dir = path15.resolve(opts.dir);
3104
+ const dir = path16.resolve(opts.dir);
2830
3105
  const target = resolveArtifactTarget(dir, {
2831
3106
  apiKey: opts.apiKey,
2832
3107
  artifact: opts.artifact
@@ -2881,7 +3156,7 @@ General access: ${visibility}`);
2881
3156
  console.log("");
2882
3157
  }
2883
3158
  async function unshare(opts) {
2884
- const dir = path15.resolve(opts.dir);
3159
+ const dir = path16.resolve(opts.dir);
2885
3160
  const target = resolveArtifactTarget(dir, {
2886
3161
  apiKey: opts.apiKey,
2887
3162
  artifact: opts.artifact
@@ -2904,7 +3179,7 @@ async function unshare(opts) {
2904
3179
  }
2905
3180
 
2906
3181
  // src/status.ts
2907
- import path16 from "path";
3182
+ import path17 from "path";
2908
3183
  async function status(opts) {
2909
3184
  if (!AGENT_STATES.includes(opts.state)) {
2910
3185
  throw new Error(
@@ -2912,7 +3187,7 @@ async function status(opts) {
2912
3187
  );
2913
3188
  }
2914
3189
  const state = opts.state;
2915
- const dir = path16.resolve(opts.dir);
3190
+ const dir = path17.resolve(opts.dir);
2916
3191
  const target = resolveArtifactTarget(dir, {
2917
3192
  apiKey: opts.apiKey,
2918
3193
  artifact: opts.artifact
@@ -2941,7 +3216,7 @@ async function status(opts) {
2941
3216
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
2942
3217
  import { createRequire } from "module";
2943
3218
  import { homedir as homedir2 } from "os";
2944
- import path17 from "path";
3219
+ import path18 from "path";
2945
3220
  import { z as z20 } from "zod";
2946
3221
  var REGISTRY_LATEST_URL = "https://registry.npmjs.org/artifact-cafe/latest";
2947
3222
  var SKILL_SOURCE_URL = "https://raw.githubusercontent.com/Artifact-Cafe/skill/main/artifact-cafe/SKILL.md";
@@ -2960,7 +3235,7 @@ function cliVersion() {
2960
3235
  return pkg.version;
2961
3236
  }
2962
3237
  function cachePath() {
2963
- return path17.join(homedir2(), ".artifactcafe", "update-check.json");
3238
+ return path18.join(homedir2(), ".artifactcafe", "update-check.json");
2964
3239
  }
2965
3240
  function readCache() {
2966
3241
  try {
@@ -2971,7 +3246,7 @@ function readCache() {
2971
3246
  }
2972
3247
  function writeCache(cache) {
2973
3248
  try {
2974
- mkdirSync3(path17.dirname(cachePath()), { mode: 448, recursive: true });
3249
+ mkdirSync3(path18.dirname(cachePath()), { mode: 448, recursive: true });
2975
3250
  writeFileSync3(cachePath(), `${JSON.stringify(cache, null, 2)}
2976
3251
  `);
2977
3252
  } catch {
@@ -3024,7 +3299,7 @@ function installedSkillVersions() {
3024
3299
  const versions = [];
3025
3300
  for (const root of roots) {
3026
3301
  for (const dir of dirs) {
3027
- const file = path17.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
3302
+ const file = path18.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
3028
3303
  if (!existsSync3(file)) {
3029
3304
  continue;
3030
3305
  }
@@ -3259,7 +3534,11 @@ var list2 = defineCommand({
3259
3534
  description: "List with this API key (default: stored login)",
3260
3535
  type: "string"
3261
3536
  },
3262
- json: { description: "Machine-readable output", type: "boolean" }
3537
+ json: { description: "Machine-readable output", type: "boolean" },
3538
+ workspace: {
3539
+ description: "List this workspace's artifacts, grouped by folder",
3540
+ type: "string"
3541
+ }
3263
3542
  },
3264
3543
  meta: {
3265
3544
  description: "List the artifacts your account owns",
@@ -3267,7 +3546,11 @@ var list2 = defineCommand({
3267
3546
  },
3268
3547
  async run({ args }) {
3269
3548
  try {
3270
- await list({ apiKey: args["api-key"], json: Boolean(args.json) });
3549
+ await list({
3550
+ apiKey: args["api-key"],
3551
+ json: Boolean(args.json),
3552
+ workspace: args.workspace
3553
+ });
3271
3554
  } catch (err) {
3272
3555
  printCliError(err, Boolean(args.json));
3273
3556
  process.exit(1);
@@ -3276,26 +3559,43 @@ var list2 = defineCommand({
3276
3559
  });
3277
3560
  var folders2 = defineCommand({
3278
3561
  args: {
3562
+ action: {
3563
+ description: 'Optional action: "create"',
3564
+ required: false,
3565
+ type: "positional"
3566
+ },
3279
3567
  "api-key": {
3280
- description: "List with this API key (default: stored login)",
3568
+ description: "Authorize with an account API key (default: stored login)",
3281
3569
  type: "string"
3282
3570
  },
3283
3571
  json: { description: "Machine-readable output", type: "boolean" },
3572
+ name: {
3573
+ description: "Folder name (with create)",
3574
+ required: false,
3575
+ type: "positional"
3576
+ },
3577
+ visibility: {
3578
+ description: "With create: workspace (default) | public",
3579
+ type: "string"
3580
+ },
3284
3581
  workspace: {
3285
- description: "Workspace handle whose folders to list",
3582
+ description: "Workspace handle to list or create in",
3286
3583
  required: true,
3287
3584
  type: "string"
3288
3585
  }
3289
3586
  },
3290
3587
  meta: {
3291
- description: "List folders in a workspace",
3588
+ description: "List a workspace's folders, or create one (folders create)",
3292
3589
  name: "folders"
3293
3590
  },
3294
3591
  async run({ args }) {
3295
3592
  try {
3296
3593
  await folders({
3594
+ action: args.action,
3297
3595
  apiKey: args["api-key"],
3298
3596
  json: Boolean(args.json),
3597
+ name: args.name,
3598
+ visibility: args.visibility,
3299
3599
  workspace: args.workspace
3300
3600
  });
3301
3601
  } catch (err) {
@@ -3304,6 +3604,51 @@ var folders2 = defineCommand({
3304
3604
  }
3305
3605
  }
3306
3606
  });
3607
+ var move2 = defineCommand({
3608
+ args: {
3609
+ "api-key": {
3610
+ description: "Authorize with an account API key (default: stored login)",
3611
+ type: "string"
3612
+ },
3613
+ artifact: {
3614
+ description: "Move this artifact ID instead of the linked folder",
3615
+ type: "string"
3616
+ },
3617
+ dir: {
3618
+ default: ".",
3619
+ description: "Linked directory to resolve the artifact from (default: .)",
3620
+ type: "string"
3621
+ },
3622
+ folder: {
3623
+ description: "Target folder name (or id) in the artifact's workspace",
3624
+ type: "string"
3625
+ },
3626
+ json: { description: "Machine-readable output", type: "boolean" },
3627
+ unfiled: {
3628
+ description: "Move out of its folder (workspace root)",
3629
+ type: "boolean"
3630
+ }
3631
+ },
3632
+ meta: {
3633
+ description: "Move an artifact into a workspace folder (or --unfiled)",
3634
+ name: "move"
3635
+ },
3636
+ async run({ args }) {
3637
+ try {
3638
+ await move({
3639
+ apiKey: args["api-key"],
3640
+ artifact: args.artifact,
3641
+ dir: args.dir,
3642
+ folder: args.folder,
3643
+ json: Boolean(args.json),
3644
+ unfiled: Boolean(args.unfiled)
3645
+ });
3646
+ } catch (err) {
3647
+ printCliError(err, Boolean(args.json));
3648
+ process.exit(1);
3649
+ }
3650
+ }
3651
+ });
3307
3652
  var workspaces2 = defineCommand({
3308
3653
  args: {
3309
3654
  "api-key": {
@@ -4023,6 +4368,7 @@ var main = defineCommand({
4023
4368
  listen,
4024
4369
  login: login2,
4025
4370
  logout: logout2,
4371
+ move: move2,
4026
4372
  open,
4027
4373
  protect: protect2,
4028
4374
  publish: publish2,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artifact-cafe",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "Publish AI-generated artifacts for review on artifact.cafe",
5
5
  "type": "module",
6
6
  "bin": {