artifact-cafe 0.0.14 → 0.0.16

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 +1407 -676
  2. package/package.json +9 -9
package/dist/index.js CHANGED
@@ -83,6 +83,19 @@ var artifactListResponseSchema = z.object({
83
83
  artifacts: z.array(artifactListItemSchema)
84
84
  });
85
85
 
86
+ // ../shared/src/agent-tokens.ts
87
+ import { z as z2 } from "zod";
88
+ var createAgentTokenSchema = z2.object({
89
+ externalId: z2.string().min(1).max(1024),
90
+ name: z2.string().trim().min(1).max(100).optional(),
91
+ ttlSeconds: z2.number().int().positive().max(3600)
92
+ });
93
+ var agentTokenResponseSchema = z2.object({
94
+ expiresAt: z2.string().datetime(),
95
+ token: z2.string(),
96
+ workspaceId: z2.string()
97
+ });
98
+
86
99
  // ../shared/src/agents.ts
87
100
  var AGENT_BRANDS = [
88
101
  {
@@ -152,43 +165,43 @@ function detectAgentFromEnv(env) {
152
165
  }
153
166
 
154
167
  // ../shared/src/avatars.ts
155
- import { z as z2 } from "zod";
168
+ import { z as z3 } from "zod";
156
169
  var AVATAR_CONTENT_TYPES = [
157
170
  "image/png",
158
171
  "image/jpeg",
159
172
  "image/webp"
160
173
  ];
161
174
  var AVATAR_MAX_BYTES = 2 * 1024 * 1024;
162
- var avatarContentTypeSchema = z2.enum(AVATAR_CONTENT_TYPES);
163
- var userAvatarResponseSchema = z2.object({
164
- image: z2.string().nullable()
175
+ var avatarContentTypeSchema = z3.enum(AVATAR_CONTENT_TYPES);
176
+ var userAvatarResponseSchema = z3.object({
177
+ image: z3.string().nullable()
165
178
  });
166
- var workspaceAvatarResponseSchema = z2.object({
167
- logo: z2.string().nullable()
179
+ var workspaceAvatarResponseSchema = z3.object({
180
+ logo: z3.string().nullable()
168
181
  });
169
182
 
170
183
  // ../shared/src/comments.ts
171
- import { z as z6 } from "zod";
184
+ import { z as z7 } from "zod";
172
185
 
173
186
  // ../shared/src/emoji.ts
174
- import { z as z3 } from "zod";
187
+ import { z as z4 } from "zod";
175
188
  var SINGLE_EMOJI = new RegExp("^\\p{RGI_Emoji}$", "v");
176
189
  var REDUNDANT_VS16 = new RegExp("(\\p{Emoji_Presentation})\\uFE0F", "gu");
177
190
  function canonicalizeEmoji(s) {
178
191
  return s.replace(REDUNDANT_VS16, "$1");
179
192
  }
180
- var iconEmojiSchema = z3.string().max(64).transform(canonicalizeEmoji).refine((s) => SINGLE_EMOJI.test(s), { message: "Must be a single emoji." }).nullable().describe("A single emoji shown as the icon, or null for the default.");
193
+ var iconEmojiSchema = z4.string().max(64).transform(canonicalizeEmoji).refine((s) => SINGLE_EMOJI.test(s), { message: "Must be a single emoji." }).nullable().describe("A single emoji shown as the icon, or null for the default.");
181
194
 
182
195
  // ../shared/src/publish.ts
183
- import { z as z5 } from "zod";
196
+ import { z as z6 } from "zod";
184
197
 
185
198
  // ../shared/src/review-mode.ts
186
- import { z as z4 } from "zod";
187
- var reviewModeSchema = z4.enum(["open", "readonly", "off"]);
188
- var setReviewModeSchema = z4.object({
199
+ import { z as z5 } from "zod";
200
+ var reviewModeSchema = z5.enum(["open", "readonly", "off"]);
201
+ var setReviewModeSchema = z5.object({
189
202
  mode: reviewModeSchema
190
203
  });
191
- var reviewModeResponseSchema = z4.object({
204
+ var reviewModeResponseSchema = z5.object({
192
205
  mode: reviewModeSchema
193
206
  });
194
207
 
@@ -207,273 +220,274 @@ function isSafeArtifactPath(p) {
207
220
  }
208
221
  var CARD_DESCRIPTION_MAX = 300;
209
222
  var CARD_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
210
- var manifestFileSchema = z5.object({
211
- hash: z5.string().regex(/^sha256_[a-f0-9]{64}$/),
212
- mimeType: z5.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
213
- path: z5.string().min(1).max(1024).refine(isSafeArtifactPath, {
223
+ var manifestFileSchema = z6.object({
224
+ hash: z6.string().regex(/^sha256_[a-f0-9]{64}$/),
225
+ mimeType: z6.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
226
+ path: z6.string().min(1).max(1024).refine(isSafeArtifactPath, {
214
227
  message: "path must be a safe relative path"
215
228
  }),
216
- size: z5.number().int().positive()
229
+ size: z6.number().int().positive()
217
230
  });
218
- var publishSourceSchema = z5.object({
219
- agent: z5.string().optional(),
220
- runId: z5.string().optional(),
221
- type: z5.literal("cli")
231
+ var publishSourceSchema = z6.object({
232
+ agent: z6.string().optional(),
233
+ runId: z6.string().optional(),
234
+ type: z6.literal("cli")
222
235
  });
223
- var createPublishSessionSchema = z5.object({
224
- artifactId: z5.string().nullable(),
236
+ var createPublishSessionSchema = z6.object({
237
+ artifactId: z6.string().nullable(),
225
238
  // One-line share-card summary (`--description`). Overrides whatever the
226
239
  // entry HTML declares; leave it off and the API reads the document's own
227
240
  // `og:description`/`description` at finalize.
228
- description: z5.string().max(CARD_DESCRIPTION_MAX).optional(),
229
- entryPath: z5.string().optional(),
230
- files: z5.array(manifestFileSchema).min(1).max(1e3),
241
+ description: z6.string().max(CARD_DESCRIPTION_MAX).optional(),
242
+ entryPath: z6.string().optional(),
243
+ files: z6.array(manifestFileSchema).min(1).max(1e3),
231
244
  // SPEC-2 §6.4 — workspace placement is available only to authenticated
232
245
  // members; the API enforces membership and folder ownership. folderId
233
246
  // without workspaceId resolves against the account's personal workspace.
234
- folderId: z5.string().min(1).optional(),
247
+ folderId: z6.string().min(1).optional(),
235
248
  // Bullet-point changelog for this version, authored by the publisher.
236
249
  // Optional on every publish (incl. v1); stored immutably on the version.
237
- notes: z5.string().max(4e3).optional(),
250
+ notes: z6.string().max(4e3).optional(),
238
251
  source: publishSourceSchema,
239
- title: z5.string().max(200).optional(),
240
- workspaceId: z5.string().min(1).optional()
241
- });
242
- var publishSessionResponseSchema = z5.object({
243
- artifactId: z5.string(),
244
- expiresAt: z5.string(),
245
- filesToUpload: z5.array(
246
- z5.object({
247
- method: z5.literal("PUT"),
248
- path: z5.string(),
249
- uploadUrl: z5.string().url()
252
+ title: z6.string().max(200).optional(),
253
+ workspaceId: z6.string().min(1).optional()
254
+ });
255
+ var publishSessionResponseSchema = z6.object({
256
+ artifactId: z6.string(),
257
+ expiresAt: z6.string(),
258
+ filesToUpload: z6.array(
259
+ z6.object({
260
+ method: z6.literal("PUT"),
261
+ path: z6.string(),
262
+ uploadUrl: z6.string().url()
250
263
  })
251
264
  ),
252
- publishSessionId: z5.string(),
253
- skippedFiles: z5.array(z5.string()),
254
- versionId: z5.string()
265
+ publishSessionId: z6.string(),
266
+ skippedFiles: z6.array(z6.string()),
267
+ versionId: z6.string()
255
268
  });
256
- var finalizeResponseSchema = z5.object({
257
- artifactId: z5.string(),
269
+ var finalizeResponseSchema = z6.object({
270
+ artifactId: z6.string(),
258
271
  // API-key publishes create the artifact already claimed (SPEC §5.4) —
259
272
  // the CLI uses this to tell "claim URL pending" from "already yours".
260
- claimed: z5.boolean(),
273
+ claimed: z6.boolean(),
261
274
  // Returned only on first publish of a new artifact (one-time).
262
- claimUrl: z5.string().url().optional(),
263
- publishToken: z5.string().optional(),
264
- url: z5.string().url(),
265
- versionId: z5.string(),
266
- versionNumber: z5.number().int().positive(),
275
+ claimUrl: z6.string().url().optional(),
276
+ publishToken: z6.string().optional(),
277
+ url: z6.string().url(),
278
+ versionId: z6.string(),
279
+ versionNumber: z6.number().int().positive(),
267
280
  // SPEC-2 §5.2 — who can open the review URL. A publish into a folder
268
281
  // inherits that folder's visibility, so this is the only place the CLI
269
282
  // learns that a link it just printed is members-only. Optional so a new
270
283
  // CLI against an older API degrades to "unknown" instead of failing the
271
284
  // publish over a field that only shapes the closing message.
272
- visibility: z5.enum(["public", "unlisted", "workspace", "private"]).optional()
273
- });
274
- var artifactResponseSchema = z5.object({
275
- claimed: z5.boolean(),
276
- currentVersion: z5.object({
277
- entryUrl: z5.string().url(),
278
- id: z5.string(),
279
- versionNumber: z5.number().int().positive()
285
+ visibility: z6.enum(["public", "unlisted", "workspace", "private"]).optional(),
286
+ workspaceId: z6.string().nullable().optional()
287
+ });
288
+ var artifactResponseSchema = z6.object({
289
+ claimed: z6.boolean(),
290
+ currentVersion: z6.object({
291
+ entryUrl: z6.string().url(),
292
+ id: z6.string(),
293
+ versionNumber: z6.number().int().positive()
280
294
  }).nullable(),
281
295
  // Custom icon, or null to render the default document glyph.
282
- emoji: z5.string().nullable(),
296
+ emoji: z6.string().nullable(),
283
297
  // SPEC-2 §6.4 — placement + visibility surface on the artifact.
284
- folderId: z5.string().nullable(),
285
- id: z5.string(),
298
+ folderId: z6.string().nullable(),
299
+ id: z6.string(),
286
300
  // SPEC-2 §5.8 — what a viewer may do once in (open / readonly / off).
287
301
  reviewMode: reviewModeSchema,
288
- slug: z5.string(),
289
- title: z5.string(),
290
- visibility: z5.enum(["public", "unlisted", "workspace", "private"]),
291
- workspaceId: z5.string().nullable()
302
+ slug: z6.string(),
303
+ title: z6.string(),
304
+ visibility: z6.enum(["public", "unlisted", "workspace", "private"]),
305
+ workspaceId: z6.string().nullable()
292
306
  });
293
307
 
294
308
  // ../shared/src/comments.ts
295
- var anchorPageSchema = z6.string().max(1024).refine(isSafeArtifactPath, {
309
+ var anchorPageSchema = z7.string().max(1024).refine(isSafeArtifactPath, {
296
310
  message: "page must be a safe relative path"
297
311
  }).optional();
298
- var elementSnapshotSchema = z6.object({
299
- classes: z6.string().max(256).optional(),
300
- id: z6.string().max(256).optional(),
301
- tag: z6.string().min(1).max(128),
302
- text: z6.string().max(200).optional()
312
+ var elementSnapshotSchema = z7.object({
313
+ classes: z7.string().max(256).optional(),
314
+ id: z7.string().max(256).optional(),
315
+ tag: z7.string().min(1).max(128),
316
+ text: z7.string().max(200).optional()
303
317
  });
304
- var pointAnchorSchema = z6.object({
318
+ var pointAnchorSchema = z7.object({
305
319
  element: elementSnapshotSchema.optional(),
306
320
  // elementPath is the primary anchor; x/y are offsets within the
307
321
  // element's box, normalized 0..1 (SPEC.md §8.1).
308
- elementPath: z6.string().max(2048),
322
+ elementPath: z7.string().max(2048),
309
323
  page: anchorPageSchema,
310
- type: z6.literal("point"),
311
- viewportHeight: z6.number().int().positive().max(1e5),
312
- viewportWidth: z6.number().int().positive().max(1e5),
313
- x: z6.number().min(0).max(1),
314
- y: z6.number().min(0).max(1)
315
- });
316
- var textAnchorSchema = z6.object({
317
- elementPath: z6.string().max(2048),
318
- endOffset: z6.number().int().nonnegative().max(1e8),
324
+ type: z7.literal("point"),
325
+ viewportHeight: z7.number().int().positive().max(1e5),
326
+ viewportWidth: z7.number().int().positive().max(1e5),
327
+ x: z7.number().min(0).max(1),
328
+ y: z7.number().min(0).max(1)
329
+ });
330
+ var textAnchorSchema = z7.object({
331
+ elementPath: z7.string().max(2048),
332
+ endOffset: z7.number().int().nonnegative().max(1e8),
319
333
  page: anchorPageSchema,
320
- prefix: z6.string().max(200).optional(),
321
- quote: z6.string().min(1).max(1e3),
322
- startOffset: z6.number().int().nonnegative().max(1e8),
323
- suffix: z6.string().max(200).optional(),
324
- type: z6.literal("text")
334
+ prefix: z7.string().max(200).optional(),
335
+ quote: z7.string().min(1).max(1e3),
336
+ startOffset: z7.number().int().nonnegative().max(1e8),
337
+ suffix: z7.string().max(200).optional(),
338
+ type: z7.literal("text")
325
339
  });
326
- var pageAnchorSchema = z6.object({
340
+ var pageAnchorSchema = z7.object({
327
341
  page: anchorPageSchema,
328
- type: z6.literal("page")
342
+ type: z7.literal("page")
329
343
  });
330
- var anchorSchema = z6.discriminatedUnion("type", [
344
+ var anchorSchema = z7.discriminatedUnion("type", [
331
345
  pointAnchorSchema,
332
346
  textAnchorSchema,
333
347
  pageAnchorSchema
334
348
  ]);
335
- var displayNameSchema = z6.string().trim().min(1).max(64);
336
- var guestSchema = z6.object({
349
+ var displayNameSchema = z7.string().trim().min(1).max(64);
350
+ var guestSchema = z7.object({
337
351
  displayName: displayNameSchema,
338
- guestId: z6.string().min(16)
352
+ guestId: z7.string().min(16)
339
353
  });
340
- var viaLabelSchema = z6.string().trim().min(1).max(64);
341
- var createCommentThreadSchema = z6.object({
354
+ var viaLabelSchema = z7.string().trim().min(1).max(64);
355
+ var createCommentThreadSchema = z7.object({
342
356
  anchor: anchorSchema,
343
- body: z6.string().min(1).max(1e4),
357
+ body: z7.string().min(1).max(1e4),
344
358
  guest: guestSchema.optional(),
345
- versionId: z6.string(),
359
+ versionId: z7.string(),
346
360
  via: viaLabelSchema.optional()
347
361
  });
348
- var createCommentMessageSchema = z6.object({
349
- body: z6.string().min(1).max(1e4),
362
+ var createCommentMessageSchema = z7.object({
363
+ body: z7.string().min(1).max(1e4),
350
364
  guest: guestSchema.optional(),
351
365
  via: viaLabelSchema.optional()
352
366
  });
353
- var editCommentMessageSchema = z6.object({
354
- body: z6.string().min(1).max(1e4),
367
+ var editCommentMessageSchema = z7.object({
368
+ body: z7.string().min(1).max(1e4),
355
369
  guest: guestSchema.optional()
356
370
  });
357
- var threadActionSchema = z6.object({
371
+ var threadActionSchema = z7.object({
358
372
  guest: guestSchema.optional()
359
373
  });
360
- var reactionEmojiSchema = z6.string().max(64).transform(canonicalizeEmoji).refine((s) => SINGLE_EMOJI.test(s), { message: "Must be a single emoji." }).describe("A single emoji, e.g. \u{1F44D}");
361
- var toggleReactionSchema = z6.object({
374
+ var reactionEmojiSchema = z7.string().max(64).transform(canonicalizeEmoji).refine((s) => SINGLE_EMOJI.test(s), { message: "Must be a single emoji." }).describe("A single emoji, e.g. \u{1F44D}");
375
+ var toggleReactionSchema = z7.object({
362
376
  emoji: reactionEmojiSchema,
363
377
  guest: guestSchema.optional()
364
378
  });
365
- var commentAgentSchema = z6.object({
366
- id: z6.string().nullable(),
367
- label: z6.string()
379
+ var commentAgentSchema = z7.object({
380
+ id: z7.string().nullable(),
381
+ label: z7.string()
368
382
  });
369
- var commentAuthorSchema = z6.object({
383
+ var commentAuthorSchema = z7.object({
370
384
  agent: commentAgentSchema.nullable().default(null),
371
- avatarUrl: z6.string().nullable(),
372
- displayName: z6.string(),
373
- guestId: z6.string().nullable(),
374
- isViewer: z6.boolean(),
375
- kind: z6.enum(["member", "guest"]),
385
+ avatarUrl: z7.string().nullable(),
386
+ displayName: z7.string(),
387
+ guestId: z7.string().nullable(),
388
+ isViewer: z7.boolean(),
389
+ kind: z7.enum(["member", "guest"]),
376
390
  // Optional "via" attribution (§6.4). Null for plain comments; a bounded
377
391
  // label the shell appends after the display name. Defaulted for the same
378
392
  // reason as `agent`: a server predating §6.4 omits the key entirely.
379
- via: z6.string().nullable().default(null)
393
+ via: z7.string().nullable().default(null)
380
394
  });
381
- var messageReactionSchema = z6.object({
382
- emoji: z6.string(),
383
- reactors: z6.array(commentAuthorSchema)
395
+ var messageReactionSchema = z7.object({
396
+ emoji: z7.string(),
397
+ reactors: z7.array(commentAuthorSchema)
384
398
  });
385
- var commentMessageSchema = z6.object({
399
+ var commentMessageSchema = z7.object({
386
400
  author: commentAuthorSchema,
387
401
  // Empty string for a deleted message — the body is stripped server-side
388
402
  // (§6.7.1 tombstone), never sent to the client and hidden there.
389
- body: z6.string(),
390
- createdAt: z6.string(),
403
+ body: z7.string(),
404
+ createdAt: z7.string(),
391
405
  // Soft-deleted (§6.7.1). A tombstone keeps thread structure and replies;
392
406
  // the shell renders "Comment deleted" instead of the (absent) body.
393
- deleted: z6.boolean(),
407
+ deleted: z7.boolean(),
394
408
  // Last edit time (§6.7.1), or null if never edited — drives the "edited"
395
409
  // badge. No edit history is kept.
396
- editedAt: z6.string().nullable(),
397
- id: z6.string(),
410
+ editedAt: z7.string().nullable(),
411
+ id: z7.string(),
398
412
  // Defaulted, not required: a new CLI parsing an older server's
399
413
  // response must not fail on the missing key.
400
- reactions: z6.array(messageReactionSchema).default([])
414
+ reactions: z7.array(messageReactionSchema).default([])
401
415
  });
402
- var threadActorSchema = z6.object({
403
- displayName: z6.string(),
404
- kind: z6.enum(["member", "guest", "agent"])
416
+ var threadActorSchema = z7.object({
417
+ displayName: z7.string(),
418
+ kind: z7.enum(["member", "guest", "agent"])
405
419
  });
406
- var commentThreadSchema = z6.object({
420
+ var commentThreadSchema = z7.object({
407
421
  // §6.6.1 — set while open to signal "seen, working on it"; cleared on
408
422
  // reopen, kept (but superseded in display) once resolved. Defaulted so
409
423
  // clients parsing an older server's response don't fail.
410
- acknowledgedAt: z6.string().nullable().default(null),
424
+ acknowledgedAt: z7.string().nullable().default(null),
411
425
  acknowledgedBy: threadActorSchema.nullable().default(null),
412
426
  anchor: anchorSchema,
413
- artifactId: z6.string(),
414
- createdAt: z6.string(),
427
+ artifactId: z7.string(),
428
+ createdAt: z7.string(),
415
429
  createdBy: commentAuthorSchema,
416
- id: z6.string(),
417
- messages: z6.array(commentMessageSchema),
430
+ id: z7.string(),
431
+ messages: z7.array(commentMessageSchema),
418
432
  // Null on threads resolved before actor recording existed, and always
419
433
  // null while open.
420
434
  resolvedBy: threadActorSchema.nullable().default(null),
421
- status: z6.enum(["open", "resolved"]),
422
- versionId: z6.string()
435
+ status: z7.enum(["open", "resolved"]),
436
+ versionId: z7.string()
423
437
  });
424
- var commentThreadListSchema = z6.object({
425
- threads: z6.array(commentThreadSchema)
438
+ var commentThreadListSchema = z7.object({
439
+ threads: z7.array(commentThreadSchema)
426
440
  });
427
- var commentExportQuerySchema = z6.object({
428
- status: z6.enum(["open", "resolved", "all"]).default("open"),
429
- version: z6.enum(["current", "all"]).default("current")
441
+ var commentExportQuerySchema = z7.object({
442
+ status: z7.enum(["open", "resolved", "all"]).default("open"),
443
+ version: z7.enum(["current", "all"]).default("current")
430
444
  });
431
- var exportReactionSchema = z6.object({
432
- count: z6.number().int().positive(),
433
- emoji: z6.string()
445
+ var exportReactionSchema = z7.object({
446
+ count: z7.number().int().positive(),
447
+ emoji: z7.string()
434
448
  });
435
- var commentExportReplySchema = z6.object({
436
- author: z6.string(),
437
- authorKind: z6.enum(["member", "guest"]),
438
- body: z6.string(),
439
- createdAt: z6.string(),
449
+ var commentExportReplySchema = z7.object({
450
+ author: z7.string(),
451
+ authorKind: z7.enum(["member", "guest"]),
452
+ body: z7.string(),
453
+ createdAt: z7.string(),
440
454
  // Soft-delete tombstone (§6.7.1): body is "" when true.
441
- deleted: z6.boolean(),
442
- editedAt: z6.string().nullable(),
455
+ deleted: z7.boolean(),
456
+ editedAt: z7.string().nullable(),
443
457
  // The message id — what `edit`/`delete` target (§5.1).
444
- id: z6.string(),
445
- reactions: z6.array(exportReactionSchema).optional()
458
+ id: z7.string(),
459
+ reactions: z7.array(exportReactionSchema).optional()
446
460
  });
447
- var commentExportThreadSchema = z6.object({
461
+ var commentExportThreadSchema = z7.object({
448
462
  // §6.6.1 — lets a polling agent skip threads it already acknowledged
449
463
  // (acknowledged threads stay in status=open exports on purpose).
450
- acknowledgedAt: z6.string().nullable().default(null),
464
+ acknowledgedAt: z7.string().nullable().default(null),
451
465
  anchor: anchorSchema,
452
- author: z6.string(),
453
- authorKind: z6.enum(["member", "guest"]),
454
- body: z6.string(),
455
- createdAt: z6.string(),
456
- deleted: z6.boolean(),
457
- editedAt: z6.string().nullable(),
458
- id: z6.string(),
466
+ author: z7.string(),
467
+ authorKind: z7.enum(["member", "guest"]),
468
+ body: z7.string(),
469
+ createdAt: z7.string(),
470
+ deleted: z7.boolean(),
471
+ editedAt: z7.string().nullable(),
472
+ id: z7.string(),
459
473
  // The opening message's id (distinct from the thread `id`) — `edit`/
460
474
  // `delete` operate on messages, so the CLI needs it to target the first
461
475
  // comment, not just the replies.
462
- messageId: z6.string(),
463
- reactions: z6.array(exportReactionSchema).optional(),
464
- replies: z6.array(commentExportReplySchema),
476
+ messageId: z7.string(),
477
+ reactions: z7.array(exportReactionSchema).optional(),
478
+ replies: z7.array(commentExportReplySchema),
465
479
  resolvedBy: threadActorSchema.nullable().default(null),
466
- status: z6.enum(["open", "resolved"]),
467
- versionNumber: z6.number().int().positive()
480
+ status: z7.enum(["open", "resolved"]),
481
+ versionNumber: z7.number().int().positive()
468
482
  });
469
- var commentExportSchema = z6.object({
470
- artifactId: z6.string(),
471
- comments: z6.array(commentExportThreadSchema),
483
+ var commentExportSchema = z7.object({
484
+ artifactId: z7.string(),
485
+ comments: z7.array(commentExportThreadSchema),
472
486
  // The artifact's current version (null before the first finalize).
473
- versionNumber: z6.number().int().positive().nullable()
487
+ versionNumber: z7.number().int().positive().nullable()
474
488
  });
475
- var commentReadResponseSchema = z6.object({
476
- lastSeenAt: z6.string()
489
+ var commentReadResponseSchema = z7.object({
490
+ lastSeenAt: z7.string()
477
491
  });
478
492
  function elementLabel(el) {
479
493
  if (el.id) {
@@ -483,245 +497,573 @@ function elementLabel(el) {
483
497
  return classes.length ? `<${el.tag}.${classes.join(".")}>` : `<${el.tag}>`;
484
498
  }
485
499
 
500
+ // ../shared/src/data.ts
501
+ var DATA_MANIFEST_PATH = ".artifact/data.json";
502
+ var DATA_NAME_RE = /^[a-z][a-z0-9_]*$/;
503
+ var DATA_NAME_MAX = 64;
504
+ var DATA_RESERVED_FIELDS = /* @__PURE__ */ new Set([
505
+ "id",
506
+ "created_at",
507
+ "updated_at",
508
+ "created_by",
509
+ "mine"
510
+ ]);
511
+ var DATA_LIMITS = {
512
+ artifactRateWindowSeconds: 3600,
513
+ artifactReadHourlyLimit: 1e4,
514
+ artifactWriteHourlyLimit: 1e3,
515
+ defaultReadRate: "120/minute/ip",
516
+ defaultWriteRate: "10/minute/ip",
517
+ maxCollections: 10,
518
+ maxFieldsPerCollection: 50,
519
+ maxListLimit: 200,
520
+ maxManifestBytes: 64 * 1024,
521
+ maxRecordBytes: 16 * 1024,
522
+ maxRecordsPerArtifact: 1e4
523
+ };
524
+ var DATA_ACCESS_DEFAULTS = {
525
+ delete: "owner",
526
+ insert: "owner",
527
+ read: "public",
528
+ update: "owner"
529
+ };
530
+ var DATA_RATE_LIMIT_RE = /^([1-9]\d{0,5})\/(hour|minute)\/ip$/;
531
+ var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
532
+ function validateFieldValue(name, field, value, errors) {
533
+ switch (field.type) {
534
+ case "string": {
535
+ if (typeof value !== "string") {
536
+ errors.push(`${name}: expected a string`);
537
+ return value;
538
+ }
539
+ const s = field.trim ? value.trim() : value;
540
+ if (field.maxLength !== void 0 && s.length > field.maxLength) {
541
+ errors.push(`${name}: longer than maxLength ${field.maxLength}`);
542
+ }
543
+ return s;
544
+ }
545
+ case "number":
546
+ case "integer": {
547
+ if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
548
+ errors.push(`${name}: expected a ${field.type}`);
549
+ return value;
550
+ }
551
+ if (field.type === "integer" && !Number.isInteger(value)) {
552
+ errors.push(`${name}: expected an integer`);
553
+ }
554
+ if (field.minimum !== void 0 && value < field.minimum) {
555
+ errors.push(`${name}: below minimum ${field.minimum}`);
556
+ }
557
+ if (field.maximum !== void 0 && value > field.maximum) {
558
+ errors.push(`${name}: above maximum ${field.maximum}`);
559
+ }
560
+ return value;
561
+ }
562
+ case "boolean": {
563
+ if (typeof value !== "boolean") {
564
+ errors.push(`${name}: expected a boolean`);
565
+ }
566
+ return value;
567
+ }
568
+ case "datetime": {
569
+ if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
570
+ errors.push(`${name}: expected an ISO 8601 datetime string`);
571
+ }
572
+ return value;
573
+ }
574
+ case "url": {
575
+ if (typeof value !== "string" || !canParseUrl(value)) {
576
+ errors.push(`${name}: expected a valid URL`);
577
+ }
578
+ return value;
579
+ }
580
+ case "email": {
581
+ if (typeof value !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
582
+ errors.push(`${name}: expected an email address`);
583
+ }
584
+ return value;
585
+ }
586
+ case "array": {
587
+ if (!Array.isArray(value)) {
588
+ errors.push(`${name}: expected an array`);
589
+ return value;
590
+ }
591
+ if (field.maxLength !== void 0 && value.length > field.maxLength) {
592
+ errors.push(`${name}: more than maxLength ${field.maxLength} items`);
593
+ }
594
+ return value;
595
+ }
596
+ case "object": {
597
+ if (!isPlainObject(value)) {
598
+ errors.push(`${name}: expected an object`);
599
+ }
600
+ return value;
601
+ }
602
+ default: {
603
+ errors.push(`${name}: unknown field type`);
604
+ return value;
605
+ }
606
+ }
607
+ }
608
+ function canParseUrl(value) {
609
+ try {
610
+ return Boolean(new URL(value));
611
+ } catch {
612
+ return false;
613
+ }
614
+ }
615
+ function validateRecordInput(collection, input, opts = {}) {
616
+ if (!isPlainObject(input)) {
617
+ return { errors: ["record body must be a JSON object"], ok: false };
618
+ }
619
+ const errors = [];
620
+ const data2 = {};
621
+ for (const key of Object.keys(input)) {
622
+ if (DATA_RESERVED_FIELDS.has(key)) {
623
+ errors.push(`${key}: reserved field (server-stamped)`);
624
+ continue;
625
+ }
626
+ const field = collection.fields[key];
627
+ if (!(Object.hasOwn(collection.fields, key) && field)) {
628
+ errors.push(`${key}: not a declared field`);
629
+ continue;
630
+ }
631
+ const value = input[key];
632
+ if (value === null || value === void 0) {
633
+ if (!field.required) {
634
+ data2[key] = null;
635
+ } else if (opts.partial) {
636
+ errors.push(`${key}: required (cannot be null)`);
637
+ }
638
+ continue;
639
+ }
640
+ data2[key] = validateFieldValue(key, field, value, errors);
641
+ }
642
+ if (opts.partial) {
643
+ if (Object.keys(data2).length === 0 && errors.length === 0) {
644
+ errors.push("no fields to update");
645
+ }
646
+ } else {
647
+ for (const [name, field] of Object.entries(collection.fields)) {
648
+ const present = data2[name] !== void 0 && data2[name] !== null;
649
+ if (present) {
650
+ continue;
651
+ }
652
+ if (field.default !== void 0) {
653
+ data2[name] = field.default;
654
+ } else if (field.required) {
655
+ errors.push(`${name}: required`);
656
+ }
657
+ }
658
+ }
659
+ if (errors.length > 0) {
660
+ return { errors, ok: false };
661
+ }
662
+ return { data: data2, ok: true };
663
+ }
664
+
665
+ // ../shared/src/data-api.ts
666
+ import { z as z8 } from "zod";
667
+ var dataRecordSchema = z8.object({
668
+ created_at: z8.string(),
669
+ created_by: z8.string().nullable(),
670
+ id: z8.string(),
671
+ updated_at: z8.string()
672
+ }).catchall(z8.unknown());
673
+ var dataPublicRecordSchema = z8.object({
674
+ created_at: z8.string(),
675
+ created_by: z8.never().optional(),
676
+ id: z8.string(),
677
+ mine: z8.boolean(),
678
+ updated_at: z8.string()
679
+ }).catchall(z8.unknown());
680
+ var dataPublicListResponseSchema = z8.object({
681
+ nextCursor: z8.string().nullable(),
682
+ records: z8.array(dataPublicRecordSchema)
683
+ });
684
+ var dataListResponseSchema = z8.object({
685
+ nextCursor: z8.string().nullable(),
686
+ records: z8.array(dataRecordSchema)
687
+ });
688
+ var dataCollectionSummarySchema = z8.object({
689
+ access: z8.record(z8.string(), z8.string()).optional(),
690
+ count: z8.number().int().nonnegative().optional(),
691
+ name: z8.string(),
692
+ rateLimit: z8.string().optional()
693
+ });
694
+ var dataSummaryResponseSchema = z8.object({
695
+ collections: z8.array(dataCollectionSummarySchema),
696
+ enabled: z8.boolean()
697
+ });
698
+ var dataClearResponseSchema = z8.object({
699
+ cleared: z8.number().int().nonnegative()
700
+ });
701
+
702
+ // ../shared/src/data-manifest.ts
703
+ import { z as z9 } from "zod";
704
+ var nameSchema = z9.string().max(DATA_NAME_MAX).regex(DATA_NAME_RE, "must match ^[a-z][a-z0-9_]*$");
705
+ var fieldTypeSchema = z9.enum([
706
+ "string",
707
+ "number",
708
+ "integer",
709
+ "boolean",
710
+ "datetime",
711
+ "url",
712
+ "email",
713
+ "array",
714
+ "object"
715
+ ]);
716
+ var fieldSchema = z9.strictObject({
717
+ default: z9.unknown().optional(),
718
+ maximum: z9.number().optional(),
719
+ maxLength: z9.number().int().positive().optional(),
720
+ minimum: z9.number().optional(),
721
+ required: z9.boolean().optional(),
722
+ trim: z9.boolean().optional(),
723
+ type: fieldTypeSchema
724
+ }).superRefine((field, ctx) => {
725
+ if (field.default === void 0) {
726
+ return;
727
+ }
728
+ if (!defaultMatchesType(field)) {
729
+ ctx.addIssue({
730
+ code: "custom",
731
+ message: `default value does not match type "${field.type}"`,
732
+ path: ["default"]
733
+ });
734
+ }
735
+ });
736
+ function defaultMatchesType(field) {
737
+ if (field.required && field.default === null) {
738
+ return false;
739
+ }
740
+ return validateRecordInput(
741
+ {
742
+ access: DATA_ACCESS_DEFAULTS,
743
+ fields: { value: { ...field, default: void 0 } }
744
+ },
745
+ { value: field.default }
746
+ ).ok;
747
+ }
748
+ var accessLevelSchema = z9.enum(["public", "creator", "owner", "none"]);
749
+ var accessSchema = z9.strictObject({
750
+ delete: accessLevelSchema.default("owner"),
751
+ insert: accessLevelSchema.default("owner"),
752
+ read: accessLevelSchema.default("public"),
753
+ update: accessLevelSchema.default("owner")
754
+ }).prefault({});
755
+ var collectionSchema = z9.strictObject({
756
+ access: accessSchema,
757
+ fields: z9.record(nameSchema, fieldSchema),
758
+ publicMutation: z9.literal("open").optional(),
759
+ rateLimit: z9.string().regex(DATA_RATE_LIMIT_RE, 'expected "N/hour/ip" or "N/minute/ip"').optional()
760
+ }).superRefine((collection, ctx) => {
761
+ const fieldNames = Object.keys(collection.fields);
762
+ if (fieldNames.length > DATA_LIMITS.maxFieldsPerCollection) {
763
+ ctx.addIssue({
764
+ code: "custom",
765
+ message: `at most ${DATA_LIMITS.maxFieldsPerCollection} fields per collection`,
766
+ path: ["fields"]
767
+ });
768
+ }
769
+ for (const name of fieldNames) {
770
+ if (DATA_RESERVED_FIELDS.has(name)) {
771
+ ctx.addIssue({
772
+ code: "custom",
773
+ message: `"${name}" is reserved (server-stamped)`,
774
+ path: ["fields", name]
775
+ });
776
+ }
777
+ }
778
+ for (const action of ["update", "delete"]) {
779
+ if (collection.access[action] === "public" && collection.publicMutation !== "open") {
780
+ ctx.addIssue({
781
+ code: "custom",
782
+ message: `access.${action} may only be "public" alongside "publicMutation": "open"`,
783
+ path: ["access", action]
784
+ });
785
+ }
786
+ }
787
+ });
788
+ var dataManifestSchema = z9.strictObject({
789
+ collections: z9.record(nameSchema, collectionSchema)
790
+ }).superRefine((manifest, ctx) => {
791
+ if (Object.keys(manifest.collections).length > DATA_LIMITS.maxCollections) {
792
+ ctx.addIssue({
793
+ code: "custom",
794
+ message: `at most ${DATA_LIMITS.maxCollections} collections`,
795
+ path: ["collections"]
796
+ });
797
+ }
798
+ });
799
+ function parseDataManifest(text) {
800
+ if (new TextEncoder().encode(text).length > DATA_LIMITS.maxManifestBytes) {
801
+ return {
802
+ errors: [`manifest larger than ${DATA_LIMITS.maxManifestBytes} bytes`],
803
+ ok: false
804
+ };
805
+ }
806
+ let raw;
807
+ try {
808
+ raw = JSON.parse(text);
809
+ } catch (err) {
810
+ return {
811
+ errors: [`not valid JSON: ${err instanceof Error ? err.message : err}`],
812
+ ok: false
813
+ };
814
+ }
815
+ const parsed = dataManifestSchema.safeParse(raw);
816
+ if (!parsed.success) {
817
+ return {
818
+ errors: parsed.error.issues.map(
819
+ (issue) => issue.path.length ? `${issue.path.join(".")}: ${issue.message}` : issue.message
820
+ ),
821
+ ok: false
822
+ };
823
+ }
824
+ return { config: parsed.data, ok: true };
825
+ }
826
+
486
827
  // ../shared/src/errors.ts
487
- import { z as z7 } from "zod";
488
- var apiErrorSchema = z7.object({
489
- code: z7.string(),
490
- docsUrl: z7.string().url(),
491
- message: z7.string(),
492
- retryAfter: z7.number().int().positive().optional()
828
+ import { z as z10 } from "zod";
829
+ var apiErrorSchema = z10.object({
830
+ code: z10.string(),
831
+ docsUrl: z10.string().url(),
832
+ message: z10.string(),
833
+ retryAfter: z10.number().int().positive().optional(),
834
+ scope: z10.enum(["ip", "artifact"]).optional()
493
835
  });
494
836
 
495
837
  // ../shared/src/feedback.ts
496
- import { z as z8 } from "zod";
497
- var feedbackRequestSchema = z8.object({
498
- email: z8.string().trim().toLowerCase().email().optional().or(z8.literal("")),
499
- message: z8.string().trim().min(1).max(4e3)
838
+ import { z as z11 } from "zod";
839
+ var feedbackRequestSchema = z11.object({
840
+ email: z11.string().trim().toLowerCase().email().optional().or(z11.literal("")),
841
+ message: z11.string().trim().min(1).max(4e3)
500
842
  });
501
- var feedbackResponseSchema = z8.object({
502
- sent: z8.literal(true)
843
+ var feedbackResponseSchema = z11.object({
844
+ sent: z11.literal(true)
503
845
  });
504
846
 
505
847
  // ../shared/src/grants.ts
506
- import { z as z9 } from "zod";
507
- var grantRoleSchema = z9.enum(["viewer"]);
508
- var createGrantSchema = z9.object({
509
- email: z9.string().trim().toLowerCase().email().optional(),
848
+ import { z as z12 } from "zod";
849
+ var grantRoleSchema = z12.enum(["viewer"]);
850
+ var createGrantSchema = z12.object({
851
+ email: z12.string().trim().toLowerCase().email().optional(),
510
852
  role: grantRoleSchema.default("viewer"),
511
- userId: z9.string().min(1).optional()
853
+ userId: z12.string().min(1).optional()
512
854
  }).refine((body) => Boolean(body.email) !== Boolean(body.userId), {
513
855
  message: "provide exactly one of email or userId"
514
856
  });
515
- var grantSchema = z9.object({
516
- createdAt: z9.string(),
517
- email: z9.string().email().nullable(),
518
- expiresAt: z9.string().nullable(),
519
- id: z9.string(),
857
+ var grantSchema = z12.object({
858
+ createdAt: z12.string(),
859
+ email: z12.string().email().nullable(),
860
+ expiresAt: z12.string().nullable(),
861
+ id: z12.string(),
520
862
  // The subject's avatar once they have an account; null otherwise.
521
- image: z9.string().nullable(),
863
+ image: z12.string().nullable(),
522
864
  // Present on the invited account after redemption / when granting an
523
865
  // existing user; null for a still-pending email invite.
524
- name: z9.string().nullable(),
525
- redeemedAt: z9.string().nullable(),
866
+ name: z12.string().nullable(),
867
+ redeemedAt: z12.string().nullable(),
526
868
  role: grantRoleSchema,
527
869
  // Shown as "· pending" beside the role until the link is redeemed.
528
- status: z9.enum(["pending", "active"]),
870
+ status: z12.enum(["pending", "active"]),
529
871
  // Display fragment of the live invitation token, or null once redeemed.
530
- tokenPrefix: z9.string().nullable(),
531
- userId: z9.string().nullable()
872
+ tokenPrefix: z12.string().nullable(),
873
+ userId: z12.string().nullable()
532
874
  });
533
875
  var createdGrantSchema = grantSchema.extend({
534
- url: z9.string().url()
876
+ url: z12.string().url()
535
877
  });
536
- var grantListResponseSchema = z9.object({
537
- grants: z9.array(grantSchema),
878
+ var grantListResponseSchema = z12.object({
879
+ grants: z12.array(grantSchema),
538
880
  // The artifact's current general-access value, so the share surface can
539
881
  // render the "General access" line without a second read.
540
- visibility: z9.enum(["public", "unlisted", "workspace", "private"]),
882
+ visibility: z12.enum(["public", "unlisted", "workspace", "private"]),
541
883
  // The owning workspace's logo, so the share modal can draw the workspace
542
884
  // itself rather than a generic glyph. Null when none was uploaded.
543
- workspaceLogo: z9.string().nullable().optional(),
885
+ workspaceLogo: z12.string().nullable().optional(),
544
886
  // The owning workspace's display name, so "People with access" can name the
545
887
  // audience `workspace` visibility actually admits ("All Acme members") rather
546
888
  // than leaving an empty list to imply nobody. Null for a workspace-less
547
889
  // artifact; optional so an older server still parses.
548
- workspaceName: z9.string().nullable().optional()
890
+ workspaceName: z12.string().nullable().optional()
549
891
  });
550
- var revokeGrantResponseSchema = z9.object({
551
- revoked: z9.literal(true)
892
+ var revokeGrantResponseSchema = z12.object({
893
+ revoked: z12.literal(true)
552
894
  });
553
- var acceptGrantSchema = z9.object({
554
- token: z9.string().min(1)
895
+ var acceptGrantSchema = z12.object({
896
+ token: z12.string().min(1)
555
897
  });
556
- var acceptGrantResponseSchema = z9.object({
557
- artifactId: z9.string(),
898
+ var acceptGrantResponseSchema = z12.object({
899
+ artifactId: z12.string(),
558
900
  // The review shell URL to land on, carrying ?invited=1 for the banner.
559
- artifactUrl: z9.string().url(),
560
- slug: z9.string(),
561
- title: z9.string()
562
- });
563
- var workspaceGuestSchema = z9.object({
564
- artifacts: z9.array(
565
- z9.object({
566
- artifactId: z9.string(),
567
- grantId: z9.string(),
568
- slug: z9.string(),
569
- status: z9.enum(["pending", "active"]),
570
- title: z9.string()
901
+ artifactUrl: z12.string().url(),
902
+ slug: z12.string(),
903
+ title: z12.string()
904
+ });
905
+ var workspaceGuestSchema = z12.object({
906
+ artifacts: z12.array(
907
+ z12.object({
908
+ artifactId: z12.string(),
909
+ grantId: z12.string(),
910
+ slug: z12.string(),
911
+ status: z12.enum(["pending", "active"]),
912
+ title: z12.string()
571
913
  })
572
914
  ),
573
- email: z9.string().nullable(),
574
- image: z9.string().nullable(),
575
- name: z9.string().nullable(),
576
- userId: z9.string().nullable()
577
- });
578
- var workspaceGuestListResponseSchema = z9.object({
579
- guests: z9.array(workspaceGuestSchema)
580
- });
581
- var sharedWithMeArtifactSchema = z9.object({
582
- artifactId: z9.string(),
583
- slug: z9.string(),
584
- title: z9.string(),
585
- url: z9.string().url(),
915
+ email: z12.string().nullable(),
916
+ image: z12.string().nullable(),
917
+ name: z12.string().nullable(),
918
+ userId: z12.string().nullable()
919
+ });
920
+ var workspaceGuestListResponseSchema = z12.object({
921
+ guests: z12.array(workspaceGuestSchema)
922
+ });
923
+ var sharedWithMeArtifactSchema = z12.object({
924
+ artifactId: z12.string(),
925
+ slug: z12.string(),
926
+ title: z12.string(),
927
+ url: z12.string().url(),
586
928
  // Who shared it, for the "Shared with me" list.
587
- workspaceName: z9.string()
929
+ workspaceName: z12.string()
588
930
  });
589
- var sharedWithMeListResponseSchema = z9.object({
590
- artifacts: z9.array(sharedWithMeArtifactSchema)
931
+ var sharedWithMeListResponseSchema = z12.object({
932
+ artifacts: z12.array(sharedWithMeArtifactSchema)
591
933
  });
592
934
 
593
935
  // ../shared/src/html-meta.ts
594
936
  var HEAD_SCAN_CHARS = 64 * 1024;
595
937
 
596
938
  // ../shared/src/me.ts
597
- import { z as z11 } from "zod";
939
+ import { z as z14 } from "zod";
598
940
 
599
941
  // ../shared/src/workspaces.ts
600
- import { z as z10 } from "zod";
942
+ import { z as z13 } from "zod";
601
943
  var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/;
602
- var workspaceHandleUnavailableReasonSchema = z10.enum([
944
+ var workspaceHandleUnavailableReasonSchema = z13.enum([
603
945
  "held",
604
946
  "invalid",
605
947
  "reserved",
606
948
  "taken"
607
949
  ]);
608
- var workspaceHandleAvailabilitySchema = z10.object({
609
- available: z10.boolean(),
610
- handle: z10.string(),
950
+ var workspaceHandleAvailabilitySchema = z13.object({
951
+ available: z13.boolean(),
952
+ handle: z13.string(),
611
953
  // Human-readable sentence for the field hint; null when available.
612
- message: z10.string().nullable(),
954
+ message: z13.string().nullable(),
613
955
  reason: workspaceHandleUnavailableReasonSchema.nullable()
614
956
  });
615
- var workspaceRoleSchema = z10.enum(["owner", "admin", "member"]);
616
- var workspaceInviteRoleSchema = z10.enum(["admin", "member"]);
617
- var workspaceHandleSchema = z10.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
957
+ var workspaceRoleSchema = z13.enum(["owner", "admin", "member"]);
958
+ var workspaceInviteRoleSchema = z13.enum(["admin", "member"]);
959
+ var workspaceHandleSchema = z13.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
618
960
  message: "handle must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
619
961
  });
620
- var createWorkspaceSchema = z10.object({
962
+ var createWorkspaceSchema = z13.object({
621
963
  handle: workspaceHandleSchema.optional(),
622
- name: z10.string().trim().min(1).max(100)
964
+ name: z13.string().trim().min(1).max(100)
623
965
  });
624
- var updateWorkspaceSchema = z10.object({
966
+ var updateWorkspaceSchema = z13.object({
625
967
  handle: workspaceHandleSchema.optional(),
626
- name: z10.string().trim().min(1).max(100).optional()
968
+ name: z13.string().trim().min(1).max(100).optional()
627
969
  }).refine((body) => body.handle !== void 0 || body.name !== void 0, {
628
970
  message: "provide at least one of name or handle"
629
971
  });
630
- var workspaceSchema = z10.object({
631
- createdAt: z10.string(),
632
- handle: z10.string(),
972
+ var workspaceSchema = z13.object({
973
+ createdAt: z13.string(),
974
+ handle: z13.string(),
633
975
  // ISO timestamp when `{handle}.artifact.cafe` was provisioned, or null.
634
976
  // Set by the one-time, self-serve enable flow (§4.4); never auto at
635
977
  // create, and always null for personal workspaces.
636
- hostnameProvisionedAt: z10.string().nullable(),
637
- id: z10.string(),
978
+ hostnameProvisionedAt: z13.string().nullable(),
979
+ id: z13.string(),
638
980
  // Auto-provisioned solo workspace, one per user: non-deletable, takes
639
981
  // no members, renameable like any workspace.
640
- isPersonal: z10.boolean(),
982
+ isPersonal: z13.boolean(),
641
983
  // Uploaded logo URL, or null for the initials fallback. Set through the
642
984
  // dedicated /workspaces/:id/avatar route, never the generic update.
643
- logo: z10.string().nullable(),
644
- name: z10.string(),
985
+ logo: z13.string().nullable(),
986
+ name: z13.string(),
645
987
  role: workspaceRoleSchema.nullable(),
646
- updatedAt: z10.string()
988
+ updatedAt: z13.string()
647
989
  });
648
- var workspaceListResponseSchema = z10.object({
649
- workspaces: z10.array(workspaceSchema)
990
+ var workspaceListResponseSchema = z13.object({
991
+ workspaces: z13.array(workspaceSchema)
650
992
  });
651
- var deleteWorkspaceResponseSchema = z10.object({
652
- deleted: z10.literal(true)
993
+ var deleteWorkspaceResponseSchema = z13.object({
994
+ deleted: z13.literal(true)
653
995
  });
654
- var workspaceMemberSchema = z10.object({
655
- createdAt: z10.string(),
656
- email: z10.string().email(),
657
- image: z10.string().nullable(),
658
- name: z10.string(),
996
+ var workspaceMemberSchema = z13.object({
997
+ createdAt: z13.string(),
998
+ email: z13.string().email(),
999
+ image: z13.string().nullable(),
1000
+ name: z13.string(),
659
1001
  role: workspaceRoleSchema,
660
- userId: z10.string()
1002
+ userId: z13.string()
661
1003
  });
662
- var workspaceMemberListResponseSchema = z10.object({
663
- members: z10.array(workspaceMemberSchema)
1004
+ var workspaceMemberListResponseSchema = z13.object({
1005
+ members: z13.array(workspaceMemberSchema)
664
1006
  });
665
- var updateWorkspaceMemberSchema = z10.object({
1007
+ var updateWorkspaceMemberSchema = z13.object({
666
1008
  role: workspaceRoleSchema
667
1009
  });
668
- var removeWorkspaceMemberResponseSchema = z10.object({
669
- removed: z10.literal(true)
1010
+ var removeWorkspaceMemberResponseSchema = z13.object({
1011
+ removed: z13.literal(true)
670
1012
  });
671
- var createWorkspaceInviteSchema = z10.object({
672
- email: z10.string().trim().toLowerCase().email().optional(),
673
- expiresAt: z10.string().datetime(),
674
- maxUses: z10.number().int().positive().optional(),
1013
+ var createWorkspaceInviteSchema = z13.object({
1014
+ email: z13.string().trim().toLowerCase().email().optional(),
1015
+ expiresAt: z13.string().datetime(),
1016
+ maxUses: z13.number().int().positive().optional(),
675
1017
  role: workspaceInviteRoleSchema
676
1018
  });
677
- var workspaceInviteSchema = z10.object({
678
- createdAt: z10.string(),
679
- email: z10.string().email().nullable(),
680
- expiresAt: z10.string(),
681
- id: z10.string(),
682
- maxUses: z10.number().int().positive().nullable(),
1019
+ var workspaceInviteSchema = z13.object({
1020
+ createdAt: z13.string(),
1021
+ email: z13.string().email().nullable(),
1022
+ expiresAt: z13.string(),
1023
+ id: z13.string(),
1024
+ maxUses: z13.number().int().positive().nullable(),
683
1025
  role: workspaceInviteRoleSchema,
684
- tokenPrefix: z10.string(),
685
- useCount: z10.number().int().nonnegative()
1026
+ tokenPrefix: z13.string(),
1027
+ useCount: z13.number().int().nonnegative()
686
1028
  });
687
1029
  var createdWorkspaceInviteSchema = workspaceInviteSchema.extend({
688
- url: z10.string().url()
1030
+ url: z13.string().url()
689
1031
  });
690
- var workspaceInviteListResponseSchema = z10.object({
691
- invites: z10.array(workspaceInviteSchema)
1032
+ var workspaceInviteListResponseSchema = z13.object({
1033
+ invites: z13.array(workspaceInviteSchema)
692
1034
  });
693
- var revokeWorkspaceInviteResponseSchema = z10.object({
694
- revoked: z10.literal(true)
1035
+ var revokeWorkspaceInviteResponseSchema = z13.object({
1036
+ revoked: z13.literal(true)
695
1037
  });
696
- var acceptWorkspaceInviteSchema = z10.object({
697
- token: z10.string().min(1)
1038
+ var acceptWorkspaceInviteSchema = z13.object({
1039
+ token: z13.string().min(1)
698
1040
  });
699
- var acceptWorkspaceInviteResponseSchema = z10.object({
1041
+ var acceptWorkspaceInviteResponseSchema = z13.object({
700
1042
  role: workspaceRoleSchema,
701
1043
  workspace: workspaceSchema
702
1044
  });
703
- var folderVisibilitySchema = z10.enum(["public", "workspace"]);
704
- var artifactVisibilitySchema = z10.enum([
1045
+ var folderVisibilitySchema = z13.enum(["public", "workspace"]);
1046
+ var artifactVisibilitySchema = z13.enum([
705
1047
  "public",
706
1048
  "unlisted",
707
1049
  "workspace",
708
1050
  "private"
709
1051
  ]);
710
- var createFolderSchema = z10.object({
1052
+ var createFolderSchema = z13.object({
711
1053
  // Optional custom icon; omitted (or null) leaves the default glyph.
712
1054
  emoji: iconEmojiSchema.optional(),
713
- name: z10.string().trim().min(1).max(100),
1055
+ name: z13.string().trim().min(1).max(100),
714
1056
  visibility: folderVisibilitySchema
715
1057
  });
716
- var updateFolderSchema = z10.object({
1058
+ var updateFolderSchema = z13.object({
717
1059
  // With `visibility`: bulk-update the folder's artifacts to match
718
1060
  // (SPEC-2 §3.4 "Changing a folder's visibility offers a bulk update").
719
- cascade: z10.boolean().optional(),
1061
+ cascade: z13.boolean().optional(),
720
1062
  // Explicit null clears the icon back to the default glyph, so this is
721
1063
  // a field where "sent as null" and "omitted" mean different things.
722
1064
  emoji: iconEmojiSchema.optional(),
723
- name: z10.string().trim().min(1).max(100).optional(),
724
- sortOrder: z10.number().int().min(0).optional(),
1065
+ name: z13.string().trim().min(1).max(100).optional(),
1066
+ sortOrder: z13.number().int().min(0).optional(),
725
1067
  visibility: folderVisibilitySchema.optional()
726
1068
  }).refine(
727
1069
  (body) => body.name !== void 0 || body.visibility !== void 0 || body.sortOrder !== void 0 || body.emoji !== void 0,
@@ -729,122 +1071,139 @@ var updateFolderSchema = z10.object({
729
1071
  ).refine((body) => !body.cascade || body.visibility !== void 0, {
730
1072
  message: "cascade requires visibility"
731
1073
  });
732
- var folderSchema = z10.object({
733
- createdAt: z10.string(),
1074
+ var folderSchema = z13.object({
1075
+ createdAt: z13.string(),
734
1076
  // Custom icon, or null to render the default folder glyph.
735
- emoji: z10.string().nullable(),
736
- id: z10.string(),
737
- name: z10.string(),
1077
+ emoji: z13.string().nullable(),
1078
+ id: z13.string(),
1079
+ name: z13.string(),
738
1080
  // Permalink segment for the public folder page (/w/{handle}/f/{slug}).
739
1081
  // Server-derived from the name; renames regenerate it.
740
- slug: z10.string(),
741
- sortOrder: z10.number().int(),
742
- updatedAt: z10.string(),
1082
+ slug: z13.string(),
1083
+ sortOrder: z13.number().int(),
1084
+ updatedAt: z13.string(),
743
1085
  visibility: folderVisibilitySchema,
744
- workspaceId: z10.string()
1086
+ workspaceId: z13.string()
745
1087
  });
746
- var folderListResponseSchema = z10.object({
747
- folders: z10.array(folderSchema)
1088
+ var folderListResponseSchema = z13.object({
1089
+ folders: z13.array(folderSchema)
748
1090
  });
749
- var deleteFolderResponseSchema = z10.object({
750
- deleted: z10.literal(true)
1091
+ var deleteFolderResponseSchema = z13.object({
1092
+ deleted: z13.literal(true)
751
1093
  });
752
- var workspaceArtifactSchema = z10.object({
753
- createdAt: z10.string(),
754
- currentVersionNumber: z10.number().int().nullable(),
1094
+ var workspaceArtifactSchema = z13.object({
1095
+ createdAt: z13.string(),
1096
+ currentVersionNumber: z13.number().int().nullable(),
755
1097
  // Custom icon, or null to render the default document glyph.
756
- emoji: z10.string().nullable(),
757
- folderId: z10.string().nullable(),
758
- id: z10.string(),
759
- openCommentCount: z10.number().int().nonnegative(),
1098
+ emoji: z13.string().nullable(),
1099
+ folderId: z13.string().nullable(),
1100
+ id: z13.string(),
1101
+ openCommentCount: z13.number().int().nonnegative(),
760
1102
  reviewMode: reviewModeSchema,
761
- slug: z10.string(),
762
- title: z10.string(),
763
- updatedAt: z10.string(),
764
- url: z10.string().url(),
765
- versionCount: z10.number().int().nonnegative(),
1103
+ slug: z13.string(),
1104
+ title: z13.string(),
1105
+ updatedAt: z13.string(),
1106
+ url: z13.string().url(),
1107
+ versionCount: z13.number().int().nonnegative(),
766
1108
  visibility: artifactVisibilitySchema
767
1109
  });
768
- var workspaceArtifactListResponseSchema = z10.object({
769
- artifacts: z10.array(workspaceArtifactSchema)
1110
+ var workspaceArtifactListResponseSchema = z13.object({
1111
+ artifacts: z13.array(workspaceArtifactSchema)
770
1112
  });
771
- var workspaceArtifactSearchResultSchema = z10.object({
772
- id: z10.string(),
773
- slug: z10.string(),
774
- title: z10.string()
1113
+ var workspaceArtifactSearchResultSchema = z13.object({
1114
+ id: z13.string(),
1115
+ slug: z13.string(),
1116
+ title: z13.string()
775
1117
  });
776
- var workspaceArtifactSearchResponseSchema = z10.object({
777
- results: z10.array(workspaceArtifactSearchResultSchema)
1118
+ var workspaceArtifactSearchResponseSchema = z13.object({
1119
+ results: z13.array(workspaceArtifactSearchResultSchema)
778
1120
  });
779
1121
 
780
1122
  // ../shared/src/me.ts
781
- var ownArtifactSchema = z11.object({
782
- createdAt: z11.string(),
783
- currentVersionNumber: z11.number().nullable(),
1123
+ var personSchema = z14.object({
1124
+ image: z14.string().nullable(),
1125
+ name: z14.string()
1126
+ });
1127
+ var ownArtifactSchema = z14.object({
1128
+ // Everyone else who has published a version, oldest first. Any member may
1129
+ // publish a new one (SPEC-2 §5.1), so an artifact's byline is often more
1130
+ // than one person and the card stacks their faces rather than crediting
1131
+ // whoever shipped last. Excludes `publishedBy`; empty on a solo artifact.
1132
+ coPublishers: z14.array(personSchema),
1133
+ createdAt: z14.string(),
1134
+ // SPEC-2 §5.1 — who *made* the artifact: the author of its first
1135
+ // finalized version, the same row `viewerCanManage` is decided on. Not
1136
+ // the same person as `publishedBy` once an agent ships v2, which is why
1137
+ // the dashboard filters on both. Null for a token-only publish.
1138
+ createdBy: z14.object({ image: z14.string().nullable(), name: z14.string() }).nullable(),
1139
+ currentVersionNumber: z14.number().nullable(),
784
1140
  // Custom icon, or null to render the default document glyph.
785
- emoji: z11.string().nullable(),
1141
+ emoji: z14.string().nullable(),
786
1142
  // Owned artifacts normally never expire; non-null only while an
787
1143
  // expiry is still set (e.g. an unclaimed publish surfaced here later).
788
- expiresAt: z11.string().nullable(),
1144
+ expiresAt: z14.string().nullable(),
789
1145
  // SPEC-2 §8.4 — the containing folder for workspace-owned artifacts;
790
1146
  // both null for personal artifacts. `folderId` drives the dashboard's
791
1147
  // move control and folder filter; `folderName` is the display label,
792
1148
  // `folderEmoji` its icon (null = the default folder glyph).
793
- folderEmoji: z11.string().nullable(),
794
- folderId: z11.string().nullable(),
795
- folderName: z11.string().nullable(),
1149
+ folderEmoji: z14.string().nullable(),
1150
+ folderId: z14.string().nullable(),
1151
+ folderName: z14.string().nullable(),
796
1152
  // Whether a publish token is still active — revocable from the
797
1153
  // dashboard (§6.18); the token itself is never returned.
798
- hasPublishToken: z11.boolean(),
799
- id: z11.string(),
800
- openThreads: z11.number(),
801
- protected: z11.boolean(),
1154
+ hasPublishToken: z14.boolean(),
1155
+ // Owner preference: hide the version list from reviewers in the review
1156
+ // shell. Managers still see it.
1157
+ hideVersions: z14.boolean(),
1158
+ id: z14.string(),
1159
+ openThreads: z14.number(),
1160
+ protected: z14.boolean(),
802
1161
  // Who published the current version (falling back to the claiming
803
1162
  // owner) — the dashboard shows a name and avatar per artifact so a
804
1163
  // shared workspace reads as people's work, not anonymous rows. Null
805
1164
  // when the publish was token-only and the artifact is unclaimed.
806
- publishedBy: z11.object({ image: z11.string().nullable(), name: z11.string() }).nullable(),
1165
+ publishedBy: personSchema.nullable(),
807
1166
  // SPEC-2 §5.8 — the review dial, so the dashboard's settings dialog can
808
1167
  // render current state without a second fetch.
809
1168
  reviewMode: reviewModeSchema,
810
- slug: z11.string(),
811
- title: z11.string(),
812
- totalViews: z11.number(),
1169
+ slug: z14.string(),
1170
+ title: z14.string(),
1171
+ totalViews: z14.number(),
813
1172
  // SPEC §6.20 — messages by others newer than the viewer's last-seen
814
1173
  // watermark. 0 for signed-out/API-key-less contexts.
815
- unreadComments: z11.number(),
816
- updatedAt: z11.string(),
817
- url: z11.string(),
818
- versionCount: z11.number(),
1174
+ unreadComments: z14.number(),
1175
+ updatedAt: z14.string(),
1176
+ url: z14.string(),
1177
+ versionCount: z14.number(),
819
1178
  // SPEC-2 §5.1 — whether the caller clears the manage bar for this
820
1179
  // artifact (workspace admin, or the person who first published it).
821
1180
  // Only a manager's folder move inherits the folder's visibility, so the
822
1181
  // dashboard warns exactly the people the change can actually affect, and
823
1182
  // only a manager is offered the Share entry (§5.7).
824
- viewerCanManage: z11.boolean(),
1183
+ viewerCanManage: z14.boolean(),
825
1184
  // SPEC-2 §5.2 — the artifact's own visibility, the single enforcement
826
1185
  // source. The dashboard needs it to tell whether a folder move would
827
1186
  // widen exposure before it sends the PATCH (§3.4), and to render the
828
1187
  // audience glyph — one per level — on rows and cards (§5.7).
829
1188
  visibility: artifactVisibilitySchema
830
1189
  });
831
- var ownArtifactsResponseSchema = z11.object({
832
- artifacts: z11.array(ownArtifactSchema)
1190
+ var ownArtifactsResponseSchema = z14.object({
1191
+ artifacts: z14.array(ownArtifactSchema)
833
1192
  });
834
- var revokePublishTokenResponseSchema = z11.object({
835
- revoked: z11.boolean()
1193
+ var revokePublishTokenResponseSchema = z14.object({
1194
+ revoked: z14.boolean()
836
1195
  });
837
1196
 
838
1197
  // ../shared/src/owner.ts
839
- import { z as z12 } from "zod";
1198
+ import { z as z15 } from "zod";
840
1199
  var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
841
- var claimResponseSchema = z12.object({
842
- artifactId: z12.string(),
843
- slug: z12.string(),
844
- title: z12.string(),
845
- url: z12.string().url()
1200
+ var claimResponseSchema = z15.object({
1201
+ artifactId: z15.string(),
1202
+ slug: z15.string(),
1203
+ title: z15.string(),
1204
+ url: z15.string().url()
846
1205
  });
847
- var updateArtifactSchema = z12.object({
1206
+ var updateArtifactSchema = z15.object({
848
1207
  // Custom icon; explicit null clears it back to the default glyph.
849
1208
  // Part of what the artifact *is* (like the title), so it takes the
850
1209
  // same manage rights as title/slug — not the member-level folder move.
@@ -852,150 +1211,159 @@ var updateArtifactSchema = z12.object({
852
1211
  // SPEC-2 §6.4 — placement + visibility. `folderId: null` clears the
853
1212
  // folder; `workspaceId: null` moves the artifact out of its workspace
854
1213
  // (§5.5 ownership transfer).
855
- folderId: z12.string().min(1).nullable().optional(),
856
- slug: z12.string().min(3).max(63).regex(SLUG_PATTERN, {
1214
+ folderId: z15.string().min(1).nullable().optional(),
1215
+ // Keep the version list out of the review shell. Presentation only:
1216
+ // versions stay immutable, and a `/vN/` URL still resolves.
1217
+ hideVersions: z15.boolean().optional(),
1218
+ slug: z15.string().min(3).max(63).regex(SLUG_PATTERN, {
857
1219
  message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
858
1220
  }).optional(),
859
- title: z12.string().min(1).max(200).optional(),
1221
+ title: z15.string().min(1).max(200).optional(),
860
1222
  visibility: artifactVisibilitySchema.optional(),
861
- workspaceId: z12.string().min(1).nullable().optional()
1223
+ workspaceId: z15.string().min(1).nullable().optional()
862
1224
  }).refine(
863
- (body) => body.title !== void 0 || body.slug !== void 0 || body.visibility !== void 0 || body.folderId !== void 0 || body.workspaceId !== void 0 || body.emoji !== void 0,
1225
+ (body) => body.title !== void 0 || body.slug !== void 0 || body.visibility !== void 0 || body.folderId !== void 0 || body.workspaceId !== void 0 || body.emoji !== void 0 || body.hideVersions !== void 0,
864
1226
  {
865
- message: "provide at least one of title, slug, visibility, folderId, workspaceId, emoji"
1227
+ message: "provide at least one of title, slug, visibility, folderId, workspaceId, emoji, hideVersions"
866
1228
  }
867
1229
  );
868
- var deleteArtifactResponseSchema = z12.object({
869
- deleted: z12.literal(true)
1230
+ var deleteArtifactResponseSchema = z15.object({
1231
+ deleted: z15.literal(true)
870
1232
  });
871
1233
 
872
1234
  // ../shared/src/presence.ts
873
- import { z as z13 } from "zod";
1235
+ import { z as z16 } from "zod";
874
1236
  var AGENT_STATES = ["listening", "editing", "publishing"];
875
- var agentStateSchema = z13.enum(AGENT_STATES);
1237
+ var agentStateSchema = z16.enum(AGENT_STATES);
876
1238
  var AGENT_PRESENCE = {
877
1239
  editingTtlSeconds: 300,
878
1240
  heartbeatSeconds: 15,
879
1241
  shellPollSeconds: 15,
880
1242
  ttlSeconds: 45
881
1243
  };
882
- var agentHeartbeatBodySchema = z13.object({
1244
+ var agentHeartbeatBodySchema = z16.object({
883
1245
  state: agentStateSchema.optional()
884
1246
  });
885
- var agentHeartbeatResponseSchema = z13.object({
886
- listening: z13.literal(true),
1247
+ var agentHeartbeatResponseSchema = z16.object({
1248
+ listening: z16.literal(true),
887
1249
  state: agentStateSchema.optional(),
888
- ttlSeconds: z13.number().int().positive()
1250
+ ttlSeconds: z16.number().int().positive()
889
1251
  });
890
- var agentPresenceSchema = z13.object({
891
- agent: z13.object({ id: z13.string().nullable(), label: z13.string() }).nullable().default(null),
892
- listening: z13.boolean(),
1252
+ var agentPresenceSchema = z16.object({
1253
+ agent: z16.object({ id: z16.string().nullable(), label: z16.string() }).nullable().default(null),
1254
+ listening: z16.boolean(),
893
1255
  state: agentStateSchema.nullable().optional(),
894
- versionNumber: z13.number().int().positive().nullable()
1256
+ versionNumber: z16.number().int().positive().nullable()
895
1257
  });
896
1258
 
897
1259
  // ../shared/src/protection.ts
898
- import { z as z14 } from "zod";
1260
+ import { z as z17 } from "zod";
899
1261
  var PASSWORD_MIN = 6;
900
1262
  var PASSWORD_MAX = 128;
901
- var gatePasswordSchema = z14.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
902
- var setProtectionSchema = z14.object({
1263
+ var gatePasswordSchema = z17.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
1264
+ var setProtectionSchema = z17.object({
903
1265
  password: gatePasswordSchema.nullable()
904
1266
  });
905
- var protectionResponseSchema = z14.object({
906
- protected: z14.boolean()
1267
+ var protectionResponseSchema = z17.object({
1268
+ protected: z17.boolean()
907
1269
  });
908
- var unlockSchema = z14.object({
909
- password: z14.string().min(1).max(1024)
1270
+ var unlockSchema = z17.object({
1271
+ password: z17.string().min(1).max(1024)
910
1272
  });
911
- var unlockResponseSchema = z14.object({
912
- unlocked: z14.literal(true)
1273
+ var unlockResponseSchema = z17.object({
1274
+ unlocked: z17.literal(true)
913
1275
  });
914
1276
 
915
1277
  // ../shared/src/report.ts
916
- import { z as z15 } from "zod";
917
- var reportRequestSchema = z15.object({
918
- reason: z15.string().max(1e3).optional()
1278
+ import { z as z18 } from "zod";
1279
+ var reportRequestSchema = z18.object({
1280
+ reason: z18.string().max(1e3).optional()
919
1281
  });
920
- var reportResponseSchema = z15.object({
921
- reported: z15.literal(true)
1282
+ var reportResponseSchema = z18.object({
1283
+ reported: z18.literal(true)
922
1284
  });
923
1285
 
924
1286
  // ../shared/src/reviewers.ts
925
- import { z as z16 } from "zod";
926
- var artifactReviewerSchema = z16.object({
927
- avatarUrl: z16.string().nullable(),
928
- displayName: z16.string(),
1287
+ import { z as z19 } from "zod";
1288
+ var artifactReviewerSchema = z19.object({
1289
+ avatarUrl: z19.string().nullable(),
1290
+ displayName: z19.string(),
929
1291
  // Exactly one of userId/guestId is set, per `kind`.
930
- guestId: z16.string().nullable(),
1292
+ guestId: z19.string().nullable(),
931
1293
  // "account", not commentAuthorSchema's "member" — the whole point here is
932
1294
  // that these people need not be workspace members. A guest can be named in
933
1295
  // a comment but never notified: guest_identities carries a display name and
934
1296
  // no email, so there is nowhere to send anything.
935
- kind: z16.enum(["account", "guest"]),
936
- userId: z16.string().nullable()
1297
+ kind: z19.enum(["account", "guest"]),
1298
+ userId: z19.string().nullable()
937
1299
  });
938
- var artifactReviewerListResponseSchema = z16.object({
939
- reviewers: z16.array(artifactReviewerSchema)
1300
+ var artifactReviewerListResponseSchema = z19.object({
1301
+ reviewers: z19.array(artifactReviewerSchema)
940
1302
  });
941
1303
 
942
1304
  // ../shared/src/stats.ts
943
- import { z as z17 } from "zod";
944
- var avgViewSecondsSchema = z17.number().int().nonnegative().nullable();
945
- var versionViewStatsSchema = z17.object({
1305
+ import { z as z20 } from "zod";
1306
+ var avgViewSecondsSchema = z20.number().int().nonnegative().nullable();
1307
+ var versionViewStatsSchema = z20.object({
946
1308
  avgViewSeconds: avgViewSecondsSchema,
947
1309
  // Views from inside the review shell's iframe vs. direct visits to
948
1310
  // the render URL. views = embeddedViews + directViews.
949
- directViews: z17.number().int().nonnegative(),
950
- embeddedViews: z17.number().int().nonnegative(),
1311
+ directViews: z20.number().int().nonnegative(),
1312
+ embeddedViews: z20.number().int().nonnegative(),
951
1313
  // Distinct hashed viewer IPs across the version's lifetime.
952
- uniqueViewers: z17.number().int().nonnegative(),
953
- versionNumber: z17.number().int().positive(),
954
- views: z17.number().int().nonnegative()
1314
+ uniqueViewers: z20.number().int().nonnegative(),
1315
+ versionNumber: z20.number().int().positive(),
1316
+ views: z20.number().int().nonnegative()
955
1317
  });
956
- var dailyViewsSchema = z17.object({
1318
+ var dailyViewsSchema = z20.object({
957
1319
  avgViewSeconds: avgViewSecondsSchema,
958
1320
  // UTC day, YYYY-MM-DD.
959
- day: z17.string(),
960
- uniqueViewers: z17.number().int().nonnegative(),
961
- views: z17.number().int().nonnegative()
1321
+ day: z20.string(),
1322
+ uniqueViewers: z20.number().int().nonnegative(),
1323
+ views: z20.number().int().nonnegative()
962
1324
  });
963
- var artifactStatsResponseSchema = z17.object({
1325
+ var artifactStatsResponseSchema = z20.object({
964
1326
  avgViewSeconds: avgViewSecondsSchema,
965
- daily: z17.array(dailyViewsSchema),
1327
+ daily: z20.array(dailyViewsSchema),
966
1328
  // Distinct hashed viewer IPs across all versions and days.
967
- totalUniqueViewers: z17.number().int().nonnegative(),
968
- totalViews: z17.number().int().nonnegative(),
969
- versions: z17.array(versionViewStatsSchema)
1329
+ totalUniqueViewers: z20.number().int().nonnegative(),
1330
+ totalViews: z20.number().int().nonnegative(),
1331
+ versions: z20.array(versionViewStatsSchema)
970
1332
  });
971
1333
 
972
1334
  // ../shared/src/versions.ts
973
- import { z as z18 } from "zod";
974
- var versionSourceSchema = z18.object({
975
- agentName: z18.string().nullable(),
976
- type: z18.string()
1335
+ import { z as z21 } from "zod";
1336
+ var versionSourceSchema = z21.object({
1337
+ agentName: z21.string().nullable(),
1338
+ type: z21.string()
977
1339
  });
978
- var versionChangesSchema = z18.object({
979
- added: z18.array(z18.string()),
980
- changed: z18.array(z18.string()),
981
- removed: z18.array(z18.string())
1340
+ var versionChangesSchema = z21.object({
1341
+ added: z21.array(z21.string()),
1342
+ changed: z21.array(z21.string()),
1343
+ removed: z21.array(z21.string())
982
1344
  });
983
- var versionSummarySchema = z18.object({
1345
+ var versionSummarySchema = z21.object({
984
1346
  // Absent for the first version — there is no previous version to diff against.
985
1347
  changes: versionChangesSchema.nullable(),
986
- createdAt: z18.string(),
987
- entryUrl: z18.string().url(),
988
- fileCount: z18.number().int().nonnegative(),
989
- id: z18.string(),
990
- isCurrent: z18.boolean(),
1348
+ createdAt: z21.string(),
1349
+ entryUrl: z21.string().url(),
1350
+ fileCount: z21.number().int().nonnegative(),
1351
+ id: z21.string(),
1352
+ isCurrent: z21.boolean(),
991
1353
  // Author-supplied bullet-point changelog; null when the publish omitted it.
992
- notes: z18.string().nullable(),
1354
+ notes: z21.string().nullable(),
1355
+ // The account that published this version, with their own avatar where
1356
+ // they've set one. Any workspace member can publish a new version
1357
+ // (SPEC-2 §5.1), so "which teammate" outranks "which agent" — the agent
1358
+ // is the tool, this is the person. Null for token-only publishes, which
1359
+ // carry no account.
1360
+ publishedBy: z21.object({ image: z21.string().nullable(), name: z21.string() }).nullable(),
993
1361
  source: versionSourceSchema,
994
- totalSizeBytes: z18.number().int().nonnegative(),
995
- versionNumber: z18.number().int().positive()
1362
+ totalSizeBytes: z21.number().int().nonnegative(),
1363
+ versionNumber: z21.number().int().positive()
996
1364
  });
997
- var versionListResponseSchema = z18.object({
998
- versions: z18.array(versionSummarySchema)
1365
+ var versionListResponseSchema = z21.object({
1366
+ versions: z21.array(versionSummarySchema)
999
1367
  });
1000
1368
 
1001
1369
  // src/agent-identity.ts
@@ -1080,6 +1448,13 @@ function createApiClient(apiUrl2, publishToken) {
1080
1448
  (d) => agentHeartbeatResponseSchema.parse(d)
1081
1449
  );
1082
1450
  },
1451
+ async clearData(artifactId, collection) {
1452
+ return request(
1453
+ `${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}`,
1454
+ { headers, method: "DELETE" },
1455
+ (d) => dataClearResponseSchema.parse(d)
1456
+ );
1457
+ },
1083
1458
  // SPEC-2 §6.3 — create a folder in a workspace. Account key required
1084
1459
  // (owner/admin; in a personal workspace that's always the user).
1085
1460
  async createFolder(workspaceId, body) {
@@ -1162,6 +1537,13 @@ function createApiClient(apiUrl2, publishToken) {
1162
1537
  (d) => artifactResponseSchema.parse(d)
1163
1538
  );
1164
1539
  },
1540
+ async getDataSummary(artifactId) {
1541
+ return request(
1542
+ `${base}/api/v1/artifacts/${artifactId}/data`,
1543
+ { headers, method: "GET" },
1544
+ (d) => dataSummaryResponseSchema.parse(d)
1545
+ );
1546
+ },
1165
1547
  // SPEC §6.18 — everything owned by the account this client's API
1166
1548
  // key (or session) belongs to.
1167
1549
  async listArtifacts() {
@@ -1171,6 +1553,21 @@ function createApiClient(apiUrl2, publishToken) {
1171
1553
  (d) => artifactListResponseSchema.parse(d)
1172
1554
  );
1173
1555
  },
1556
+ async listData(artifactId, collection, query = {}) {
1557
+ const params = new URLSearchParams();
1558
+ if (query.limit !== void 0) {
1559
+ params.set("limit", String(query.limit));
1560
+ }
1561
+ if (query.cursor !== void 0) {
1562
+ params.set("cursor", query.cursor);
1563
+ }
1564
+ const qs = params.toString();
1565
+ return request(
1566
+ `${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}${qs ? `?${qs}` : ""}`,
1567
+ { headers, method: "GET" },
1568
+ (d) => dataListResponseSchema.parse(d)
1569
+ );
1570
+ },
1174
1571
  async listFolders(workspaceId) {
1175
1572
  return request(
1176
1573
  `${base}/api/v1/workspaces/${workspaceId}/folders`,
@@ -1306,17 +1703,17 @@ import {
1306
1703
  writeFileSync as writeFileSync2
1307
1704
  } from "fs";
1308
1705
  import path2 from "path";
1309
- import { z as z20 } from "zod";
1706
+ import { z as z23 } from "zod";
1310
1707
 
1311
1708
  // src/credentials.ts
1312
1709
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1313
1710
  import { homedir } from "os";
1314
1711
  import path from "path";
1315
- import { z as z19 } from "zod";
1712
+ import { z as z22 } from "zod";
1316
1713
  var DEFAULT_API_URL = "https://artifact.cafe";
1317
- var credentialsSchema = z19.object({
1318
- keys: z19.record(z19.string(), z19.string()),
1319
- version: z19.literal(1)
1714
+ var credentialsSchema = z22.object({
1715
+ keys: z22.record(z22.string(), z22.string()),
1716
+ version: z22.literal(1)
1320
1717
  });
1321
1718
  function credentialsPath() {
1322
1719
  return path.join(homedir(), ".artifactcafe", "credentials");
@@ -1366,24 +1763,24 @@ function resolveApiKey(apiUrl2, flag) {
1366
1763
  }
1367
1764
 
1368
1765
  // src/config.ts
1369
- var configSchema = z20.object({
1370
- apiUrl: z20.string(),
1371
- artifactId: z20.string(),
1372
- folder: z20.string().optional(),
1373
- folderId: z20.string().optional(),
1766
+ var configSchema = z23.object({
1767
+ apiUrl: z23.string(),
1768
+ artifactId: z23.string(),
1769
+ folder: z23.string().optional(),
1770
+ folderId: z23.string().optional(),
1374
1771
  // Optional: a folder linked by `link` to an artifact you own carries no
1375
1772
  // token — owner actions authorize via your stored login instead. A first
1376
1773
  // publish still writes the anonymous-mode token here.
1377
- publishToken: z20.string().optional(),
1378
- slug: z20.string(),
1379
- url: z20.string(),
1380
- workspace: z20.string().optional(),
1381
- workspaceId: z20.string().optional(),
1382
- workspaceKind: z20.enum(["personal", "workspace"]).optional()
1383
- });
1384
- var projectConfigSchema = z20.object({
1385
- folder: z20.string().trim().min(1).optional(),
1386
- workspace: z20.string().trim().min(1)
1774
+ publishToken: z23.string().optional(),
1775
+ slug: z23.string(),
1776
+ url: z23.string(),
1777
+ workspace: z23.string().optional(),
1778
+ workspaceId: z23.string().optional(),
1779
+ workspaceKind: z23.enum(["personal", "workspace"]).optional()
1780
+ });
1781
+ var projectConfigSchema = z23.object({
1782
+ folder: z23.string().trim().min(1).optional(),
1783
+ workspace: z23.string().trim().min(1)
1387
1784
  }).strict();
1388
1785
  var CONFIG_DIR = ".artifactcafe";
1389
1786
  var CONFIG_FILE = "config.json";
@@ -1419,7 +1816,7 @@ function readProjectConfig(dir) {
1419
1816
  JSON.parse(readFileSync2(file, "utf8"))
1420
1817
  );
1421
1818
  } catch (error) {
1422
- const message = error instanceof z20.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
1819
+ const message = error instanceof z23.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
1423
1820
  throw new Error(`Invalid ${file}: ${message}.`);
1424
1821
  }
1425
1822
  }
@@ -1484,7 +1881,8 @@ function resolveArtifactTarget(dir, opts = {}) {
1484
1881
  );
1485
1882
  }
1486
1883
  const folderToken = artifactId === config?.artifactId ? config.publishToken : void 0;
1487
- const credential = folderToken ?? process.env.ARTIFACT_CAFE_TOKEN ?? resolveApiKey(apiUrl2, opts.apiKey);
1884
+ const apiKey = resolveApiKey(apiUrl2, opts.apiKey);
1885
+ const credential = (apiKey?.startsWith("aat_") ? apiKey : void 0) ?? folderToken ?? process.env.ARTIFACT_CAFE_TOKEN ?? apiKey;
1488
1886
  const url = artifactId === config?.artifactId ? config.url : void 0;
1489
1887
  return { apiUrl: apiUrl2, artifactId, credential, url };
1490
1888
  }
@@ -1628,27 +2026,27 @@ async function comments(opts) {
1628
2026
  });
1629
2027
  return;
1630
2028
  }
1631
- const data = await client.exportComments(target.artifactId, {
2029
+ const data2 = await client.exportComments(target.artifactId, {
1632
2030
  status: status2,
1633
2031
  version
1634
2032
  });
1635
2033
  if (opts.json) {
1636
- console.log(JSON.stringify(data, null, 2));
2034
+ console.log(JSON.stringify(data2, null, 2));
1637
2035
  return;
1638
2036
  }
1639
- printHuman(data, url, status2, version);
2037
+ printHuman(data2, url, status2, version);
1640
2038
  }
1641
2039
  async function deriveReviewUrl(client, target) {
1642
2040
  const artifact = await client.getArtifact(target.artifactId);
1643
2041
  return reviewUrl(target.apiUrl, artifact.slug);
1644
2042
  }
1645
2043
  var sleep = (s) => new Promise((r) => setTimeout(r, s * 1e3));
1646
- function messageCounts(data) {
1647
- return new Map(data.comments.map((t) => [t.id, 1 + t.replies.length]));
2044
+ function messageCounts(data2) {
2045
+ return new Map(data2.comments.map((t) => [t.id, 1 + t.replies.length]));
1648
2046
  }
1649
- function diffThreads(baseline, data) {
2047
+ function diffThreads(baseline, data2) {
1650
2048
  const changed = [];
1651
- for (const t of data.comments) {
2049
+ for (const t of data2.comments) {
1652
2050
  const before = baseline.get(t.id);
1653
2051
  if (before === void 0) {
1654
2052
  changed.push({ ...t, change: "new" });
@@ -1691,8 +2089,8 @@ async function waitForFeedback(client, config, opts) {
1691
2089
  while (Date.now() < deadline) {
1692
2090
  await sleep(Math.min(pollSeconds(), (deadline - Date.now()) / 1e3));
1693
2091
  await heartbeat();
1694
- const data = await client.exportComments(config.artifactId, query);
1695
- if (diffThreads(baseline, data).length === 0) {
2092
+ const data2 = await client.exportComments(config.artifactId, query);
2093
+ if (diffThreads(baseline, data2).length === 0) {
1696
2094
  continue;
1697
2095
  }
1698
2096
  await sleep(settleSeconds());
@@ -1725,7 +2123,7 @@ async function waitForFeedback(client, config, opts) {
1725
2123
  }
1726
2124
  process.exit(WAIT_TIMEOUT_EXIT);
1727
2125
  }
1728
- function printWaitResult(config, data, changed, json) {
2126
+ function printWaitResult(config, data2, changed, json) {
1729
2127
  if (json) {
1730
2128
  console.log(
1731
2129
  JSON.stringify(
@@ -1733,7 +2131,7 @@ function printWaitResult(config, data, changed, json) {
1733
2131
  artifactId: config.artifactId,
1734
2132
  threads: changed,
1735
2133
  timedOut: false,
1736
- versionNumber: data.versionNumber
2134
+ versionNumber: data2.versionNumber
1737
2135
  },
1738
2136
  null,
1739
2137
  2
@@ -1752,22 +2150,22 @@ ${changed.length} thread${plural} with new feedback \u2014 ${config.url}
1752
2150
  printThread(thread);
1753
2151
  }
1754
2152
  }
1755
- function printHuman(data, url, status2, version) {
2153
+ function printHuman(data2, url, status2, version) {
1756
2154
  const statusLabel = status2 === "all" ? "" : `${status2} `;
1757
2155
  let scope = "across all versions";
1758
2156
  if (version === "current") {
1759
- scope = data.versionNumber === null ? "on the current version" : `on v${data.versionNumber}`;
2157
+ scope = data2.versionNumber === null ? "on the current version" : `on v${data2.versionNumber}`;
1760
2158
  }
1761
- if (data.comments.length === 0) {
2159
+ if (data2.comments.length === 0) {
1762
2160
  console.log(`No ${statusLabel}comments ${scope}.`);
1763
2161
  return;
1764
2162
  }
1765
- const plural = data.comments.length === 1 ? "" : "s";
2163
+ const plural = data2.comments.length === 1 ? "" : "s";
1766
2164
  console.log(
1767
- `${data.comments.length} ${statusLabel}thread${plural} ${scope} \u2014 ${url}
2165
+ `${data2.comments.length} ${statusLabel}thread${plural} ${scope} \u2014 ${url}
1768
2166
  `
1769
2167
  );
1770
- for (const thread of data.comments) {
2168
+ for (const thread of data2.comments) {
1771
2169
  printThread(thread);
1772
2170
  }
1773
2171
  }
@@ -1812,18 +2210,161 @@ function indent(text, pad) {
1812
2210
  }
1813
2211
 
1814
2212
  // src/dashboard.ts
1815
- import path7 from "path";
2213
+ import path8 from "path";
1816
2214
 
1817
2215
  // src/publish.ts
1818
2216
  import { exec } from "child_process";
1819
- import { readFileSync as readFileSync4 } from "fs";
1820
- import path6 from "path";
2217
+ import { readFileSync as readFileSync5 } from "fs";
2218
+ import path7 from "path";
1821
2219
  import { createInterface } from "readline";
1822
2220
 
2221
+ // src/data.ts
2222
+ import { readFileSync as readFileSync3 } from "fs";
2223
+ import path5 from "path";
2224
+ function inspectDataManifest(files) {
2225
+ const info = { announcements: [] };
2226
+ const manifestFile = files.find((f) => f.path === DATA_MANIFEST_PATH);
2227
+ if (manifestFile) {
2228
+ const parsed = parseDataManifest(
2229
+ readFileSync3(manifestFile.absPath, "utf8")
2230
+ );
2231
+ if (!parsed.ok) {
2232
+ throw new Error(
2233
+ `${DATA_MANIFEST_PATH} is invalid:
2234
+ ${(parsed.errors ?? []).map((e) => ` - ${e}`).join("\n")}`
2235
+ );
2236
+ }
2237
+ info.config = parsed.config;
2238
+ for (const [name, collection] of Object.entries(
2239
+ parsed.config?.collections ?? {}
2240
+ )) {
2241
+ const bits = [
2242
+ `read: ${collection.access.read}`,
2243
+ `insert: ${collection.access.insert}`
2244
+ ];
2245
+ if (collection.access.update !== "owner") {
2246
+ bits.push(`update: ${collection.access.update}`);
2247
+ }
2248
+ if (collection.access.delete !== "owner") {
2249
+ bits.push(`delete: ${collection.access.delete}`);
2250
+ }
2251
+ if (collection.rateLimit) {
2252
+ bits.push(collection.rateLimit);
2253
+ }
2254
+ info.announcements.push(`\u26C1 Data enabled: ${name} (${bits.join(", ")})`);
2255
+ }
2256
+ if (info.announcements.length === 0) {
2257
+ info.announcements.push(
2258
+ "\u26C1 Data disabled (empty collections in .artifact/data.json)"
2259
+ );
2260
+ }
2261
+ }
2262
+ return info;
2263
+ }
2264
+ function requireLinkedArtifact(dir) {
2265
+ const resolved = path5.resolve(dir);
2266
+ const config = readConfig(resolved);
2267
+ if (!config) {
2268
+ throw new Error(
2269
+ `No artifact linked in ${resolved} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
2270
+ );
2271
+ }
2272
+ return {
2273
+ apiUrl: process.env.ARTIFACT_CAFE_URL ?? config.apiUrl,
2274
+ artifactId: config.artifactId,
2275
+ publishToken: resolveApiKey(process.env.ARTIFACT_CAFE_URL ?? config.apiUrl) ?? config.publishToken ?? process.env.ARTIFACT_CAFE_TOKEN,
2276
+ url: config.url
2277
+ };
2278
+ }
2279
+ async function dataLs(opts) {
2280
+ const linked = requireLinkedArtifact(opts.dir);
2281
+ const client = createApiClient(linked.apiUrl, linked.publishToken);
2282
+ const records = [];
2283
+ let cursor;
2284
+ do {
2285
+ const page = await client.listData(linked.artifactId, opts.collection, {
2286
+ cursor,
2287
+ limit: 200
2288
+ });
2289
+ records.push(...page.records);
2290
+ cursor = page.nextCursor ?? void 0;
2291
+ } while (cursor && (opts.limit === void 0 || records.length < opts.limit));
2292
+ const shown = opts.limit === void 0 ? records : records.slice(0, opts.limit);
2293
+ if (opts.json) {
2294
+ console.log(
2295
+ JSON.stringify({ collection: opts.collection, records: shown }, null, 2)
2296
+ );
2297
+ return;
2298
+ }
2299
+ if (shown.length === 0) {
2300
+ console.log(`No records in "${opts.collection}".`);
2301
+ return;
2302
+ }
2303
+ const plural = shown.length === 1 ? "" : "s";
2304
+ console.log(
2305
+ `${shown.length} record${plural} in "${opts.collection}" \u2014 ${linked.url}
2306
+ `
2307
+ );
2308
+ for (const record of shown) {
2309
+ const { created_at, created_by, id, updated_at: _u, ...fields } = record;
2310
+ const author = created_by ?? "anonymous";
2311
+ console.log(`\u25CF ${id} \xB7 ${String(created_at).slice(0, 10)} \xB7 ${author}`);
2312
+ console.log(` ${JSON.stringify(fields)}`);
2313
+ }
2314
+ }
2315
+ async function dataExport(opts) {
2316
+ const linked = requireLinkedArtifact(opts.dir);
2317
+ const client = createApiClient(linked.apiUrl, linked.publishToken);
2318
+ const summary = await client.getDataSummary(linked.artifactId);
2319
+ const collections = {};
2320
+ for (const { name } of summary.collections) {
2321
+ const records = [];
2322
+ let cursor;
2323
+ do {
2324
+ const page = await client.listData(linked.artifactId, name, {
2325
+ cursor,
2326
+ limit: 200
2327
+ });
2328
+ records.push(...page.records);
2329
+ cursor = page.nextCursor ?? void 0;
2330
+ } while (cursor);
2331
+ collections[name] = records;
2332
+ }
2333
+ console.log(
2334
+ JSON.stringify(
2335
+ {
2336
+ artifactId: linked.artifactId,
2337
+ collections,
2338
+ enabled: summary.enabled,
2339
+ url: linked.url
2340
+ },
2341
+ null,
2342
+ 2
2343
+ )
2344
+ );
2345
+ }
2346
+ async function dataClear(opts) {
2347
+ if (!opts.yes) {
2348
+ throw new Error(
2349
+ `Clearing "${opts.collection}" deletes every record in it. Re-run with --yes to confirm.`
2350
+ );
2351
+ }
2352
+ const linked = requireLinkedArtifact(opts.dir);
2353
+ const client = createApiClient(linked.apiUrl, linked.publishToken);
2354
+ const result = await client.clearData(linked.artifactId, opts.collection);
2355
+ if (opts.json) {
2356
+ console.log(
2357
+ JSON.stringify({ cleared: result.cleared, collection: opts.collection })
2358
+ );
2359
+ return;
2360
+ }
2361
+ console.log(`Cleared ${result.cleared} record(s) from "${opts.collection}".`);
2362
+ }
2363
+
1823
2364
  // src/manifest.ts
1824
2365
  import { createHash } from "crypto";
1825
- import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
1826
- import path5 from "path";
2366
+ import { readdirSync, readFileSync as readFileSync4, statSync } from "fs";
2367
+ import path6 from "path";
1827
2368
  var IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".artifactcafe"]);
1828
2369
  var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store", "artifact-cafe.json"]);
1829
2370
  function isIgnoredFile(name) {
@@ -1872,7 +2413,7 @@ var MIME_TYPES = {
1872
2413
  ".xml": "application/xml"
1873
2414
  };
1874
2415
  function mimeFor(filePath) {
1875
- return MIME_TYPES[path5.extname(filePath).toLowerCase()] ?? "application/octet-stream";
2416
+ return MIME_TYPES[path6.extname(filePath).toLowerCase()] ?? "application/octet-stream";
1876
2417
  }
1877
2418
  function scanDirectory(dir) {
1878
2419
  const files = [];
@@ -1880,7 +2421,7 @@ function scanDirectory(dir) {
1880
2421
  function walk(current, relPrefix) {
1881
2422
  for (const entry of readdirSync(current, { withFileTypes: true })) {
1882
2423
  const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
1883
- const abs = path5.join(current, entry.name);
2424
+ const abs = path6.join(current, entry.name);
1884
2425
  if (entry.isSymbolicLink()) {
1885
2426
  skipped.push(rel);
1886
2427
  continue;
@@ -1905,7 +2446,7 @@ function scanDirectory(dir) {
1905
2446
  skipped.push(rel);
1906
2447
  continue;
1907
2448
  }
1908
- const content = readFileSync3(abs);
2449
+ const content = readFileSync4(abs);
1909
2450
  files.push({
1910
2451
  absPath: abs,
1911
2452
  hash: `sha256_${createHash("sha256").update(content).digest("hex")}`,
@@ -1951,7 +2492,7 @@ var REVIEW_LABEL = {
1951
2492
  readonly: "read-only \u2014 existing comments only"
1952
2493
  };
1953
2494
  async function publish(opts) {
1954
- const dir = path6.resolve(opts.dir);
2495
+ const dir = path7.resolve(opts.dir);
1955
2496
  const config = readConfig(dir);
1956
2497
  if (opts.password !== void 0) {
1957
2498
  const parsed = gatePasswordSchema.safeParse(opts.password);
@@ -1971,6 +2512,7 @@ async function publish(opts) {
1971
2512
  const token = (artifactId !== null && artifactId === config?.artifactId ? config.publishToken : void 0) ?? process.env.ARTIFACT_CAFE_TOKEN;
1972
2513
  const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
1973
2514
  const apiKey = resolveApiKey(apiUrl2, opts.apiKey);
2515
+ const usingAgentToken = apiKey?.startsWith("aat_") === true;
1974
2516
  const placement = await resolvePlacement({
1975
2517
  apiKey,
1976
2518
  apiUrl: apiUrl2,
@@ -1983,6 +2525,7 @@ async function publish(opts) {
1983
2525
  throw new Error(`Nothing to publish in ${dir} (after ignore rules).`);
1984
2526
  }
1985
2527
  const entryPath = resolveEntry(files, opts.entry);
2528
+ const dataInfo = inspectDataManifest(files);
1986
2529
  const log = (msg) => {
1987
2530
  if (!opts.json) {
1988
2531
  console.log(msg);
@@ -2019,7 +2562,7 @@ async function publish(opts) {
2019
2562
  try {
2020
2563
  session = await client.createSession(sessionBody);
2021
2564
  } catch (err) {
2022
- const keyRefused = apiKey !== void 0 && token !== void 0 && apiKey !== token && artifactId !== null && err instanceof ApiRequestError && (err.status === 401 || err.status === 403);
2565
+ const keyRefused = apiKey !== void 0 && !usingAgentToken && token !== void 0 && apiKey !== token && artifactId !== null && err instanceof ApiRequestError && (err.status === 401 || err.status === 403);
2023
2566
  if (!keyRefused) {
2024
2567
  throw err;
2025
2568
  }
@@ -2043,7 +2586,7 @@ async function publish(opts) {
2043
2586
  }
2044
2587
  await uploadFile(
2045
2588
  upload.uploadUrl,
2046
- readFileSync4(file.absPath),
2589
+ readFileSync5(file.absPath),
2047
2590
  file.mimeType
2048
2591
  );
2049
2592
  }
@@ -2052,16 +2595,19 @@ async function publish(opts) {
2052
2595
  presence.stop();
2053
2596
  }
2054
2597
  const result = await client.finalize(session.publishSessionId);
2598
+ if (usingAgentToken && result.workspaceId) {
2599
+ placement.workspaceId = result.workspaceId;
2600
+ }
2055
2601
  const rememberedProjectFile = placement.rememberProjectDefault ? writeProjectConfig(dir, placement.rememberProjectDefault) : null;
2056
- const existingToken = config?.artifactId === result.artifactId ? config.publishToken : void 0;
2057
- const shouldWriteConfig = result.publishToken !== void 0 || config?.artifactId === result.artifactId || opts.workspace !== void 0 || opts.folder !== void 0 || placement.workspaceId !== void 0;
2602
+ const existingToken = !usingAgentToken && config?.artifactId === result.artifactId ? config.publishToken : void 0;
2603
+ const shouldWriteConfig = usingAgentToken || result.publishToken !== void 0 || config?.artifactId === result.artifactId || opts.workspace !== void 0 || opts.folder !== void 0 || placement.workspaceId !== void 0;
2058
2604
  if (shouldWriteConfig) {
2059
2605
  writeConfig(dir, {
2060
2606
  apiUrl: apiUrl2,
2061
2607
  artifactId: result.artifactId,
2062
2608
  ...placement.folder ? { folder: placement.folder } : {},
2063
2609
  ...placement.folderId ? { folderId: placement.folderId } : {},
2064
- ...result.publishToken ?? existingToken ? { publishToken: result.publishToken ?? existingToken } : {},
2610
+ ...!usingAgentToken && (result.publishToken ?? existingToken) ? { publishToken: result.publishToken ?? existingToken } : {},
2065
2611
  slug: slugFromUrl(result.url),
2066
2612
  url: result.url,
2067
2613
  ...placement.workspace ? { workspace: placement.workspace } : {},
@@ -2088,6 +2634,7 @@ async function publish(opts) {
2088
2634
  JSON.stringify(
2089
2635
  {
2090
2636
  artifactId: result.artifactId,
2637
+ ...dataInfo.config ? { data: dataInfo.config } : {},
2091
2638
  claimed: result.claimed,
2092
2639
  destination: placement.destination,
2093
2640
  destinationSource: placement.destinationSource,
@@ -2166,6 +2713,11 @@ ${result.claimUrl}
2166
2713
  `);
2167
2714
  }
2168
2715
  }
2716
+ if (!opts.json) {
2717
+ for (const announcement of dataInfo.announcements) {
2718
+ console.log(announcement);
2719
+ }
2720
+ }
2169
2721
  if (opts.open && !opts.json && process.stdout.isTTY) {
2170
2722
  openInBrowser(result.url);
2171
2723
  }
@@ -2181,6 +2733,18 @@ function accessNote(visibility) {
2181
2733
  }
2182
2734
  async function resolvePlacement(params) {
2183
2735
  const { apiKey, apiUrl: apiUrl2, config, isNewArtifact, opts } = params;
2736
+ if (apiKey?.startsWith("aat_")) {
2737
+ if (opts.workspace !== void 0 || opts.folder !== void 0) {
2738
+ throw new Error(
2739
+ "An agent token selects its own workspace; omit --workspace and --folder."
2740
+ );
2741
+ }
2742
+ return {
2743
+ destination: "workspace",
2744
+ destinationLabel: "Agent token's workspace",
2745
+ destinationSource: "agent-token"
2746
+ };
2747
+ }
2184
2748
  const hasPlacementFlag = opts.workspace !== void 0 || opts.folder !== void 0;
2185
2749
  if (!hasPlacementFlag && config?.workspaceId) {
2186
2750
  const isPersonal = config.workspaceKind === "personal";
@@ -2223,9 +2787,9 @@ async function resolvePlacement(params) {
2223
2787
  };
2224
2788
  }
2225
2789
  if (shouldPromptForWorkspace(opts)) {
2226
- const data2 = await createApiClient(apiUrl2, apiKey).listWorkspaces();
2227
- if (data2.workspaces.length > 1) {
2228
- const choice = await pickWorkspace(data2.workspaces);
2790
+ const data3 = await createApiClient(apiUrl2, apiKey).listWorkspaces();
2791
+ if (data3.workspaces.length > 1) {
2792
+ const choice = await pickWorkspace(data3.workspaces);
2229
2793
  return {
2230
2794
  destination: choice.workspace.isPersonal ? "personal" : "workspace",
2231
2795
  destinationLabel: choice.workspace.isPersonal ? "Personal" : choice.workspace.name,
@@ -2251,19 +2815,19 @@ async function resolvePlacement(params) {
2251
2815
  throw new Error("--folder requires --workspace or a stored workspace.");
2252
2816
  }
2253
2817
  const client = createApiClient(apiUrl2, apiKey);
2254
- const data = await client.listWorkspaces();
2818
+ const data2 = await client.listWorkspaces();
2255
2819
  const workspaceHandle = opts.workspace ?? config?.workspace ?? projectConfig?.workspace;
2256
2820
  let workspace;
2257
2821
  if (workspaceHandle) {
2258
- workspace = data.workspaces.find(
2822
+ workspace = data2.workspaces.find(
2259
2823
  (candidate) => candidate.handle === workspaceHandle
2260
2824
  );
2261
2825
  } else if (config?.workspaceId) {
2262
- workspace = data.workspaces.find(
2826
+ workspace = data2.workspaces.find(
2263
2827
  (candidate) => candidate.id === config.workspaceId
2264
2828
  );
2265
2829
  } else {
2266
- workspace = data.workspaces.find((candidate) => candidate.isPersonal);
2830
+ workspace = data2.workspaces.find((candidate) => candidate.isPersonal);
2267
2831
  }
2268
2832
  if (!workspace) {
2269
2833
  const target = workspaceHandle ? `workspace with handle "${workspaceHandle}"` : "personal workspace";
@@ -2401,7 +2965,7 @@ function openInBrowser(url) {
2401
2965
 
2402
2966
  // src/dashboard.ts
2403
2967
  function openDashboard(opts) {
2404
- const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path7.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
2968
+ const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path8.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
2405
2969
  const url = `${base.replace(/\/$/, "")}/dashboard`;
2406
2970
  console.log(url);
2407
2971
  if (process.stdout.isTTY) {
@@ -2487,21 +3051,21 @@ async function folders(opts) {
2487
3051
  await createFolder(client, workspace, opts);
2488
3052
  return;
2489
3053
  }
2490
- const data = await client.listFolders(workspace.id);
3054
+ const data2 = await client.listFolders(workspace.id);
2491
3055
  if (opts.json) {
2492
- console.log(JSON.stringify(data, null, 2));
3056
+ console.log(JSON.stringify(data2, null, 2));
2493
3057
  return;
2494
3058
  }
2495
- if (data.folders.length === 0) {
3059
+ if (data2.folders.length === 0) {
2496
3060
  console.log(`No folders in workspace ${workspace.handle}.`);
2497
3061
  return;
2498
3062
  }
2499
- const plural = data.folders.length === 1 ? "" : "s";
3063
+ const plural = data2.folders.length === 1 ? "" : "s";
2500
3064
  console.log(
2501
- `${data.folders.length} folder${plural} in ${workspace.handle}
3065
+ `${data2.folders.length} folder${plural} in ${workspace.handle}
2502
3066
  `
2503
3067
  );
2504
- for (const folder of data.folders) {
3068
+ for (const folder of data2.folders) {
2505
3069
  printFolder(folder);
2506
3070
  }
2507
3071
  }
@@ -2539,9 +3103,48 @@ function printFolder(folder) {
2539
3103
  }
2540
3104
 
2541
3105
  // src/link.ts
2542
- import path8 from "path";
3106
+ import path9 from "path";
2543
3107
  import { createInterface as createInterface2 } from "readline";
2544
- async function link(opts) {
3108
+
3109
+ // src/list.ts
3110
+ async function teamGroups(client) {
3111
+ const { workspaces: workspaces3 } = await client.listWorkspaces();
3112
+ return await Promise.all(
3113
+ workspaces3.filter((workspace) => !workspace.isPersonal).map(async (workspace) => ({
3114
+ artifacts: (await client.listWorkspaceArtifacts(workspace.id)).artifacts,
3115
+ handle: workspace.handle,
3116
+ id: workspace.id,
3117
+ name: workspace.name
3118
+ }))
3119
+ );
3120
+ }
3121
+ async function reachableArtifacts(client) {
3122
+ const [own, groups] = await Promise.all([
3123
+ client.listArtifacts(),
3124
+ teamGroups(client)
3125
+ ]);
3126
+ return [
3127
+ ...own.artifacts.map((artifact) => ({
3128
+ currentVersionNumber: artifact.currentVersionNumber,
3129
+ id: artifact.id,
3130
+ slug: artifact.slug,
3131
+ title: artifact.title,
3132
+ url: artifact.url,
3133
+ workspaceHandle: null
3134
+ })),
3135
+ ...groups.flatMap(
3136
+ (group) => group.artifacts.map((artifact) => ({
3137
+ currentVersionNumber: artifact.currentVersionNumber,
3138
+ id: artifact.id,
3139
+ slug: artifact.slug,
3140
+ title: artifact.title,
3141
+ url: artifact.url,
3142
+ workspaceHandle: group.handle
3143
+ }))
3144
+ )
3145
+ ];
3146
+ }
3147
+ async function list(opts) {
2545
3148
  const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
2546
3149
  /\/$/,
2547
3150
  ""
@@ -2549,124 +3152,52 @@ async function link(opts) {
2549
3152
  const key = resolveApiKey(apiUrl2, opts.apiKey);
2550
3153
  if (!key) {
2551
3154
  throw new Error(
2552
- `Linking needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
3155
+ `Listing your artifacts needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
2553
3156
  );
2554
3157
  }
2555
- const { artifacts } = await createApiClient(apiUrl2, key).listArtifacts();
2556
- if (artifacts.length === 0) {
2557
- throw new Error(
2558
- "No artifacts in your account yet \u2014 publish one first, or claim one via its claim link."
2559
- );
3158
+ const client = createApiClient(apiUrl2, key);
3159
+ if (opts.workspace !== void 0) {
3160
+ await listWorkspace(client, opts);
3161
+ return;
2560
3162
  }
2561
- const selector = opts.artifact ?? opts.slug;
2562
- let chosen;
2563
- if (opts.artifact) {
2564
- chosen = artifacts.find((a) => a.id === opts.artifact);
2565
- } else if (opts.slug) {
2566
- chosen = artifacts.find((a) => a.slug === opts.slug);
2567
- }
2568
- if (selector && !chosen) {
2569
- throw new Error(
2570
- `No artifact "${selector}" in your account \u2014 run \`artifact-cafe list\` to see them.`
2571
- );
2572
- }
2573
- if (opts.json && !chosen) {
2574
- console.log(JSON.stringify({ artifacts }, null, 2));
2575
- return;
2576
- }
2577
- if (!chosen) {
2578
- if (!process.stdin.isTTY) {
2579
- throw new Error(
2580
- "Pick an artifact with --artifact <id> or --slug <slug> (or --json to list them). Interactive selection needs a terminal."
2581
- );
2582
- }
2583
- chosen = await pickArtifact(artifacts);
2584
- }
2585
- const dir = path8.resolve(opts.dir);
2586
- writeConfig(dir, {
2587
- apiUrl: apiUrl2,
2588
- artifactId: chosen.id,
2589
- slug: chosen.slug,
2590
- url: chosen.url
2591
- // No publishToken — this folder authorizes via your login, not a secret.
2592
- });
3163
+ const [data2, groups] = await Promise.all([
3164
+ client.listArtifacts(),
3165
+ teamGroups(client)
3166
+ ]);
2593
3167
  if (opts.json) {
2594
3168
  console.log(
2595
- JSON.stringify(
2596
- {
2597
- artifactId: chosen.id,
2598
- linked: true,
2599
- slug: chosen.slug,
2600
- url: chosen.url
2601
- },
2602
- null,
2603
- 2
2604
- )
3169
+ JSON.stringify({ artifacts: data2.artifacts, workspaces: groups }, null, 2)
2605
3170
  );
2606
3171
  return;
2607
3172
  }
2608
- console.log(`
2609
- \u2713 Linked ${dir} \u2192 "${chosen.title}" \u2014 ${chosen.url}`);
2610
- console.log(
2611
- " Owner actions (publish, comments --wait, protect) use your login.\n"
2612
- );
2613
- }
2614
- function pickArtifact(artifacts) {
2615
- console.log("Your artifacts:\n");
2616
- artifacts.forEach((a, i) => {
2617
- const v = a.currentVersionNumber ? `v${a.currentVersionNumber}` : "\u2014";
3173
+ const total = data2.artifacts.length + groups.reduce((sum, group) => sum + group.artifacts.length, 0);
3174
+ if (total === 0) {
2618
3175
  console.log(
2619
- ` ${String(i + 1).padStart(2)}. ${a.title} \xB7 ${v} \xB7 ${a.url}`
2620
- );
2621
- });
2622
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
2623
- return new Promise((resolve2, reject) => {
2624
- rl.question(`
2625
- Link which? [1-${artifacts.length}]: `, (answer) => {
2626
- rl.close();
2627
- const n = Number(answer.trim());
2628
- if (!(Number.isInteger(n) && n >= 1 && n <= artifacts.length)) {
2629
- reject(new Error(`Enter a number between 1 and ${artifacts.length}.`));
2630
- return;
2631
- }
2632
- resolve2(artifacts[n - 1]);
2633
- });
2634
- });
2635
- }
2636
-
2637
- // src/list.ts
2638
- async function list(opts) {
2639
- const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
2640
- /\/$/,
2641
- ""
2642
- );
2643
- const key = resolveApiKey(apiUrl2, opts.apiKey);
2644
- if (!key) {
2645
- throw new Error(
2646
- `Listing your artifacts needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
3176
+ "No artifacts you can publish to yet \u2014 publish while logged in, or claim one via its claim link."
2647
3177
  );
2648
- }
2649
- const client = createApiClient(apiUrl2, key);
2650
- if (opts.workspace !== void 0) {
2651
- await listWorkspace(client, opts);
2652
3178
  return;
2653
3179
  }
2654
- const data = await client.listArtifacts();
2655
- if (opts.json) {
2656
- console.log(JSON.stringify(data, null, 2));
2657
- return;
3180
+ if (data2.artifacts.length > 0) {
3181
+ const plural = data2.artifacts.length === 1 ? "" : "s";
3182
+ console.log(`${data2.artifacts.length} artifact${plural}
3183
+ `);
3184
+ for (const artifact of data2.artifacts) {
3185
+ printArtifact(artifact);
3186
+ }
2658
3187
  }
2659
- if (data.artifacts.length === 0) {
3188
+ for (const group of groups) {
3189
+ if (group.artifacts.length === 0) {
3190
+ continue;
3191
+ }
3192
+ const plural = group.artifacts.length === 1 ? "" : "s";
2660
3193
  console.log(
2661
- "No artifacts in your account yet \u2014 publish while logged in, or claim one via its claim link."
3194
+ `${group.artifacts.length} artifact${plural} in ${group.handle}
3195
+ `
2662
3196
  );
2663
- return;
2664
- }
2665
- const plural = data.artifacts.length === 1 ? "" : "s";
2666
- console.log(`${data.artifacts.length} artifact${plural}
2667
- `);
2668
- for (const artifact of data.artifacts) {
2669
- printArtifact(artifact);
3197
+ for (const artifact of group.artifacts) {
3198
+ printWorkspaceArtifact(artifact);
3199
+ }
3200
+ console.log("");
2670
3201
  }
2671
3202
  }
2672
3203
  async function listWorkspace(client, opts) {
@@ -2759,6 +3290,101 @@ function relativeTime(iso) {
2759
3290
  return iso.slice(0, 10);
2760
3291
  }
2761
3292
 
3293
+ // src/link.ts
3294
+ async function link(opts) {
3295
+ const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
3296
+ /\/$/,
3297
+ ""
3298
+ );
3299
+ const key = resolveApiKey(apiUrl2, opts.apiKey);
3300
+ if (!key) {
3301
+ throw new Error(
3302
+ `Linking needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
3303
+ );
3304
+ }
3305
+ const artifacts = await reachableArtifacts(createApiClient(apiUrl2, key));
3306
+ if (artifacts.length === 0) {
3307
+ throw new Error(
3308
+ "No artifacts you can publish to yet \u2014 publish one first, or claim one via its claim link."
3309
+ );
3310
+ }
3311
+ const selector = opts.artifact ?? opts.slug;
3312
+ let chosen;
3313
+ if (opts.artifact) {
3314
+ chosen = artifacts.find((a) => a.id === opts.artifact);
3315
+ } else if (opts.slug) {
3316
+ chosen = artifacts.find((a) => a.slug === opts.slug);
3317
+ }
3318
+ if (selector && !chosen) {
3319
+ throw new Error(
3320
+ `No artifact "${selector}" you can publish to \u2014 run \`artifact-cafe list\` to see them.`
3321
+ );
3322
+ }
3323
+ if (opts.json && !chosen) {
3324
+ console.log(JSON.stringify({ artifacts }, null, 2));
3325
+ return;
3326
+ }
3327
+ if (!chosen) {
3328
+ if (!process.stdin.isTTY) {
3329
+ throw new Error(
3330
+ "Pick an artifact with --artifact <id> or --slug <slug> (or --json to list them). Interactive selection needs a terminal."
3331
+ );
3332
+ }
3333
+ chosen = await pickArtifact(artifacts);
3334
+ }
3335
+ const dir = path9.resolve(opts.dir);
3336
+ writeConfig(dir, {
3337
+ apiUrl: apiUrl2,
3338
+ artifactId: chosen.id,
3339
+ slug: chosen.slug,
3340
+ url: chosen.url
3341
+ // No publishToken — this folder authorizes via your login, not a secret.
3342
+ });
3343
+ if (opts.json) {
3344
+ console.log(
3345
+ JSON.stringify(
3346
+ {
3347
+ artifactId: chosen.id,
3348
+ linked: true,
3349
+ slug: chosen.slug,
3350
+ url: chosen.url
3351
+ },
3352
+ null,
3353
+ 2
3354
+ )
3355
+ );
3356
+ return;
3357
+ }
3358
+ console.log(`
3359
+ \u2713 Linked ${dir} \u2192 "${chosen.title}" \u2014 ${chosen.url}`);
3360
+ console.log(
3361
+ " Owner actions (publish, comments --wait, protect) use your login.\n"
3362
+ );
3363
+ }
3364
+ function pickArtifact(artifacts) {
3365
+ console.log("Artifacts you can publish to:\n");
3366
+ artifacts.forEach((a, i) => {
3367
+ const v = a.currentVersionNumber ? `v${a.currentVersionNumber}` : "\u2014";
3368
+ const where = a.workspaceHandle ? ` \xB7 ${a.workspaceHandle}` : "";
3369
+ console.log(
3370
+ ` ${String(i + 1).padStart(2)}. ${a.title} \xB7 ${v}${where} \xB7 ${a.url}`
3371
+ );
3372
+ });
3373
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
3374
+ return new Promise((resolve2, reject) => {
3375
+ rl.question(`
3376
+ Link which? [1-${artifacts.length}]: `, (answer) => {
3377
+ rl.close();
3378
+ const n = Number(answer.trim());
3379
+ if (!(Number.isInteger(n) && n >= 1 && n <= artifacts.length)) {
3380
+ reject(new Error(`Enter a number between 1 and ${artifacts.length}.`));
3381
+ return;
3382
+ }
3383
+ resolve2(artifacts[n - 1]);
3384
+ });
3385
+ });
3386
+ }
3387
+
2762
3388
  // src/login.ts
2763
3389
  import { createInterface as createInterface3 } from "readline";
2764
3390
  function apiUrl() {
@@ -2874,7 +3500,7 @@ function logout(opts) {
2874
3500
  }
2875
3501
 
2876
3502
  // src/message.ts
2877
- import path9 from "path";
3503
+ import path10 from "path";
2878
3504
  async function readStdin2() {
2879
3505
  if (process.stdin.isTTY) {
2880
3506
  return "";
@@ -2898,7 +3524,7 @@ async function editMessage(opts) {
2898
3524
  'Nothing to save. Pass the new text with --body "\u2026", or pipe it on stdin.'
2899
3525
  );
2900
3526
  }
2901
- const dir = path9.resolve(opts.dir);
3527
+ const dir = path10.resolve(opts.dir);
2902
3528
  const target = resolveArtifactTarget(dir, {
2903
3529
  apiKey: opts.apiKey,
2904
3530
  artifact: opts.artifact
@@ -2918,7 +3544,7 @@ async function deleteMessage(opts) {
2918
3544
  "Pass the message id to delete \u2014 get it from `artifact-cafe comments --json`."
2919
3545
  );
2920
3546
  }
2921
- const dir = path9.resolve(opts.dir);
3547
+ const dir = path10.resolve(opts.dir);
2922
3548
  const target = resolveArtifactTarget(dir, {
2923
3549
  apiKey: opts.apiKey,
2924
3550
  artifact: opts.artifact
@@ -2933,12 +3559,12 @@ async function deleteMessage(opts) {
2933
3559
  }
2934
3560
 
2935
3561
  // src/move.ts
2936
- import path10 from "path";
3562
+ import path11 from "path";
2937
3563
  async function move(opts) {
2938
3564
  if (Boolean(opts.folder) === Boolean(opts.unfiled)) {
2939
3565
  throw new Error("Pass exactly one of --folder <name-or-id> or --unfiled.");
2940
3566
  }
2941
- const dir = path10.resolve(opts.dir);
3567
+ const dir = path11.resolve(opts.dir);
2942
3568
  const target = resolveArtifactTarget(dir, {
2943
3569
  apiKey: opts.apiKey,
2944
3570
  artifact: opts.artifact
@@ -2959,8 +3585,8 @@ async function move(opts) {
2959
3585
  let folderId = null;
2960
3586
  let folderName = null;
2961
3587
  if (opts.folder) {
2962
- const data = await client.listFolders(artifact.workspaceId);
2963
- const matches = data.folders.filter(
3588
+ const data2 = await client.listFolders(artifact.workspaceId);
3589
+ const matches = data2.folders.filter(
2964
3590
  (candidate) => candidate.name === opts.folder || candidate.id === opts.folder
2965
3591
  );
2966
3592
  if (matches.length === 0) {
@@ -3028,9 +3654,9 @@ async function move(opts) {
3028
3654
  }
3029
3655
 
3030
3656
  // src/open.ts
3031
- import path11 from "path";
3657
+ import path12 from "path";
3032
3658
  async function openArtifact(opts) {
3033
- const dir = path11.resolve(opts.dir);
3659
+ const dir = path12.resolve(opts.dir);
3034
3660
  const target = resolveArtifactTarget(dir, {
3035
3661
  apiKey: opts.apiKey,
3036
3662
  artifact: opts.artifact
@@ -3048,7 +3674,7 @@ async function openArtifact(opts) {
3048
3674
  }
3049
3675
 
3050
3676
  // src/protect.ts
3051
- import path12 from "path";
3677
+ import path13 from "path";
3052
3678
  async function protect(opts) {
3053
3679
  if (opts.off && opts.password !== void 0) {
3054
3680
  throw new Error("Pass either --password or --off, not both.");
@@ -3065,7 +3691,7 @@ async function protect(opts) {
3065
3691
  throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
3066
3692
  }
3067
3693
  }
3068
- const dir = path12.resolve(opts.dir);
3694
+ const dir = path13.resolve(opts.dir);
3069
3695
  const target = resolveArtifactTarget(dir, {
3070
3696
  apiKey: opts.apiKey,
3071
3697
  artifact: opts.artifact
@@ -3090,7 +3716,7 @@ async function protect(opts) {
3090
3716
  }
3091
3717
 
3092
3718
  // src/reply.ts
3093
- import path13 from "path";
3719
+ import path14 from "path";
3094
3720
  async function readStdin3() {
3095
3721
  if (process.stdin.isTTY) {
3096
3722
  return "";
@@ -3114,7 +3740,7 @@ async function replyToThread(opts) {
3114
3740
  'Nothing to post. Pass the reply with --body "\u2026", or pipe it on stdin.'
3115
3741
  );
3116
3742
  }
3117
- const dir = path13.resolve(opts.dir);
3743
+ const dir = path14.resolve(opts.dir);
3118
3744
  const target = resolveArtifactTarget(dir, {
3119
3745
  apiKey: opts.apiKey,
3120
3746
  artifact: opts.artifact
@@ -3143,7 +3769,7 @@ async function replyToThread(opts) {
3143
3769
  }
3144
3770
 
3145
3771
  // src/resolve.ts
3146
- import path14 from "path";
3772
+ import path15 from "path";
3147
3773
  async function changeThreadStatus(opts) {
3148
3774
  const threadId = opts.thread?.trim();
3149
3775
  if (!threadId) {
@@ -3151,7 +3777,7 @@ async function changeThreadStatus(opts) {
3151
3777
  `Pass the thread id to ${opts.action} \u2014 get it from \`artifact-cafe comments --json\`.`
3152
3778
  );
3153
3779
  }
3154
- const dir = path14.resolve(opts.dir);
3780
+ const dir = path15.resolve(opts.dir);
3155
3781
  const target = resolveArtifactTarget(dir, {
3156
3782
  apiKey: opts.apiKey,
3157
3783
  artifact: opts.artifact
@@ -3188,7 +3814,7 @@ async function changeThreadStatus(opts) {
3188
3814
  }
3189
3815
 
3190
3816
  // src/review.ts
3191
- import path15 from "path";
3817
+ import path16 from "path";
3192
3818
  var OUTCOME = {
3193
3819
  off: "Plain artifact \u2014 the review tools are hidden entirely",
3194
3820
  open: "Open for review \u2014 anyone who can open it can comment",
@@ -3202,7 +3828,7 @@ async function review(opts) {
3202
3828
  );
3203
3829
  }
3204
3830
  const mode = parsed.data;
3205
- const dir = path15.resolve(opts.dir);
3831
+ const dir = path16.resolve(opts.dir);
3206
3832
  const target = resolveArtifactTarget(dir, {
3207
3833
  apiKey: opts.apiKey,
3208
3834
  artifact: opts.artifact
@@ -3224,10 +3850,10 @@ async function review(opts) {
3224
3850
  }
3225
3851
 
3226
3852
  // src/share.ts
3227
- import path16 from "path";
3853
+ import path17 from "path";
3228
3854
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3229
3855
  async function share(opts) {
3230
- const dir = path16.resolve(opts.dir);
3856
+ const dir = path17.resolve(opts.dir);
3231
3857
  const target = resolveArtifactTarget(dir, {
3232
3858
  apiKey: opts.apiKey,
3233
3859
  artifact: opts.artifact
@@ -3282,7 +3908,7 @@ General access: ${visibility}`);
3282
3908
  console.log("");
3283
3909
  }
3284
3910
  async function unshare(opts) {
3285
- const dir = path16.resolve(opts.dir);
3911
+ const dir = path17.resolve(opts.dir);
3286
3912
  const target = resolveArtifactTarget(dir, {
3287
3913
  apiKey: opts.apiKey,
3288
3914
  artifact: opts.artifact
@@ -3305,7 +3931,7 @@ async function unshare(opts) {
3305
3931
  }
3306
3932
 
3307
3933
  // src/status.ts
3308
- import path17 from "path";
3934
+ import path18 from "path";
3309
3935
  async function status(opts) {
3310
3936
  if (!AGENT_STATES.includes(opts.state)) {
3311
3937
  throw new Error(
@@ -3313,7 +3939,7 @@ async function status(opts) {
3313
3939
  );
3314
3940
  }
3315
3941
  const state = opts.state;
3316
- const dir = path17.resolve(opts.dir);
3942
+ const dir = path18.resolve(opts.dir);
3317
3943
  const target = resolveArtifactTarget(dir, {
3318
3944
  apiKey: opts.apiKey,
3319
3945
  artifact: opts.artifact
@@ -3339,40 +3965,40 @@ async function status(opts) {
3339
3965
  }
3340
3966
 
3341
3967
  // src/update.ts
3342
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
3968
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
3343
3969
  import { createRequire } from "module";
3344
3970
  import { homedir as homedir2 } from "os";
3345
- import path18 from "path";
3346
- import { z as z21 } from "zod";
3971
+ import path19 from "path";
3972
+ import { z as z24 } from "zod";
3347
3973
  var REGISTRY_LATEST_URL = "https://registry.npmjs.org/artifact-cafe/latest";
3348
3974
  var SKILL_SOURCE_URL = "https://raw.githubusercontent.com/Artifact-Cafe/skill/main/artifact-cafe/SKILL.md";
3349
3975
  var SKILL_UPDATE_COMMAND = "npx skills add artifact-cafe/skill --skill artifact-cafe -g";
3350
3976
  var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
3351
3977
  var FETCH_TIMEOUT_MS = 2e3;
3352
3978
  var SKILL_VERSION_PATTERN = /\*\*Skill version:\s*(\d+\.\d+\.\d+)\*\*/;
3353
- var cacheSchema = z21.object({
3354
- checkedAt: z21.number(),
3355
- cliLatest: z21.string().nullable(),
3356
- skillLatest: z21.string().nullable(),
3357
- version: z21.literal(1)
3979
+ var cacheSchema = z24.object({
3980
+ checkedAt: z24.number(),
3981
+ cliLatest: z24.string().nullable(),
3982
+ skillLatest: z24.string().nullable(),
3983
+ version: z24.literal(1)
3358
3984
  });
3359
3985
  function cliVersion() {
3360
3986
  const pkg = createRequire(import.meta.url)("../package.json");
3361
3987
  return pkg.version;
3362
3988
  }
3363
3989
  function cachePath() {
3364
- return path18.join(homedir2(), ".artifactcafe", "update-check.json");
3990
+ return path19.join(homedir2(), ".artifactcafe", "update-check.json");
3365
3991
  }
3366
3992
  function readCache() {
3367
3993
  try {
3368
- return cacheSchema.parse(JSON.parse(readFileSync5(cachePath(), "utf8")));
3994
+ return cacheSchema.parse(JSON.parse(readFileSync6(cachePath(), "utf8")));
3369
3995
  } catch {
3370
3996
  return null;
3371
3997
  }
3372
3998
  }
3373
3999
  function writeCache(cache) {
3374
4000
  try {
3375
- mkdirSync3(path18.dirname(cachePath()), { mode: 448, recursive: true });
4001
+ mkdirSync3(path19.dirname(cachePath()), { mode: 448, recursive: true });
3376
4002
  writeFileSync3(cachePath(), `${JSON.stringify(cache, null, 2)}
3377
4003
  `);
3378
4004
  } catch {
@@ -3409,7 +4035,7 @@ async function fetchText(url) {
3409
4035
  }
3410
4036
  async function fetchLatestVersions(previous) {
3411
4037
  const [cli, skill] = await Promise.all([
3412
- fetchJson(REGISTRY_LATEST_URL).then((body) => z21.object({ version: z21.string() }).parse(body).version).catch(() => null),
4038
+ fetchJson(REGISTRY_LATEST_URL).then((body) => z24.object({ version: z24.string() }).parse(body).version).catch(() => null),
3413
4039
  fetchText(SKILL_SOURCE_URL).then((body) => SKILL_VERSION_PATTERN.exec(body)?.[1] ?? null).catch(() => null)
3414
4040
  ]);
3415
4041
  return {
@@ -3425,12 +4051,12 @@ function installedSkillVersions() {
3425
4051
  const versions = [];
3426
4052
  for (const root of roots) {
3427
4053
  for (const dir of dirs) {
3428
- const file = path18.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
4054
+ const file = path19.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
3429
4055
  if (!existsSync3(file)) {
3430
4056
  continue;
3431
4057
  }
3432
4058
  try {
3433
- const found = SKILL_VERSION_PATTERN.exec(readFileSync5(file, "utf8"));
4059
+ const found = SKILL_VERSION_PATTERN.exec(readFileSync6(file, "utf8"));
3434
4060
  if (found?.[1]) {
3435
4061
  versions.push(found[1]);
3436
4062
  }
@@ -3499,19 +4125,19 @@ async function workspaces(opts) {
3499
4125
  `Listing workspaces needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
3500
4126
  );
3501
4127
  }
3502
- const data = await createApiClient(apiUrl2, key).listWorkspaces();
4128
+ const data2 = await createApiClient(apiUrl2, key).listWorkspaces();
3503
4129
  if (opts.json) {
3504
- console.log(JSON.stringify(data, null, 2));
4130
+ console.log(JSON.stringify(data2, null, 2));
3505
4131
  return;
3506
4132
  }
3507
- if (data.workspaces.length === 0) {
4133
+ if (data2.workspaces.length === 0) {
3508
4134
  console.log("No workspaces yet \u2014 create one from the dashboard.");
3509
4135
  return;
3510
4136
  }
3511
- const plural = data.workspaces.length === 1 ? "" : "s";
3512
- console.log(`${data.workspaces.length} workspace${plural}
4137
+ const plural = data2.workspaces.length === 1 ? "" : "s";
4138
+ console.log(`${data2.workspaces.length} workspace${plural}
3513
4139
  `);
3514
- for (const workspace of data.workspaces) {
4140
+ for (const workspace of data2.workspaces) {
3515
4141
  printWorkspace(workspace);
3516
4142
  }
3517
4143
  }
@@ -3540,7 +4166,7 @@ function readStringArg(argv, flag) {
3540
4166
  var publish2 = defineCommand({
3541
4167
  args: {
3542
4168
  "api-key": {
3543
- description: "Publish with an account API key (default: stored login)",
4169
+ description: "Publish with an account key or agent token (default: env or login)",
3544
4170
  type: "string"
3545
4171
  },
3546
4172
  artifact: {
@@ -3673,7 +4299,7 @@ var list2 = defineCommand({
3673
4299
  }
3674
4300
  },
3675
4301
  meta: {
3676
- description: "List the artifacts your account owns",
4302
+ description: "List the artifacts your account can publish to",
3677
4303
  name: "list"
3678
4304
  },
3679
4305
  async run({ args }) {
@@ -3827,7 +4453,7 @@ var link2 = defineCommand({
3827
4453
  }
3828
4454
  },
3829
4455
  meta: {
3830
- description: "Link a folder to one of your artifacts (pick from your account)",
4456
+ description: "Link a folder to an artifact you can publish to (yours or a teammate's)",
3831
4457
  name: "link"
3832
4458
  },
3833
4459
  async run({ args }) {
@@ -3848,7 +4474,7 @@ var link2 = defineCommand({
3848
4474
  var comments2 = defineCommand({
3849
4475
  args: {
3850
4476
  "api-key": {
3851
- description: "Authorize with an account API key (default: stored login)",
4477
+ description: "Authorize with an account key or agent token (default: env or login)",
3852
4478
  type: "string"
3853
4479
  },
3854
4480
  artifact: {
@@ -3912,7 +4538,7 @@ var comments2 = defineCommand({
3912
4538
  var listen = defineCommand({
3913
4539
  args: {
3914
4540
  "api-key": {
3915
- description: "Authorize with an account API key (default: stored login)",
4541
+ description: "Authorize with an account key or agent token (default: env or login)",
3916
4542
  type: "string"
3917
4543
  },
3918
4544
  artifact: {
@@ -3972,7 +4598,7 @@ var listen = defineCommand({
3972
4598
  var statusCmd = defineCommand({
3973
4599
  args: {
3974
4600
  "api-key": {
3975
- description: "Authorize with an account API key (default: stored login)",
4601
+ description: "Authorize with an account key or agent token (default: env or login)",
3976
4602
  type: "string"
3977
4603
  },
3978
4604
  artifact: {
@@ -4013,7 +4639,7 @@ var statusCmd = defineCommand({
4013
4639
  var protect2 = defineCommand({
4014
4640
  args: {
4015
4641
  "api-key": {
4016
- description: "Authorize with an account API key (default: stored login)",
4642
+ description: "Authorize with an account key or agent token (default: env or login)",
4017
4643
  type: "string"
4018
4644
  },
4019
4645
  artifact: {
@@ -4052,7 +4678,7 @@ var protect2 = defineCommand({
4052
4678
  var review2 = defineCommand({
4053
4679
  args: {
4054
4680
  "api-key": {
4055
- description: "Authorize with an account API key (default: stored login)",
4681
+ description: "Authorize with an account key or agent token (default: env or login)",
4056
4682
  type: "string"
4057
4683
  },
4058
4684
  artifact: {
@@ -4093,7 +4719,7 @@ var review2 = defineCommand({
4093
4719
  var threadStatusCommand = (action) => defineCommand({
4094
4720
  args: {
4095
4721
  "api-key": {
4096
- description: "Authorize with an account API key (default: stored login)",
4722
+ description: "Authorize with an account key or agent token (default: env or login)",
4097
4723
  type: "string"
4098
4724
  },
4099
4725
  artifact: {
@@ -4142,7 +4768,7 @@ var ack = threadStatusCommand("ack");
4142
4768
  var reply = defineCommand({
4143
4769
  args: {
4144
4770
  "api-key": {
4145
- description: "Authorize with an account API key (default: stored login)",
4771
+ description: "Authorize with an account key or agent token (default: env or login)",
4146
4772
  type: "string"
4147
4773
  },
4148
4774
  artifact: {
@@ -4268,7 +4894,7 @@ var comment2 = defineCommand({
4268
4894
  var edit = defineCommand({
4269
4895
  args: {
4270
4896
  "api-key": {
4271
- description: "Authorize with an account API key (default: stored login)",
4897
+ description: "Authorize with an account key or agent token (default: env or login)",
4272
4898
  type: "string"
4273
4899
  },
4274
4900
  artifact: {
@@ -4311,7 +4937,7 @@ var edit = defineCommand({
4311
4937
  var del = defineCommand({
4312
4938
  args: {
4313
4939
  "api-key": {
4314
- description: "Authorize with an account API key (default: stored login)",
4940
+ description: "Authorize with an account key or agent token (default: env or login)",
4315
4941
  type: "string"
4316
4942
  },
4317
4943
  artifact: {
@@ -4480,6 +5106,110 @@ var dashboard = defineCommand({
4480
5106
  }
4481
5107
  }
4482
5108
  });
5109
+ var dataLsCommand = defineCommand({
5110
+ args: {
5111
+ collection: {
5112
+ description: "Collection name",
5113
+ required: true,
5114
+ type: "positional"
5115
+ },
5116
+ dir: {
5117
+ default: ".",
5118
+ description: "Published directory",
5119
+ type: "positional"
5120
+ },
5121
+ json: { description: "Machine-readable output", type: "boolean" },
5122
+ limit: { description: "Max records to show", type: "string" }
5123
+ },
5124
+ meta: {
5125
+ description: "List records in a collection",
5126
+ name: "ls"
5127
+ },
5128
+ async run({ args }) {
5129
+ try {
5130
+ await dataLs({
5131
+ collection: args.collection,
5132
+ dir: args.dir,
5133
+ json: Boolean(args.json),
5134
+ limit: args.limit === void 0 ? void 0 : Number(args.limit)
5135
+ });
5136
+ } catch (err) {
5137
+ printCliError(err, Boolean(args.json));
5138
+ process.exit(1);
5139
+ }
5140
+ }
5141
+ });
5142
+ var dataExportCommand = defineCommand({
5143
+ args: {
5144
+ dir: {
5145
+ default: ".",
5146
+ description: "Published directory",
5147
+ type: "positional"
5148
+ },
5149
+ // Output is always JSON; accepted so --json works uniformly across
5150
+ // every command (SPEC-2 milestone 20 → CLI integration).
5151
+ json: {
5152
+ description: "Machine-readable output (always on)",
5153
+ type: "boolean"
5154
+ }
5155
+ },
5156
+ meta: {
5157
+ description: "Export every collection's records as JSON",
5158
+ name: "export"
5159
+ },
5160
+ async run({ args }) {
5161
+ try {
5162
+ await dataExport({ dir: args.dir });
5163
+ } catch (err) {
5164
+ printCliError(err, true);
5165
+ process.exit(1);
5166
+ }
5167
+ }
5168
+ });
5169
+ var dataClearCommand = defineCommand({
5170
+ args: {
5171
+ collection: {
5172
+ description: "Collection name",
5173
+ required: true,
5174
+ type: "positional"
5175
+ },
5176
+ dir: {
5177
+ default: ".",
5178
+ description: "Published directory",
5179
+ type: "positional"
5180
+ },
5181
+ json: { description: "Machine-readable output", type: "boolean" },
5182
+ yes: { description: "Confirm deleting every record", type: "boolean" }
5183
+ },
5184
+ meta: {
5185
+ description: "Delete every record in a collection",
5186
+ name: "clear"
5187
+ },
5188
+ async run({ args }) {
5189
+ try {
5190
+ await dataClear({
5191
+ collection: args.collection,
5192
+ dir: args.dir,
5193
+ json: Boolean(args.json),
5194
+ yes: Boolean(args.yes)
5195
+ });
5196
+ } catch (err) {
5197
+ printCliError(err, Boolean(args.json));
5198
+ process.exit(1);
5199
+ }
5200
+ }
5201
+ });
5202
+ var data = defineCommand({
5203
+ meta: {
5204
+ description: "Read or clear the artifact's shared records",
5205
+ name: "data"
5206
+ },
5207
+ subCommands: {
5208
+ clear: dataClearCommand,
5209
+ export: dataExportCommand,
5210
+ ls: dataLsCommand
5211
+ }
5212
+ });
4483
5213
  var main = defineCommand({
4484
5214
  meta: {
4485
5215
  description: "Publish AI-generated artifacts for review \u2014 https://artifact.cafe",
@@ -4502,6 +5232,7 @@ var main = defineCommand({
4502
5232
  comment: comment2,
4503
5233
  comments: comments2,
4504
5234
  dashboard,
5235
+ data,
4505
5236
  delete: del,
4506
5237
  edit,
4507
5238
  folders: folders2,