artifact-cafe 0.0.12 → 0.0.14

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 +998 -510
  2. package/package.json +12 -1
package/dist/index.js CHANGED
@@ -83,6 +83,74 @@ var artifactListResponseSchema = z.object({
83
83
  artifacts: z.array(artifactListItemSchema)
84
84
  });
85
85
 
86
+ // ../shared/src/agents.ts
87
+ var AGENT_BRANDS = [
88
+ {
89
+ id: "claude-code",
90
+ label: "Claude Code",
91
+ match: ["claude"],
92
+ short: "Claude"
93
+ },
94
+ {
95
+ id: "codex",
96
+ label: "OpenAI Codex",
97
+ match: ["codex"],
98
+ short: "Codex"
99
+ },
100
+ { id: "cursor", label: "Cursor", match: ["cursor"], short: "Cursor" },
101
+ {
102
+ id: "copilot",
103
+ label: "GitHub Copilot",
104
+ match: ["copilot"],
105
+ short: "Copilot"
106
+ },
107
+ {
108
+ id: "gemini-cli",
109
+ label: "Gemini CLI",
110
+ match: ["gemini"],
111
+ short: "Gemini"
112
+ },
113
+ { id: "opencode", label: "opencode", match: ["opencode"], short: "opencode" },
114
+ {
115
+ id: "openclaw",
116
+ label: "OpenClaw",
117
+ match: ["openclaw"],
118
+ short: "OpenClaw"
119
+ },
120
+ { id: "hermes", label: "Hermes", match: ["hermes"], short: "Hermes" }
121
+ ];
122
+ function normalizeAgent(raw) {
123
+ const value = raw?.trim().toLowerCase();
124
+ if (!value) {
125
+ return null;
126
+ }
127
+ return AGENT_BRANDS.find((brand) => brand.id === value) ?? AGENT_BRANDS.find((brand) => brand.match.some((m) => value.includes(m))) ?? null;
128
+ }
129
+ var ENV_FINGERPRINTS = [
130
+ { id: "claude-code", prefixes: ["CLAUDECODE", "CLAUDE_CODE_"] },
131
+ { id: "codex", prefixes: ["CODEX_"] },
132
+ { id: "cursor", prefixes: ["CURSOR_"] },
133
+ { id: "copilot", prefixes: ["COPILOT_", "GITHUB_COPILOT_"] },
134
+ { id: "gemini-cli", prefixes: ["GEMINI_CLI", "GEMINI_SANDBOX"] },
135
+ { id: "opencode", prefixes: ["OPENCODE"] }
136
+ ];
137
+ function detectAgentFromEnv(env) {
138
+ const explicit = env.ARTIFACT_CAFE_AGENT?.trim();
139
+ if (explicit) {
140
+ return explicit;
141
+ }
142
+ const keys = Object.keys(env).filter((key) => env[key]);
143
+ for (const { id, prefixes } of ENV_FINGERPRINTS) {
144
+ const hit = keys.some(
145
+ (key) => prefixes.some((prefix) => key.startsWith(prefix))
146
+ );
147
+ if (hit) {
148
+ return AGENT_BRANDS.find((brand) => brand.id === id)?.label;
149
+ }
150
+ }
151
+ return normalizeAgent(env.AI_AGENT)?.label;
152
+ }
153
+
86
154
  // ../shared/src/avatars.ts
87
155
  import { z as z2 } from "zod";
88
156
  var AVATAR_CONTENT_TYPES = [
@@ -100,18 +168,27 @@ var workspaceAvatarResponseSchema = z2.object({
100
168
  });
101
169
 
102
170
  // ../shared/src/comments.ts
103
- import { z as z5 } from "zod";
171
+ import { z as z6 } from "zod";
172
+
173
+ // ../shared/src/emoji.ts
174
+ import { z as z3 } from "zod";
175
+ var SINGLE_EMOJI = new RegExp("^\\p{RGI_Emoji}$", "v");
176
+ var REDUNDANT_VS16 = new RegExp("(\\p{Emoji_Presentation})\\uFE0F", "gu");
177
+ function canonicalizeEmoji(s) {
178
+ return s.replace(REDUNDANT_VS16, "$1");
179
+ }
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.");
104
181
 
105
182
  // ../shared/src/publish.ts
106
- import { z as z4 } from "zod";
183
+ import { z as z5 } from "zod";
107
184
 
108
185
  // ../shared/src/review-mode.ts
109
- import { z as z3 } from "zod";
110
- var reviewModeSchema = z3.enum(["open", "readonly", "off"]);
111
- var setReviewModeSchema = z3.object({
186
+ import { z as z4 } from "zod";
187
+ var reviewModeSchema = z4.enum(["open", "readonly", "off"]);
188
+ var setReviewModeSchema = z4.object({
112
189
  mode: reviewModeSchema
113
190
  });
114
- var reviewModeResponseSchema = z3.object({
191
+ var reviewModeResponseSchema = z4.object({
115
192
  mode: reviewModeSchema
116
193
  });
117
194
 
@@ -128,260 +205,275 @@ function isSafeArtifactPath(p) {
128
205
  }
129
206
  return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
130
207
  }
131
- var manifestFileSchema = z4.object({
132
- hash: z4.string().regex(/^sha256_[a-f0-9]{64}$/),
133
- mimeType: z4.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
134
- path: z4.string().min(1).max(1024).refine(isSafeArtifactPath, {
208
+ var CARD_DESCRIPTION_MAX = 300;
209
+ 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, {
135
214
  message: "path must be a safe relative path"
136
215
  }),
137
- size: z4.number().int().positive()
138
- });
139
- var publishSourceSchema = z4.object({
140
- agent: z4.string().optional(),
141
- runId: z4.string().optional(),
142
- type: z4.literal("cli")
143
- });
144
- var createPublishSessionSchema = z4.object({
145
- artifactId: z4.string().nullable(),
146
- entryPath: z4.string().optional(),
147
- files: z4.array(manifestFileSchema).min(1).max(1e3),
216
+ size: z5.number().int().positive()
217
+ });
218
+ var publishSourceSchema = z5.object({
219
+ agent: z5.string().optional(),
220
+ runId: z5.string().optional(),
221
+ type: z5.literal("cli")
222
+ });
223
+ var createPublishSessionSchema = z5.object({
224
+ artifactId: z5.string().nullable(),
225
+ // One-line share-card summary (`--description`). Overrides whatever the
226
+ // entry HTML declares; leave it off and the API reads the document's own
227
+ // `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),
148
231
  // SPEC-2 §6.4 — workspace placement is available only to authenticated
149
232
  // members; the API enforces membership and folder ownership. folderId
150
233
  // without workspaceId resolves against the account's personal workspace.
151
- folderId: z4.string().min(1).optional(),
234
+ folderId: z5.string().min(1).optional(),
152
235
  // Bullet-point changelog for this version, authored by the publisher.
153
236
  // Optional on every publish (incl. v1); stored immutably on the version.
154
- notes: z4.string().max(4e3).optional(),
237
+ notes: z5.string().max(4e3).optional(),
155
238
  source: publishSourceSchema,
156
- title: z4.string().max(200).optional(),
157
- workspaceId: z4.string().min(1).optional()
158
- });
159
- var publishSessionResponseSchema = z4.object({
160
- artifactId: z4.string(),
161
- expiresAt: z4.string(),
162
- filesToUpload: z4.array(
163
- z4.object({
164
- method: z4.literal("PUT"),
165
- path: z4.string(),
166
- uploadUrl: z4.string().url()
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()
167
250
  })
168
251
  ),
169
- publishSessionId: z4.string(),
170
- skippedFiles: z4.array(z4.string()),
171
- versionId: z4.string()
252
+ publishSessionId: z5.string(),
253
+ skippedFiles: z5.array(z5.string()),
254
+ versionId: z5.string()
172
255
  });
173
- var finalizeResponseSchema = z4.object({
174
- artifactId: z4.string(),
256
+ var finalizeResponseSchema = z5.object({
257
+ artifactId: z5.string(),
175
258
  // API-key publishes create the artifact already claimed (SPEC §5.4) —
176
259
  // the CLI uses this to tell "claim URL pending" from "already yours".
177
- claimed: z4.boolean(),
260
+ claimed: z5.boolean(),
178
261
  // Returned only on first publish of a new artifact (one-time).
179
- claimUrl: z4.string().url().optional(),
180
- publishToken: z4.string().optional(),
181
- url: z4.string().url(),
182
- versionId: z4.string(),
183
- versionNumber: z4.number().int().positive()
184
- });
185
- var artifactResponseSchema = z4.object({
186
- claimed: z4.boolean(),
187
- currentVersion: z4.object({
188
- entryUrl: z4.string().url(),
189
- id: z4.string(),
190
- versionNumber: z4.number().int().positive()
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(),
267
+ // SPEC-2 §5.2 — who can open the review URL. A publish into a folder
268
+ // inherits that folder's visibility, so this is the only place the CLI
269
+ // learns that a link it just printed is members-only. Optional so a new
270
+ // CLI against an older API degrades to "unknown" instead of failing the
271
+ // 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()
191
280
  }).nullable(),
281
+ // Custom icon, or null to render the default document glyph.
282
+ emoji: z5.string().nullable(),
192
283
  // SPEC-2 §6.4 — placement + visibility surface on the artifact.
193
- folderId: z4.string().nullable(),
194
- id: z4.string(),
284
+ folderId: z5.string().nullable(),
285
+ id: z5.string(),
195
286
  // SPEC-2 §5.8 — what a viewer may do once in (open / readonly / off).
196
287
  reviewMode: reviewModeSchema,
197
- slug: z4.string(),
198
- title: z4.string(),
199
- visibility: z4.enum(["public", "unlisted", "workspace", "private"]),
200
- workspaceId: z4.string().nullable()
288
+ slug: z5.string(),
289
+ title: z5.string(),
290
+ visibility: z5.enum(["public", "unlisted", "workspace", "private"]),
291
+ workspaceId: z5.string().nullable()
201
292
  });
202
293
 
203
294
  // ../shared/src/comments.ts
204
- var anchorPageSchema = z5.string().max(1024).refine(isSafeArtifactPath, {
295
+ var anchorPageSchema = z6.string().max(1024).refine(isSafeArtifactPath, {
205
296
  message: "page must be a safe relative path"
206
297
  }).optional();
207
- var elementSnapshotSchema = z5.object({
208
- classes: z5.string().max(256).optional(),
209
- id: z5.string().max(256).optional(),
210
- tag: z5.string().min(1).max(128),
211
- text: z5.string().max(200).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()
212
303
  });
213
- var pointAnchorSchema = z5.object({
304
+ var pointAnchorSchema = z6.object({
214
305
  element: elementSnapshotSchema.optional(),
215
306
  // elementPath is the primary anchor; x/y are offsets within the
216
307
  // element's box, normalized 0..1 (SPEC.md §8.1).
217
- elementPath: z5.string().max(2048),
308
+ elementPath: z6.string().max(2048),
218
309
  page: anchorPageSchema,
219
- type: z5.literal("point"),
220
- viewportHeight: z5.number().int().positive().max(1e5),
221
- viewportWidth: z5.number().int().positive().max(1e5),
222
- x: z5.number().min(0).max(1),
223
- y: z5.number().min(0).max(1)
224
- });
225
- var textAnchorSchema = z5.object({
226
- elementPath: z5.string().max(2048),
227
- endOffset: z5.number().int().nonnegative().max(1e8),
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),
228
319
  page: anchorPageSchema,
229
- prefix: z5.string().max(200).optional(),
230
- quote: z5.string().min(1).max(1e3),
231
- startOffset: z5.number().int().nonnegative().max(1e8),
232
- suffix: z5.string().max(200).optional(),
233
- type: z5.literal("text")
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")
234
325
  });
235
- var pageAnchorSchema = z5.object({
326
+ var pageAnchorSchema = z6.object({
236
327
  page: anchorPageSchema,
237
- type: z5.literal("page")
328
+ type: z6.literal("page")
238
329
  });
239
- var anchorSchema = z5.discriminatedUnion("type", [
330
+ var anchorSchema = z6.discriminatedUnion("type", [
240
331
  pointAnchorSchema,
241
332
  textAnchorSchema,
242
333
  pageAnchorSchema
243
334
  ]);
244
- var displayNameSchema = z5.string().trim().min(1).max(64);
245
- var guestSchema = z5.object({
335
+ var displayNameSchema = z6.string().trim().min(1).max(64);
336
+ var guestSchema = z6.object({
246
337
  displayName: displayNameSchema,
247
- guestId: z5.string().min(16)
338
+ guestId: z6.string().min(16)
248
339
  });
249
- var viaLabelSchema = z5.string().trim().min(1).max(64);
250
- var createCommentThreadSchema = z5.object({
340
+ var viaLabelSchema = z6.string().trim().min(1).max(64);
341
+ var createCommentThreadSchema = z6.object({
251
342
  anchor: anchorSchema,
252
- body: z5.string().min(1).max(1e4),
343
+ body: z6.string().min(1).max(1e4),
253
344
  guest: guestSchema.optional(),
254
- versionId: z5.string(),
345
+ versionId: z6.string(),
255
346
  via: viaLabelSchema.optional()
256
347
  });
257
- var createCommentMessageSchema = z5.object({
258
- body: z5.string().min(1).max(1e4),
348
+ var createCommentMessageSchema = z6.object({
349
+ body: z6.string().min(1).max(1e4),
259
350
  guest: guestSchema.optional(),
260
351
  via: viaLabelSchema.optional()
261
352
  });
262
- var editCommentMessageSchema = z5.object({
263
- body: z5.string().min(1).max(1e4),
353
+ var editCommentMessageSchema = z6.object({
354
+ body: z6.string().min(1).max(1e4),
264
355
  guest: guestSchema.optional()
265
356
  });
266
- var threadActionSchema = z5.object({
357
+ var threadActionSchema = z6.object({
267
358
  guest: guestSchema.optional()
268
359
  });
269
- var SINGLE_EMOJI = new RegExp("^\\p{RGI_Emoji}$", "v");
270
- var REDUNDANT_VS16 = new RegExp("(\\p{Emoji_Presentation})\\uFE0F", "gu");
271
- function canonicalizeEmoji(s) {
272
- return s.replace(REDUNDANT_VS16, "$1");
273
- }
274
- var reactionEmojiSchema = z5.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}");
275
- var toggleReactionSchema = z5.object({
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({
276
362
  emoji: reactionEmojiSchema,
277
363
  guest: guestSchema.optional()
278
364
  });
279
- var commentAuthorSchema = z5.object({
280
- avatarUrl: z5.string().nullable(),
281
- displayName: z5.string(),
282
- guestId: z5.string().nullable(),
283
- isViewer: z5.boolean(),
284
- kind: z5.enum(["member", "guest"]),
365
+ var commentAgentSchema = z6.object({
366
+ id: z6.string().nullable(),
367
+ label: z6.string()
368
+ });
369
+ var commentAuthorSchema = z6.object({
370
+ 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"]),
285
376
  // Optional "via" attribution (§6.4). Null for plain comments; a bounded
286
- // label the shell appends after the display name.
287
- via: z5.string().nullable()
377
+ // label the shell appends after the display name. Defaulted for the same
378
+ // reason as `agent`: a server predating §6.4 omits the key entirely.
379
+ via: z6.string().nullable().default(null)
288
380
  });
289
- var messageReactionSchema = z5.object({
290
- emoji: z5.string(),
291
- reactors: z5.array(commentAuthorSchema)
381
+ var messageReactionSchema = z6.object({
382
+ emoji: z6.string(),
383
+ reactors: z6.array(commentAuthorSchema)
292
384
  });
293
- var commentMessageSchema = z5.object({
385
+ var commentMessageSchema = z6.object({
294
386
  author: commentAuthorSchema,
295
387
  // Empty string for a deleted message — the body is stripped server-side
296
388
  // (§6.7.1 tombstone), never sent to the client and hidden there.
297
- body: z5.string(),
298
- createdAt: z5.string(),
389
+ body: z6.string(),
390
+ createdAt: z6.string(),
299
391
  // Soft-deleted (§6.7.1). A tombstone keeps thread structure and replies;
300
392
  // the shell renders "Comment deleted" instead of the (absent) body.
301
- deleted: z5.boolean(),
393
+ deleted: z6.boolean(),
302
394
  // Last edit time (§6.7.1), or null if never edited — drives the "edited"
303
395
  // badge. No edit history is kept.
304
- editedAt: z5.string().nullable(),
305
- id: z5.string(),
396
+ editedAt: z6.string().nullable(),
397
+ id: z6.string(),
306
398
  // Defaulted, not required: a new CLI parsing an older server's
307
399
  // response must not fail on the missing key.
308
- reactions: z5.array(messageReactionSchema).default([])
400
+ reactions: z6.array(messageReactionSchema).default([])
309
401
  });
310
- var threadActorSchema = z5.object({
311
- displayName: z5.string(),
312
- kind: z5.enum(["member", "guest", "agent"])
402
+ var threadActorSchema = z6.object({
403
+ displayName: z6.string(),
404
+ kind: z6.enum(["member", "guest", "agent"])
313
405
  });
314
- var commentThreadSchema = z5.object({
406
+ var commentThreadSchema = z6.object({
315
407
  // §6.6.1 — set while open to signal "seen, working on it"; cleared on
316
408
  // reopen, kept (but superseded in display) once resolved. Defaulted so
317
409
  // clients parsing an older server's response don't fail.
318
- acknowledgedAt: z5.string().nullable().default(null),
410
+ acknowledgedAt: z6.string().nullable().default(null),
319
411
  acknowledgedBy: threadActorSchema.nullable().default(null),
320
412
  anchor: anchorSchema,
321
- artifactId: z5.string(),
322
- createdAt: z5.string(),
413
+ artifactId: z6.string(),
414
+ createdAt: z6.string(),
323
415
  createdBy: commentAuthorSchema,
324
- id: z5.string(),
325
- messages: z5.array(commentMessageSchema),
416
+ id: z6.string(),
417
+ messages: z6.array(commentMessageSchema),
326
418
  // Null on threads resolved before actor recording existed, and always
327
419
  // null while open.
328
420
  resolvedBy: threadActorSchema.nullable().default(null),
329
- status: z5.enum(["open", "resolved"]),
330
- versionId: z5.string()
421
+ status: z6.enum(["open", "resolved"]),
422
+ versionId: z6.string()
331
423
  });
332
- var commentThreadListSchema = z5.object({
333
- threads: z5.array(commentThreadSchema)
424
+ var commentThreadListSchema = z6.object({
425
+ threads: z6.array(commentThreadSchema)
334
426
  });
335
- var commentExportQuerySchema = z5.object({
336
- status: z5.enum(["open", "resolved", "all"]).default("open"),
337
- version: z5.enum(["current", "all"]).default("current")
427
+ var commentExportQuerySchema = z6.object({
428
+ status: z6.enum(["open", "resolved", "all"]).default("open"),
429
+ version: z6.enum(["current", "all"]).default("current")
338
430
  });
339
- var exportReactionSchema = z5.object({
340
- count: z5.number().int().positive(),
341
- emoji: z5.string()
431
+ var exportReactionSchema = z6.object({
432
+ count: z6.number().int().positive(),
433
+ emoji: z6.string()
342
434
  });
343
- var commentExportReplySchema = z5.object({
344
- author: z5.string(),
345
- authorKind: z5.enum(["member", "guest"]),
346
- body: z5.string(),
347
- createdAt: z5.string(),
435
+ var commentExportReplySchema = z6.object({
436
+ author: z6.string(),
437
+ authorKind: z6.enum(["member", "guest"]),
438
+ body: z6.string(),
439
+ createdAt: z6.string(),
348
440
  // Soft-delete tombstone (§6.7.1): body is "" when true.
349
- deleted: z5.boolean(),
350
- editedAt: z5.string().nullable(),
441
+ deleted: z6.boolean(),
442
+ editedAt: z6.string().nullable(),
351
443
  // The message id — what `edit`/`delete` target (§5.1).
352
- id: z5.string(),
353
- reactions: z5.array(exportReactionSchema).optional()
444
+ id: z6.string(),
445
+ reactions: z6.array(exportReactionSchema).optional()
354
446
  });
355
- var commentExportThreadSchema = z5.object({
447
+ var commentExportThreadSchema = z6.object({
356
448
  // §6.6.1 — lets a polling agent skip threads it already acknowledged
357
449
  // (acknowledged threads stay in status=open exports on purpose).
358
- acknowledgedAt: z5.string().nullable().default(null),
450
+ acknowledgedAt: z6.string().nullable().default(null),
359
451
  anchor: anchorSchema,
360
- author: z5.string(),
361
- authorKind: z5.enum(["member", "guest"]),
362
- body: z5.string(),
363
- createdAt: z5.string(),
364
- deleted: z5.boolean(),
365
- editedAt: z5.string().nullable(),
366
- id: z5.string(),
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(),
367
459
  // The opening message's id (distinct from the thread `id`) — `edit`/
368
460
  // `delete` operate on messages, so the CLI needs it to target the first
369
461
  // comment, not just the replies.
370
- messageId: z5.string(),
371
- reactions: z5.array(exportReactionSchema).optional(),
372
- replies: z5.array(commentExportReplySchema),
462
+ messageId: z6.string(),
463
+ reactions: z6.array(exportReactionSchema).optional(),
464
+ replies: z6.array(commentExportReplySchema),
373
465
  resolvedBy: threadActorSchema.nullable().default(null),
374
- status: z5.enum(["open", "resolved"]),
375
- versionNumber: z5.number().int().positive()
466
+ status: z6.enum(["open", "resolved"]),
467
+ versionNumber: z6.number().int().positive()
376
468
  });
377
- var commentExportSchema = z5.object({
378
- artifactId: z5.string(),
379
- comments: z5.array(commentExportThreadSchema),
469
+ var commentExportSchema = z6.object({
470
+ artifactId: z6.string(),
471
+ comments: z6.array(commentExportThreadSchema),
380
472
  // The artifact's current version (null before the first finalize).
381
- versionNumber: z5.number().int().positive().nullable()
473
+ versionNumber: z6.number().int().positive().nullable()
382
474
  });
383
- var commentReadResponseSchema = z5.object({
384
- lastSeenAt: z5.string()
475
+ var commentReadResponseSchema = z6.object({
476
+ lastSeenAt: z6.string()
385
477
  });
386
478
  function elementLabel(el) {
387
479
  if (el.id) {
@@ -392,491 +484,528 @@ function elementLabel(el) {
392
484
  }
393
485
 
394
486
  // ../shared/src/errors.ts
395
- import { z as z6 } from "zod";
396
- var apiErrorSchema = z6.object({
397
- code: z6.string(),
398
- docsUrl: z6.string().url(),
399
- message: z6.string(),
400
- retryAfter: z6.number().int().positive().optional()
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()
401
493
  });
402
494
 
403
495
  // ../shared/src/feedback.ts
404
- import { z as z7 } from "zod";
405
- var feedbackRequestSchema = z7.object({
406
- email: z7.string().trim().toLowerCase().email().optional().or(z7.literal("")),
407
- message: z7.string().trim().min(1).max(4e3)
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)
408
500
  });
409
- var feedbackResponseSchema = z7.object({
410
- sent: z7.literal(true)
501
+ var feedbackResponseSchema = z8.object({
502
+ sent: z8.literal(true)
411
503
  });
412
504
 
413
505
  // ../shared/src/grants.ts
414
- import { z as z8 } from "zod";
415
- var grantRoleSchema = z8.enum(["viewer"]);
416
- var createGrantSchema = z8.object({
417
- email: z8.string().trim().toLowerCase().email().optional(),
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(),
418
510
  role: grantRoleSchema.default("viewer"),
419
- userId: z8.string().min(1).optional()
511
+ userId: z9.string().min(1).optional()
420
512
  }).refine((body) => Boolean(body.email) !== Boolean(body.userId), {
421
513
  message: "provide exactly one of email or userId"
422
514
  });
423
- var grantSchema = z8.object({
424
- createdAt: z8.string(),
425
- email: z8.string().email().nullable(),
426
- expiresAt: z8.string().nullable(),
427
- id: z8.string(),
515
+ var grantSchema = z9.object({
516
+ createdAt: z9.string(),
517
+ email: z9.string().email().nullable(),
518
+ expiresAt: z9.string().nullable(),
519
+ id: z9.string(),
428
520
  // The subject's avatar once they have an account; null otherwise.
429
- image: z8.string().nullable(),
521
+ image: z9.string().nullable(),
430
522
  // Present on the invited account after redemption / when granting an
431
523
  // existing user; null for a still-pending email invite.
432
- name: z8.string().nullable(),
433
- redeemedAt: z8.string().nullable(),
524
+ name: z9.string().nullable(),
525
+ redeemedAt: z9.string().nullable(),
434
526
  role: grantRoleSchema,
435
527
  // Shown as "· pending" beside the role until the link is redeemed.
436
- status: z8.enum(["pending", "active"]),
528
+ status: z9.enum(["pending", "active"]),
437
529
  // Display fragment of the live invitation token, or null once redeemed.
438
- tokenPrefix: z8.string().nullable(),
439
- userId: z8.string().nullable()
530
+ tokenPrefix: z9.string().nullable(),
531
+ userId: z9.string().nullable()
440
532
  });
441
533
  var createdGrantSchema = grantSchema.extend({
442
- url: z8.string().url()
534
+ url: z9.string().url()
443
535
  });
444
- var grantListResponseSchema = z8.object({
445
- grants: z8.array(grantSchema),
536
+ var grantListResponseSchema = z9.object({
537
+ grants: z9.array(grantSchema),
446
538
  // The artifact's current general-access value, so the share surface can
447
539
  // render the "General access" line without a second read.
448
- visibility: z8.enum(["public", "unlisted", "workspace", "private"])
449
- });
450
- var revokeGrantResponseSchema = z8.object({
451
- revoked: z8.literal(true)
540
+ visibility: z9.enum(["public", "unlisted", "workspace", "private"]),
541
+ // The owning workspace's logo, so the share modal can draw the workspace
542
+ // itself rather than a generic glyph. Null when none was uploaded.
543
+ workspaceLogo: z9.string().nullable().optional(),
544
+ // The owning workspace's display name, so "People with access" can name the
545
+ // audience `workspace` visibility actually admits ("All Acme members") rather
546
+ // than leaving an empty list to imply nobody. Null for a workspace-less
547
+ // artifact; optional so an older server still parses.
548
+ workspaceName: z9.string().nullable().optional()
549
+ });
550
+ var revokeGrantResponseSchema = z9.object({
551
+ revoked: z9.literal(true)
452
552
  });
453
- var acceptGrantSchema = z8.object({
454
- token: z8.string().min(1)
553
+ var acceptGrantSchema = z9.object({
554
+ token: z9.string().min(1)
455
555
  });
456
- var acceptGrantResponseSchema = z8.object({
457
- artifactId: z8.string(),
556
+ var acceptGrantResponseSchema = z9.object({
557
+ artifactId: z9.string(),
458
558
  // The review shell URL to land on, carrying ?invited=1 for the banner.
459
- artifactUrl: z8.string().url(),
460
- slug: z8.string(),
461
- title: z8.string()
462
- });
463
- var workspaceGuestSchema = z8.object({
464
- artifacts: z8.array(
465
- z8.object({
466
- artifactId: z8.string(),
467
- grantId: z8.string(),
468
- slug: z8.string(),
469
- status: z8.enum(["pending", "active"]),
470
- title: z8.string()
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()
471
571
  })
472
572
  ),
473
- email: z8.string().nullable(),
474
- image: z8.string().nullable(),
475
- name: z8.string().nullable(),
476
- userId: z8.string().nullable()
477
- });
478
- var workspaceGuestListResponseSchema = z8.object({
479
- guests: z8.array(workspaceGuestSchema)
480
- });
481
- var sharedWithMeArtifactSchema = z8.object({
482
- artifactId: z8.string(),
483
- slug: z8.string(),
484
- title: z8.string(),
485
- url: z8.string().url(),
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(),
486
586
  // Who shared it, for the "Shared with me" list.
487
- workspaceName: z8.string()
587
+ workspaceName: z9.string()
488
588
  });
489
- var sharedWithMeListResponseSchema = z8.object({
490
- artifacts: z8.array(sharedWithMeArtifactSchema)
589
+ var sharedWithMeListResponseSchema = z9.object({
590
+ artifacts: z9.array(sharedWithMeArtifactSchema)
491
591
  });
492
592
 
593
+ // ../shared/src/html-meta.ts
594
+ var HEAD_SCAN_CHARS = 64 * 1024;
595
+
493
596
  // ../shared/src/me.ts
494
- import { z as z10 } from "zod";
597
+ import { z as z11 } from "zod";
495
598
 
496
599
  // ../shared/src/workspaces.ts
497
- import { z as z9 } from "zod";
600
+ import { z as z10 } from "zod";
498
601
  var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/;
499
- var workspaceHandleUnavailableReasonSchema = z9.enum([
602
+ var workspaceHandleUnavailableReasonSchema = z10.enum([
500
603
  "held",
501
604
  "invalid",
502
605
  "reserved",
503
606
  "taken"
504
607
  ]);
505
- var workspaceHandleAvailabilitySchema = z9.object({
506
- available: z9.boolean(),
507
- handle: z9.string(),
608
+ var workspaceHandleAvailabilitySchema = z10.object({
609
+ available: z10.boolean(),
610
+ handle: z10.string(),
508
611
  // Human-readable sentence for the field hint; null when available.
509
- message: z9.string().nullable(),
612
+ message: z10.string().nullable(),
510
613
  reason: workspaceHandleUnavailableReasonSchema.nullable()
511
614
  });
512
- var workspaceRoleSchema = z9.enum(["owner", "admin", "member"]);
513
- var workspaceInviteRoleSchema = z9.enum(["admin", "member"]);
514
- var workspaceHandleSchema = z9.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
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, {
515
618
  message: "handle must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
516
619
  });
517
- var createWorkspaceSchema = z9.object({
620
+ var createWorkspaceSchema = z10.object({
518
621
  handle: workspaceHandleSchema.optional(),
519
- name: z9.string().trim().min(1).max(100)
622
+ name: z10.string().trim().min(1).max(100)
520
623
  });
521
- var updateWorkspaceSchema = z9.object({
624
+ var updateWorkspaceSchema = z10.object({
522
625
  handle: workspaceHandleSchema.optional(),
523
- name: z9.string().trim().min(1).max(100).optional()
626
+ name: z10.string().trim().min(1).max(100).optional()
524
627
  }).refine((body) => body.handle !== void 0 || body.name !== void 0, {
525
628
  message: "provide at least one of name or handle"
526
629
  });
527
- var workspaceSchema = z9.object({
528
- createdAt: z9.string(),
529
- handle: z9.string(),
630
+ var workspaceSchema = z10.object({
631
+ createdAt: z10.string(),
632
+ handle: z10.string(),
530
633
  // ISO timestamp when `{handle}.artifact.cafe` was provisioned, or null.
531
634
  // Set by the one-time, self-serve enable flow (§4.4); never auto at
532
635
  // create, and always null for personal workspaces.
533
- hostnameProvisionedAt: z9.string().nullable(),
534
- id: z9.string(),
636
+ hostnameProvisionedAt: z10.string().nullable(),
637
+ id: z10.string(),
535
638
  // Auto-provisioned solo workspace, one per user: non-deletable, takes
536
639
  // no members, renameable like any workspace.
537
- isPersonal: z9.boolean(),
640
+ isPersonal: z10.boolean(),
538
641
  // Uploaded logo URL, or null for the initials fallback. Set through the
539
642
  // dedicated /workspaces/:id/avatar route, never the generic update.
540
- logo: z9.string().nullable(),
541
- name: z9.string(),
643
+ logo: z10.string().nullable(),
644
+ name: z10.string(),
542
645
  role: workspaceRoleSchema.nullable(),
543
- updatedAt: z9.string()
646
+ updatedAt: z10.string()
544
647
  });
545
- var workspaceListResponseSchema = z9.object({
546
- workspaces: z9.array(workspaceSchema)
648
+ var workspaceListResponseSchema = z10.object({
649
+ workspaces: z10.array(workspaceSchema)
547
650
  });
548
- var deleteWorkspaceResponseSchema = z9.object({
549
- deleted: z9.literal(true)
651
+ var deleteWorkspaceResponseSchema = z10.object({
652
+ deleted: z10.literal(true)
550
653
  });
551
- var workspaceMemberSchema = z9.object({
552
- createdAt: z9.string(),
553
- email: z9.string().email(),
554
- image: z9.string().nullable(),
555
- name: z9.string(),
654
+ var workspaceMemberSchema = z10.object({
655
+ createdAt: z10.string(),
656
+ email: z10.string().email(),
657
+ image: z10.string().nullable(),
658
+ name: z10.string(),
556
659
  role: workspaceRoleSchema,
557
- userId: z9.string()
660
+ userId: z10.string()
558
661
  });
559
- var workspaceMemberListResponseSchema = z9.object({
560
- members: z9.array(workspaceMemberSchema)
662
+ var workspaceMemberListResponseSchema = z10.object({
663
+ members: z10.array(workspaceMemberSchema)
561
664
  });
562
- var updateWorkspaceMemberSchema = z9.object({
665
+ var updateWorkspaceMemberSchema = z10.object({
563
666
  role: workspaceRoleSchema
564
667
  });
565
- var removeWorkspaceMemberResponseSchema = z9.object({
566
- removed: z9.literal(true)
668
+ var removeWorkspaceMemberResponseSchema = z10.object({
669
+ removed: z10.literal(true)
567
670
  });
568
- var createWorkspaceInviteSchema = z9.object({
569
- email: z9.string().trim().toLowerCase().email().optional(),
570
- expiresAt: z9.string().datetime(),
571
- maxUses: z9.number().int().positive().optional(),
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(),
572
675
  role: workspaceInviteRoleSchema
573
676
  });
574
- var workspaceInviteSchema = z9.object({
575
- createdAt: z9.string(),
576
- email: z9.string().email().nullable(),
577
- expiresAt: z9.string(),
578
- id: z9.string(),
579
- maxUses: z9.number().int().positive().nullable(),
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(),
580
683
  role: workspaceInviteRoleSchema,
581
- tokenPrefix: z9.string(),
582
- useCount: z9.number().int().nonnegative()
684
+ tokenPrefix: z10.string(),
685
+ useCount: z10.number().int().nonnegative()
583
686
  });
584
687
  var createdWorkspaceInviteSchema = workspaceInviteSchema.extend({
585
- url: z9.string().url()
688
+ url: z10.string().url()
586
689
  });
587
- var workspaceInviteListResponseSchema = z9.object({
588
- invites: z9.array(workspaceInviteSchema)
690
+ var workspaceInviteListResponseSchema = z10.object({
691
+ invites: z10.array(workspaceInviteSchema)
589
692
  });
590
- var revokeWorkspaceInviteResponseSchema = z9.object({
591
- revoked: z9.literal(true)
693
+ var revokeWorkspaceInviteResponseSchema = z10.object({
694
+ revoked: z10.literal(true)
592
695
  });
593
- var acceptWorkspaceInviteSchema = z9.object({
594
- token: z9.string().min(1)
696
+ var acceptWorkspaceInviteSchema = z10.object({
697
+ token: z10.string().min(1)
595
698
  });
596
- var acceptWorkspaceInviteResponseSchema = z9.object({
699
+ var acceptWorkspaceInviteResponseSchema = z10.object({
597
700
  role: workspaceRoleSchema,
598
701
  workspace: workspaceSchema
599
702
  });
600
- var folderVisibilitySchema = z9.enum(["public", "workspace"]);
601
- var artifactVisibilitySchema = z9.enum([
703
+ var folderVisibilitySchema = z10.enum(["public", "workspace"]);
704
+ var artifactVisibilitySchema = z10.enum([
602
705
  "public",
603
706
  "unlisted",
604
707
  "workspace",
605
708
  "private"
606
709
  ]);
607
- var createFolderSchema = z9.object({
608
- name: z9.string().trim().min(1).max(100),
710
+ var createFolderSchema = z10.object({
711
+ // Optional custom icon; omitted (or null) leaves the default glyph.
712
+ emoji: iconEmojiSchema.optional(),
713
+ name: z10.string().trim().min(1).max(100),
609
714
  visibility: folderVisibilitySchema
610
715
  });
611
- var updateFolderSchema = z9.object({
716
+ var updateFolderSchema = z10.object({
612
717
  // With `visibility`: bulk-update the folder's artifacts to match
613
718
  // (SPEC-2 §3.4 "Changing a folder's visibility offers a bulk update").
614
- cascade: z9.boolean().optional(),
615
- name: z9.string().trim().min(1).max(100).optional(),
616
- sortOrder: z9.number().int().min(0).optional(),
719
+ cascade: z10.boolean().optional(),
720
+ // Explicit null clears the icon back to the default glyph, so this is
721
+ // a field where "sent as null" and "omitted" mean different things.
722
+ emoji: iconEmojiSchema.optional(),
723
+ name: z10.string().trim().min(1).max(100).optional(),
724
+ sortOrder: z10.number().int().min(0).optional(),
617
725
  visibility: folderVisibilitySchema.optional()
618
726
  }).refine(
619
- (body) => body.name !== void 0 || body.visibility !== void 0 || body.sortOrder !== void 0,
620
- { message: "provide at least one of name, visibility, sortOrder" }
727
+ (body) => body.name !== void 0 || body.visibility !== void 0 || body.sortOrder !== void 0 || body.emoji !== void 0,
728
+ { message: "provide at least one of name, visibility, sortOrder, emoji" }
621
729
  ).refine((body) => !body.cascade || body.visibility !== void 0, {
622
730
  message: "cascade requires visibility"
623
731
  });
624
- var folderSchema = z9.object({
625
- createdAt: z9.string(),
626
- id: z9.string(),
627
- name: z9.string(),
732
+ var folderSchema = z10.object({
733
+ createdAt: z10.string(),
734
+ // Custom icon, or null to render the default folder glyph.
735
+ emoji: z10.string().nullable(),
736
+ id: z10.string(),
737
+ name: z10.string(),
628
738
  // Permalink segment for the public folder page (/w/{handle}/f/{slug}).
629
739
  // Server-derived from the name; renames regenerate it.
630
- slug: z9.string(),
631
- sortOrder: z9.number().int(),
632
- updatedAt: z9.string(),
740
+ slug: z10.string(),
741
+ sortOrder: z10.number().int(),
742
+ updatedAt: z10.string(),
633
743
  visibility: folderVisibilitySchema,
634
- workspaceId: z9.string()
744
+ workspaceId: z10.string()
635
745
  });
636
- var folderListResponseSchema = z9.object({
637
- folders: z9.array(folderSchema)
746
+ var folderListResponseSchema = z10.object({
747
+ folders: z10.array(folderSchema)
638
748
  });
639
- var deleteFolderResponseSchema = z9.object({
640
- deleted: z9.literal(true)
749
+ var deleteFolderResponseSchema = z10.object({
750
+ deleted: z10.literal(true)
641
751
  });
642
- var workspaceArtifactSchema = z9.object({
643
- createdAt: z9.string(),
644
- currentVersionNumber: z9.number().int().nullable(),
645
- folderId: z9.string().nullable(),
646
- id: z9.string(),
647
- openCommentCount: z9.number().int().nonnegative(),
752
+ var workspaceArtifactSchema = z10.object({
753
+ createdAt: z10.string(),
754
+ currentVersionNumber: z10.number().int().nullable(),
755
+ // 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(),
648
760
  reviewMode: reviewModeSchema,
649
- slug: z9.string(),
650
- title: z9.string(),
651
- updatedAt: z9.string(),
652
- url: z9.string().url(),
653
- versionCount: z9.number().int().nonnegative(),
761
+ slug: z10.string(),
762
+ title: z10.string(),
763
+ updatedAt: z10.string(),
764
+ url: z10.string().url(),
765
+ versionCount: z10.number().int().nonnegative(),
654
766
  visibility: artifactVisibilitySchema
655
767
  });
656
- var workspaceArtifactListResponseSchema = z9.object({
657
- artifacts: z9.array(workspaceArtifactSchema)
768
+ var workspaceArtifactListResponseSchema = z10.object({
769
+ artifacts: z10.array(workspaceArtifactSchema)
658
770
  });
659
- var workspaceArtifactSearchResultSchema = z9.object({
660
- id: z9.string(),
661
- slug: z9.string(),
662
- title: z9.string()
771
+ var workspaceArtifactSearchResultSchema = z10.object({
772
+ id: z10.string(),
773
+ slug: z10.string(),
774
+ title: z10.string()
663
775
  });
664
- var workspaceArtifactSearchResponseSchema = z9.object({
665
- results: z9.array(workspaceArtifactSearchResultSchema)
776
+ var workspaceArtifactSearchResponseSchema = z10.object({
777
+ results: z10.array(workspaceArtifactSearchResultSchema)
666
778
  });
667
779
 
668
780
  // ../shared/src/me.ts
669
- var ownArtifactSchema = z10.object({
670
- createdAt: z10.string(),
671
- currentVersionNumber: z10.number().nullable(),
781
+ var ownArtifactSchema = z11.object({
782
+ createdAt: z11.string(),
783
+ currentVersionNumber: z11.number().nullable(),
784
+ // Custom icon, or null to render the default document glyph.
785
+ emoji: z11.string().nullable(),
672
786
  // Owned artifacts normally never expire; non-null only while an
673
787
  // expiry is still set (e.g. an unclaimed publish surfaced here later).
674
- expiresAt: z10.string().nullable(),
788
+ expiresAt: z11.string().nullable(),
675
789
  // SPEC-2 §8.4 — the containing folder for workspace-owned artifacts;
676
790
  // both null for personal artifacts. `folderId` drives the dashboard's
677
- // move control and folder filter; `folderName` is the display label.
678
- folderId: z10.string().nullable(),
679
- folderName: z10.string().nullable(),
791
+ // move control and folder filter; `folderName` is the display label,
792
+ // `folderEmoji` its icon (null = the default folder glyph).
793
+ folderEmoji: z11.string().nullable(),
794
+ folderId: z11.string().nullable(),
795
+ folderName: z11.string().nullable(),
680
796
  // Whether a publish token is still active — revocable from the
681
797
  // dashboard (§6.18); the token itself is never returned.
682
- hasPublishToken: z10.boolean(),
683
- id: z10.string(),
684
- openThreads: z10.number(),
685
- protected: z10.boolean(),
798
+ hasPublishToken: z11.boolean(),
799
+ id: z11.string(),
800
+ openThreads: z11.number(),
801
+ protected: z11.boolean(),
686
802
  // Who published the current version (falling back to the claiming
687
803
  // owner) — the dashboard shows a name and avatar per artifact so a
688
804
  // shared workspace reads as people's work, not anonymous rows. Null
689
805
  // when the publish was token-only and the artifact is unclaimed.
690
- publishedBy: z10.object({ image: z10.string().nullable(), name: z10.string() }).nullable(),
806
+ publishedBy: z11.object({ image: z11.string().nullable(), name: z11.string() }).nullable(),
691
807
  // SPEC-2 §5.8 — the review dial, so the dashboard's settings dialog can
692
808
  // render current state without a second fetch.
693
809
  reviewMode: reviewModeSchema,
694
- slug: z10.string(),
695
- title: z10.string(),
696
- totalViews: z10.number(),
810
+ slug: z11.string(),
811
+ title: z11.string(),
812
+ totalViews: z11.number(),
697
813
  // SPEC §6.20 — messages by others newer than the viewer's last-seen
698
814
  // watermark. 0 for signed-out/API-key-less contexts.
699
- unreadComments: z10.number(),
700
- updatedAt: z10.string(),
701
- url: z10.string(),
702
- versionCount: z10.number(),
815
+ unreadComments: z11.number(),
816
+ updatedAt: z11.string(),
817
+ url: z11.string(),
818
+ versionCount: z11.number(),
703
819
  // SPEC-2 §5.1 — whether the caller clears the manage bar for this
704
820
  // artifact (workspace admin, or the person who first published it).
705
821
  // Only a manager's folder move inherits the folder's visibility, so the
706
822
  // dashboard warns exactly the people the change can actually affect, and
707
823
  // only a manager is offered the Share entry (§5.7).
708
- viewerCanManage: z10.boolean(),
824
+ viewerCanManage: z11.boolean(),
709
825
  // SPEC-2 §5.2 — the artifact's own visibility, the single enforcement
710
826
  // source. The dashboard needs it to tell whether a folder move would
711
- // widen exposure before it sends the PATCH (§3.4), and to render the lock
712
- // glyph on `private` rows and cards (§5.7).
827
+ // widen exposure before it sends the PATCH (§3.4), and to render the
828
+ // audience glyph — one per level — on rows and cards (§5.7).
713
829
  visibility: artifactVisibilitySchema
714
830
  });
715
- var ownArtifactsResponseSchema = z10.object({
716
- artifacts: z10.array(ownArtifactSchema)
831
+ var ownArtifactsResponseSchema = z11.object({
832
+ artifacts: z11.array(ownArtifactSchema)
717
833
  });
718
- var revokePublishTokenResponseSchema = z10.object({
719
- revoked: z10.boolean()
834
+ var revokePublishTokenResponseSchema = z11.object({
835
+ revoked: z11.boolean()
720
836
  });
721
837
 
722
838
  // ../shared/src/owner.ts
723
- import { z as z11 } from "zod";
839
+ import { z as z12 } from "zod";
724
840
  var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
725
- var claimResponseSchema = z11.object({
726
- artifactId: z11.string(),
727
- slug: z11.string(),
728
- title: z11.string(),
729
- url: z11.string().url()
730
- });
731
- var updateArtifactSchema = z11.object({
841
+ var claimResponseSchema = z12.object({
842
+ artifactId: z12.string(),
843
+ slug: z12.string(),
844
+ title: z12.string(),
845
+ url: z12.string().url()
846
+ });
847
+ var updateArtifactSchema = z12.object({
848
+ // Custom icon; explicit null clears it back to the default glyph.
849
+ // Part of what the artifact *is* (like the title), so it takes the
850
+ // same manage rights as title/slug — not the member-level folder move.
851
+ emoji: iconEmojiSchema.optional(),
732
852
  // SPEC-2 §6.4 — placement + visibility. `folderId: null` clears the
733
853
  // folder; `workspaceId: null` moves the artifact out of its workspace
734
854
  // (§5.5 ownership transfer).
735
- folderId: z11.string().min(1).nullable().optional(),
736
- slug: z11.string().min(3).max(63).regex(SLUG_PATTERN, {
855
+ folderId: z12.string().min(1).nullable().optional(),
856
+ slug: z12.string().min(3).max(63).regex(SLUG_PATTERN, {
737
857
  message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
738
858
  }).optional(),
739
- title: z11.string().min(1).max(200).optional(),
859
+ title: z12.string().min(1).max(200).optional(),
740
860
  visibility: artifactVisibilitySchema.optional(),
741
- workspaceId: z11.string().min(1).nullable().optional()
861
+ workspaceId: z12.string().min(1).nullable().optional()
742
862
  }).refine(
743
- (body) => body.title !== void 0 || body.slug !== void 0 || body.visibility !== void 0 || body.folderId !== void 0 || body.workspaceId !== void 0,
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,
744
864
  {
745
- message: "provide at least one of title, slug, visibility, folderId, workspaceId"
865
+ message: "provide at least one of title, slug, visibility, folderId, workspaceId, emoji"
746
866
  }
747
867
  );
748
- var deleteArtifactResponseSchema = z11.object({
749
- deleted: z11.literal(true)
868
+ var deleteArtifactResponseSchema = z12.object({
869
+ deleted: z12.literal(true)
750
870
  });
751
871
 
752
872
  // ../shared/src/presence.ts
753
- import { z as z12 } from "zod";
873
+ import { z as z13 } from "zod";
754
874
  var AGENT_STATES = ["listening", "editing", "publishing"];
755
- var agentStateSchema = z12.enum(AGENT_STATES);
875
+ var agentStateSchema = z13.enum(AGENT_STATES);
756
876
  var AGENT_PRESENCE = {
757
877
  editingTtlSeconds: 300,
758
878
  heartbeatSeconds: 15,
759
879
  shellPollSeconds: 15,
760
880
  ttlSeconds: 45
761
881
  };
762
- var agentHeartbeatBodySchema = z12.object({
882
+ var agentHeartbeatBodySchema = z13.object({
763
883
  state: agentStateSchema.optional()
764
884
  });
765
- var agentHeartbeatResponseSchema = z12.object({
766
- listening: z12.literal(true),
885
+ var agentHeartbeatResponseSchema = z13.object({
886
+ listening: z13.literal(true),
767
887
  state: agentStateSchema.optional(),
768
- ttlSeconds: z12.number().int().positive()
888
+ ttlSeconds: z13.number().int().positive()
769
889
  });
770
- var agentPresenceSchema = z12.object({
771
- listening: z12.boolean(),
890
+ var agentPresenceSchema = z13.object({
891
+ agent: z13.object({ id: z13.string().nullable(), label: z13.string() }).nullable().default(null),
892
+ listening: z13.boolean(),
772
893
  state: agentStateSchema.nullable().optional(),
773
- versionNumber: z12.number().int().positive().nullable()
894
+ versionNumber: z13.number().int().positive().nullable()
774
895
  });
775
896
 
776
897
  // ../shared/src/protection.ts
777
- import { z as z13 } from "zod";
898
+ import { z as z14 } from "zod";
778
899
  var PASSWORD_MIN = 6;
779
900
  var PASSWORD_MAX = 128;
780
- var gatePasswordSchema = z13.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
781
- var setProtectionSchema = z13.object({
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({
782
903
  password: gatePasswordSchema.nullable()
783
904
  });
784
- var protectionResponseSchema = z13.object({
785
- protected: z13.boolean()
905
+ var protectionResponseSchema = z14.object({
906
+ protected: z14.boolean()
786
907
  });
787
- var unlockSchema = z13.object({
788
- password: z13.string().min(1).max(1024)
908
+ var unlockSchema = z14.object({
909
+ password: z14.string().min(1).max(1024)
789
910
  });
790
- var unlockResponseSchema = z13.object({
791
- unlocked: z13.literal(true)
911
+ var unlockResponseSchema = z14.object({
912
+ unlocked: z14.literal(true)
792
913
  });
793
914
 
794
915
  // ../shared/src/report.ts
795
- import { z as z14 } from "zod";
796
- var reportRequestSchema = z14.object({
797
- reason: z14.string().max(1e3).optional()
916
+ import { z as z15 } from "zod";
917
+ var reportRequestSchema = z15.object({
918
+ reason: z15.string().max(1e3).optional()
798
919
  });
799
- var reportResponseSchema = z14.object({
800
- reported: z14.literal(true)
920
+ var reportResponseSchema = z15.object({
921
+ reported: z15.literal(true)
801
922
  });
802
923
 
803
924
  // ../shared/src/reviewers.ts
804
- import { z as z15 } from "zod";
805
- var artifactReviewerSchema = z15.object({
806
- avatarUrl: z15.string().nullable(),
807
- displayName: z15.string(),
925
+ import { z as z16 } from "zod";
926
+ var artifactReviewerSchema = z16.object({
927
+ avatarUrl: z16.string().nullable(),
928
+ displayName: z16.string(),
808
929
  // Exactly one of userId/guestId is set, per `kind`.
809
- guestId: z15.string().nullable(),
930
+ guestId: z16.string().nullable(),
810
931
  // "account", not commentAuthorSchema's "member" — the whole point here is
811
932
  // that these people need not be workspace members. A guest can be named in
812
933
  // a comment but never notified: guest_identities carries a display name and
813
934
  // no email, so there is nowhere to send anything.
814
- kind: z15.enum(["account", "guest"]),
815
- userId: z15.string().nullable()
935
+ kind: z16.enum(["account", "guest"]),
936
+ userId: z16.string().nullable()
816
937
  });
817
- var artifactReviewerListResponseSchema = z15.object({
818
- reviewers: z15.array(artifactReviewerSchema)
938
+ var artifactReviewerListResponseSchema = z16.object({
939
+ reviewers: z16.array(artifactReviewerSchema)
819
940
  });
820
941
 
821
942
  // ../shared/src/stats.ts
822
- import { z as z16 } from "zod";
823
- var avgViewSecondsSchema = z16.number().int().nonnegative().nullable();
824
- var versionViewStatsSchema = z16.object({
943
+ import { z as z17 } from "zod";
944
+ var avgViewSecondsSchema = z17.number().int().nonnegative().nullable();
945
+ var versionViewStatsSchema = z17.object({
825
946
  avgViewSeconds: avgViewSecondsSchema,
826
947
  // Views from inside the review shell's iframe vs. direct visits to
827
948
  // the render URL. views = embeddedViews + directViews.
828
- directViews: z16.number().int().nonnegative(),
829
- embeddedViews: z16.number().int().nonnegative(),
949
+ directViews: z17.number().int().nonnegative(),
950
+ embeddedViews: z17.number().int().nonnegative(),
830
951
  // Distinct hashed viewer IPs across the version's lifetime.
831
- uniqueViewers: z16.number().int().nonnegative(),
832
- versionNumber: z16.number().int().positive(),
833
- views: z16.number().int().nonnegative()
952
+ uniqueViewers: z17.number().int().nonnegative(),
953
+ versionNumber: z17.number().int().positive(),
954
+ views: z17.number().int().nonnegative()
834
955
  });
835
- var dailyViewsSchema = z16.object({
956
+ var dailyViewsSchema = z17.object({
836
957
  avgViewSeconds: avgViewSecondsSchema,
837
958
  // UTC day, YYYY-MM-DD.
838
- day: z16.string(),
839
- uniqueViewers: z16.number().int().nonnegative(),
840
- views: z16.number().int().nonnegative()
959
+ day: z17.string(),
960
+ uniqueViewers: z17.number().int().nonnegative(),
961
+ views: z17.number().int().nonnegative()
841
962
  });
842
- var artifactStatsResponseSchema = z16.object({
963
+ var artifactStatsResponseSchema = z17.object({
843
964
  avgViewSeconds: avgViewSecondsSchema,
844
- daily: z16.array(dailyViewsSchema),
965
+ daily: z17.array(dailyViewsSchema),
845
966
  // Distinct hashed viewer IPs across all versions and days.
846
- totalUniqueViewers: z16.number().int().nonnegative(),
847
- totalViews: z16.number().int().nonnegative(),
848
- versions: z16.array(versionViewStatsSchema)
967
+ totalUniqueViewers: z17.number().int().nonnegative(),
968
+ totalViews: z17.number().int().nonnegative(),
969
+ versions: z17.array(versionViewStatsSchema)
849
970
  });
850
971
 
851
972
  // ../shared/src/versions.ts
852
- import { z as z17 } from "zod";
853
- var versionSourceSchema = z17.object({
854
- agentName: z17.string().nullable(),
855
- type: z17.string()
973
+ import { z as z18 } from "zod";
974
+ var versionSourceSchema = z18.object({
975
+ agentName: z18.string().nullable(),
976
+ type: z18.string()
856
977
  });
857
- var versionChangesSchema = z17.object({
858
- added: z17.array(z17.string()),
859
- changed: z17.array(z17.string()),
860
- removed: z17.array(z17.string())
978
+ var versionChangesSchema = z18.object({
979
+ added: z18.array(z18.string()),
980
+ changed: z18.array(z18.string()),
981
+ removed: z18.array(z18.string())
861
982
  });
862
- var versionSummarySchema = z17.object({
983
+ var versionSummarySchema = z18.object({
863
984
  // Absent for the first version — there is no previous version to diff against.
864
985
  changes: versionChangesSchema.nullable(),
865
- createdAt: z17.string(),
866
- entryUrl: z17.string().url(),
867
- fileCount: z17.number().int().nonnegative(),
868
- id: z17.string(),
869
- isCurrent: z17.boolean(),
986
+ createdAt: z18.string(),
987
+ entryUrl: z18.string().url(),
988
+ fileCount: z18.number().int().nonnegative(),
989
+ id: z18.string(),
990
+ isCurrent: z18.boolean(),
870
991
  // Author-supplied bullet-point changelog; null when the publish omitted it.
871
- notes: z17.string().nullable(),
992
+ notes: z18.string().nullable(),
872
993
  source: versionSourceSchema,
873
- totalSizeBytes: z17.number().int().nonnegative(),
874
- versionNumber: z17.number().int().positive()
994
+ totalSizeBytes: z18.number().int().nonnegative(),
995
+ versionNumber: z18.number().int().positive()
875
996
  });
876
- var versionListResponseSchema = z17.object({
877
- versions: z17.array(versionSummarySchema)
997
+ var versionListResponseSchema = z18.object({
998
+ versions: z18.array(versionSummarySchema)
878
999
  });
879
1000
 
1001
+ // src/agent-identity.ts
1002
+ function agentLabel(explicit) {
1003
+ if (explicit !== void 0) {
1004
+ return explicit.trim() || void 0;
1005
+ }
1006
+ return detectAgentFromEnv(process.env);
1007
+ }
1008
+
880
1009
  // src/api.ts
881
1010
  var ApiRequestError = class extends Error {
882
1011
  constructor(status2, code, message, retryAfter) {
@@ -951,6 +1080,15 @@ function createApiClient(apiUrl2, publishToken) {
951
1080
  (d) => agentHeartbeatResponseSchema.parse(d)
952
1081
  );
953
1082
  },
1083
+ // SPEC-2 §6.3 — create a folder in a workspace. Account key required
1084
+ // (owner/admin; in a personal workspace that's always the user).
1085
+ async createFolder(workspaceId, body) {
1086
+ return request(
1087
+ `${base}/api/v1/workspaces/${workspaceId}/folders`,
1088
+ { body: JSON.stringify(body), headers, method: "POST" },
1089
+ (d) => folderSchema.parse(d)
1090
+ );
1091
+ },
954
1092
  // SPEC-2 §6.6 — share an artifact with a person (email or user id).
955
1093
  // Authorized by this client's credential: the publish token (an agent
956
1094
  // sharing a private draft) or an account key/session. The invitation
@@ -1049,6 +1187,15 @@ function createApiClient(apiUrl2, publishToken) {
1049
1187
  (d) => grantListResponseSchema.parse(d)
1050
1188
  );
1051
1189
  },
1190
+ // SPEC-2 §6.4 — every artifact in a workspace (members see all, with
1191
+ // each artifact's folderId). The only folder-aware artifact listing.
1192
+ async listWorkspaceArtifacts(workspaceId) {
1193
+ return request(
1194
+ `${base}/api/v1/workspaces/${workspaceId}/artifacts`,
1195
+ { headers, method: "GET" },
1196
+ (d) => workspaceArtifactListResponseSchema.parse(d)
1197
+ );
1198
+ },
1052
1199
  async listWorkspaces() {
1053
1200
  return request(
1054
1201
  `${base}/api/v1/workspaces`,
@@ -1076,11 +1223,13 @@ function createApiClient(apiUrl2, publishToken) {
1076
1223
  // SPEC §6.5 — post a reply into a thread. Authorized by this client's
1077
1224
  // credential: the artifact's publish token (the reply renders as the
1078
1225
  // agent) or the owner's account key (renders as the owner). No guest
1079
- // payload — the credential is the identity.
1080
- async replyThread(threadId, body) {
1226
+ // payload — the credential is the identity. `via` names the tool: it
1227
+ // becomes the agent's byline on a token reply, a "via" label on an
1228
+ // account one.
1229
+ async replyThread(threadId, body, via) {
1081
1230
  return request(
1082
1231
  `${base}/api/v1/comment-threads/${threadId}/messages`,
1083
- { body: JSON.stringify({ body }), headers, method: "POST" },
1232
+ { body: JSON.stringify({ body, via }), headers, method: "POST" },
1084
1233
  (d) => commentThreadSchema.parse(d)
1085
1234
  );
1086
1235
  },
@@ -1119,6 +1268,16 @@ function createApiClient(apiUrl2, publishToken) {
1119
1268
  { body: JSON.stringify({ mode }), headers, method: "PUT" },
1120
1269
  (d) => reviewModeResponseSchema.parse(d)
1121
1270
  );
1271
+ },
1272
+ // SPEC.md §6.11 / SPEC-2 §6.4 — update an artifact (folder placement,
1273
+ // title, slug, visibility). Workspace-member action: account key only,
1274
+ // a publish token can't re-file.
1275
+ async updateArtifact(artifactId, body) {
1276
+ return request(
1277
+ `${base}/api/v1/artifacts/${artifactId}`,
1278
+ { body: JSON.stringify(body), headers, method: "PATCH" },
1279
+ (d) => artifactResponseSchema.parse(d)
1280
+ );
1122
1281
  }
1123
1282
  };
1124
1283
  }
@@ -1147,17 +1306,17 @@ import {
1147
1306
  writeFileSync as writeFileSync2
1148
1307
  } from "fs";
1149
1308
  import path2 from "path";
1150
- import { z as z19 } from "zod";
1309
+ import { z as z20 } from "zod";
1151
1310
 
1152
1311
  // src/credentials.ts
1153
1312
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1154
1313
  import { homedir } from "os";
1155
1314
  import path from "path";
1156
- import { z as z18 } from "zod";
1315
+ import { z as z19 } from "zod";
1157
1316
  var DEFAULT_API_URL = "https://artifact.cafe";
1158
- var credentialsSchema = z18.object({
1159
- keys: z18.record(z18.string(), z18.string()),
1160
- version: z18.literal(1)
1317
+ var credentialsSchema = z19.object({
1318
+ keys: z19.record(z19.string(), z19.string()),
1319
+ version: z19.literal(1)
1161
1320
  });
1162
1321
  function credentialsPath() {
1163
1322
  return path.join(homedir(), ".artifactcafe", "credentials");
@@ -1207,24 +1366,24 @@ function resolveApiKey(apiUrl2, flag) {
1207
1366
  }
1208
1367
 
1209
1368
  // src/config.ts
1210
- var configSchema = z19.object({
1211
- apiUrl: z19.string(),
1212
- artifactId: z19.string(),
1213
- folder: z19.string().optional(),
1214
- folderId: z19.string().optional(),
1369
+ var configSchema = z20.object({
1370
+ apiUrl: z20.string(),
1371
+ artifactId: z20.string(),
1372
+ folder: z20.string().optional(),
1373
+ folderId: z20.string().optional(),
1215
1374
  // Optional: a folder linked by `link` to an artifact you own carries no
1216
1375
  // token — owner actions authorize via your stored login instead. A first
1217
1376
  // publish still writes the anonymous-mode token here.
1218
- publishToken: z19.string().optional(),
1219
- slug: z19.string(),
1220
- url: z19.string(),
1221
- workspace: z19.string().optional(),
1222
- workspaceId: z19.string().optional(),
1223
- workspaceKind: z19.enum(["personal", "workspace"]).optional()
1224
- });
1225
- var projectConfigSchema = z19.object({
1226
- folder: z19.string().trim().min(1).optional(),
1227
- workspace: z19.string().trim().min(1)
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)
1228
1387
  }).strict();
1229
1388
  var CONFIG_DIR = ".artifactcafe";
1230
1389
  var CONFIG_FILE = "config.json";
@@ -1260,7 +1419,7 @@ function readProjectConfig(dir) {
1260
1419
  JSON.parse(readFileSync2(file, "utf8"))
1261
1420
  );
1262
1421
  } catch (error) {
1263
- const message = error instanceof z19.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
1422
+ const message = error instanceof z20.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
1264
1423
  throw new Error(`Invalid ${file}: ${message}.`);
1265
1424
  }
1266
1425
  }
@@ -1399,7 +1558,10 @@ async function comment(opts) {
1399
1558
  anchor,
1400
1559
  body,
1401
1560
  versionId: artifact.currentVersion.id,
1402
- via: opts.via
1561
+ // An account comment keeps its own name and takes the tool as a "via"
1562
+ // label (§6.4) — detected when neither flag is given, so an agent
1563
+ // commenting with your key still says which tool typed it.
1564
+ via: agentLabel(opts.as ?? opts.via)
1403
1565
  });
1404
1566
  } catch (err) {
1405
1567
  if (err instanceof ApiRequestError && err.status === 401) {
@@ -1831,6 +1993,9 @@ async function publish(opts) {
1831
1993
  }
1832
1994
  const sessionBody = {
1833
1995
  artifactId,
1996
+ // Overrides the entry HTML's own meta description on the share card.
1997
+ // Omitted entirely when absent, so the document keeps speaking for itself.
1998
+ ...opts.description?.trim() ? { description: opts.description.trim() } : {},
1834
1999
  entryPath,
1835
2000
  files: files.map(({ absPath: _absPath, ...manifest }) => manifest),
1836
2001
  ...placement.folderId ? { folderId: placement.folderId } : {},
@@ -1838,7 +2003,10 @@ async function publish(opts) {
1838
2003
  // backslash-n; turn those back into real newlines so bullets survive.
1839
2004
  ...opts.notes?.trim() ? { notes: opts.notes.replace(/\\n/g, "\n").trim() } : {},
1840
2005
  source: {
1841
- agent: process.env.ARTIFACT_CAFE_AGENT,
2006
+ // ARTIFACT_CAFE_AGENT if it's set, else whichever agent's environment
2007
+ // we recognize — this is what names the agent on every reply to a
2008
+ // thread opened against this version (§6.5).
2009
+ agent: agentLabel(),
1842
2010
  runId: process.env.ARTIFACT_CAFE_RUN_ID,
1843
2011
  type: "cli"
1844
2012
  },
@@ -1927,6 +2095,7 @@ async function publish(opts) {
1927
2095
  folderId: placement.folderId ?? null,
1928
2096
  url: result.url,
1929
2097
  versionNumber: result.versionNumber,
2098
+ visibility: result.visibility ?? null,
1930
2099
  workspace: placement.workspace ?? null,
1931
2100
  workspaceId: placement.workspaceId ?? null,
1932
2101
  ...result.claimUrl ? { claimUrl: result.claimUrl } : {},
@@ -1951,6 +2120,11 @@ async function publish(opts) {
1951
2120
  console.log(`Review URL:
1952
2121
  ${result.url}
1953
2122
  `);
2123
+ const access = accessNote(result.visibility);
2124
+ if (access) {
2125
+ console.log(`${access}
2126
+ `);
2127
+ }
1954
2128
  if (result.claimUrl) {
1955
2129
  console.log(
1956
2130
  `Claim URL (shown once \u2014 claim to keep this artifact):
@@ -1979,6 +2153,11 @@ ${result.claimUrl}
1979
2153
  }
1980
2154
  console.log(`${result.url}
1981
2155
  `);
2156
+ const access = accessNote(result.visibility);
2157
+ if (access) {
2158
+ console.log(`${access}
2159
+ `);
2160
+ }
1982
2161
  if (opts.password !== void 0) {
1983
2162
  console.log("Password protection: on\n");
1984
2163
  }
@@ -1991,6 +2170,15 @@ ${result.claimUrl}
1991
2170
  openInBrowser(result.url);
1992
2171
  }
1993
2172
  }
2173
+ function accessNote(visibility) {
2174
+ if (visibility === "workspace") {
2175
+ return "Access: workspace members only \u2014 anyone else needs `artifact-cafe share <email>` to open it.";
2176
+ }
2177
+ if (visibility === "private") {
2178
+ return "Access: private \u2014 you, the workspace's admins, and anyone you `artifact-cafe share` it with.";
2179
+ }
2180
+ return null;
2181
+ }
1994
2182
  async function resolvePlacement(params) {
1995
2183
  const { apiKey, apiUrl: apiUrl2, config, isNewArtifact, opts } = params;
1996
2184
  const hasPlacementFlag = opts.workspace !== void 0 || opts.folder !== void 0;
@@ -2257,6 +2445,24 @@ function printCliError(err, json) {
2257
2445
 
2258
2446
  // src/folders.ts
2259
2447
  async function folders(opts) {
2448
+ if (opts.action !== void 0 && opts.action !== "create") {
2449
+ throw new Error(
2450
+ `Unknown folders action "${opts.action}" \u2014 run \`folders\` to list, or \`folders create <name>\` to create one.`
2451
+ );
2452
+ }
2453
+ if (opts.action === "create") {
2454
+ if (!opts.name?.trim()) {
2455
+ throw new Error(
2456
+ `Pass the folder name \u2014 e.g. \`artifact-cafe folders create "Design specs" --workspace ${opts.workspace}\`.`
2457
+ );
2458
+ }
2459
+ const visibility = opts.visibility ?? "workspace";
2460
+ if (visibility !== "workspace" && visibility !== "public") {
2461
+ throw new Error(
2462
+ `--visibility must be "workspace" or "public" (got "${opts.visibility}").`
2463
+ );
2464
+ }
2465
+ }
2260
2466
  const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
2261
2467
  /\/$/,
2262
2468
  ""
@@ -2264,7 +2470,7 @@ async function folders(opts) {
2264
2470
  const key = resolveApiKey(apiUrl2, opts.apiKey);
2265
2471
  if (!key) {
2266
2472
  throw new Error(
2267
- `Listing folders needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
2473
+ `Folder commands need an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
2268
2474
  );
2269
2475
  }
2270
2476
  const client = createApiClient(apiUrl2, key);
@@ -2277,6 +2483,10 @@ async function folders(opts) {
2277
2483
  `No workspace with handle "${opts.workspace}" belongs to this account.`
2278
2484
  );
2279
2485
  }
2486
+ if (opts.action === "create") {
2487
+ await createFolder(client, workspace, opts);
2488
+ return;
2489
+ }
2280
2490
  const data = await client.listFolders(workspace.id);
2281
2491
  if (opts.json) {
2282
2492
  console.log(JSON.stringify(data, null, 2));
@@ -2295,6 +2505,33 @@ async function folders(opts) {
2295
2505
  printFolder(folder);
2296
2506
  }
2297
2507
  }
2508
+ async function createFolder(client, workspace, opts) {
2509
+ const name = opts.name.trim();
2510
+ const visibility = opts.visibility ?? "workspace";
2511
+ const existing = await client.listFolders(workspace.id);
2512
+ const duplicate = existing.folders.find(
2513
+ (candidate) => candidate.name.toLowerCase() === name.toLowerCase()
2514
+ );
2515
+ if (duplicate) {
2516
+ throw new Error(
2517
+ `A folder named "${duplicate.name}" already exists in ${workspace.handle} (${duplicate.id}) \u2014 publish into it with --folder, or pick a different name.`
2518
+ );
2519
+ }
2520
+ const folder = await client.createFolder(workspace.id, { name, visibility });
2521
+ if (opts.json) {
2522
+ console.log(JSON.stringify(folder, null, 2));
2523
+ return;
2524
+ }
2525
+ console.log(
2526
+ `\u2713 Created folder "${folder.name}" (${folder.visibility}) in ${workspace.handle}`
2527
+ );
2528
+ console.log(` ${folder.id}`);
2529
+ if (folder.visibility === "public") {
2530
+ console.log(
2531
+ " Artifacts placed in it default to public \u2014 visible to anyone."
2532
+ );
2533
+ }
2534
+ }
2298
2535
  function printFolder(folder) {
2299
2536
  console.log(`${folder.name} (${folder.visibility})`);
2300
2537
  console.log(` ${folder.id}
@@ -2409,7 +2646,12 @@ async function list(opts) {
2409
2646
  `Listing your artifacts needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
2410
2647
  );
2411
2648
  }
2412
- const data = await createApiClient(apiUrl2, key).listArtifacts();
2649
+ const client = createApiClient(apiUrl2, key);
2650
+ if (opts.workspace !== void 0) {
2651
+ await listWorkspace(client, opts);
2652
+ return;
2653
+ }
2654
+ const data = await client.listArtifacts();
2413
2655
  if (opts.json) {
2414
2656
  console.log(JSON.stringify(data, null, 2));
2415
2657
  return;
@@ -2427,6 +2669,70 @@ async function list(opts) {
2427
2669
  printArtifact(artifact);
2428
2670
  }
2429
2671
  }
2672
+ async function listWorkspace(client, opts) {
2673
+ const workspaceData = await client.listWorkspaces();
2674
+ const workspace = workspaceData.workspaces.find(
2675
+ (candidate) => candidate.handle === opts.workspace
2676
+ );
2677
+ if (!workspace) {
2678
+ throw new Error(
2679
+ `No workspace with handle "${opts.workspace}" belongs to this account.`
2680
+ );
2681
+ }
2682
+ const [artifactData, folderData] = await Promise.all([
2683
+ client.listWorkspaceArtifacts(workspace.id),
2684
+ client.listFolders(workspace.id)
2685
+ ]);
2686
+ if (opts.json) {
2687
+ console.log(
2688
+ JSON.stringify(
2689
+ { artifacts: artifactData.artifacts, folders: folderData.folders },
2690
+ null,
2691
+ 2
2692
+ )
2693
+ );
2694
+ return;
2695
+ }
2696
+ if (artifactData.artifacts.length === 0) {
2697
+ console.log(`No artifacts in workspace ${workspace.handle}.`);
2698
+ return;
2699
+ }
2700
+ const plural = artifactData.artifacts.length === 1 ? "" : "s";
2701
+ console.log(
2702
+ `${artifactData.artifacts.length} artifact${plural} in ${workspace.handle}
2703
+ `
2704
+ );
2705
+ for (const folder of folderData.folders) {
2706
+ const inFolder = artifactData.artifacts.filter(
2707
+ (artifact) => artifact.folderId === folder.id
2708
+ );
2709
+ if (inFolder.length === 0) {
2710
+ continue;
2711
+ }
2712
+ console.log(`${folder.name} (${folder.visibility})`);
2713
+ for (const artifact of inFolder) {
2714
+ printWorkspaceArtifact(artifact);
2715
+ }
2716
+ }
2717
+ const unfiled = artifactData.artifacts.filter(
2718
+ (artifact) => artifact.folderId === null
2719
+ );
2720
+ if (unfiled.length > 0) {
2721
+ console.log("Unfiled");
2722
+ for (const artifact of unfiled) {
2723
+ printWorkspaceArtifact(artifact);
2724
+ }
2725
+ }
2726
+ }
2727
+ function printWorkspaceArtifact(a) {
2728
+ const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
2729
+ const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
2730
+ const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
2731
+ console.log(
2732
+ ` ${marker} ${a.title} \u2014 ${version} \xB7 ${open2} \xB7 ${a.visibility}`
2733
+ );
2734
+ console.log(` ${a.url}`);
2735
+ }
2430
2736
  function printArtifact(a) {
2431
2737
  const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
2432
2738
  const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
@@ -2626,14 +2932,109 @@ async function deleteMessage(opts) {
2626
2932
  console.log(`\u2713 Deleted ${messageId}.`);
2627
2933
  }
2628
2934
 
2629
- // src/open.ts
2935
+ // src/move.ts
2630
2936
  import path10 from "path";
2631
- async function openArtifact(opts) {
2937
+ async function move(opts) {
2938
+ if (Boolean(opts.folder) === Boolean(opts.unfiled)) {
2939
+ throw new Error("Pass exactly one of --folder <name-or-id> or --unfiled.");
2940
+ }
2632
2941
  const dir = path10.resolve(opts.dir);
2633
2942
  const target = resolveArtifactTarget(dir, {
2634
2943
  apiKey: opts.apiKey,
2635
2944
  artifact: opts.artifact
2636
2945
  });
2946
+ const key = resolveApiKey(target.apiUrl, opts.apiKey);
2947
+ if (!key) {
2948
+ throw new Error(
2949
+ `Moving an artifact needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(target.apiUrl)}).`
2950
+ );
2951
+ }
2952
+ const client = createApiClient(target.apiUrl, key);
2953
+ const artifact = await client.getArtifact(target.artifactId);
2954
+ if (!artifact.workspaceId) {
2955
+ throw new Error(
2956
+ "This artifact isn't in a workspace \u2014 folders live in workspaces. Claim it (or publish it while logged in) first."
2957
+ );
2958
+ }
2959
+ let folderId = null;
2960
+ let folderName = null;
2961
+ if (opts.folder) {
2962
+ const data = await client.listFolders(artifact.workspaceId);
2963
+ const matches = data.folders.filter(
2964
+ (candidate) => candidate.name === opts.folder || candidate.id === opts.folder
2965
+ );
2966
+ if (matches.length === 0) {
2967
+ throw new Error(
2968
+ `No folder named "${opts.folder}" exists in this workspace \u2014 list them with \`artifact-cafe folders --workspace <handle>\`, or create it with \`folders create\`.`
2969
+ );
2970
+ }
2971
+ if (matches.length > 1) {
2972
+ throw new Error(
2973
+ `More than one folder is named "${opts.folder}"; pass the folder id instead.`
2974
+ );
2975
+ }
2976
+ folderId = matches[0].id;
2977
+ folderName = matches[0].name;
2978
+ }
2979
+ if (artifact.folderId === folderId) {
2980
+ const where = folderName ? `folder "${folderName}"` : "no folder";
2981
+ if (opts.json) {
2982
+ console.log(
2983
+ JSON.stringify(
2984
+ {
2985
+ artifactId: artifact.id,
2986
+ folder: folderName ? { id: folderId, name: folderName } : null,
2987
+ moved: false,
2988
+ title: artifact.title,
2989
+ visibility: artifact.visibility
2990
+ },
2991
+ null,
2992
+ 2
2993
+ )
2994
+ );
2995
+ return;
2996
+ }
2997
+ console.log(`"${artifact.title}" is already in ${where} \u2014 nothing to do.`);
2998
+ return;
2999
+ }
3000
+ const updated = await client.updateArtifact(artifact.id, { folderId });
3001
+ if (opts.json) {
3002
+ console.log(
3003
+ JSON.stringify(
3004
+ {
3005
+ artifactId: updated.id,
3006
+ folder: folderName ? { id: folderId, name: folderName } : null,
3007
+ moved: true,
3008
+ previousVisibility: artifact.visibility,
3009
+ title: updated.title,
3010
+ visibility: updated.visibility
3011
+ },
3012
+ null,
3013
+ 2
3014
+ )
3015
+ );
3016
+ return;
3017
+ }
3018
+ const destination = folderName ? `"${folderName}"` : "Unfiled";
3019
+ console.log(`\u2713 Moved "${updated.title}" \u2192 ${destination}`);
3020
+ if (updated.visibility !== artifact.visibility) {
3021
+ console.log(
3022
+ ` Visibility: ${artifact.visibility} \u2192 ${updated.visibility} (folder default)`
3023
+ );
3024
+ if (updated.visibility === "public") {
3025
+ console.log(" Now public \u2014 visible to anyone.");
3026
+ }
3027
+ }
3028
+ }
3029
+
3030
+ // src/open.ts
3031
+ import path11 from "path";
3032
+ async function openArtifact(opts) {
3033
+ const dir = path11.resolve(opts.dir);
3034
+ const target = resolveArtifactTarget(dir, {
3035
+ apiKey: opts.apiKey,
3036
+ artifact: opts.artifact
3037
+ });
2637
3038
  const url = target.url ?? reviewUrl(
2638
3039
  target.apiUrl,
2639
3040
  (await createApiClient(target.apiUrl, target.credential).getArtifact(
@@ -2647,7 +3048,7 @@ async function openArtifact(opts) {
2647
3048
  }
2648
3049
 
2649
3050
  // src/protect.ts
2650
- import path11 from "path";
3051
+ import path12 from "path";
2651
3052
  async function protect(opts) {
2652
3053
  if (opts.off && opts.password !== void 0) {
2653
3054
  throw new Error("Pass either --password or --off, not both.");
@@ -2664,7 +3065,7 @@ async function protect(opts) {
2664
3065
  throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
2665
3066
  }
2666
3067
  }
2667
- const dir = path11.resolve(opts.dir);
3068
+ const dir = path12.resolve(opts.dir);
2668
3069
  const target = resolveArtifactTarget(dir, {
2669
3070
  apiKey: opts.apiKey,
2670
3071
  artifact: opts.artifact
@@ -2689,7 +3090,7 @@ async function protect(opts) {
2689
3090
  }
2690
3091
 
2691
3092
  // src/reply.ts
2692
- import path12 from "path";
3093
+ import path13 from "path";
2693
3094
  async function readStdin3() {
2694
3095
  if (process.stdin.isTTY) {
2695
3096
  return "";
@@ -2713,13 +3114,13 @@ async function replyToThread(opts) {
2713
3114
  'Nothing to post. Pass the reply with --body "\u2026", or pipe it on stdin.'
2714
3115
  );
2715
3116
  }
2716
- const dir = path12.resolve(opts.dir);
3117
+ const dir = path13.resolve(opts.dir);
2717
3118
  const target = resolveArtifactTarget(dir, {
2718
3119
  apiKey: opts.apiKey,
2719
3120
  artifact: opts.artifact
2720
3121
  });
2721
3122
  const client = createApiClient(target.apiUrl, target.credential);
2722
- let thread = await client.replyThread(threadId, body);
3123
+ let thread = await client.replyThread(threadId, body, agentLabel(opts.as));
2723
3124
  if (opts.resolve) {
2724
3125
  thread = await client.resolveThread(threadId);
2725
3126
  }
@@ -2742,7 +3143,7 @@ async function replyToThread(opts) {
2742
3143
  }
2743
3144
 
2744
3145
  // src/resolve.ts
2745
- import path13 from "path";
3146
+ import path14 from "path";
2746
3147
  async function changeThreadStatus(opts) {
2747
3148
  const threadId = opts.thread?.trim();
2748
3149
  if (!threadId) {
@@ -2750,7 +3151,7 @@ async function changeThreadStatus(opts) {
2750
3151
  `Pass the thread id to ${opts.action} \u2014 get it from \`artifact-cafe comments --json\`.`
2751
3152
  );
2752
3153
  }
2753
- const dir = path13.resolve(opts.dir);
3154
+ const dir = path14.resolve(opts.dir);
2754
3155
  const target = resolveArtifactTarget(dir, {
2755
3156
  apiKey: opts.apiKey,
2756
3157
  artifact: opts.artifact
@@ -2787,7 +3188,7 @@ async function changeThreadStatus(opts) {
2787
3188
  }
2788
3189
 
2789
3190
  // src/review.ts
2790
- import path14 from "path";
3191
+ import path15 from "path";
2791
3192
  var OUTCOME = {
2792
3193
  off: "Plain artifact \u2014 the review tools are hidden entirely",
2793
3194
  open: "Open for review \u2014 anyone who can open it can comment",
@@ -2801,7 +3202,7 @@ async function review(opts) {
2801
3202
  );
2802
3203
  }
2803
3204
  const mode = parsed.data;
2804
- const dir = path14.resolve(opts.dir);
3205
+ const dir = path15.resolve(opts.dir);
2805
3206
  const target = resolveArtifactTarget(dir, {
2806
3207
  apiKey: opts.apiKey,
2807
3208
  artifact: opts.artifact
@@ -2823,10 +3224,10 @@ async function review(opts) {
2823
3224
  }
2824
3225
 
2825
3226
  // src/share.ts
2826
- import path15 from "path";
3227
+ import path16 from "path";
2827
3228
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2828
3229
  async function share(opts) {
2829
- const dir = path15.resolve(opts.dir);
3230
+ const dir = path16.resolve(opts.dir);
2830
3231
  const target = resolveArtifactTarget(dir, {
2831
3232
  apiKey: opts.apiKey,
2832
3233
  artifact: opts.artifact
@@ -2881,7 +3282,7 @@ General access: ${visibility}`);
2881
3282
  console.log("");
2882
3283
  }
2883
3284
  async function unshare(opts) {
2884
- const dir = path15.resolve(opts.dir);
3285
+ const dir = path16.resolve(opts.dir);
2885
3286
  const target = resolveArtifactTarget(dir, {
2886
3287
  apiKey: opts.apiKey,
2887
3288
  artifact: opts.artifact
@@ -2904,7 +3305,7 @@ async function unshare(opts) {
2904
3305
  }
2905
3306
 
2906
3307
  // src/status.ts
2907
- import path16 from "path";
3308
+ import path17 from "path";
2908
3309
  async function status(opts) {
2909
3310
  if (!AGENT_STATES.includes(opts.state)) {
2910
3311
  throw new Error(
@@ -2912,7 +3313,7 @@ async function status(opts) {
2912
3313
  );
2913
3314
  }
2914
3315
  const state = opts.state;
2915
- const dir = path16.resolve(opts.dir);
3316
+ const dir = path17.resolve(opts.dir);
2916
3317
  const target = resolveArtifactTarget(dir, {
2917
3318
  apiKey: opts.apiKey,
2918
3319
  artifact: opts.artifact
@@ -2941,26 +3342,26 @@ async function status(opts) {
2941
3342
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
2942
3343
  import { createRequire } from "module";
2943
3344
  import { homedir as homedir2 } from "os";
2944
- import path17 from "path";
2945
- import { z as z20 } from "zod";
3345
+ import path18 from "path";
3346
+ import { z as z21 } from "zod";
2946
3347
  var REGISTRY_LATEST_URL = "https://registry.npmjs.org/artifact-cafe/latest";
2947
3348
  var SKILL_SOURCE_URL = "https://raw.githubusercontent.com/Artifact-Cafe/skill/main/artifact-cafe/SKILL.md";
2948
3349
  var SKILL_UPDATE_COMMAND = "npx skills add artifact-cafe/skill --skill artifact-cafe -g";
2949
3350
  var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
2950
3351
  var FETCH_TIMEOUT_MS = 2e3;
2951
3352
  var SKILL_VERSION_PATTERN = /\*\*Skill version:\s*(\d+\.\d+\.\d+)\*\*/;
2952
- var cacheSchema = z20.object({
2953
- checkedAt: z20.number(),
2954
- cliLatest: z20.string().nullable(),
2955
- skillLatest: z20.string().nullable(),
2956
- version: z20.literal(1)
3353
+ var cacheSchema = z21.object({
3354
+ checkedAt: z21.number(),
3355
+ cliLatest: z21.string().nullable(),
3356
+ skillLatest: z21.string().nullable(),
3357
+ version: z21.literal(1)
2957
3358
  });
2958
3359
  function cliVersion() {
2959
3360
  const pkg = createRequire(import.meta.url)("../package.json");
2960
3361
  return pkg.version;
2961
3362
  }
2962
3363
  function cachePath() {
2963
- return path17.join(homedir2(), ".artifactcafe", "update-check.json");
3364
+ return path18.join(homedir2(), ".artifactcafe", "update-check.json");
2964
3365
  }
2965
3366
  function readCache() {
2966
3367
  try {
@@ -2971,7 +3372,7 @@ function readCache() {
2971
3372
  }
2972
3373
  function writeCache(cache) {
2973
3374
  try {
2974
- mkdirSync3(path17.dirname(cachePath()), { mode: 448, recursive: true });
3375
+ mkdirSync3(path18.dirname(cachePath()), { mode: 448, recursive: true });
2975
3376
  writeFileSync3(cachePath(), `${JSON.stringify(cache, null, 2)}
2976
3377
  `);
2977
3378
  } catch {
@@ -3008,7 +3409,7 @@ async function fetchText(url) {
3008
3409
  }
3009
3410
  async function fetchLatestVersions(previous) {
3010
3411
  const [cli, skill] = await Promise.all([
3011
- fetchJson(REGISTRY_LATEST_URL).then((body) => z20.object({ version: z20.string() }).parse(body).version).catch(() => null),
3412
+ fetchJson(REGISTRY_LATEST_URL).then((body) => z21.object({ version: z21.string() }).parse(body).version).catch(() => null),
3012
3413
  fetchText(SKILL_SOURCE_URL).then((body) => SKILL_VERSION_PATTERN.exec(body)?.[1] ?? null).catch(() => null)
3013
3414
  ]);
3014
3415
  return {
@@ -3024,7 +3425,7 @@ function installedSkillVersions() {
3024
3425
  const versions = [];
3025
3426
  for (const root of roots) {
3026
3427
  for (const dir of dirs) {
3027
- const file = path17.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
3428
+ const file = path18.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
3028
3429
  if (!existsSync3(file)) {
3029
3430
  continue;
3030
3431
  }
@@ -3121,17 +3522,18 @@ function printWorkspace(workspace) {
3121
3522
  }
3122
3523
 
3123
3524
  // src/index.ts
3124
- function readNotesArg(argv) {
3525
+ function readStringArg(argv, flag) {
3526
+ const prefix = `${flag}=`;
3125
3527
  for (let i = 0; i < argv.length; i++) {
3126
3528
  const token = argv[i];
3127
3529
  if (token === void 0) {
3128
3530
  continue;
3129
3531
  }
3130
- if (token === "--notes") {
3532
+ if (token === flag) {
3131
3533
  return argv[i + 1];
3132
3534
  }
3133
- if (token.startsWith("--notes=")) {
3134
- return token.slice("--notes=".length);
3535
+ if (token.startsWith(prefix)) {
3536
+ return token.slice(prefix.length);
3135
3537
  }
3136
3538
  }
3137
3539
  }
@@ -3145,6 +3547,10 @@ var publish2 = defineCommand({
3145
3547
  description: "Publish to an existing artifact ID",
3146
3548
  type: "string"
3147
3549
  },
3550
+ description: {
3551
+ description: "One-line share-card summary (default: the entry HTML's own meta description)",
3552
+ type: "string"
3553
+ },
3148
3554
  dir: {
3149
3555
  default: ".",
3150
3556
  description: "Directory to publish",
@@ -3181,11 +3587,12 @@ var publish2 = defineCommand({
3181
3587
  await publish({
3182
3588
  apiKey: args["api-key"],
3183
3589
  artifact: args.artifact,
3590
+ description: readStringArg(process.argv.slice(2), "--description"),
3184
3591
  dir: args.dir,
3185
3592
  entry: args.entry,
3186
3593
  folder: args.folder,
3187
3594
  json: Boolean(args.json),
3188
- notes: readNotesArg(process.argv.slice(2)),
3595
+ notes: readStringArg(process.argv.slice(2), "--notes"),
3189
3596
  open: !args["no-open"],
3190
3597
  password: args.password,
3191
3598
  review: args.review,
@@ -3259,7 +3666,11 @@ var list2 = defineCommand({
3259
3666
  description: "List with this API key (default: stored login)",
3260
3667
  type: "string"
3261
3668
  },
3262
- json: { description: "Machine-readable output", type: "boolean" }
3669
+ json: { description: "Machine-readable output", type: "boolean" },
3670
+ workspace: {
3671
+ description: "List this workspace's artifacts, grouped by folder",
3672
+ type: "string"
3673
+ }
3263
3674
  },
3264
3675
  meta: {
3265
3676
  description: "List the artifacts your account owns",
@@ -3267,7 +3678,11 @@ var list2 = defineCommand({
3267
3678
  },
3268
3679
  async run({ args }) {
3269
3680
  try {
3270
- await list({ apiKey: args["api-key"], json: Boolean(args.json) });
3681
+ await list({
3682
+ apiKey: args["api-key"],
3683
+ json: Boolean(args.json),
3684
+ workspace: args.workspace
3685
+ });
3271
3686
  } catch (err) {
3272
3687
  printCliError(err, Boolean(args.json));
3273
3688
  process.exit(1);
@@ -3276,26 +3691,43 @@ var list2 = defineCommand({
3276
3691
  });
3277
3692
  var folders2 = defineCommand({
3278
3693
  args: {
3694
+ action: {
3695
+ description: 'Optional action: "create"',
3696
+ required: false,
3697
+ type: "positional"
3698
+ },
3279
3699
  "api-key": {
3280
- description: "List with this API key (default: stored login)",
3700
+ description: "Authorize with an account API key (default: stored login)",
3281
3701
  type: "string"
3282
3702
  },
3283
3703
  json: { description: "Machine-readable output", type: "boolean" },
3704
+ name: {
3705
+ description: "Folder name (with create)",
3706
+ required: false,
3707
+ type: "positional"
3708
+ },
3709
+ visibility: {
3710
+ description: "With create: workspace (default) | public",
3711
+ type: "string"
3712
+ },
3284
3713
  workspace: {
3285
- description: "Workspace handle whose folders to list",
3714
+ description: "Workspace handle to list or create in",
3286
3715
  required: true,
3287
3716
  type: "string"
3288
3717
  }
3289
3718
  },
3290
3719
  meta: {
3291
- description: "List folders in a workspace",
3720
+ description: "List a workspace's folders, or create one (folders create)",
3292
3721
  name: "folders"
3293
3722
  },
3294
3723
  async run({ args }) {
3295
3724
  try {
3296
3725
  await folders({
3726
+ action: args.action,
3297
3727
  apiKey: args["api-key"],
3298
3728
  json: Boolean(args.json),
3729
+ name: args.name,
3730
+ visibility: args.visibility,
3299
3731
  workspace: args.workspace
3300
3732
  });
3301
3733
  } catch (err) {
@@ -3304,6 +3736,51 @@ var folders2 = defineCommand({
3304
3736
  }
3305
3737
  }
3306
3738
  });
3739
+ var move2 = defineCommand({
3740
+ args: {
3741
+ "api-key": {
3742
+ description: "Authorize with an account API key (default: stored login)",
3743
+ type: "string"
3744
+ },
3745
+ artifact: {
3746
+ description: "Move this artifact ID instead of the linked folder",
3747
+ type: "string"
3748
+ },
3749
+ dir: {
3750
+ default: ".",
3751
+ description: "Linked directory to resolve the artifact from (default: .)",
3752
+ type: "string"
3753
+ },
3754
+ folder: {
3755
+ description: "Target folder name (or id) in the artifact's workspace",
3756
+ type: "string"
3757
+ },
3758
+ json: { description: "Machine-readable output", type: "boolean" },
3759
+ unfiled: {
3760
+ description: "Move out of its folder (workspace root)",
3761
+ type: "boolean"
3762
+ }
3763
+ },
3764
+ meta: {
3765
+ description: "Move an artifact into a workspace folder (or --unfiled)",
3766
+ name: "move"
3767
+ },
3768
+ async run({ args }) {
3769
+ try {
3770
+ await move({
3771
+ apiKey: args["api-key"],
3772
+ artifact: args.artifact,
3773
+ dir: args.dir,
3774
+ folder: args.folder,
3775
+ json: Boolean(args.json),
3776
+ unfiled: Boolean(args.unfiled)
3777
+ });
3778
+ } catch (err) {
3779
+ printCliError(err, Boolean(args.json));
3780
+ process.exit(1);
3781
+ }
3782
+ }
3783
+ });
3307
3784
  var workspaces2 = defineCommand({
3308
3785
  args: {
3309
3786
  "api-key": {
@@ -3672,6 +4149,10 @@ var reply = defineCommand({
3672
4149
  description: "Act on an artifact ID instead of the linked folder",
3673
4150
  type: "string"
3674
4151
  },
4152
+ as: {
4153
+ description: 'Who is replying, e.g. "Claude Code" (default: detected; "" to opt out)',
4154
+ type: "string"
4155
+ },
3675
4156
  body: {
3676
4157
  description: "Reply text (omit to read from stdin)",
3677
4158
  type: "string"
@@ -3701,6 +4182,7 @@ var reply = defineCommand({
3701
4182
  await replyToThread({
3702
4183
  apiKey: args["api-key"],
3703
4184
  artifact: args.artifact,
4185
+ as: args.as,
3704
4186
  body: args.body,
3705
4187
  dir: args.dir,
3706
4188
  json: Boolean(args.json),
@@ -3723,6 +4205,10 @@ var comment2 = defineCommand({
3723
4205
  description: "Act on an artifact ID instead of the linked folder",
3724
4206
  type: "string"
3725
4207
  },
4208
+ as: {
4209
+ description: 'Tool posting on your behalf, e.g. "Claude Code" (default: detected)',
4210
+ type: "string"
4211
+ },
3726
4212
  body: {
3727
4213
  description: "Comment text (omit to read from stdin)",
3728
4214
  type: "string"
@@ -3750,7 +4236,7 @@ var comment2 = defineCommand({
3750
4236
  type: "string"
3751
4237
  },
3752
4238
  via: {
3753
- description: 'Attribution label, e.g. "Claude Code"',
4239
+ description: "Older name for --as",
3754
4240
  type: "string"
3755
4241
  }
3756
4242
  },
@@ -3763,6 +4249,7 @@ var comment2 = defineCommand({
3763
4249
  await comment({
3764
4250
  apiKey: args["api-key"],
3765
4251
  artifact: args.artifact,
4252
+ as: args.as,
3766
4253
  body: args.body,
3767
4254
  dir: args.dir,
3768
4255
  json: Boolean(args.json),
@@ -4023,6 +4510,7 @@ var main = defineCommand({
4023
4510
  listen,
4024
4511
  login: login2,
4025
4512
  logout: logout2,
4513
+ move: move2,
4026
4514
  open,
4027
4515
  protect: protect2,
4028
4516
  publish: publish2,