artifact-cafe 0.0.5 → 0.0.7

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 +617 -134
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -130,8 +130,11 @@ var threadActionSchema = z2.object({
130
130
  guest: guestSchema.optional()
131
131
  });
132
132
  var commentAuthorSchema = z2.object({
133
+ avatarUrl: z2.string().nullable(),
133
134
  displayName: z2.string(),
134
- guestId: z2.string().nullable()
135
+ guestId: z2.string().nullable(),
136
+ isViewer: z2.boolean(),
137
+ kind: z2.enum(["member", "guest"])
135
138
  });
136
139
  var commentMessageSchema = z2.object({
137
140
  author: commentAuthorSchema,
@@ -158,12 +161,14 @@ var commentExportQuerySchema = z2.object({
158
161
  });
159
162
  var commentExportReplySchema = z2.object({
160
163
  author: z2.string(),
164
+ authorKind: z2.enum(["member", "guest"]),
161
165
  body: z2.string(),
162
166
  createdAt: z2.string()
163
167
  });
164
168
  var commentExportThreadSchema = z2.object({
165
169
  anchor: anchorSchema,
166
170
  author: z2.string(),
171
+ authorKind: z2.enum(["member", "guest"]),
167
172
  body: z2.string(),
168
173
  createdAt: z2.string(),
169
174
  id: z2.string(),
@@ -199,6 +204,15 @@ import { z as z4 } from "zod";
199
204
  var ownArtifactSchema = z4.object({
200
205
  createdAt: z4.string(),
201
206
  currentVersionNumber: z4.number().nullable(),
207
+ // Owned artifacts normally never expire; non-null only while an
208
+ // expiry is still set (e.g. an unclaimed publish surfaced here later).
209
+ expiresAt: z4.string().nullable(),
210
+ // SPEC-2 §8.4 — the containing folder's name for workspace-owned
211
+ // artifacts; always null for personal artifacts.
212
+ folderName: z4.string().nullable(),
213
+ // Whether a publish token is still active — revocable from the
214
+ // dashboard (§6.18); the token itself is never returned.
215
+ hasPublishToken: z4.boolean(),
202
216
  id: z4.string(),
203
217
  openThreads: z4.number(),
204
218
  protected: z4.boolean(),
@@ -212,64 +226,217 @@ var ownArtifactSchema = z4.object({
212
226
  var ownArtifactsResponseSchema = z4.object({
213
227
  artifacts: z4.array(ownArtifactSchema)
214
228
  });
229
+ var revokePublishTokenResponseSchema = z4.object({
230
+ revoked: z4.boolean()
231
+ });
215
232
 
216
233
  // ../shared/src/owner.ts
234
+ import { z as z6 } from "zod";
235
+
236
+ // ../shared/src/workspaces.ts
217
237
  import { z as z5 } from "zod";
218
- var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
219
- var claimResponseSchema = z5.object({
220
- artifactId: z5.string(),
238
+ var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/;
239
+ var workspaceRoleSchema = z5.enum(["owner", "admin", "member"]);
240
+ var workspaceInviteRoleSchema = z5.enum(["admin", "member"]);
241
+ var workspaceHandleSchema = z5.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
242
+ message: "handle must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
243
+ });
244
+ var createWorkspaceSchema = z5.object({
245
+ handle: workspaceHandleSchema.optional(),
246
+ name: z5.string().trim().min(1).max(100)
247
+ });
248
+ var updateWorkspaceSchema = z5.object({
249
+ handle: workspaceHandleSchema.optional(),
250
+ name: z5.string().trim().min(1).max(100).optional()
251
+ }).refine((body) => body.handle !== void 0 || body.name !== void 0, {
252
+ message: "provide at least one of name or handle"
253
+ });
254
+ var workspaceSchema = z5.object({
255
+ createdAt: z5.string(),
256
+ handle: z5.string(),
257
+ id: z5.string(),
258
+ name: z5.string(),
259
+ role: workspaceRoleSchema.nullable(),
260
+ updatedAt: z5.string()
261
+ });
262
+ var workspaceListResponseSchema = z5.object({
263
+ workspaces: z5.array(workspaceSchema)
264
+ });
265
+ var deleteWorkspaceResponseSchema = z5.object({
266
+ deleted: z5.literal(true)
267
+ });
268
+ var workspaceMemberSchema = z5.object({
269
+ createdAt: z5.string(),
270
+ email: z5.string().email(),
271
+ image: z5.string().nullable(),
272
+ name: z5.string(),
273
+ role: workspaceRoleSchema,
274
+ userId: z5.string()
275
+ });
276
+ var workspaceMemberListResponseSchema = z5.object({
277
+ members: z5.array(workspaceMemberSchema)
278
+ });
279
+ var updateWorkspaceMemberSchema = z5.object({
280
+ role: workspaceRoleSchema
281
+ });
282
+ var removeWorkspaceMemberResponseSchema = z5.object({
283
+ removed: z5.literal(true)
284
+ });
285
+ var createWorkspaceInviteSchema = z5.object({
286
+ email: z5.string().trim().toLowerCase().email().optional(),
287
+ expiresAt: z5.string().datetime(),
288
+ maxUses: z5.number().int().positive().optional(),
289
+ role: workspaceInviteRoleSchema
290
+ });
291
+ var workspaceInviteSchema = z5.object({
292
+ createdAt: z5.string(),
293
+ email: z5.string().email().nullable(),
294
+ expiresAt: z5.string(),
295
+ id: z5.string(),
296
+ maxUses: z5.number().int().positive().nullable(),
297
+ role: workspaceInviteRoleSchema,
298
+ tokenPrefix: z5.string(),
299
+ useCount: z5.number().int().nonnegative()
300
+ });
301
+ var createdWorkspaceInviteSchema = workspaceInviteSchema.extend({
302
+ url: z5.string().url()
303
+ });
304
+ var workspaceInviteListResponseSchema = z5.object({
305
+ invites: z5.array(workspaceInviteSchema)
306
+ });
307
+ var revokeWorkspaceInviteResponseSchema = z5.object({
308
+ revoked: z5.literal(true)
309
+ });
310
+ var acceptWorkspaceInviteSchema = z5.object({
311
+ token: z5.string().min(1)
312
+ });
313
+ var acceptWorkspaceInviteResponseSchema = z5.object({
314
+ role: workspaceRoleSchema,
315
+ workspace: workspaceSchema
316
+ });
317
+ var folderVisibilitySchema = z5.enum(["public", "workspace"]);
318
+ var artifactVisibilitySchema = z5.enum([
319
+ "public",
320
+ "unlisted",
321
+ "workspace"
322
+ ]);
323
+ var createFolderSchema = z5.object({
324
+ name: z5.string().trim().min(1).max(100),
325
+ visibility: folderVisibilitySchema
326
+ });
327
+ var updateFolderSchema = z5.object({
328
+ // With `visibility`: bulk-update the folder's artifacts to match
329
+ // (SPEC-2 §3.4 "Changing a folder's visibility offers a bulk update").
330
+ cascade: z5.boolean().optional(),
331
+ name: z5.string().trim().min(1).max(100).optional(),
332
+ sortOrder: z5.number().int().min(0).optional(),
333
+ visibility: folderVisibilitySchema.optional()
334
+ }).refine(
335
+ (body) => body.name !== void 0 || body.visibility !== void 0 || body.sortOrder !== void 0,
336
+ { message: "provide at least one of name, visibility, sortOrder" }
337
+ ).refine((body) => !body.cascade || body.visibility !== void 0, {
338
+ message: "cascade requires visibility"
339
+ });
340
+ var folderSchema = z5.object({
341
+ createdAt: z5.string(),
342
+ id: z5.string(),
343
+ name: z5.string(),
344
+ // Permalink segment for the public folder page (/w/{handle}/f/{slug}).
345
+ // Server-derived from the name; renames regenerate it.
346
+ slug: z5.string(),
347
+ sortOrder: z5.number().int(),
348
+ updatedAt: z5.string(),
349
+ visibility: folderVisibilitySchema,
350
+ workspaceId: z5.string()
351
+ });
352
+ var folderListResponseSchema = z5.object({
353
+ folders: z5.array(folderSchema)
354
+ });
355
+ var deleteFolderResponseSchema = z5.object({
356
+ deleted: z5.literal(true)
357
+ });
358
+ var workspaceArtifactSchema = z5.object({
359
+ createdAt: z5.string(),
360
+ currentVersionNumber: z5.number().int().nullable(),
361
+ folderId: z5.string().nullable(),
362
+ id: z5.string(),
363
+ openCommentCount: z5.number().int().nonnegative(),
221
364
  slug: z5.string(),
222
365
  title: z5.string(),
223
- url: z5.string().url()
366
+ updatedAt: z5.string(),
367
+ url: z5.string().url(),
368
+ versionCount: z5.number().int().nonnegative(),
369
+ visibility: artifactVisibilitySchema
370
+ });
371
+ var workspaceArtifactListResponseSchema = z5.object({
372
+ artifacts: z5.array(workspaceArtifactSchema)
373
+ });
374
+
375
+ // ../shared/src/owner.ts
376
+ var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
377
+ var claimResponseSchema = z6.object({
378
+ artifactId: z6.string(),
379
+ slug: z6.string(),
380
+ title: z6.string(),
381
+ url: z6.string().url()
224
382
  });
225
- var updateArtifactSchema = z5.object({
226
- slug: z5.string().min(3).max(63).regex(SLUG_PATTERN, {
383
+ var updateArtifactSchema = z6.object({
384
+ // SPEC-2 §6.4 — placement + visibility. `folderId: null` clears the
385
+ // folder; `workspaceId: null` moves the artifact out of its workspace
386
+ // (§5.5 ownership transfer).
387
+ folderId: z6.string().min(1).nullable().optional(),
388
+ slug: z6.string().min(3).max(63).regex(SLUG_PATTERN, {
227
389
  message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
228
390
  }).optional(),
229
- title: z5.string().min(1).max(200).optional()
230
- }).refine((body) => body.title !== void 0 || body.slug !== void 0, {
231
- message: "provide at least one of title, slug"
232
- });
233
- var deleteArtifactResponseSchema = z5.object({
234
- deleted: z5.literal(true)
391
+ title: z6.string().min(1).max(200).optional(),
392
+ visibility: artifactVisibilitySchema.optional(),
393
+ workspaceId: z6.string().min(1).nullable().optional()
394
+ }).refine(
395
+ (body) => body.title !== void 0 || body.slug !== void 0 || body.visibility !== void 0 || body.folderId !== void 0 || body.workspaceId !== void 0,
396
+ {
397
+ message: "provide at least one of title, slug, visibility, folderId, workspaceId"
398
+ }
399
+ );
400
+ var deleteArtifactResponseSchema = z6.object({
401
+ deleted: z6.literal(true)
235
402
  });
236
403
 
237
404
  // ../shared/src/presence.ts
238
- import { z as z6 } from "zod";
405
+ import { z as z7 } from "zod";
239
406
  var AGENT_PRESENCE = {
240
407
  heartbeatSeconds: 15,
241
408
  shellPollSeconds: 15,
242
409
  ttlSeconds: 45
243
410
  };
244
- var agentHeartbeatResponseSchema = z6.object({
245
- listening: z6.literal(true),
246
- ttlSeconds: z6.number().int().positive()
411
+ var agentHeartbeatResponseSchema = z7.object({
412
+ listening: z7.literal(true),
413
+ ttlSeconds: z7.number().int().positive()
247
414
  });
248
- var agentPresenceSchema = z6.object({
249
- listening: z6.boolean(),
250
- versionNumber: z6.number().int().positive().nullable()
415
+ var agentPresenceSchema = z7.object({
416
+ listening: z7.boolean(),
417
+ versionNumber: z7.number().int().positive().nullable()
251
418
  });
252
419
 
253
420
  // ../shared/src/protection.ts
254
- import { z as z7 } from "zod";
421
+ import { z as z8 } from "zod";
255
422
  var PASSWORD_MIN = 6;
256
423
  var PASSWORD_MAX = 128;
257
- var gatePasswordSchema = z7.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
258
- var setProtectionSchema = z7.object({
424
+ var gatePasswordSchema = z8.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
425
+ var setProtectionSchema = z8.object({
259
426
  password: gatePasswordSchema.nullable()
260
427
  });
261
- var protectionResponseSchema = z7.object({
262
- protected: z7.boolean()
428
+ var protectionResponseSchema = z8.object({
429
+ protected: z8.boolean()
263
430
  });
264
- var unlockSchema = z7.object({
265
- password: z7.string().min(1).max(1024)
431
+ var unlockSchema = z8.object({
432
+ password: z8.string().min(1).max(1024)
266
433
  });
267
- var unlockResponseSchema = z7.object({
268
- unlocked: z7.literal(true)
434
+ var unlockResponseSchema = z8.object({
435
+ unlocked: z8.literal(true)
269
436
  });
270
437
 
271
438
  // ../shared/src/publish.ts
272
- import { z as z8 } from "zod";
439
+ import { z as z9 } from "zod";
273
440
  function isSafeArtifactPath(p) {
274
441
  if (p.length === 0 || p.length > 1024) {
275
442
  return false;
@@ -282,128 +449,136 @@ function isSafeArtifactPath(p) {
282
449
  }
283
450
  return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
284
451
  }
285
- var manifestFileSchema = z8.object({
286
- hash: z8.string().regex(/^sha256_[a-f0-9]{64}$/),
287
- mimeType: z8.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
288
- path: z8.string().min(1).max(1024).refine(isSafeArtifactPath, {
452
+ var manifestFileSchema = z9.object({
453
+ hash: z9.string().regex(/^sha256_[a-f0-9]{64}$/),
454
+ mimeType: z9.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
455
+ path: z9.string().min(1).max(1024).refine(isSafeArtifactPath, {
289
456
  message: "path must be a safe relative path"
290
457
  }),
291
- size: z8.number().int().positive()
458
+ size: z9.number().int().positive()
292
459
  });
293
- var publishSourceSchema = z8.object({
294
- agent: z8.string().optional(),
295
- runId: z8.string().optional(),
296
- type: z8.literal("cli")
460
+ var publishSourceSchema = z9.object({
461
+ agent: z9.string().optional(),
462
+ runId: z9.string().optional(),
463
+ type: z9.literal("cli")
297
464
  });
298
- var createPublishSessionSchema = z8.object({
299
- artifactId: z8.string().nullable(),
300
- entryPath: z8.string().optional(),
301
- files: z8.array(manifestFileSchema).min(1).max(1e3),
465
+ var createPublishSessionSchema = z9.object({
466
+ artifactId: z9.string().nullable(),
467
+ entryPath: z9.string().optional(),
468
+ files: z9.array(manifestFileSchema).min(1).max(1e3),
469
+ // SPEC-2 §6.4 — workspace placement is available only to authenticated
470
+ // members; the API enforces membership and folder ownership.
471
+ folderId: z9.string().min(1).optional(),
302
472
  source: publishSourceSchema,
303
- title: z8.string().max(200).optional()
304
- });
305
- var publishSessionResponseSchema = z8.object({
306
- artifactId: z8.string(),
307
- expiresAt: z8.string(),
308
- filesToUpload: z8.array(
309
- z8.object({
310
- method: z8.literal("PUT"),
311
- path: z8.string(),
312
- uploadUrl: z8.string().url()
473
+ title: z9.string().max(200).optional(),
474
+ workspaceId: z9.string().min(1).optional()
475
+ });
476
+ var publishSessionResponseSchema = z9.object({
477
+ artifactId: z9.string(),
478
+ expiresAt: z9.string(),
479
+ filesToUpload: z9.array(
480
+ z9.object({
481
+ method: z9.literal("PUT"),
482
+ path: z9.string(),
483
+ uploadUrl: z9.string().url()
313
484
  })
314
485
  ),
315
- publishSessionId: z8.string(),
316
- skippedFiles: z8.array(z8.string()),
317
- versionId: z8.string()
486
+ publishSessionId: z9.string(),
487
+ skippedFiles: z9.array(z9.string()),
488
+ versionId: z9.string()
318
489
  });
319
- var finalizeResponseSchema = z8.object({
320
- artifactId: z8.string(),
490
+ var finalizeResponseSchema = z9.object({
491
+ artifactId: z9.string(),
321
492
  // API-key publishes create the artifact already claimed (SPEC §5.4) —
322
493
  // the CLI uses this to tell "claim URL pending" from "already yours".
323
- claimed: z8.boolean(),
494
+ claimed: z9.boolean(),
324
495
  // Returned only on first publish of a new artifact (one-time).
325
- claimUrl: z8.string().url().optional(),
326
- publishToken: z8.string().optional(),
327
- url: z8.string().url(),
328
- versionId: z8.string(),
329
- versionNumber: z8.number().int().positive()
330
- });
331
- var artifactResponseSchema = z8.object({
332
- claimed: z8.boolean(),
333
- currentVersion: z8.object({
334
- entryUrl: z8.string().url(),
335
- id: z8.string(),
336
- versionNumber: z8.number().int().positive()
496
+ claimUrl: z9.string().url().optional(),
497
+ publishToken: z9.string().optional(),
498
+ url: z9.string().url(),
499
+ versionId: z9.string(),
500
+ versionNumber: z9.number().int().positive()
501
+ });
502
+ var artifactResponseSchema = z9.object({
503
+ claimed: z9.boolean(),
504
+ currentVersion: z9.object({
505
+ entryUrl: z9.string().url(),
506
+ id: z9.string(),
507
+ versionNumber: z9.number().int().positive()
337
508
  }).nullable(),
338
- id: z8.string(),
339
- slug: z8.string(),
340
- title: z8.string()
509
+ // SPEC-2 §6.4 — placement + visibility surface on the artifact.
510
+ folderId: z9.string().nullable(),
511
+ id: z9.string(),
512
+ slug: z9.string(),
513
+ title: z9.string(),
514
+ visibility: z9.enum(["public", "unlisted", "workspace"]),
515
+ workspaceId: z9.string().nullable()
341
516
  });
342
517
 
343
518
  // ../shared/src/report.ts
344
- import { z as z9 } from "zod";
345
- var reportRequestSchema = z9.object({
346
- reason: z9.string().max(1e3).optional()
519
+ import { z as z10 } from "zod";
520
+ var reportRequestSchema = z10.object({
521
+ reason: z10.string().max(1e3).optional()
347
522
  });
348
- var reportResponseSchema = z9.object({
349
- reported: z9.literal(true)
523
+ var reportResponseSchema = z10.object({
524
+ reported: z10.literal(true)
350
525
  });
351
526
 
352
527
  // ../shared/src/stats.ts
353
- import { z as z10 } from "zod";
354
- var avgViewSecondsSchema = z10.number().int().nonnegative().nullable();
355
- var versionViewStatsSchema = z10.object({
528
+ import { z as z11 } from "zod";
529
+ var avgViewSecondsSchema = z11.number().int().nonnegative().nullable();
530
+ var versionViewStatsSchema = z11.object({
356
531
  avgViewSeconds: avgViewSecondsSchema,
357
532
  // Views from inside the review shell's iframe vs. direct visits to
358
533
  // the render URL. views = embeddedViews + directViews.
359
- directViews: z10.number().int().nonnegative(),
360
- embeddedViews: z10.number().int().nonnegative(),
534
+ directViews: z11.number().int().nonnegative(),
535
+ embeddedViews: z11.number().int().nonnegative(),
361
536
  // Distinct hashed viewer IPs across the version's lifetime.
362
- uniqueViewers: z10.number().int().nonnegative(),
363
- versionNumber: z10.number().int().positive(),
364
- views: z10.number().int().nonnegative()
537
+ uniqueViewers: z11.number().int().nonnegative(),
538
+ versionNumber: z11.number().int().positive(),
539
+ views: z11.number().int().nonnegative()
365
540
  });
366
- var dailyViewsSchema = z10.object({
541
+ var dailyViewsSchema = z11.object({
367
542
  avgViewSeconds: avgViewSecondsSchema,
368
543
  // UTC day, YYYY-MM-DD.
369
- day: z10.string(),
370
- uniqueViewers: z10.number().int().nonnegative(),
371
- views: z10.number().int().nonnegative()
544
+ day: z11.string(),
545
+ uniqueViewers: z11.number().int().nonnegative(),
546
+ views: z11.number().int().nonnegative()
372
547
  });
373
- var artifactStatsResponseSchema = z10.object({
548
+ var artifactStatsResponseSchema = z11.object({
374
549
  avgViewSeconds: avgViewSecondsSchema,
375
- daily: z10.array(dailyViewsSchema),
550
+ daily: z11.array(dailyViewsSchema),
376
551
  // Distinct hashed viewer IPs across all versions and days.
377
- totalUniqueViewers: z10.number().int().nonnegative(),
378
- totalViews: z10.number().int().nonnegative(),
379
- versions: z10.array(versionViewStatsSchema)
552
+ totalUniqueViewers: z11.number().int().nonnegative(),
553
+ totalViews: z11.number().int().nonnegative(),
554
+ versions: z11.array(versionViewStatsSchema)
380
555
  });
381
556
 
382
557
  // ../shared/src/versions.ts
383
- import { z as z11 } from "zod";
384
- var versionSourceSchema = z11.object({
385
- agentName: z11.string().nullable(),
386
- type: z11.string()
558
+ import { z as z12 } from "zod";
559
+ var versionSourceSchema = z12.object({
560
+ agentName: z12.string().nullable(),
561
+ type: z12.string()
387
562
  });
388
- var versionChangesSchema = z11.object({
389
- added: z11.array(z11.string()),
390
- changed: z11.array(z11.string()),
391
- removed: z11.array(z11.string())
563
+ var versionChangesSchema = z12.object({
564
+ added: z12.array(z12.string()),
565
+ changed: z12.array(z12.string()),
566
+ removed: z12.array(z12.string())
392
567
  });
393
- var versionSummarySchema = z11.object({
568
+ var versionSummarySchema = z12.object({
394
569
  // Absent for the first version — there is no previous version to diff against.
395
570
  changes: versionChangesSchema.nullable(),
396
- createdAt: z11.string(),
397
- entryUrl: z11.string().url(),
398
- fileCount: z11.number().int().nonnegative(),
399
- id: z11.string(),
400
- isCurrent: z11.boolean(),
571
+ createdAt: z12.string(),
572
+ entryUrl: z12.string().url(),
573
+ fileCount: z12.number().int().nonnegative(),
574
+ id: z12.string(),
575
+ isCurrent: z12.boolean(),
401
576
  source: versionSourceSchema,
402
- totalSizeBytes: z11.number().int().nonnegative(),
403
- versionNumber: z11.number().int().positive()
577
+ totalSizeBytes: z12.number().int().nonnegative(),
578
+ versionNumber: z12.number().int().positive()
404
579
  });
405
- var versionListResponseSchema = z11.object({
406
- versions: z11.array(versionSummarySchema)
580
+ var versionListResponseSchema = z12.object({
581
+ versions: z12.array(versionSummarySchema)
407
582
  });
408
583
 
409
584
  // src/api.ts
@@ -507,6 +682,20 @@ function createApiClient(apiUrl2, publishToken) {
507
682
  (d) => artifactListResponseSchema.parse(d)
508
683
  );
509
684
  },
685
+ async listFolders(workspaceId) {
686
+ return request(
687
+ `${base}/api/v1/workspaces/${workspaceId}/folders`,
688
+ { headers, method: "GET" },
689
+ (d) => folderListResponseSchema.parse(d)
690
+ );
691
+ },
692
+ async listWorkspaces() {
693
+ return request(
694
+ `${base}/api/v1/workspaces`,
695
+ { headers, method: "GET" },
696
+ (d) => workspaceListResponseSchema.parse(d)
697
+ );
698
+ },
510
699
  // SPEC §6.14 — who the presented credential belongs to. Used by
511
700
  // `login` to verify a pasted key and by `whoami`.
512
701
  async me() {
@@ -552,17 +741,17 @@ import {
552
741
  writeFileSync as writeFileSync2
553
742
  } from "fs";
554
743
  import path2 from "path";
555
- import { z as z13 } from "zod";
744
+ import { z as z14 } from "zod";
556
745
 
557
746
  // src/credentials.ts
558
747
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
559
748
  import { homedir } from "os";
560
749
  import path from "path";
561
- import { z as z12 } from "zod";
750
+ import { z as z13 } from "zod";
562
751
  var DEFAULT_API_URL = "https://artifact.cafe";
563
- var credentialsSchema = z12.object({
564
- keys: z12.record(z12.string(), z12.string()),
565
- version: z12.literal(1)
752
+ var credentialsSchema = z13.object({
753
+ keys: z13.record(z13.string(), z13.string()),
754
+ version: z13.literal(1)
566
755
  });
567
756
  function credentialsPath() {
568
757
  return path.join(homedir(), ".artifactcafe", "credentials");
@@ -612,15 +801,19 @@ function resolveApiKey(apiUrl2, flag) {
612
801
  }
613
802
 
614
803
  // src/config.ts
615
- var configSchema = z13.object({
616
- apiUrl: z13.string(),
617
- artifactId: z13.string(),
804
+ var configSchema = z14.object({
805
+ apiUrl: z14.string(),
806
+ artifactId: z14.string(),
807
+ folder: z14.string().optional(),
808
+ folderId: z14.string().optional(),
618
809
  // Optional: a folder linked by `link` to an artifact you own carries no
619
810
  // token — owner actions authorize via your stored login instead. A first
620
811
  // publish still writes the anonymous-mode token here.
621
- publishToken: z13.string().optional(),
622
- slug: z13.string(),
623
- url: z13.string()
812
+ publishToken: z14.string().optional(),
813
+ slug: z14.string(),
814
+ url: z14.string(),
815
+ workspace: z14.string().optional(),
816
+ workspaceId: z14.string().optional()
624
817
  });
625
818
  var CONFIG_DIR = ".artifactcafe";
626
819
  var CONFIG_FILE = "config.json";
@@ -1029,6 +1222,7 @@ async function publish(opts) {
1029
1222
  const token = (artifactId !== null && artifactId === config?.artifactId ? config.publishToken : void 0) ?? process.env.ARTIFACT_CAFE_TOKEN;
1030
1223
  const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
1031
1224
  const apiKey = resolveApiKey(apiUrl2, opts.apiKey);
1225
+ const placement = await resolvePlacement({ apiKey, apiUrl: apiUrl2, config, opts });
1032
1226
  const { files, skipped } = scanDirectory(dir);
1033
1227
  if (files.length === 0) {
1034
1228
  throw new Error(`Nothing to publish in ${dir} (after ignore rules).`);
@@ -1046,12 +1240,14 @@ async function publish(opts) {
1046
1240
  artifactId,
1047
1241
  entryPath,
1048
1242
  files: files.map(({ absPath: _absPath, ...manifest }) => manifest),
1243
+ ...placement.folderId ? { folderId: placement.folderId } : {},
1049
1244
  source: {
1050
1245
  agent: process.env.ARTIFACT_CAFE_AGENT,
1051
1246
  runId: process.env.ARTIFACT_CAFE_RUN_ID,
1052
1247
  type: "cli"
1053
1248
  },
1054
- title: opts.title
1249
+ title: opts.title,
1250
+ ...placement.workspaceId ? { workspaceId: placement.workspaceId } : {}
1055
1251
  };
1056
1252
  let credential = apiKey ?? token;
1057
1253
  let client = createApiClient(apiUrl2, credential);
@@ -1083,13 +1279,19 @@ async function publish(opts) {
1083
1279
  );
1084
1280
  });
1085
1281
  const result = await client.finalize(session.publishSessionId);
1086
- if (result.publishToken) {
1282
+ const existingToken = config?.artifactId === result.artifactId ? config.publishToken : void 0;
1283
+ const shouldWriteConfig = result.publishToken !== void 0 || config?.artifactId === result.artifactId || opts.workspace !== void 0 || opts.folder !== void 0;
1284
+ if (shouldWriteConfig) {
1087
1285
  writeConfig(dir, {
1088
1286
  apiUrl: apiUrl2,
1089
1287
  artifactId: result.artifactId,
1090
- publishToken: result.publishToken,
1288
+ ...placement.folder ? { folder: placement.folder } : {},
1289
+ ...placement.folderId ? { folderId: placement.folderId } : {},
1290
+ ...result.publishToken ?? existingToken ? { publishToken: result.publishToken ?? existingToken } : {},
1091
1291
  slug: slugFromUrl(result.url),
1092
- url: result.url
1292
+ url: result.url,
1293
+ ...placement.workspace ? { workspace: placement.workspace } : {},
1294
+ ...placement.workspaceId ? { workspaceId: placement.workspaceId } : {}
1093
1295
  });
1094
1296
  }
1095
1297
  if (opts.password !== void 0) {
@@ -1105,8 +1307,12 @@ async function publish(opts) {
1105
1307
  {
1106
1308
  artifactId: result.artifactId,
1107
1309
  claimed: result.claimed,
1310
+ folder: placement.folder ?? null,
1311
+ folderId: placement.folderId ?? null,
1108
1312
  url: result.url,
1109
1313
  versionNumber: result.versionNumber,
1314
+ workspace: placement.workspace ?? null,
1315
+ workspaceId: placement.workspaceId ?? null,
1110
1316
  ...result.claimUrl ? { claimUrl: result.claimUrl } : {},
1111
1317
  ...opts.password === void 0 ? {} : { protected: true },
1112
1318
  filesUploaded: session.filesToUpload.map((f) => f.path),
@@ -1148,6 +1354,76 @@ ${result.claimUrl}
1148
1354
  openInBrowser(result.url);
1149
1355
  }
1150
1356
  }
1357
+ async function resolvePlacement(params) {
1358
+ const { apiKey, apiUrl: apiUrl2, config, opts } = params;
1359
+ const hasPlacementFlag = opts.workspace !== void 0 || opts.folder !== void 0;
1360
+ if (!hasPlacementFlag && config?.workspaceId) {
1361
+ if (!apiKey) {
1362
+ throw new Error(
1363
+ `Publishing into workspace ${config.workspace ?? config.workspaceId} needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
1364
+ );
1365
+ }
1366
+ return {
1367
+ ...config.folder ? { folder: config.folder } : {},
1368
+ ...config.folderId ? { folderId: config.folderId } : {},
1369
+ ...config.workspace ? { workspace: config.workspace } : {},
1370
+ workspaceId: config.workspaceId
1371
+ };
1372
+ }
1373
+ if (!hasPlacementFlag) {
1374
+ return {};
1375
+ }
1376
+ if (!apiKey) {
1377
+ throw new Error(
1378
+ "Publishing into a workspace needs an account \u2014 run `artifact-cafe login` first."
1379
+ );
1380
+ }
1381
+ const workspaceHandle = opts.workspace ?? config?.workspace;
1382
+ if (!workspaceHandle) {
1383
+ throw new Error("--folder requires --workspace or a stored workspace.");
1384
+ }
1385
+ const client = createApiClient(apiUrl2, apiKey);
1386
+ let workspaceId;
1387
+ if (opts.workspace === void 0 && config?.workspaceId) {
1388
+ ({ workspaceId } = config);
1389
+ } else {
1390
+ const data2 = await client.listWorkspaces();
1391
+ const workspace = data2.workspaces.find(
1392
+ (candidate) => candidate.handle === workspaceHandle
1393
+ );
1394
+ if (!workspace) {
1395
+ throw new Error(
1396
+ `No workspace with handle "${workspaceHandle}" belongs to this account.`
1397
+ );
1398
+ }
1399
+ workspaceId = workspace.id;
1400
+ }
1401
+ const folderName = opts.workspace !== void 0 && opts.folder === void 0 ? void 0 : opts.folder ?? config?.folder;
1402
+ if (!folderName) {
1403
+ return { workspace: workspaceHandle, workspaceId };
1404
+ }
1405
+ const data = await client.listFolders(workspaceId);
1406
+ const matches = data.folders.filter(
1407
+ (candidate) => candidate.name === folderName || candidate.id === folderName
1408
+ );
1409
+ if (matches.length === 0) {
1410
+ throw new Error(
1411
+ `No folder named "${folderName}" exists in workspace ${workspaceHandle}.`
1412
+ );
1413
+ }
1414
+ if (matches.length > 1) {
1415
+ throw new Error(
1416
+ `More than one folder is named "${folderName}" in workspace ${workspaceHandle}; pass the folder id instead.`
1417
+ );
1418
+ }
1419
+ const folder = matches[0];
1420
+ return {
1421
+ folder: folder.name,
1422
+ folderId: folder.id,
1423
+ workspace: workspaceHandle,
1424
+ workspaceId
1425
+ };
1426
+ }
1151
1427
  async function inBatches(items, concurrency, fn) {
1152
1428
  let next = 0;
1153
1429
  const workers = Array.from(
@@ -1221,6 +1497,52 @@ function printCliError(err, json) {
1221
1497
  }
1222
1498
  }
1223
1499
 
1500
+ // src/folders.ts
1501
+ async function folders(opts) {
1502
+ const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
1503
+ /\/$/,
1504
+ ""
1505
+ );
1506
+ const key = resolveApiKey(apiUrl2, opts.apiKey);
1507
+ if (!key) {
1508
+ throw new Error(
1509
+ `Listing folders needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
1510
+ );
1511
+ }
1512
+ const client = createApiClient(apiUrl2, key);
1513
+ const workspaceData = await client.listWorkspaces();
1514
+ const workspace = workspaceData.workspaces.find(
1515
+ (candidate) => candidate.handle === opts.workspace
1516
+ );
1517
+ if (!workspace) {
1518
+ throw new Error(
1519
+ `No workspace with handle "${opts.workspace}" belongs to this account.`
1520
+ );
1521
+ }
1522
+ const data = await client.listFolders(workspace.id);
1523
+ if (opts.json) {
1524
+ console.log(JSON.stringify(data, null, 2));
1525
+ return;
1526
+ }
1527
+ if (data.folders.length === 0) {
1528
+ console.log(`No folders in workspace ${workspace.handle}.`);
1529
+ return;
1530
+ }
1531
+ const plural = data.folders.length === 1 ? "" : "s";
1532
+ console.log(
1533
+ `${data.folders.length} folder${plural} in ${workspace.handle}
1534
+ `
1535
+ );
1536
+ for (const folder of data.folders) {
1537
+ printFolder(folder);
1538
+ }
1539
+ }
1540
+ function printFolder(folder) {
1541
+ console.log(`${folder.name} (${folder.visibility})`);
1542
+ console.log(` ${folder.id}
1543
+ `);
1544
+ }
1545
+
1224
1546
  // src/link.ts
1225
1547
  import path7 from "path";
1226
1548
  import { createInterface } from "readline";
@@ -1401,7 +1723,7 @@ function promptSecret(question) {
1401
1723
  }
1402
1724
  async function login(opts) {
1403
1725
  const base = apiUrl();
1404
- const keysUrl = `${base}/cli`;
1726
+ const keysUrl = `${base}/dashboard?view=api-keys`;
1405
1727
  let key = opts.apiKey?.trim();
1406
1728
  if (!key) {
1407
1729
  if (!opts.json) {
@@ -1483,7 +1805,7 @@ function logout(opts) {
1483
1805
  }
1484
1806
  console.log(`\u2713 Logged out of ${apiOrigin(base)} on this machine.`);
1485
1807
  console.log(
1486
- `The key itself is still active \u2014 revoke it at ${base}/cli if this machine is shared.`
1808
+ `The key itself is still active \u2014 revoke it at ${base}/dashboard?view=api-keys if this machine is shared.`
1487
1809
  );
1488
1810
  }
1489
1811
 
@@ -1549,6 +1871,40 @@ async function protect(opts) {
1549
1871
  );
1550
1872
  }
1551
1873
 
1874
+ // src/workspaces.ts
1875
+ async function workspaces(opts) {
1876
+ const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
1877
+ /\/$/,
1878
+ ""
1879
+ );
1880
+ const key = resolveApiKey(apiUrl2, opts.apiKey);
1881
+ if (!key) {
1882
+ throw new Error(
1883
+ `Listing workspaces needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
1884
+ );
1885
+ }
1886
+ const data = await createApiClient(apiUrl2, key).listWorkspaces();
1887
+ if (opts.json) {
1888
+ console.log(JSON.stringify(data, null, 2));
1889
+ return;
1890
+ }
1891
+ if (data.workspaces.length === 0) {
1892
+ console.log("No workspaces yet \u2014 create one from the dashboard.");
1893
+ return;
1894
+ }
1895
+ const plural = data.workspaces.length === 1 ? "" : "s";
1896
+ console.log(`${data.workspaces.length} workspace${plural}
1897
+ `);
1898
+ for (const workspace of data.workspaces) {
1899
+ printWorkspace(workspace);
1900
+ }
1901
+ }
1902
+ function printWorkspace(workspace) {
1903
+ console.log(`${workspace.name} (${workspace.role ?? "non-member"})`);
1904
+ console.log(` ${workspace.handle} \xB7 ${workspace.id}
1905
+ `);
1906
+ }
1907
+
1552
1908
  // src/index.ts
1553
1909
  var publish2 = defineCommand({
1554
1910
  args: {
@@ -1566,13 +1922,21 @@ var publish2 = defineCommand({
1566
1922
  type: "positional"
1567
1923
  },
1568
1924
  entry: { description: "Entry file (default: auto-detect)", type: "string" },
1925
+ folder: {
1926
+ description: "Publish into this workspace folder name (or id)",
1927
+ type: "string"
1928
+ },
1569
1929
  json: { description: "Machine-readable output", type: "boolean" },
1570
1930
  "no-open": { description: "Don't open the review URL", type: "boolean" },
1571
1931
  password: {
1572
1932
  description: "Password-protect the artifact (min 6 chars)",
1573
1933
  type: "string"
1574
1934
  },
1575
- title: { description: "Artifact title", type: "string" }
1935
+ title: { description: "Artifact title", type: "string" },
1936
+ workspace: {
1937
+ description: "Publish into this workspace handle",
1938
+ type: "string"
1939
+ }
1576
1940
  },
1577
1941
  meta: { description: "Publish a folder as an artifact", name: "publish" },
1578
1942
  async run({ args }) {
@@ -1582,10 +1946,12 @@ var publish2 = defineCommand({
1582
1946
  artifact: args.artifact,
1583
1947
  dir: args.dir,
1584
1948
  entry: args.entry,
1949
+ folder: args.folder,
1585
1950
  json: Boolean(args.json),
1586
1951
  open: !args["no-open"],
1587
1952
  password: args.password,
1588
- title: args.title
1953
+ title: args.title,
1954
+ workspace: args.workspace
1589
1955
  });
1590
1956
  } catch (err) {
1591
1957
  printCliError(err, Boolean(args.json));
@@ -1669,6 +2035,60 @@ var list2 = defineCommand({
1669
2035
  }
1670
2036
  }
1671
2037
  });
2038
+ var folders2 = defineCommand({
2039
+ args: {
2040
+ "api-key": {
2041
+ description: "List with this API key (default: stored login)",
2042
+ type: "string"
2043
+ },
2044
+ json: { description: "Machine-readable output", type: "boolean" },
2045
+ workspace: {
2046
+ description: "Workspace handle whose folders to list",
2047
+ required: true,
2048
+ type: "string"
2049
+ }
2050
+ },
2051
+ meta: {
2052
+ description: "List folders in a workspace",
2053
+ name: "folders"
2054
+ },
2055
+ async run({ args }) {
2056
+ try {
2057
+ await folders({
2058
+ apiKey: args["api-key"],
2059
+ json: Boolean(args.json),
2060
+ workspace: args.workspace
2061
+ });
2062
+ } catch (err) {
2063
+ printCliError(err, Boolean(args.json));
2064
+ process.exit(1);
2065
+ }
2066
+ }
2067
+ });
2068
+ var workspaces2 = defineCommand({
2069
+ args: {
2070
+ "api-key": {
2071
+ description: "List with this API key (default: stored login)",
2072
+ type: "string"
2073
+ },
2074
+ json: { description: "Machine-readable output", type: "boolean" }
2075
+ },
2076
+ meta: {
2077
+ description: "List the workspaces your account belongs to",
2078
+ name: "workspaces"
2079
+ },
2080
+ async run({ args }) {
2081
+ try {
2082
+ await workspaces({
2083
+ apiKey: args["api-key"],
2084
+ json: Boolean(args.json)
2085
+ });
2086
+ } catch (err) {
2087
+ printCliError(err, Boolean(args.json));
2088
+ process.exit(1);
2089
+ }
2090
+ }
2091
+ });
1672
2092
  var link2 = defineCommand({
1673
2093
  args: {
1674
2094
  "api-key": {
@@ -1773,6 +2193,66 @@ var comments2 = defineCommand({
1773
2193
  }
1774
2194
  }
1775
2195
  });
2196
+ var listen = defineCommand({
2197
+ args: {
2198
+ "api-key": {
2199
+ description: "Authorize with an account API key (default: stored login)",
2200
+ type: "string"
2201
+ },
2202
+ artifact: {
2203
+ description: "Act on an artifact ID instead of the linked folder",
2204
+ type: "string"
2205
+ },
2206
+ dir: {
2207
+ default: ".",
2208
+ description: "Published directory",
2209
+ type: "positional"
2210
+ },
2211
+ json: { description: "Machine-readable output", type: "boolean" },
2212
+ status: {
2213
+ default: "open",
2214
+ description: "Filter: open | resolved | all",
2215
+ type: "string"
2216
+ },
2217
+ timeout: {
2218
+ default: "900",
2219
+ description: "Give up after this many seconds (exit 2)",
2220
+ type: "string"
2221
+ },
2222
+ version: {
2223
+ default: "current",
2224
+ description: "Filter: current | all",
2225
+ type: "string"
2226
+ }
2227
+ },
2228
+ meta: {
2229
+ description: "Wait for new reviewer feedback (alias for comments --wait)",
2230
+ name: "listen"
2231
+ },
2232
+ async run({ args }) {
2233
+ try {
2234
+ const timeout = Number(args.timeout);
2235
+ if (!Number.isFinite(timeout) || timeout <= 0) {
2236
+ throw new Error(
2237
+ `--timeout must be a positive number of seconds (got "${args.timeout}").`
2238
+ );
2239
+ }
2240
+ await comments({
2241
+ apiKey: args["api-key"],
2242
+ artifact: args.artifact,
2243
+ dir: args.dir,
2244
+ json: Boolean(args.json),
2245
+ status: args.status,
2246
+ timeout,
2247
+ version: args.version,
2248
+ wait: true
2249
+ });
2250
+ } catch (err) {
2251
+ printCliError(err, Boolean(args.json));
2252
+ process.exit(1);
2253
+ }
2254
+ }
2255
+ });
1776
2256
  var protect2 = defineCommand({
1777
2257
  args: {
1778
2258
  "api-key": {
@@ -1882,14 +2362,17 @@ var main = defineCommand({
1882
2362
  subCommands: {
1883
2363
  comments: comments2,
1884
2364
  dashboard,
2365
+ folders: folders2,
1885
2366
  link: link2,
1886
2367
  list: list2,
2368
+ listen,
1887
2369
  login: login2,
1888
2370
  logout: logout2,
1889
2371
  open,
1890
2372
  protect: protect2,
1891
2373
  publish: publish2,
1892
- whoami: whoami2
2374
+ whoami: whoami2,
2375
+ workspaces: workspaces2
1893
2376
  }
1894
2377
  });
1895
2378
  runMain(main);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artifact-cafe",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Publish AI-generated artifacts for review on artifact.cafe",
5
5
  "type": "module",
6
6
  "bin": {