artifact-cafe 0.0.8 → 0.0.10

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 +1150 -436
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ function printBanner() {
30
30
  console.log();
31
31
  }
32
32
 
33
- // src/comments.ts
33
+ // src/comment.ts
34
34
  import path3 from "path";
35
35
 
36
36
  // ../shared/src/account.ts
@@ -58,6 +58,9 @@ var apiKeyListResponseSchema = z.object({
58
58
  var revokeApiKeyResponseSchema = z.object({
59
59
  revoked: z.literal(true)
60
60
  });
61
+ var notificationPrefsSchema = z.object({
62
+ commentEmailsEnabled: z.boolean()
63
+ });
61
64
  var meResponseSchema = z.object({
62
65
  // Best-effort profile field from our own user table; null when unknown.
63
66
  email: z.string().nullable(),
@@ -80,108 +83,291 @@ var artifactListResponseSchema = z.object({
80
83
  artifacts: z.array(artifactListItemSchema)
81
84
  });
82
85
 
83
- // ../shared/src/comments.ts
86
+ // ../shared/src/avatars.ts
84
87
  import { z as z2 } from "zod";
85
- var elementSnapshotSchema = z2.object({
86
- classes: z2.string().max(256).optional(),
87
- id: z2.string().max(256).optional(),
88
- tag: z2.string().min(1).max(128),
89
- text: z2.string().max(200).optional()
88
+ var AVATAR_CONTENT_TYPES = [
89
+ "image/png",
90
+ "image/jpeg",
91
+ "image/webp"
92
+ ];
93
+ var AVATAR_MAX_BYTES = 2 * 1024 * 1024;
94
+ var avatarContentTypeSchema = z2.enum(AVATAR_CONTENT_TYPES);
95
+ var userAvatarResponseSchema = z2.object({
96
+ image: z2.string().nullable()
97
+ });
98
+ var workspaceAvatarResponseSchema = z2.object({
99
+ logo: z2.string().nullable()
100
+ });
101
+
102
+ // ../shared/src/comments.ts
103
+ import { z as z4 } from "zod";
104
+
105
+ // ../shared/src/publish.ts
106
+ import { z as z3 } from "zod";
107
+ function isSafeArtifactPath(p) {
108
+ if (p.length === 0 || p.length > 1024) {
109
+ return false;
110
+ }
111
+ if (p.includes("\\") || p.includes("\0")) {
112
+ return false;
113
+ }
114
+ if (p.startsWith("/") || p.startsWith("~")) {
115
+ return false;
116
+ }
117
+ return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
118
+ }
119
+ var manifestFileSchema = z3.object({
120
+ hash: z3.string().regex(/^sha256_[a-f0-9]{64}$/),
121
+ mimeType: z3.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
122
+ path: z3.string().min(1).max(1024).refine(isSafeArtifactPath, {
123
+ message: "path must be a safe relative path"
124
+ }),
125
+ size: z3.number().int().positive()
126
+ });
127
+ var publishSourceSchema = z3.object({
128
+ agent: z3.string().optional(),
129
+ runId: z3.string().optional(),
130
+ type: z3.literal("cli")
90
131
  });
91
- var pointAnchorSchema = z2.object({
132
+ var createPublishSessionSchema = z3.object({
133
+ artifactId: z3.string().nullable(),
134
+ entryPath: z3.string().optional(),
135
+ files: z3.array(manifestFileSchema).min(1).max(1e3),
136
+ // SPEC-2 §6.4 — workspace placement is available only to authenticated
137
+ // members; the API enforces membership and folder ownership. folderId
138
+ // without workspaceId resolves against the account's personal workspace.
139
+ folderId: z3.string().min(1).optional(),
140
+ // Bullet-point changelog for this version, authored by the publisher.
141
+ // Optional on every publish (incl. v1); stored immutably on the version.
142
+ notes: z3.string().max(4e3).optional(),
143
+ source: publishSourceSchema,
144
+ title: z3.string().max(200).optional(),
145
+ workspaceId: z3.string().min(1).optional()
146
+ });
147
+ var publishSessionResponseSchema = z3.object({
148
+ artifactId: z3.string(),
149
+ expiresAt: z3.string(),
150
+ filesToUpload: z3.array(
151
+ z3.object({
152
+ method: z3.literal("PUT"),
153
+ path: z3.string(),
154
+ uploadUrl: z3.string().url()
155
+ })
156
+ ),
157
+ publishSessionId: z3.string(),
158
+ skippedFiles: z3.array(z3.string()),
159
+ versionId: z3.string()
160
+ });
161
+ var finalizeResponseSchema = z3.object({
162
+ artifactId: z3.string(),
163
+ // API-key publishes create the artifact already claimed (SPEC §5.4) —
164
+ // the CLI uses this to tell "claim URL pending" from "already yours".
165
+ claimed: z3.boolean(),
166
+ // Returned only on first publish of a new artifact (one-time).
167
+ claimUrl: z3.string().url().optional(),
168
+ publishToken: z3.string().optional(),
169
+ url: z3.string().url(),
170
+ versionId: z3.string(),
171
+ versionNumber: z3.number().int().positive()
172
+ });
173
+ var artifactResponseSchema = z3.object({
174
+ claimed: z3.boolean(),
175
+ currentVersion: z3.object({
176
+ entryUrl: z3.string().url(),
177
+ id: z3.string(),
178
+ versionNumber: z3.number().int().positive()
179
+ }).nullable(),
180
+ // SPEC-2 §6.4 — placement + visibility surface on the artifact.
181
+ folderId: z3.string().nullable(),
182
+ id: z3.string(),
183
+ slug: z3.string(),
184
+ title: z3.string(),
185
+ visibility: z3.enum(["public", "unlisted", "workspace"]),
186
+ workspaceId: z3.string().nullable()
187
+ });
188
+
189
+ // ../shared/src/comments.ts
190
+ var anchorPageSchema = z4.string().max(1024).refine(isSafeArtifactPath, {
191
+ message: "page must be a safe relative path"
192
+ }).optional();
193
+ var elementSnapshotSchema = z4.object({
194
+ classes: z4.string().max(256).optional(),
195
+ id: z4.string().max(256).optional(),
196
+ tag: z4.string().min(1).max(128),
197
+ text: z4.string().max(200).optional()
198
+ });
199
+ var pointAnchorSchema = z4.object({
92
200
  element: elementSnapshotSchema.optional(),
93
201
  // elementPath is the primary anchor; x/y are offsets within the
94
202
  // element's box, normalized 0..1 (SPEC.md §8.1).
95
- elementPath: z2.string().max(2048),
96
- type: z2.literal("point"),
97
- viewportHeight: z2.number().int().positive().max(1e5),
98
- viewportWidth: z2.number().int().positive().max(1e5),
99
- x: z2.number().min(0).max(1),
100
- y: z2.number().min(0).max(1)
101
- });
102
- var textAnchorSchema = z2.object({
103
- elementPath: z2.string().max(2048),
104
- endOffset: z2.number().int().nonnegative().max(1e8),
105
- prefix: z2.string().max(200).optional(),
106
- quote: z2.string().min(1).max(1e3),
107
- startOffset: z2.number().int().nonnegative().max(1e8),
108
- suffix: z2.string().max(200).optional(),
109
- type: z2.literal("text")
110
- });
111
- var anchorSchema = z2.discriminatedUnion("type", [
203
+ elementPath: z4.string().max(2048),
204
+ page: anchorPageSchema,
205
+ type: z4.literal("point"),
206
+ viewportHeight: z4.number().int().positive().max(1e5),
207
+ viewportWidth: z4.number().int().positive().max(1e5),
208
+ x: z4.number().min(0).max(1),
209
+ y: z4.number().min(0).max(1)
210
+ });
211
+ var textAnchorSchema = z4.object({
212
+ elementPath: z4.string().max(2048),
213
+ endOffset: z4.number().int().nonnegative().max(1e8),
214
+ page: anchorPageSchema,
215
+ prefix: z4.string().max(200).optional(),
216
+ quote: z4.string().min(1).max(1e3),
217
+ startOffset: z4.number().int().nonnegative().max(1e8),
218
+ suffix: z4.string().max(200).optional(),
219
+ type: z4.literal("text")
220
+ });
221
+ var pageAnchorSchema = z4.object({
222
+ page: anchorPageSchema,
223
+ type: z4.literal("page")
224
+ });
225
+ var anchorSchema = z4.discriminatedUnion("type", [
112
226
  pointAnchorSchema,
113
- textAnchorSchema
227
+ textAnchorSchema,
228
+ pageAnchorSchema
114
229
  ]);
115
- var displayNameSchema = z2.string().trim().min(1).max(64);
116
- var guestSchema = z2.object({
230
+ var displayNameSchema = z4.string().trim().min(1).max(64);
231
+ var guestSchema = z4.object({
117
232
  displayName: displayNameSchema,
118
- guestId: z2.string().min(16)
233
+ guestId: z4.string().min(16)
119
234
  });
120
- var createCommentThreadSchema = z2.object({
235
+ var viaLabelSchema = z4.string().trim().min(1).max(64);
236
+ var createCommentThreadSchema = z4.object({
121
237
  anchor: anchorSchema,
122
- body: z2.string().min(1).max(1e4),
238
+ body: z4.string().min(1).max(1e4),
239
+ guest: guestSchema.optional(),
240
+ versionId: z4.string(),
241
+ via: viaLabelSchema.optional()
242
+ });
243
+ var createCommentMessageSchema = z4.object({
244
+ body: z4.string().min(1).max(1e4),
123
245
  guest: guestSchema.optional(),
124
- versionId: z2.string()
246
+ via: viaLabelSchema.optional()
125
247
  });
126
- var createCommentMessageSchema = z2.object({
127
- body: z2.string().min(1).max(1e4),
248
+ var editCommentMessageSchema = z4.object({
249
+ body: z4.string().min(1).max(1e4),
128
250
  guest: guestSchema.optional()
129
251
  });
130
- var threadActionSchema = z2.object({
252
+ var threadActionSchema = z4.object({
131
253
  guest: guestSchema.optional()
132
254
  });
133
- var commentAuthorSchema = z2.object({
134
- avatarUrl: z2.string().nullable(),
135
- displayName: z2.string(),
136
- guestId: z2.string().nullable(),
137
- isViewer: z2.boolean(),
138
- kind: z2.enum(["member", "guest"])
255
+ var SINGLE_EMOJI = new RegExp("^\\p{RGI_Emoji}$", "v");
256
+ var REDUNDANT_VS16 = new RegExp("(\\p{Emoji_Presentation})\\uFE0F", "gu");
257
+ function canonicalizeEmoji(s) {
258
+ return s.replace(REDUNDANT_VS16, "$1");
259
+ }
260
+ var reactionEmojiSchema = z4.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}");
261
+ var toggleReactionSchema = z4.object({
262
+ emoji: reactionEmojiSchema,
263
+ guest: guestSchema.optional()
139
264
  });
140
- var commentMessageSchema = z2.object({
265
+ var commentAuthorSchema = z4.object({
266
+ avatarUrl: z4.string().nullable(),
267
+ displayName: z4.string(),
268
+ guestId: z4.string().nullable(),
269
+ isViewer: z4.boolean(),
270
+ kind: z4.enum(["member", "guest"]),
271
+ // Optional "via" attribution (§6.4). Null for plain comments; a bounded
272
+ // label the shell appends after the display name.
273
+ via: z4.string().nullable()
274
+ });
275
+ var messageReactionSchema = z4.object({
276
+ emoji: z4.string(),
277
+ reactors: z4.array(commentAuthorSchema)
278
+ });
279
+ var commentMessageSchema = z4.object({
141
280
  author: commentAuthorSchema,
142
- body: z2.string(),
143
- createdAt: z2.string(),
144
- id: z2.string()
145
- });
146
- var commentThreadSchema = z2.object({
281
+ // Empty string for a deleted message — the body is stripped server-side
282
+ // (§6.7.1 tombstone), never sent to the client and hidden there.
283
+ body: z4.string(),
284
+ createdAt: z4.string(),
285
+ // Soft-deleted (§6.7.1). A tombstone keeps thread structure and replies;
286
+ // the shell renders "Comment deleted" instead of the (absent) body.
287
+ deleted: z4.boolean(),
288
+ // Last edit time (§6.7.1), or null if never edited — drives the "edited"
289
+ // badge. No edit history is kept.
290
+ editedAt: z4.string().nullable(),
291
+ id: z4.string(),
292
+ // Defaulted, not required: a new CLI parsing an older server's
293
+ // response must not fail on the missing key.
294
+ reactions: z4.array(messageReactionSchema).default([])
295
+ });
296
+ var threadActorSchema = z4.object({
297
+ displayName: z4.string(),
298
+ kind: z4.enum(["member", "guest", "agent"])
299
+ });
300
+ var commentThreadSchema = z4.object({
301
+ // §6.6.1 — set while open to signal "seen, working on it"; cleared on
302
+ // reopen, kept (but superseded in display) once resolved. Defaulted so
303
+ // clients parsing an older server's response don't fail.
304
+ acknowledgedAt: z4.string().nullable().default(null),
305
+ acknowledgedBy: threadActorSchema.nullable().default(null),
147
306
  anchor: anchorSchema,
148
- artifactId: z2.string(),
149
- createdAt: z2.string(),
307
+ artifactId: z4.string(),
308
+ createdAt: z4.string(),
150
309
  createdBy: commentAuthorSchema,
151
- id: z2.string(),
152
- messages: z2.array(commentMessageSchema),
153
- status: z2.enum(["open", "resolved"]),
154
- versionId: z2.string()
155
- });
156
- var commentThreadListSchema = z2.object({
157
- threads: z2.array(commentThreadSchema)
158
- });
159
- var commentExportQuerySchema = z2.object({
160
- status: z2.enum(["open", "resolved", "all"]).default("open"),
161
- version: z2.enum(["current", "all"]).default("current")
162
- });
163
- var commentExportReplySchema = z2.object({
164
- author: z2.string(),
165
- authorKind: z2.enum(["member", "guest"]),
166
- body: z2.string(),
167
- createdAt: z2.string()
310
+ id: z4.string(),
311
+ messages: z4.array(commentMessageSchema),
312
+ // Null on threads resolved before actor recording existed, and always
313
+ // null while open.
314
+ resolvedBy: threadActorSchema.nullable().default(null),
315
+ status: z4.enum(["open", "resolved"]),
316
+ versionId: z4.string()
317
+ });
318
+ var commentThreadListSchema = z4.object({
319
+ threads: z4.array(commentThreadSchema)
320
+ });
321
+ var commentExportQuerySchema = z4.object({
322
+ status: z4.enum(["open", "resolved", "all"]).default("open"),
323
+ version: z4.enum(["current", "all"]).default("current")
324
+ });
325
+ var exportReactionSchema = z4.object({
326
+ count: z4.number().int().positive(),
327
+ emoji: z4.string()
328
+ });
329
+ var commentExportReplySchema = z4.object({
330
+ author: z4.string(),
331
+ authorKind: z4.enum(["member", "guest"]),
332
+ body: z4.string(),
333
+ createdAt: z4.string(),
334
+ // Soft-delete tombstone (§6.7.1): body is "" when true.
335
+ deleted: z4.boolean(),
336
+ editedAt: z4.string().nullable(),
337
+ // The message id — what `edit`/`delete` target (§5.1).
338
+ id: z4.string(),
339
+ reactions: z4.array(exportReactionSchema).optional()
168
340
  });
169
- var commentExportThreadSchema = z2.object({
341
+ var commentExportThreadSchema = z4.object({
342
+ // §6.6.1 — lets a polling agent skip threads it already acknowledged
343
+ // (acknowledged threads stay in status=open exports on purpose).
344
+ acknowledgedAt: z4.string().nullable().default(null),
170
345
  anchor: anchorSchema,
171
- author: z2.string(),
172
- authorKind: z2.enum(["member", "guest"]),
173
- body: z2.string(),
174
- createdAt: z2.string(),
175
- id: z2.string(),
176
- replies: z2.array(commentExportReplySchema),
177
- status: z2.enum(["open", "resolved"]),
178
- versionNumber: z2.number().int().positive()
179
- });
180
- var commentExportSchema = z2.object({
181
- artifactId: z2.string(),
182
- comments: z2.array(commentExportThreadSchema),
346
+ author: z4.string(),
347
+ authorKind: z4.enum(["member", "guest"]),
348
+ body: z4.string(),
349
+ createdAt: z4.string(),
350
+ deleted: z4.boolean(),
351
+ editedAt: z4.string().nullable(),
352
+ id: z4.string(),
353
+ // The opening message's id (distinct from the thread `id`) — `edit`/
354
+ // `delete` operate on messages, so the CLI needs it to target the first
355
+ // comment, not just the replies.
356
+ messageId: z4.string(),
357
+ reactions: z4.array(exportReactionSchema).optional(),
358
+ replies: z4.array(commentExportReplySchema),
359
+ resolvedBy: threadActorSchema.nullable().default(null),
360
+ status: z4.enum(["open", "resolved"]),
361
+ versionNumber: z4.number().int().positive()
362
+ });
363
+ var commentExportSchema = z4.object({
364
+ artifactId: z4.string(),
365
+ comments: z4.array(commentExportThreadSchema),
183
366
  // The artifact's current version (null before the first finalize).
184
- versionNumber: z2.number().int().positive().nullable()
367
+ versionNumber: z4.number().int().positive().nullable()
368
+ });
369
+ var commentReadResponseSchema = z4.object({
370
+ lastSeenAt: z4.string()
185
371
  });
186
372
  function elementLabel(el) {
187
373
  if (el.id) {
@@ -192,147 +378,166 @@ function elementLabel(el) {
192
378
  }
193
379
 
194
380
  // ../shared/src/errors.ts
195
- import { z as z3 } from "zod";
196
- var apiErrorSchema = z3.object({
197
- code: z3.string(),
198
- docsUrl: z3.string().url(),
199
- message: z3.string(),
200
- retryAfter: z3.number().int().positive().optional()
381
+ import { z as z5 } from "zod";
382
+ var apiErrorSchema = z5.object({
383
+ code: z5.string(),
384
+ docsUrl: z5.string().url(),
385
+ message: z5.string(),
386
+ retryAfter: z5.number().int().positive().optional()
387
+ });
388
+
389
+ // ../shared/src/feedback.ts
390
+ import { z as z6 } from "zod";
391
+ var feedbackRequestSchema = z6.object({
392
+ email: z6.string().trim().toLowerCase().email().optional().or(z6.literal("")),
393
+ message: z6.string().trim().min(1).max(4e3)
394
+ });
395
+ var feedbackResponseSchema = z6.object({
396
+ sent: z6.literal(true)
201
397
  });
202
398
 
203
399
  // ../shared/src/me.ts
204
- import { z as z4 } from "zod";
205
- var ownArtifactSchema = z4.object({
206
- createdAt: z4.string(),
207
- currentVersionNumber: z4.number().nullable(),
400
+ import { z as z7 } from "zod";
401
+ var ownArtifactSchema = z7.object({
402
+ createdAt: z7.string(),
403
+ currentVersionNumber: z7.number().nullable(),
208
404
  // Owned artifacts normally never expire; non-null only while an
209
405
  // expiry is still set (e.g. an unclaimed publish surfaced here later).
210
- expiresAt: z4.string().nullable(),
406
+ expiresAt: z7.string().nullable(),
211
407
  // SPEC-2 §8.4 — the containing folder for workspace-owned artifacts;
212
408
  // both null for personal artifacts. `folderId` drives the dashboard's
213
409
  // move control and folder filter; `folderName` is the display label.
214
- folderId: z4.string().nullable(),
215
- folderName: z4.string().nullable(),
410
+ folderId: z7.string().nullable(),
411
+ folderName: z7.string().nullable(),
216
412
  // Whether a publish token is still active — revocable from the
217
413
  // dashboard (§6.18); the token itself is never returned.
218
- hasPublishToken: z4.boolean(),
219
- id: z4.string(),
220
- openThreads: z4.number(),
221
- protected: z4.boolean(),
222
- slug: z4.string(),
223
- title: z4.string(),
224
- totalViews: z4.number(),
225
- updatedAt: z4.string(),
226
- url: z4.string(),
227
- versionCount: z4.number()
228
- });
229
- var ownArtifactsResponseSchema = z4.object({
230
- artifacts: z4.array(ownArtifactSchema)
231
- });
232
- var revokePublishTokenResponseSchema = z4.object({
233
- revoked: z4.boolean()
414
+ hasPublishToken: z7.boolean(),
415
+ id: z7.string(),
416
+ openThreads: z7.number(),
417
+ protected: z7.boolean(),
418
+ slug: z7.string(),
419
+ title: z7.string(),
420
+ totalViews: z7.number(),
421
+ // SPEC §6.20 — messages by others newer than the viewer's last-seen
422
+ // watermark. 0 for signed-out/API-key-less contexts.
423
+ unreadComments: z7.number(),
424
+ updatedAt: z7.string(),
425
+ url: z7.string(),
426
+ versionCount: z7.number()
427
+ });
428
+ var ownArtifactsResponseSchema = z7.object({
429
+ artifacts: z7.array(ownArtifactSchema)
430
+ });
431
+ var revokePublishTokenResponseSchema = z7.object({
432
+ revoked: z7.boolean()
234
433
  });
235
434
 
236
435
  // ../shared/src/owner.ts
237
- import { z as z6 } from "zod";
436
+ import { z as z9 } from "zod";
238
437
 
239
438
  // ../shared/src/workspaces.ts
240
- import { z as z5 } from "zod";
439
+ import { z as z8 } from "zod";
241
440
  var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/;
242
- var workspaceRoleSchema = z5.enum(["owner", "admin", "member"]);
243
- var workspaceInviteRoleSchema = z5.enum(["admin", "member"]);
244
- var workspaceHandleSchema = z5.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
441
+ var workspaceRoleSchema = z8.enum(["owner", "admin", "member"]);
442
+ var workspaceInviteRoleSchema = z8.enum(["admin", "member"]);
443
+ var workspaceHandleSchema = z8.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
245
444
  message: "handle must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
246
445
  });
247
- var createWorkspaceSchema = z5.object({
446
+ var createWorkspaceSchema = z8.object({
248
447
  handle: workspaceHandleSchema.optional(),
249
- name: z5.string().trim().min(1).max(100)
448
+ name: z8.string().trim().min(1).max(100)
250
449
  });
251
- var updateWorkspaceSchema = z5.object({
450
+ var updateWorkspaceSchema = z8.object({
252
451
  handle: workspaceHandleSchema.optional(),
253
- name: z5.string().trim().min(1).max(100).optional()
452
+ name: z8.string().trim().min(1).max(100).optional()
254
453
  }).refine((body) => body.handle !== void 0 || body.name !== void 0, {
255
454
  message: "provide at least one of name or handle"
256
455
  });
257
- var workspaceSchema = z5.object({
258
- createdAt: z5.string(),
259
- handle: z5.string(),
260
- id: z5.string(),
261
- name: z5.string(),
456
+ var workspaceSchema = z8.object({
457
+ createdAt: z8.string(),
458
+ handle: z8.string(),
459
+ id: z8.string(),
460
+ // Auto-provisioned solo workspace, one per user: non-deletable, takes
461
+ // no members, renameable like any workspace.
462
+ isPersonal: z8.boolean(),
463
+ // Uploaded logo URL, or null for the initials fallback. Set through the
464
+ // dedicated /workspaces/:id/avatar route, never the generic update.
465
+ logo: z8.string().nullable(),
466
+ name: z8.string(),
262
467
  role: workspaceRoleSchema.nullable(),
263
- updatedAt: z5.string()
468
+ updatedAt: z8.string()
264
469
  });
265
- var workspaceListResponseSchema = z5.object({
266
- workspaces: z5.array(workspaceSchema)
470
+ var workspaceListResponseSchema = z8.object({
471
+ workspaces: z8.array(workspaceSchema)
267
472
  });
268
- var deleteWorkspaceResponseSchema = z5.object({
269
- deleted: z5.literal(true)
473
+ var deleteWorkspaceResponseSchema = z8.object({
474
+ deleted: z8.literal(true)
270
475
  });
271
- var workspaceMemberSchema = z5.object({
272
- createdAt: z5.string(),
273
- email: z5.string().email(),
274
- image: z5.string().nullable(),
275
- name: z5.string(),
476
+ var workspaceMemberSchema = z8.object({
477
+ createdAt: z8.string(),
478
+ email: z8.string().email(),
479
+ image: z8.string().nullable(),
480
+ name: z8.string(),
276
481
  role: workspaceRoleSchema,
277
- userId: z5.string()
482
+ userId: z8.string()
278
483
  });
279
- var workspaceMemberListResponseSchema = z5.object({
280
- members: z5.array(workspaceMemberSchema)
484
+ var workspaceMemberListResponseSchema = z8.object({
485
+ members: z8.array(workspaceMemberSchema)
281
486
  });
282
- var updateWorkspaceMemberSchema = z5.object({
487
+ var updateWorkspaceMemberSchema = z8.object({
283
488
  role: workspaceRoleSchema
284
489
  });
285
- var removeWorkspaceMemberResponseSchema = z5.object({
286
- removed: z5.literal(true)
490
+ var removeWorkspaceMemberResponseSchema = z8.object({
491
+ removed: z8.literal(true)
287
492
  });
288
- var createWorkspaceInviteSchema = z5.object({
289
- email: z5.string().trim().toLowerCase().email().optional(),
290
- expiresAt: z5.string().datetime(),
291
- maxUses: z5.number().int().positive().optional(),
493
+ var createWorkspaceInviteSchema = z8.object({
494
+ email: z8.string().trim().toLowerCase().email().optional(),
495
+ expiresAt: z8.string().datetime(),
496
+ maxUses: z8.number().int().positive().optional(),
292
497
  role: workspaceInviteRoleSchema
293
498
  });
294
- var workspaceInviteSchema = z5.object({
295
- createdAt: z5.string(),
296
- email: z5.string().email().nullable(),
297
- expiresAt: z5.string(),
298
- id: z5.string(),
299
- maxUses: z5.number().int().positive().nullable(),
499
+ var workspaceInviteSchema = z8.object({
500
+ createdAt: z8.string(),
501
+ email: z8.string().email().nullable(),
502
+ expiresAt: z8.string(),
503
+ id: z8.string(),
504
+ maxUses: z8.number().int().positive().nullable(),
300
505
  role: workspaceInviteRoleSchema,
301
- tokenPrefix: z5.string(),
302
- useCount: z5.number().int().nonnegative()
506
+ tokenPrefix: z8.string(),
507
+ useCount: z8.number().int().nonnegative()
303
508
  });
304
509
  var createdWorkspaceInviteSchema = workspaceInviteSchema.extend({
305
- url: z5.string().url()
510
+ url: z8.string().url()
306
511
  });
307
- var workspaceInviteListResponseSchema = z5.object({
308
- invites: z5.array(workspaceInviteSchema)
512
+ var workspaceInviteListResponseSchema = z8.object({
513
+ invites: z8.array(workspaceInviteSchema)
309
514
  });
310
- var revokeWorkspaceInviteResponseSchema = z5.object({
311
- revoked: z5.literal(true)
515
+ var revokeWorkspaceInviteResponseSchema = z8.object({
516
+ revoked: z8.literal(true)
312
517
  });
313
- var acceptWorkspaceInviteSchema = z5.object({
314
- token: z5.string().min(1)
518
+ var acceptWorkspaceInviteSchema = z8.object({
519
+ token: z8.string().min(1)
315
520
  });
316
- var acceptWorkspaceInviteResponseSchema = z5.object({
521
+ var acceptWorkspaceInviteResponseSchema = z8.object({
317
522
  role: workspaceRoleSchema,
318
523
  workspace: workspaceSchema
319
524
  });
320
- var folderVisibilitySchema = z5.enum(["public", "workspace"]);
321
- var artifactVisibilitySchema = z5.enum([
525
+ var folderVisibilitySchema = z8.enum(["public", "workspace"]);
526
+ var artifactVisibilitySchema = z8.enum([
322
527
  "public",
323
528
  "unlisted",
324
529
  "workspace"
325
530
  ]);
326
- var createFolderSchema = z5.object({
327
- name: z5.string().trim().min(1).max(100),
531
+ var createFolderSchema = z8.object({
532
+ name: z8.string().trim().min(1).max(100),
328
533
  visibility: folderVisibilitySchema
329
534
  });
330
- var updateFolderSchema = z5.object({
535
+ var updateFolderSchema = z8.object({
331
536
  // With `visibility`: bulk-update the folder's artifacts to match
332
537
  // (SPEC-2 §3.4 "Changing a folder's visibility offers a bulk update").
333
- cascade: z5.boolean().optional(),
334
- name: z5.string().trim().min(1).max(100).optional(),
335
- sortOrder: z5.number().int().min(0).optional(),
538
+ cascade: z8.boolean().optional(),
539
+ name: z8.string().trim().min(1).max(100).optional(),
540
+ sortOrder: z8.number().int().min(0).optional(),
336
541
  visibility: folderVisibilitySchema.optional()
337
542
  }).refine(
338
543
  (body) => body.name !== void 0 || body.visibility !== void 0 || body.sortOrder !== void 0,
@@ -340,248 +545,170 @@ var updateFolderSchema = z5.object({
340
545
  ).refine((body) => !body.cascade || body.visibility !== void 0, {
341
546
  message: "cascade requires visibility"
342
547
  });
343
- var folderSchema = z5.object({
344
- createdAt: z5.string(),
345
- id: z5.string(),
346
- name: z5.string(),
548
+ var folderSchema = z8.object({
549
+ createdAt: z8.string(),
550
+ id: z8.string(),
551
+ name: z8.string(),
347
552
  // Permalink segment for the public folder page (/w/{handle}/f/{slug}).
348
553
  // Server-derived from the name; renames regenerate it.
349
- slug: z5.string(),
350
- sortOrder: z5.number().int(),
351
- updatedAt: z5.string(),
554
+ slug: z8.string(),
555
+ sortOrder: z8.number().int(),
556
+ updatedAt: z8.string(),
352
557
  visibility: folderVisibilitySchema,
353
- workspaceId: z5.string()
354
- });
355
- var folderListResponseSchema = z5.object({
356
- folders: z5.array(folderSchema)
357
- });
358
- var deleteFolderResponseSchema = z5.object({
359
- deleted: z5.literal(true)
360
- });
361
- var workspaceArtifactSchema = z5.object({
362
- createdAt: z5.string(),
363
- currentVersionNumber: z5.number().int().nullable(),
364
- folderId: z5.string().nullable(),
365
- id: z5.string(),
366
- openCommentCount: z5.number().int().nonnegative(),
367
- slug: z5.string(),
368
- title: z5.string(),
369
- updatedAt: z5.string(),
370
- url: z5.string().url(),
371
- versionCount: z5.number().int().nonnegative(),
558
+ workspaceId: z8.string()
559
+ });
560
+ var folderListResponseSchema = z8.object({
561
+ folders: z8.array(folderSchema)
562
+ });
563
+ var deleteFolderResponseSchema = z8.object({
564
+ deleted: z8.literal(true)
565
+ });
566
+ var workspaceArtifactSchema = z8.object({
567
+ createdAt: z8.string(),
568
+ currentVersionNumber: z8.number().int().nullable(),
569
+ folderId: z8.string().nullable(),
570
+ id: z8.string(),
571
+ openCommentCount: z8.number().int().nonnegative(),
572
+ slug: z8.string(),
573
+ title: z8.string(),
574
+ updatedAt: z8.string(),
575
+ url: z8.string().url(),
576
+ versionCount: z8.number().int().nonnegative(),
372
577
  visibility: artifactVisibilitySchema
373
578
  });
374
- var workspaceArtifactListResponseSchema = z5.object({
375
- artifacts: z5.array(workspaceArtifactSchema)
579
+ var workspaceArtifactListResponseSchema = z8.object({
580
+ artifacts: z8.array(workspaceArtifactSchema)
376
581
  });
377
582
 
378
583
  // ../shared/src/owner.ts
379
584
  var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
380
- var claimResponseSchema = z6.object({
381
- artifactId: z6.string(),
382
- slug: z6.string(),
383
- title: z6.string(),
384
- url: z6.string().url()
585
+ var claimResponseSchema = z9.object({
586
+ artifactId: z9.string(),
587
+ slug: z9.string(),
588
+ title: z9.string(),
589
+ url: z9.string().url()
385
590
  });
386
- var updateArtifactSchema = z6.object({
591
+ var updateArtifactSchema = z9.object({
387
592
  // SPEC-2 §6.4 — placement + visibility. `folderId: null` clears the
388
593
  // folder; `workspaceId: null` moves the artifact out of its workspace
389
594
  // (§5.5 ownership transfer).
390
- folderId: z6.string().min(1).nullable().optional(),
391
- slug: z6.string().min(3).max(63).regex(SLUG_PATTERN, {
595
+ folderId: z9.string().min(1).nullable().optional(),
596
+ slug: z9.string().min(3).max(63).regex(SLUG_PATTERN, {
392
597
  message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
393
598
  }).optional(),
394
- title: z6.string().min(1).max(200).optional(),
599
+ title: z9.string().min(1).max(200).optional(),
395
600
  visibility: artifactVisibilitySchema.optional(),
396
- workspaceId: z6.string().min(1).nullable().optional()
601
+ workspaceId: z9.string().min(1).nullable().optional()
397
602
  }).refine(
398
603
  (body) => body.title !== void 0 || body.slug !== void 0 || body.visibility !== void 0 || body.folderId !== void 0 || body.workspaceId !== void 0,
399
604
  {
400
605
  message: "provide at least one of title, slug, visibility, folderId, workspaceId"
401
606
  }
402
607
  );
403
- var deleteArtifactResponseSchema = z6.object({
404
- deleted: z6.literal(true)
608
+ var deleteArtifactResponseSchema = z9.object({
609
+ deleted: z9.literal(true)
405
610
  });
406
611
 
407
612
  // ../shared/src/presence.ts
408
- import { z as z7 } from "zod";
613
+ import { z as z10 } from "zod";
409
614
  var AGENT_PRESENCE = {
410
615
  heartbeatSeconds: 15,
411
616
  shellPollSeconds: 15,
412
617
  ttlSeconds: 45
413
618
  };
414
- var agentHeartbeatResponseSchema = z7.object({
415
- listening: z7.literal(true),
416
- ttlSeconds: z7.number().int().positive()
619
+ var agentHeartbeatResponseSchema = z10.object({
620
+ listening: z10.literal(true),
621
+ ttlSeconds: z10.number().int().positive()
417
622
  });
418
- var agentPresenceSchema = z7.object({
419
- listening: z7.boolean(),
420
- versionNumber: z7.number().int().positive().nullable()
623
+ var agentPresenceSchema = z10.object({
624
+ listening: z10.boolean(),
625
+ versionNumber: z10.number().int().positive().nullable()
421
626
  });
422
627
 
423
628
  // ../shared/src/protection.ts
424
- import { z as z8 } from "zod";
629
+ import { z as z11 } from "zod";
425
630
  var PASSWORD_MIN = 6;
426
631
  var PASSWORD_MAX = 128;
427
- var gatePasswordSchema = z8.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
428
- var setProtectionSchema = z8.object({
632
+ var gatePasswordSchema = z11.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
633
+ var setProtectionSchema = z11.object({
429
634
  password: gatePasswordSchema.nullable()
430
635
  });
431
- var protectionResponseSchema = z8.object({
432
- protected: z8.boolean()
433
- });
434
- var unlockSchema = z8.object({
435
- password: z8.string().min(1).max(1024)
436
- });
437
- var unlockResponseSchema = z8.object({
438
- unlocked: z8.literal(true)
439
- });
440
-
441
- // ../shared/src/publish.ts
442
- import { z as z9 } from "zod";
443
- function isSafeArtifactPath(p) {
444
- if (p.length === 0 || p.length > 1024) {
445
- return false;
446
- }
447
- if (p.includes("\\") || p.includes("\0")) {
448
- return false;
449
- }
450
- if (p.startsWith("/") || p.startsWith("~")) {
451
- return false;
452
- }
453
- return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
454
- }
455
- var manifestFileSchema = z9.object({
456
- hash: z9.string().regex(/^sha256_[a-f0-9]{64}$/),
457
- mimeType: z9.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
458
- path: z9.string().min(1).max(1024).refine(isSafeArtifactPath, {
459
- message: "path must be a safe relative path"
460
- }),
461
- size: z9.number().int().positive()
462
- });
463
- var publishSourceSchema = z9.object({
464
- agent: z9.string().optional(),
465
- runId: z9.string().optional(),
466
- type: z9.literal("cli")
636
+ var protectionResponseSchema = z11.object({
637
+ protected: z11.boolean()
467
638
  });
468
- var createPublishSessionSchema = z9.object({
469
- artifactId: z9.string().nullable(),
470
- entryPath: z9.string().optional(),
471
- files: z9.array(manifestFileSchema).min(1).max(1e3),
472
- // SPEC-2 §6.4 — workspace placement is available only to authenticated
473
- // members; the API enforces membership and folder ownership.
474
- folderId: z9.string().min(1).optional(),
475
- source: publishSourceSchema,
476
- title: z9.string().max(200).optional(),
477
- workspaceId: z9.string().min(1).optional()
639
+ var unlockSchema = z11.object({
640
+ password: z11.string().min(1).max(1024)
478
641
  });
479
- var publishSessionResponseSchema = z9.object({
480
- artifactId: z9.string(),
481
- expiresAt: z9.string(),
482
- filesToUpload: z9.array(
483
- z9.object({
484
- method: z9.literal("PUT"),
485
- path: z9.string(),
486
- uploadUrl: z9.string().url()
487
- })
488
- ),
489
- publishSessionId: z9.string(),
490
- skippedFiles: z9.array(z9.string()),
491
- versionId: z9.string()
492
- });
493
- var finalizeResponseSchema = z9.object({
494
- artifactId: z9.string(),
495
- // API-key publishes create the artifact already claimed (SPEC §5.4) —
496
- // the CLI uses this to tell "claim URL pending" from "already yours".
497
- claimed: z9.boolean(),
498
- // Returned only on first publish of a new artifact (one-time).
499
- claimUrl: z9.string().url().optional(),
500
- publishToken: z9.string().optional(),
501
- url: z9.string().url(),
502
- versionId: z9.string(),
503
- versionNumber: z9.number().int().positive()
504
- });
505
- var artifactResponseSchema = z9.object({
506
- claimed: z9.boolean(),
507
- currentVersion: z9.object({
508
- entryUrl: z9.string().url(),
509
- id: z9.string(),
510
- versionNumber: z9.number().int().positive()
511
- }).nullable(),
512
- // SPEC-2 §6.4 — placement + visibility surface on the artifact.
513
- folderId: z9.string().nullable(),
514
- id: z9.string(),
515
- slug: z9.string(),
516
- title: z9.string(),
517
- visibility: z9.enum(["public", "unlisted", "workspace"]),
518
- workspaceId: z9.string().nullable()
642
+ var unlockResponseSchema = z11.object({
643
+ unlocked: z11.literal(true)
519
644
  });
520
645
 
521
646
  // ../shared/src/report.ts
522
- import { z as z10 } from "zod";
523
- var reportRequestSchema = z10.object({
524
- reason: z10.string().max(1e3).optional()
647
+ import { z as z12 } from "zod";
648
+ var reportRequestSchema = z12.object({
649
+ reason: z12.string().max(1e3).optional()
525
650
  });
526
- var reportResponseSchema = z10.object({
527
- reported: z10.literal(true)
651
+ var reportResponseSchema = z12.object({
652
+ reported: z12.literal(true)
528
653
  });
529
654
 
530
655
  // ../shared/src/stats.ts
531
- import { z as z11 } from "zod";
532
- var avgViewSecondsSchema = z11.number().int().nonnegative().nullable();
533
- var versionViewStatsSchema = z11.object({
656
+ import { z as z13 } from "zod";
657
+ var avgViewSecondsSchema = z13.number().int().nonnegative().nullable();
658
+ var versionViewStatsSchema = z13.object({
534
659
  avgViewSeconds: avgViewSecondsSchema,
535
660
  // Views from inside the review shell's iframe vs. direct visits to
536
661
  // the render URL. views = embeddedViews + directViews.
537
- directViews: z11.number().int().nonnegative(),
538
- embeddedViews: z11.number().int().nonnegative(),
662
+ directViews: z13.number().int().nonnegative(),
663
+ embeddedViews: z13.number().int().nonnegative(),
539
664
  // Distinct hashed viewer IPs across the version's lifetime.
540
- uniqueViewers: z11.number().int().nonnegative(),
541
- versionNumber: z11.number().int().positive(),
542
- views: z11.number().int().nonnegative()
665
+ uniqueViewers: z13.number().int().nonnegative(),
666
+ versionNumber: z13.number().int().positive(),
667
+ views: z13.number().int().nonnegative()
543
668
  });
544
- var dailyViewsSchema = z11.object({
669
+ var dailyViewsSchema = z13.object({
545
670
  avgViewSeconds: avgViewSecondsSchema,
546
671
  // UTC day, YYYY-MM-DD.
547
- day: z11.string(),
548
- uniqueViewers: z11.number().int().nonnegative(),
549
- views: z11.number().int().nonnegative()
672
+ day: z13.string(),
673
+ uniqueViewers: z13.number().int().nonnegative(),
674
+ views: z13.number().int().nonnegative()
550
675
  });
551
- var artifactStatsResponseSchema = z11.object({
676
+ var artifactStatsResponseSchema = z13.object({
552
677
  avgViewSeconds: avgViewSecondsSchema,
553
- daily: z11.array(dailyViewsSchema),
678
+ daily: z13.array(dailyViewsSchema),
554
679
  // Distinct hashed viewer IPs across all versions and days.
555
- totalUniqueViewers: z11.number().int().nonnegative(),
556
- totalViews: z11.number().int().nonnegative(),
557
- versions: z11.array(versionViewStatsSchema)
680
+ totalUniqueViewers: z13.number().int().nonnegative(),
681
+ totalViews: z13.number().int().nonnegative(),
682
+ versions: z13.array(versionViewStatsSchema)
558
683
  });
559
684
 
560
685
  // ../shared/src/versions.ts
561
- import { z as z12 } from "zod";
562
- var versionSourceSchema = z12.object({
563
- agentName: z12.string().nullable(),
564
- type: z12.string()
686
+ import { z as z14 } from "zod";
687
+ var versionSourceSchema = z14.object({
688
+ agentName: z14.string().nullable(),
689
+ type: z14.string()
565
690
  });
566
- var versionChangesSchema = z12.object({
567
- added: z12.array(z12.string()),
568
- changed: z12.array(z12.string()),
569
- removed: z12.array(z12.string())
691
+ var versionChangesSchema = z14.object({
692
+ added: z14.array(z14.string()),
693
+ changed: z14.array(z14.string()),
694
+ removed: z14.array(z14.string())
570
695
  });
571
- var versionSummarySchema = z12.object({
696
+ var versionSummarySchema = z14.object({
572
697
  // Absent for the first version — there is no previous version to diff against.
573
698
  changes: versionChangesSchema.nullable(),
574
- createdAt: z12.string(),
575
- entryUrl: z12.string().url(),
576
- fileCount: z12.number().int().nonnegative(),
577
- id: z12.string(),
578
- isCurrent: z12.boolean(),
699
+ createdAt: z14.string(),
700
+ entryUrl: z14.string().url(),
701
+ fileCount: z14.number().int().nonnegative(),
702
+ id: z14.string(),
703
+ isCurrent: z14.boolean(),
704
+ // Author-supplied bullet-point changelog; null when the publish omitted it.
705
+ notes: z14.string().nullable(),
579
706
  source: versionSourceSchema,
580
- totalSizeBytes: z12.number().int().nonnegative(),
581
- versionNumber: z12.number().int().positive()
707
+ totalSizeBytes: z14.number().int().nonnegative(),
708
+ versionNumber: z14.number().int().positive()
582
709
  });
583
- var versionListResponseSchema = z12.object({
584
- versions: z12.array(versionSummarySchema)
710
+ var versionListResponseSchema = z14.object({
711
+ versions: z14.array(versionSummarySchema)
585
712
  });
586
713
 
587
714
  // src/api.ts
@@ -637,6 +764,16 @@ function createApiClient(apiUrl2, publishToken) {
637
764
  return {
638
765
  // SPEC §6.19 — mark the agent as actively waiting for feedback.
639
766
  // Authorized by the publish token this client was created with.
767
+ // SPEC §6.6.1 — mark an open thread acknowledged ("seen, working on
768
+ // it") without resolving it. Publish token or account key only —
769
+ // the server rejects guests.
770
+ async acknowledgeThread(threadId) {
771
+ return request(
772
+ `${base}/api/v1/comment-threads/${threadId}/acknowledge`,
773
+ { body: "{}", headers, method: "POST" },
774
+ (d) => commentThreadSchema.parse(d)
775
+ );
776
+ },
640
777
  async agentHeartbeat(artifactId) {
641
778
  return request(
642
779
  `${base}/api/v1/artifacts/${artifactId}/agent-presence`,
@@ -651,6 +788,36 @@ function createApiClient(apiUrl2, publishToken) {
651
788
  (d) => publishSessionResponseSchema.parse(d)
652
789
  );
653
790
  },
791
+ // SPEC §6.4 — open a new comment thread as the account this client's
792
+ // key belongs to (agent-originated review, §5.1 `comment`). The create
793
+ // route rejects a bare publish token (agents respond, accounts
794
+ // originate), so this must carry an account credential.
795
+ async createThread(artifactId, body) {
796
+ return request(
797
+ `${base}/api/v1/artifacts/${artifactId}/comments`,
798
+ { body: JSON.stringify(body), headers, method: "POST" },
799
+ (d) => commentThreadSchema.parse(d)
800
+ );
801
+ },
802
+ // SPEC §6.7.1 — soft-delete a message you authored. Authorized by this
803
+ // client's credential (account key, or the publish token for an agent
804
+ // message); a guest deletes from the browser, not the CLI.
805
+ async deleteMessage(messageId) {
806
+ await request(
807
+ `${base}/api/v1/comment-messages/${messageId}`,
808
+ { body: "{}", headers, method: "DELETE" },
809
+ () => void 0
810
+ );
811
+ },
812
+ // SPEC §6.7.1 — edit a message you authored (new body + "edited" badge).
813
+ // Same credential rule as delete; returns the updated thread.
814
+ async editMessage(messageId, body) {
815
+ return request(
816
+ `${base}/api/v1/comment-messages/${messageId}`,
817
+ { body: JSON.stringify({ body }), headers, method: "PATCH" },
818
+ (d) => commentThreadSchema.parse(d)
819
+ );
820
+ },
654
821
  async exportComments(artifactId, query) {
655
822
  const params = new URLSearchParams({
656
823
  status: query.status,
@@ -772,17 +939,17 @@ import {
772
939
  writeFileSync as writeFileSync2
773
940
  } from "fs";
774
941
  import path2 from "path";
775
- import { z as z14 } from "zod";
942
+ import { z as z16 } from "zod";
776
943
 
777
944
  // src/credentials.ts
778
945
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
779
946
  import { homedir } from "os";
780
947
  import path from "path";
781
- import { z as z13 } from "zod";
948
+ import { z as z15 } from "zod";
782
949
  var DEFAULT_API_URL = "https://artifact.cafe";
783
- var credentialsSchema = z13.object({
784
- keys: z13.record(z13.string(), z13.string()),
785
- version: z13.literal(1)
950
+ var credentialsSchema = z15.object({
951
+ keys: z15.record(z15.string(), z15.string()),
952
+ version: z15.literal(1)
786
953
  });
787
954
  function credentialsPath() {
788
955
  return path.join(homedir(), ".artifactcafe", "credentials");
@@ -832,22 +999,28 @@ function resolveApiKey(apiUrl2, flag) {
832
999
  }
833
1000
 
834
1001
  // src/config.ts
835
- var configSchema = z14.object({
836
- apiUrl: z14.string(),
837
- artifactId: z14.string(),
838
- folder: z14.string().optional(),
839
- folderId: z14.string().optional(),
1002
+ var configSchema = z16.object({
1003
+ apiUrl: z16.string(),
1004
+ artifactId: z16.string(),
1005
+ folder: z16.string().optional(),
1006
+ folderId: z16.string().optional(),
840
1007
  // Optional: a folder linked by `link` to an artifact you own carries no
841
1008
  // token — owner actions authorize via your stored login instead. A first
842
1009
  // publish still writes the anonymous-mode token here.
843
- publishToken: z14.string().optional(),
844
- slug: z14.string(),
845
- url: z14.string(),
846
- workspace: z14.string().optional(),
847
- workspaceId: z14.string().optional()
848
- });
1010
+ publishToken: z16.string().optional(),
1011
+ slug: z16.string(),
1012
+ url: z16.string(),
1013
+ workspace: z16.string().optional(),
1014
+ workspaceId: z16.string().optional(),
1015
+ workspaceKind: z16.enum(["personal", "workspace"]).optional()
1016
+ });
1017
+ var projectConfigSchema = z16.object({
1018
+ folder: z16.string().trim().min(1).optional(),
1019
+ workspace: z16.string().trim().min(1)
1020
+ }).strict();
849
1021
  var CONFIG_DIR = ".artifactcafe";
850
1022
  var CONFIG_FILE = "config.json";
1023
+ var PROJECT_CONFIG_FILE = "artifact-cafe.json";
851
1024
  function readConfig(dir) {
852
1025
  const file = path2.join(dir, CONFIG_DIR, CONFIG_FILE);
853
1026
  if (!existsSync2(file)) {
@@ -869,6 +1042,48 @@ function writeConfig(dir, config) {
869
1042
  );
870
1043
  ensureGitignored(dir);
871
1044
  }
1045
+ function readProjectConfig(dir) {
1046
+ let current = path2.resolve(dir);
1047
+ while (true) {
1048
+ const file = path2.join(current, PROJECT_CONFIG_FILE);
1049
+ if (existsSync2(file)) {
1050
+ try {
1051
+ return projectConfigSchema.parse(
1052
+ JSON.parse(readFileSync2(file, "utf8"))
1053
+ );
1054
+ } catch (error) {
1055
+ const message = error instanceof z16.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
1056
+ throw new Error(`Invalid ${file}: ${message}.`);
1057
+ }
1058
+ }
1059
+ if (existsSync2(path2.join(current, ".git"))) {
1060
+ return null;
1061
+ }
1062
+ const parent = path2.dirname(current);
1063
+ if (parent === current) {
1064
+ return null;
1065
+ }
1066
+ current = parent;
1067
+ }
1068
+ }
1069
+ function writeProjectConfig(dir, config) {
1070
+ let root = path2.resolve(dir);
1071
+ while (!existsSync2(path2.join(root, ".git"))) {
1072
+ const parent = path2.dirname(root);
1073
+ if (parent === root) {
1074
+ root = path2.resolve(dir);
1075
+ break;
1076
+ }
1077
+ root = parent;
1078
+ }
1079
+ const file = path2.join(root, PROJECT_CONFIG_FILE);
1080
+ writeFileSync2(
1081
+ file,
1082
+ `${JSON.stringify(projectConfigSchema.parse(config), null, 2)}
1083
+ `
1084
+ );
1085
+ return file;
1086
+ }
872
1087
  function ensureGitignored(dir) {
873
1088
  const gitignore = path2.join(dir, ".gitignore");
874
1089
  if (!existsSync2(gitignore)) {
@@ -907,7 +1122,107 @@ function resolveArtifactTarget(dir, opts = {}) {
907
1122
  return { apiUrl: apiUrl2, artifactId, credential, url };
908
1123
  }
909
1124
 
1125
+ // src/comment.ts
1126
+ async function readStdin() {
1127
+ if (process.stdin.isTTY) {
1128
+ return "";
1129
+ }
1130
+ const chunks = [];
1131
+ for await (const chunk of process.stdin) {
1132
+ chunks.push(chunk);
1133
+ }
1134
+ return Buffer.concat(chunks).toString("utf8");
1135
+ }
1136
+ function buildAnchor(opts) {
1137
+ if (opts.quote !== void 0) {
1138
+ const quote = opts.quote.trim();
1139
+ if (!quote) {
1140
+ throw new Error(
1141
+ "--quote can't be empty. Omit it entirely for a page-level comment."
1142
+ );
1143
+ }
1144
+ const anchor2 = {
1145
+ elementPath: "body",
1146
+ endOffset: 0,
1147
+ prefix: opts.prefix ?? "",
1148
+ quote,
1149
+ startOffset: 0,
1150
+ suffix: opts.suffix ?? "",
1151
+ type: "text"
1152
+ };
1153
+ if (opts.page) {
1154
+ anchor2.page = opts.page;
1155
+ }
1156
+ return anchor2;
1157
+ }
1158
+ if (opts.prefix || opts.suffix) {
1159
+ throw new Error("--prefix and --suffix only apply together with --quote.");
1160
+ }
1161
+ const anchor = { type: "page" };
1162
+ if (opts.page) {
1163
+ anchor.page = opts.page;
1164
+ }
1165
+ return anchor;
1166
+ }
1167
+ async function comment(opts) {
1168
+ const body = (opts.body ?? await readStdin()).trim();
1169
+ if (!body) {
1170
+ throw new Error(
1171
+ 'Nothing to post. Pass the comment with --body "\u2026", or pipe it on stdin.'
1172
+ );
1173
+ }
1174
+ const anchor = buildAnchor(opts);
1175
+ const dir = path3.resolve(opts.dir);
1176
+ const target = resolveArtifactTarget(dir, {
1177
+ apiKey: opts.apiKey,
1178
+ artifact: opts.artifact
1179
+ });
1180
+ const credential = resolveApiKey(target.apiUrl, opts.apiKey) ?? target.credential;
1181
+ const client = createApiClient(target.apiUrl, credential);
1182
+ const artifact = await client.getArtifact(target.artifactId);
1183
+ if (!artifact.currentVersion) {
1184
+ throw new Error(
1185
+ "This artifact has no published version yet \u2014 publish before commenting."
1186
+ );
1187
+ }
1188
+ let thread;
1189
+ try {
1190
+ thread = await client.createThread(target.artifactId, {
1191
+ anchor,
1192
+ body,
1193
+ versionId: artifact.currentVersion.id,
1194
+ via: opts.via
1195
+ });
1196
+ } catch (err) {
1197
+ if (err instanceof ApiRequestError && err.status === 401) {
1198
+ throw new Error(
1199
+ "Publish tokens can't open threads. Run `artifact-cafe login` to comment as your account."
1200
+ );
1201
+ }
1202
+ throw err;
1203
+ }
1204
+ if (opts.json) {
1205
+ console.log(
1206
+ JSON.stringify(
1207
+ {
1208
+ anchorType: thread.anchor.type,
1209
+ id: thread.id,
1210
+ messageId: thread.messages[0]?.id,
1211
+ versionId: thread.versionId
1212
+ },
1213
+ null,
1214
+ 2
1215
+ )
1216
+ );
1217
+ return;
1218
+ }
1219
+ const url = target.url ?? reviewUrl(target.apiUrl, artifact.slug);
1220
+ const where = anchor.type === "text" ? ` on \u201C${anchor.quote}\u201D` : " on the page";
1221
+ console.log(`\u2713 Opened thread ${thread.id}${where} \u2014 ${url}`);
1222
+ }
1223
+
910
1224
  // src/comments.ts
1225
+ import path4 from "path";
911
1226
  var STATUSES = ["open", "resolved", "all"];
912
1227
  var VERSIONS = ["current", "all"];
913
1228
  var pollSeconds = () => Number(process.env.AC_WAIT_POLL_SECONDS ?? 5);
@@ -926,7 +1241,7 @@ async function comments(opts) {
926
1241
  }
927
1242
  const status = opts.status;
928
1243
  const version = opts.version;
929
- const dir = path3.resolve(opts.dir);
1244
+ const dir = path4.resolve(opts.dir);
930
1245
  const target = resolveArtifactTarget(dir, {
931
1246
  apiKey: opts.apiKey,
932
1247
  artifact: opts.artifact
@@ -1085,21 +1400,36 @@ function printHuman(data, url, status, version) {
1085
1400
  function printThread(t) {
1086
1401
  const marker = t.status === "resolved" ? "\u2713" : "\u25CF";
1087
1402
  const date = t.createdAt.slice(0, 10);
1403
+ const edited = t.editedAt && !t.deleted ? " \xB7 edited" : "";
1404
+ let state = "";
1405
+ if (t.status === "resolved" && t.resolvedBy) {
1406
+ state = ` \xB7 resolved by ${t.resolvedBy.displayName}`;
1407
+ } else if (t.status === "open" && t.acknowledgedAt) {
1408
+ state = " \xB7 acknowledged";
1409
+ }
1088
1410
  console.log(
1089
- `${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}`
1411
+ `${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}${edited}${state}`
1090
1412
  );
1091
1413
  if (t.anchor.type === "text") {
1092
1414
  console.log(` \u21B3 "${t.anchor.quote}"`);
1093
- } else if (t.anchor.element) {
1415
+ } else if (t.anchor.type === "point") {
1094
1416
  const el = t.anchor.element;
1095
- const text = el.text ? ` "${el.text}"` : "";
1096
- console.log(` \u21B3 at ${elementLabel(el)}${text} \u2014 ${t.anchor.elementPath}`);
1417
+ if (el) {
1418
+ const text = el.text ? ` "${el.text}"` : "";
1419
+ console.log(
1420
+ ` \u21B3 at ${elementLabel(el)}${text} \u2014 ${t.anchor.elementPath}`
1421
+ );
1422
+ } else {
1423
+ console.log(` \u21B3 at ${t.anchor.elementPath}`);
1424
+ }
1097
1425
  } else {
1098
- console.log(` \u21B3 at ${t.anchor.elementPath}`);
1426
+ console.log(t.anchor.page ? ` \u21B3 on ${t.anchor.page}` : " \u21B3 page comment");
1099
1427
  }
1100
- console.log(indent(t.body, " "));
1428
+ console.log(indent(t.deleted ? "[deleted]" : t.body, " "));
1101
1429
  for (const reply2 of t.replies) {
1102
- console.log(indent(`${reply2.author}: ${reply2.body}`, " "));
1430
+ const replyEdited = reply2.editedAt && !reply2.deleted ? " (edited)" : "";
1431
+ const replyBody = reply2.deleted ? "[deleted]" : reply2.body;
1432
+ console.log(indent(`${reply2.author}: ${replyBody}${replyEdited}`, " "));
1103
1433
  }
1104
1434
  console.log("");
1105
1435
  }
@@ -1108,19 +1438,20 @@ function indent(text, pad) {
1108
1438
  }
1109
1439
 
1110
1440
  // src/dashboard.ts
1111
- import path6 from "path";
1441
+ import path7 from "path";
1112
1442
 
1113
1443
  // src/publish.ts
1114
1444
  import { exec } from "child_process";
1115
1445
  import { readFileSync as readFileSync4 } from "fs";
1116
- import path5 from "path";
1446
+ import path6 from "path";
1447
+ import { createInterface } from "readline";
1117
1448
 
1118
1449
  // src/manifest.ts
1119
1450
  import { createHash } from "crypto";
1120
1451
  import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
1121
- import path4 from "path";
1452
+ import path5 from "path";
1122
1453
  var IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".artifactcafe"]);
1123
- var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
1454
+ var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store", "artifact-cafe.json"]);
1124
1455
  function isIgnoredFile(name) {
1125
1456
  if (IGNORED_FILES.has(name)) {
1126
1457
  return true;
@@ -1167,7 +1498,7 @@ var MIME_TYPES = {
1167
1498
  ".xml": "application/xml"
1168
1499
  };
1169
1500
  function mimeFor(filePath) {
1170
- return MIME_TYPES[path4.extname(filePath).toLowerCase()] ?? "application/octet-stream";
1501
+ return MIME_TYPES[path5.extname(filePath).toLowerCase()] ?? "application/octet-stream";
1171
1502
  }
1172
1503
  function scanDirectory(dir) {
1173
1504
  const files = [];
@@ -1175,7 +1506,7 @@ function scanDirectory(dir) {
1175
1506
  function walk(current, relPrefix) {
1176
1507
  for (const entry of readdirSync(current, { withFileTypes: true })) {
1177
1508
  const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
1178
- const abs = path4.join(current, entry.name);
1509
+ const abs = path5.join(current, entry.name);
1179
1510
  if (entry.isSymbolicLink()) {
1180
1511
  skipped.push(rel);
1181
1512
  continue;
@@ -1241,7 +1572,7 @@ function resolveEntry(files, explicit) {
1241
1572
  // src/publish.ts
1242
1573
  var UPLOAD_CONCURRENCY = 6;
1243
1574
  async function publish(opts) {
1244
- const dir = path5.resolve(opts.dir);
1575
+ const dir = path6.resolve(opts.dir);
1245
1576
  const config = readConfig(dir);
1246
1577
  if (opts.password !== void 0) {
1247
1578
  const parsed = gatePasswordSchema.safeParse(opts.password);
@@ -1253,7 +1584,13 @@ async function publish(opts) {
1253
1584
  const token = (artifactId !== null && artifactId === config?.artifactId ? config.publishToken : void 0) ?? process.env.ARTIFACT_CAFE_TOKEN;
1254
1585
  const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
1255
1586
  const apiKey = resolveApiKey(apiUrl2, opts.apiKey);
1256
- const placement = await resolvePlacement({ apiKey, apiUrl: apiUrl2, config, opts });
1587
+ const placement = await resolvePlacement({
1588
+ apiKey,
1589
+ apiUrl: apiUrl2,
1590
+ config,
1591
+ isNewArtifact: artifactId === null,
1592
+ opts
1593
+ });
1257
1594
  const { files, skipped } = scanDirectory(dir);
1258
1595
  if (files.length === 0) {
1259
1596
  throw new Error(`Nothing to publish in ${dir} (after ignore rules).`);
@@ -1272,13 +1609,16 @@ async function publish(opts) {
1272
1609
  entryPath,
1273
1610
  files: files.map(({ absPath: _absPath, ...manifest }) => manifest),
1274
1611
  ...placement.folderId ? { folderId: placement.folderId } : {},
1612
+ // Agents often pass a double-quoted string where "\n" is a literal
1613
+ // backslash-n; turn those back into real newlines so bullets survive.
1614
+ ...opts.notes?.trim() ? { notes: opts.notes.replace(/\\n/g, "\n").trim() } : {},
1275
1615
  source: {
1276
1616
  agent: process.env.ARTIFACT_CAFE_AGENT,
1277
1617
  runId: process.env.ARTIFACT_CAFE_RUN_ID,
1278
1618
  type: "cli"
1279
1619
  },
1280
1620
  title: opts.title,
1281
- ...placement.workspaceId ? { workspaceId: placement.workspaceId } : {}
1621
+ ...placement.publishWorkspaceId ? { workspaceId: placement.publishWorkspaceId } : {}
1282
1622
  };
1283
1623
  let credential = apiKey ?? token;
1284
1624
  let client = createApiClient(apiUrl2, credential);
@@ -1310,8 +1650,9 @@ async function publish(opts) {
1310
1650
  );
1311
1651
  });
1312
1652
  const result = await client.finalize(session.publishSessionId);
1653
+ const rememberedProjectFile = placement.rememberProjectDefault ? writeProjectConfig(dir, placement.rememberProjectDefault) : null;
1313
1654
  const existingToken = config?.artifactId === result.artifactId ? config.publishToken : void 0;
1314
- const shouldWriteConfig = result.publishToken !== void 0 || config?.artifactId === result.artifactId || opts.workspace !== void 0 || opts.folder !== void 0;
1655
+ const shouldWriteConfig = result.publishToken !== void 0 || config?.artifactId === result.artifactId || opts.workspace !== void 0 || opts.folder !== void 0 || placement.workspaceId !== void 0;
1315
1656
  if (shouldWriteConfig) {
1316
1657
  writeConfig(dir, {
1317
1658
  apiUrl: apiUrl2,
@@ -1322,7 +1663,8 @@ async function publish(opts) {
1322
1663
  slug: slugFromUrl(result.url),
1323
1664
  url: result.url,
1324
1665
  ...placement.workspace ? { workspace: placement.workspace } : {},
1325
- ...placement.workspaceId ? { workspaceId: placement.workspaceId } : {}
1666
+ ...placement.workspaceId ? { workspaceId: placement.workspaceId } : {},
1667
+ ...placement.destination === "personal" || placement.destination === "workspace" ? { workspaceKind: placement.destination } : {}
1326
1668
  });
1327
1669
  }
1328
1670
  if (opts.password !== void 0) {
@@ -1338,6 +1680,8 @@ async function publish(opts) {
1338
1680
  {
1339
1681
  artifactId: result.artifactId,
1340
1682
  claimed: result.claimed,
1683
+ destination: placement.destination,
1684
+ destinationSource: placement.destinationSource,
1341
1685
  folder: placement.folder ?? null,
1342
1686
  folderId: placement.folderId ?? null,
1343
1687
  url: result.url,
@@ -1356,6 +1700,12 @@ async function publish(opts) {
1356
1700
  );
1357
1701
  } else if (result.versionNumber === 1) {
1358
1702
  console.log("\n\u2713 Published to artifact.cafe\n");
1703
+ console.log(`Destination: ${placement.destinationLabel}
1704
+ `);
1705
+ if (rememberedProjectFile) {
1706
+ console.log(`Project default: ${rememberedProjectFile}
1707
+ `);
1708
+ }
1359
1709
  console.log(`Review URL:
1360
1710
  ${result.url}
1361
1711
  `);
@@ -1375,6 +1725,12 @@ ${result.claimUrl}
1375
1725
  console.log(`
1376
1726
  \u2713 Published v${result.versionNumber}
1377
1727
  `);
1728
+ console.log(`Destination: ${placement.destinationLabel}
1729
+ `);
1730
+ if (rememberedProjectFile) {
1731
+ console.log(`Project default: ${rememberedProjectFile}
1732
+ `);
1733
+ }
1378
1734
  console.log(`${result.url}
1379
1735
  `);
1380
1736
  if (opts.password !== void 0) {
@@ -1386,75 +1742,181 @@ ${result.claimUrl}
1386
1742
  }
1387
1743
  }
1388
1744
  async function resolvePlacement(params) {
1389
- const { apiKey, apiUrl: apiUrl2, config, opts } = params;
1745
+ const { apiKey, apiUrl: apiUrl2, config, isNewArtifact, opts } = params;
1390
1746
  const hasPlacementFlag = opts.workspace !== void 0 || opts.folder !== void 0;
1391
1747
  if (!hasPlacementFlag && config?.workspaceId) {
1392
- if (!apiKey) {
1748
+ const isPersonal = config.workspaceKind === "personal";
1749
+ if (!(apiKey || isPersonal)) {
1393
1750
  throw new Error(
1394
1751
  `Publishing into workspace ${config.workspace ?? config.workspaceId} needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
1395
1752
  );
1396
1753
  }
1397
1754
  return {
1755
+ destination: isPersonal ? "personal" : "workspace",
1756
+ destinationLabel: isPersonal ? "Personal" : config.workspace ?? config.workspaceId,
1757
+ destinationSource: "artifact-config",
1398
1758
  ...config.folder ? { folder: config.folder } : {},
1399
1759
  ...config.folderId ? { folderId: config.folderId } : {},
1400
1760
  ...config.workspace ? { workspace: config.workspace } : {},
1761
+ ...apiKey ? { publishWorkspaceId: config.workspaceId } : {},
1401
1762
  workspaceId: config.workspaceId
1402
1763
  };
1403
1764
  }
1404
- if (!hasPlacementFlag) {
1405
- return {};
1765
+ if (!(hasPlacementFlag || isNewArtifact)) {
1766
+ return {
1767
+ destination: "existing-artifact",
1768
+ destinationLabel: "Existing artifact",
1769
+ destinationSource: "artifact-config"
1770
+ };
1406
1771
  }
1407
- if (!apiKey) {
1772
+ const projectConfig = isNewArtifact ? readProjectConfig(opts.dir) : null;
1773
+ const wantsWorkspace = opts.workspace !== void 0 || opts.folder !== void 0 || projectConfig !== null;
1774
+ if (wantsWorkspace && !apiKey) {
1408
1775
  throw new Error(
1409
1776
  "Publishing into a workspace needs an account \u2014 run `artifact-cafe login` first."
1410
1777
  );
1411
1778
  }
1412
- const workspaceHandle = opts.workspace ?? config?.workspace;
1413
- if (!workspaceHandle) {
1779
+ if (!(hasPlacementFlag || projectConfig)) {
1780
+ if (!apiKey) {
1781
+ return {
1782
+ destination: "anonymous",
1783
+ destinationLabel: "Anonymous",
1784
+ destinationSource: "anonymous-default"
1785
+ };
1786
+ }
1787
+ if (shouldPromptForWorkspace(opts)) {
1788
+ const data2 = await createApiClient(apiUrl2, apiKey).listWorkspaces();
1789
+ if (data2.workspaces.length > 1) {
1790
+ const choice = await pickWorkspace(data2.workspaces);
1791
+ return {
1792
+ destination: choice.workspace.isPersonal ? "personal" : "workspace",
1793
+ destinationLabel: choice.workspace.isPersonal ? "Personal" : choice.workspace.name,
1794
+ destinationSource: "interactive",
1795
+ publishWorkspaceId: choice.workspace.id,
1796
+ ...choice.remember ? {
1797
+ rememberProjectDefault: {
1798
+ workspace: choice.workspace.handle
1799
+ }
1800
+ } : {},
1801
+ workspace: choice.workspace.handle,
1802
+ workspaceId: choice.workspace.id
1803
+ };
1804
+ }
1805
+ }
1806
+ return {
1807
+ destination: "personal",
1808
+ destinationLabel: "Personal",
1809
+ destinationSource: "personal-default"
1810
+ };
1811
+ }
1812
+ if (!isNewArtifact && opts.folder !== void 0 && opts.workspace === void 0 && !(config?.workspace || config?.workspaceId)) {
1414
1813
  throw new Error("--folder requires --workspace or a stored workspace.");
1415
1814
  }
1416
1815
  const client = createApiClient(apiUrl2, apiKey);
1417
- let workspaceId;
1418
- if (opts.workspace === void 0 && config?.workspaceId) {
1419
- ({ workspaceId } = config);
1420
- } else {
1421
- const data2 = await client.listWorkspaces();
1422
- const workspace = data2.workspaces.find(
1816
+ const data = await client.listWorkspaces();
1817
+ const workspaceHandle = opts.workspace ?? config?.workspace ?? projectConfig?.workspace;
1818
+ let workspace;
1819
+ if (workspaceHandle) {
1820
+ workspace = data.workspaces.find(
1423
1821
  (candidate) => candidate.handle === workspaceHandle
1424
1822
  );
1425
- if (!workspace) {
1426
- throw new Error(
1427
- `No workspace with handle "${workspaceHandle}" belongs to this account.`
1428
- );
1429
- }
1430
- workspaceId = workspace.id;
1823
+ } else if (config?.workspaceId) {
1824
+ workspace = data.workspaces.find(
1825
+ (candidate) => candidate.id === config.workspaceId
1826
+ );
1827
+ } else {
1828
+ workspace = data.workspaces.find((candidate) => candidate.isPersonal);
1829
+ }
1830
+ if (!workspace) {
1831
+ const target = workspaceHandle ? `workspace with handle "${workspaceHandle}"` : "personal workspace";
1832
+ throw new Error(`No ${target} belongs to this account.`);
1431
1833
  }
1432
- const folderName = opts.workspace !== void 0 && opts.folder === void 0 ? void 0 : opts.folder ?? config?.folder;
1834
+ const folderName = opts.workspace !== void 0 && opts.folder === void 0 ? void 0 : opts.folder ?? projectConfig?.folder;
1835
+ const destinationSource = hasPlacementFlag ? "explicit" : "project-default";
1433
1836
  if (!folderName) {
1434
- return { workspace: workspaceHandle, workspaceId };
1837
+ return {
1838
+ destination: workspace.isPersonal ? "personal" : "workspace",
1839
+ destinationLabel: workspace.isPersonal ? "Personal" : workspace.name,
1840
+ destinationSource,
1841
+ publishWorkspaceId: workspace.id,
1842
+ workspace: workspace.handle,
1843
+ workspaceId: workspace.id
1844
+ };
1435
1845
  }
1436
- const data = await client.listFolders(workspaceId);
1437
- const matches = data.folders.filter(
1846
+ const folderData = await client.listFolders(workspace.id);
1847
+ const matches = folderData.folders.filter(
1438
1848
  (candidate) => candidate.name === folderName || candidate.id === folderName
1439
1849
  );
1440
1850
  if (matches.length === 0) {
1441
1851
  throw new Error(
1442
- `No folder named "${folderName}" exists in workspace ${workspaceHandle}.`
1852
+ `No folder named "${folderName}" exists in workspace ${workspace.handle}.`
1443
1853
  );
1444
1854
  }
1445
1855
  if (matches.length > 1) {
1446
1856
  throw new Error(
1447
- `More than one folder is named "${folderName}" in workspace ${workspaceHandle}; pass the folder id instead.`
1857
+ `More than one folder is named "${folderName}" in workspace ${workspace.handle}; pass the folder id instead.`
1448
1858
  );
1449
1859
  }
1450
1860
  const folder = matches[0];
1451
1861
  return {
1862
+ destination: workspace.isPersonal ? "personal" : "workspace",
1863
+ destinationLabel: workspace.isPersonal ? "Personal" : workspace.name,
1864
+ destinationSource,
1452
1865
  folder: folder.name,
1453
1866
  folderId: folder.id,
1454
- workspace: workspaceHandle,
1455
- workspaceId
1867
+ publishWorkspaceId: workspace.id,
1868
+ workspace: workspace.handle,
1869
+ workspaceId: workspace.id
1456
1870
  };
1457
1871
  }
1872
+ function shouldPromptForWorkspace(opts) {
1873
+ if (opts.json || process.env.ARTIFACT_CAFE_AGENT) {
1874
+ return false;
1875
+ }
1876
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
1877
+ }
1878
+ function pickWorkspace(workspaces3) {
1879
+ const choices = [...workspaces3].sort((a, b) => {
1880
+ if (a.isPersonal !== b.isPersonal) {
1881
+ return a.isPersonal ? -1 : 1;
1882
+ }
1883
+ return a.name.localeCompare(b.name);
1884
+ });
1885
+ console.log("Where should this artifact live?\n");
1886
+ choices.forEach((workspace, index) => {
1887
+ const label = workspace.isPersonal ? "Personal" : workspace.name;
1888
+ const handle = workspace.isPersonal ? "" : ` \xB7 ${workspace.handle}`;
1889
+ console.log(` ${String(index + 1).padStart(2)}. ${label}${handle}`);
1890
+ });
1891
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1892
+ return new Promise((resolve2, reject) => {
1893
+ rl.question(`
1894
+ Publish where? [1-${choices.length}]: `, (answer) => {
1895
+ const index = Number(answer.trim()) - 1;
1896
+ const workspace = choices[index];
1897
+ if (!(Number.isInteger(index) && workspace)) {
1898
+ rl.close();
1899
+ reject(new Error(`Enter a number between 1 and ${choices.length}.`));
1900
+ return;
1901
+ }
1902
+ if (workspace.isPersonal) {
1903
+ rl.close();
1904
+ resolve2({ remember: false, workspace });
1905
+ return;
1906
+ }
1907
+ rl.question(
1908
+ `Remember ${workspace.name} for new artifacts in this project? [y/N]: `,
1909
+ (rememberAnswer) => {
1910
+ rl.close();
1911
+ resolve2({
1912
+ remember: /^y(?:es)?$/i.test(rememberAnswer.trim()),
1913
+ workspace
1914
+ });
1915
+ }
1916
+ );
1917
+ });
1918
+ });
1919
+ }
1458
1920
  async function inBatches(items, concurrency, fn) {
1459
1921
  let next = 0;
1460
1922
  const workers = Array.from(
@@ -1491,7 +1953,7 @@ function openInBrowser(url) {
1491
1953
 
1492
1954
  // src/dashboard.ts
1493
1955
  function openDashboard(opts) {
1494
- const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path6.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
1956
+ const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path7.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
1495
1957
  const url = `${base.replace(/\/$/, "")}/dashboard`;
1496
1958
  console.log(url);
1497
1959
  if (process.stdout.isTTY) {
@@ -1580,8 +2042,8 @@ function printFolder(folder) {
1580
2042
  }
1581
2043
 
1582
2044
  // src/link.ts
1583
- import path7 from "path";
1584
- import { createInterface } from "readline";
2045
+ import path8 from "path";
2046
+ import { createInterface as createInterface2 } from "readline";
1585
2047
  async function link(opts) {
1586
2048
  const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
1587
2049
  /\/$/,
@@ -1623,7 +2085,7 @@ async function link(opts) {
1623
2085
  }
1624
2086
  chosen = await pickArtifact(artifacts);
1625
2087
  }
1626
- const dir = path7.resolve(opts.dir);
2088
+ const dir = path8.resolve(opts.dir);
1627
2089
  writeConfig(dir, {
1628
2090
  apiUrl: apiUrl2,
1629
2091
  artifactId: chosen.id,
@@ -1660,7 +2122,7 @@ function pickArtifact(artifacts) {
1660
2122
  ` ${String(i + 1).padStart(2)}. ${a.title} \xB7 ${v} \xB7 ${a.url}`
1661
2123
  );
1662
2124
  });
1663
- const rl = createInterface({ input: process.stdin, output: process.stdout });
2125
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
1664
2126
  return new Promise((resolve2, reject) => {
1665
2127
  rl.question(`
1666
2128
  Link which? [1-${artifacts.length}]: `, (answer) => {
@@ -1732,13 +2194,13 @@ function relativeTime(iso) {
1732
2194
  }
1733
2195
 
1734
2196
  // src/login.ts
1735
- import { createInterface as createInterface2 } from "readline";
2197
+ import { createInterface as createInterface3 } from "readline";
1736
2198
  function apiUrl() {
1737
2199
  return (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/, "");
1738
2200
  }
1739
2201
  function promptSecret(question) {
1740
2202
  return new Promise((resolve2) => {
1741
- const rl = createInterface2({
2203
+ const rl = createInterface3({
1742
2204
  input: process.stdin,
1743
2205
  output: process.stdout
1744
2206
  });
@@ -1845,10 +2307,69 @@ function logout(opts) {
1845
2307
  );
1846
2308
  }
1847
2309
 
2310
+ // src/message.ts
2311
+ import path9 from "path";
2312
+ async function readStdin2() {
2313
+ if (process.stdin.isTTY) {
2314
+ return "";
2315
+ }
2316
+ const chunks = [];
2317
+ for await (const chunk of process.stdin) {
2318
+ chunks.push(chunk);
2319
+ }
2320
+ return Buffer.concat(chunks).toString("utf8");
2321
+ }
2322
+ async function editMessage(opts) {
2323
+ const messageId = opts.message?.trim();
2324
+ if (!messageId) {
2325
+ throw new Error(
2326
+ "Pass the message id to edit \u2014 get it from `artifact-cafe comments --json`."
2327
+ );
2328
+ }
2329
+ const body = (opts.body ?? await readStdin2()).trim();
2330
+ if (!body) {
2331
+ throw new Error(
2332
+ 'Nothing to save. Pass the new text with --body "\u2026", or pipe it on stdin.'
2333
+ );
2334
+ }
2335
+ const dir = path9.resolve(opts.dir);
2336
+ const target = resolveArtifactTarget(dir, {
2337
+ apiKey: opts.apiKey,
2338
+ artifact: opts.artifact
2339
+ });
2340
+ const client = createApiClient(target.apiUrl, target.credential);
2341
+ const thread = await client.editMessage(messageId, body);
2342
+ if (opts.json) {
2343
+ console.log(JSON.stringify({ id: thread.id, messageId }, null, 2));
2344
+ return;
2345
+ }
2346
+ console.log(`\u2713 Edited ${messageId}.`);
2347
+ }
2348
+ async function deleteMessage(opts) {
2349
+ const messageId = opts.message?.trim();
2350
+ if (!messageId) {
2351
+ throw new Error(
2352
+ "Pass the message id to delete \u2014 get it from `artifact-cafe comments --json`."
2353
+ );
2354
+ }
2355
+ const dir = path9.resolve(opts.dir);
2356
+ const target = resolveArtifactTarget(dir, {
2357
+ apiKey: opts.apiKey,
2358
+ artifact: opts.artifact
2359
+ });
2360
+ const client = createApiClient(target.apiUrl, target.credential);
2361
+ await client.deleteMessage(messageId);
2362
+ if (opts.json) {
2363
+ console.log(JSON.stringify({ deleted: true, messageId }, null, 2));
2364
+ return;
2365
+ }
2366
+ console.log(`\u2713 Deleted ${messageId}.`);
2367
+ }
2368
+
1848
2369
  // src/open.ts
1849
- import path8 from "path";
2370
+ import path10 from "path";
1850
2371
  async function openArtifact(opts) {
1851
- const dir = path8.resolve(opts.dir);
2372
+ const dir = path10.resolve(opts.dir);
1852
2373
  const target = resolveArtifactTarget(dir, {
1853
2374
  apiKey: opts.apiKey,
1854
2375
  artifact: opts.artifact
@@ -1866,7 +2387,7 @@ async function openArtifact(opts) {
1866
2387
  }
1867
2388
 
1868
2389
  // src/protect.ts
1869
- import path9 from "path";
2390
+ import path11 from "path";
1870
2391
  async function protect(opts) {
1871
2392
  if (opts.off && opts.password !== void 0) {
1872
2393
  throw new Error("Pass either --password or --off, not both.");
@@ -1883,7 +2404,7 @@ async function protect(opts) {
1883
2404
  throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
1884
2405
  }
1885
2406
  }
1886
- const dir = path9.resolve(opts.dir);
2407
+ const dir = path11.resolve(opts.dir);
1887
2408
  const target = resolveArtifactTarget(dir, {
1888
2409
  apiKey: opts.apiKey,
1889
2410
  artifact: opts.artifact
@@ -1908,8 +2429,8 @@ async function protect(opts) {
1908
2429
  }
1909
2430
 
1910
2431
  // src/reply.ts
1911
- import path10 from "path";
1912
- async function readStdin() {
2432
+ import path12 from "path";
2433
+ async function readStdin3() {
1913
2434
  if (process.stdin.isTTY) {
1914
2435
  return "";
1915
2436
  }
@@ -1926,13 +2447,13 @@ async function replyToThread(opts) {
1926
2447
  "Pass the thread id to reply to \u2014 get it from `artifact-cafe comments --json`."
1927
2448
  );
1928
2449
  }
1929
- const body = (opts.body ?? await readStdin()).trim();
2450
+ const body = (opts.body ?? await readStdin3()).trim();
1930
2451
  if (!body) {
1931
2452
  throw new Error(
1932
2453
  'Nothing to post. Pass the reply with --body "\u2026", or pipe it on stdin.'
1933
2454
  );
1934
2455
  }
1935
- const dir = path10.resolve(opts.dir);
2456
+ const dir = path12.resolve(opts.dir);
1936
2457
  const target = resolveArtifactTarget(dir, {
1937
2458
  apiKey: opts.apiKey,
1938
2459
  artifact: opts.artifact
@@ -1961,7 +2482,7 @@ async function replyToThread(opts) {
1961
2482
  }
1962
2483
 
1963
2484
  // src/resolve.ts
1964
- import path11 from "path";
2485
+ import path13 from "path";
1965
2486
  async function changeThreadStatus(opts) {
1966
2487
  const threadId = opts.thread?.trim();
1967
2488
  if (!threadId) {
@@ -1969,16 +2490,35 @@ async function changeThreadStatus(opts) {
1969
2490
  `Pass the thread id to ${opts.action} \u2014 get it from \`artifact-cafe comments --json\`.`
1970
2491
  );
1971
2492
  }
1972
- const dir = path11.resolve(opts.dir);
2493
+ const dir = path13.resolve(opts.dir);
1973
2494
  const target = resolveArtifactTarget(dir, {
1974
2495
  apiKey: opts.apiKey,
1975
2496
  artifact: opts.artifact
1976
2497
  });
1977
2498
  const client = createApiClient(target.apiUrl, target.credential);
1978
- const thread = opts.action === "resolve" ? await client.resolveThread(threadId) : await client.reopenThread(threadId);
2499
+ const call = {
2500
+ ack: () => client.acknowledgeThread(threadId),
2501
+ reopen: () => client.reopenThread(threadId),
2502
+ resolve: () => client.resolveThread(threadId)
2503
+ }[opts.action];
2504
+ const thread = await call();
1979
2505
  if (opts.json) {
1980
2506
  console.log(
1981
- JSON.stringify({ id: thread.id, status: thread.status }, null, 2)
2507
+ JSON.stringify(
2508
+ {
2509
+ acknowledgedAt: thread.acknowledgedAt,
2510
+ id: thread.id,
2511
+ status: thread.status
2512
+ },
2513
+ null,
2514
+ 2
2515
+ )
2516
+ );
2517
+ return;
2518
+ }
2519
+ if (opts.action === "ack") {
2520
+ console.log(
2521
+ `\u2713 Acknowledged ${thread.id} \u2014 reviewers now see it as being handled.`
1982
2522
  );
1983
2523
  return;
1984
2524
  }
@@ -1990,26 +2530,26 @@ async function changeThreadStatus(opts) {
1990
2530
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
1991
2531
  import { createRequire } from "module";
1992
2532
  import { homedir as homedir2 } from "os";
1993
- import path12 from "path";
1994
- import { z as z15 } from "zod";
2533
+ import path14 from "path";
2534
+ import { z as z17 } from "zod";
1995
2535
  var REGISTRY_LATEST_URL = "https://registry.npmjs.org/artifact-cafe/latest";
1996
2536
  var SKILL_SOURCE_URL = "https://raw.githubusercontent.com/Artifact-Cafe/skill/main/artifact-cafe/SKILL.md";
1997
2537
  var SKILL_UPDATE_COMMAND = "npx skills add artifact-cafe/skill --skill artifact-cafe -g";
1998
2538
  var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
1999
2539
  var FETCH_TIMEOUT_MS = 2e3;
2000
2540
  var SKILL_VERSION_PATTERN = /\*\*Skill version:\s*(\d+\.\d+\.\d+)\*\*/;
2001
- var cacheSchema = z15.object({
2002
- checkedAt: z15.number(),
2003
- cliLatest: z15.string().nullable(),
2004
- skillLatest: z15.string().nullable(),
2005
- version: z15.literal(1)
2541
+ var cacheSchema = z17.object({
2542
+ checkedAt: z17.number(),
2543
+ cliLatest: z17.string().nullable(),
2544
+ skillLatest: z17.string().nullable(),
2545
+ version: z17.literal(1)
2006
2546
  });
2007
2547
  function cliVersion() {
2008
2548
  const pkg = createRequire(import.meta.url)("../package.json");
2009
2549
  return pkg.version;
2010
2550
  }
2011
2551
  function cachePath() {
2012
- return path12.join(homedir2(), ".artifactcafe", "update-check.json");
2552
+ return path14.join(homedir2(), ".artifactcafe", "update-check.json");
2013
2553
  }
2014
2554
  function readCache() {
2015
2555
  try {
@@ -2020,7 +2560,7 @@ function readCache() {
2020
2560
  }
2021
2561
  function writeCache(cache) {
2022
2562
  try {
2023
- mkdirSync3(path12.dirname(cachePath()), { mode: 448, recursive: true });
2563
+ mkdirSync3(path14.dirname(cachePath()), { mode: 448, recursive: true });
2024
2564
  writeFileSync3(cachePath(), `${JSON.stringify(cache, null, 2)}
2025
2565
  `);
2026
2566
  } catch {
@@ -2057,7 +2597,7 @@ async function fetchText(url) {
2057
2597
  }
2058
2598
  async function fetchLatestVersions(previous) {
2059
2599
  const [cli, skill] = await Promise.all([
2060
- fetchJson(REGISTRY_LATEST_URL).then((body) => z15.object({ version: z15.string() }).parse(body).version).catch(() => null),
2600
+ fetchJson(REGISTRY_LATEST_URL).then((body) => z17.object({ version: z17.string() }).parse(body).version).catch(() => null),
2061
2601
  fetchText(SKILL_SOURCE_URL).then((body) => SKILL_VERSION_PATTERN.exec(body)?.[1] ?? null).catch(() => null)
2062
2602
  ]);
2063
2603
  return {
@@ -2073,7 +2613,7 @@ function installedSkillVersions() {
2073
2613
  const versions = [];
2074
2614
  for (const root of roots) {
2075
2615
  for (const dir of dirs) {
2076
- const file = path12.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
2616
+ const file = path14.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
2077
2617
  if (!existsSync3(file)) {
2078
2618
  continue;
2079
2619
  }
@@ -2170,6 +2710,20 @@ function printWorkspace(workspace) {
2170
2710
  }
2171
2711
 
2172
2712
  // src/index.ts
2713
+ function readNotesArg(argv) {
2714
+ for (let i = 0; i < argv.length; i++) {
2715
+ const token = argv[i];
2716
+ if (token === void 0) {
2717
+ continue;
2718
+ }
2719
+ if (token === "--notes") {
2720
+ return argv[i + 1];
2721
+ }
2722
+ if (token.startsWith("--notes=")) {
2723
+ return token.slice("--notes=".length);
2724
+ }
2725
+ }
2726
+ }
2173
2727
  var publish2 = defineCommand({
2174
2728
  args: {
2175
2729
  "api-key": {
@@ -2192,6 +2746,10 @@ var publish2 = defineCommand({
2192
2746
  },
2193
2747
  json: { description: "Machine-readable output", type: "boolean" },
2194
2748
  "no-open": { description: "Don't open the review URL", type: "boolean" },
2749
+ notes: {
2750
+ description: "Version notes \u2014 bullet-point changelog of what changed (newline-separated)",
2751
+ type: "string"
2752
+ },
2195
2753
  password: {
2196
2754
  description: "Password-protect the artifact (min 6 chars)",
2197
2755
  type: "string"
@@ -2212,6 +2770,7 @@ var publish2 = defineCommand({
2212
2770
  entry: args.entry,
2213
2771
  folder: args.folder,
2214
2772
  json: Boolean(args.json),
2773
+ notes: readNotesArg(process.argv.slice(2)),
2215
2774
  open: !args["no-open"],
2216
2775
  password: args.password,
2217
2776
  title: args.title,
@@ -2579,7 +3138,11 @@ var threadStatusCommand = (action) => defineCommand({
2579
3138
  }
2580
3139
  },
2581
3140
  meta: {
2582
- description: action === "resolve" ? "Mark a comment thread resolved" : "Reopen a resolved comment thread",
3141
+ description: {
3142
+ ack: "Mark a thread acknowledged (seen, working on it)",
3143
+ reopen: "Reopen a resolved comment thread",
3144
+ resolve: "Mark a comment thread resolved"
3145
+ }[action],
2583
3146
  name: action
2584
3147
  },
2585
3148
  async run({ args }) {
@@ -2600,6 +3163,7 @@ var threadStatusCommand = (action) => defineCommand({
2600
3163
  });
2601
3164
  var resolve = threadStatusCommand("resolve");
2602
3165
  var reopen = threadStatusCommand("reopen");
3166
+ var ack = threadStatusCommand("ack");
2603
3167
  var reply = defineCommand({
2604
3168
  args: {
2605
3169
  "api-key": {
@@ -2651,6 +3215,152 @@ var reply = defineCommand({
2651
3215
  }
2652
3216
  }
2653
3217
  });
3218
+ var comment2 = defineCommand({
3219
+ args: {
3220
+ "api-key": {
3221
+ description: "Authorize with an account API key (default: stored login)",
3222
+ type: "string"
3223
+ },
3224
+ artifact: {
3225
+ description: "Act on an artifact ID instead of the linked folder",
3226
+ type: "string"
3227
+ },
3228
+ body: {
3229
+ description: "Comment text (omit to read from stdin)",
3230
+ type: "string"
3231
+ },
3232
+ dir: {
3233
+ default: ".",
3234
+ description: "Published/linked directory",
3235
+ type: "positional"
3236
+ },
3237
+ json: { description: "Machine-readable output", type: "boolean" },
3238
+ page: {
3239
+ description: "Scope to one document (e.g. pages/notes.html)",
3240
+ type: "string"
3241
+ },
3242
+ prefix: {
3243
+ description: "Text just before --quote (disambiguates repeats)",
3244
+ type: "string"
3245
+ },
3246
+ quote: {
3247
+ description: "Anchor to this text (omit for a page-level comment)",
3248
+ type: "string"
3249
+ },
3250
+ suffix: {
3251
+ description: "Text just after --quote (disambiguates repeats)",
3252
+ type: "string"
3253
+ },
3254
+ via: {
3255
+ description: 'Attribution label, e.g. "Claude Code"',
3256
+ type: "string"
3257
+ }
3258
+ },
3259
+ meta: {
3260
+ description: "Open a comment thread (as your account)",
3261
+ name: "comment"
3262
+ },
3263
+ async run({ args }) {
3264
+ try {
3265
+ await comment({
3266
+ apiKey: args["api-key"],
3267
+ artifact: args.artifact,
3268
+ body: args.body,
3269
+ dir: args.dir,
3270
+ json: Boolean(args.json),
3271
+ page: args.page,
3272
+ prefix: args.prefix,
3273
+ quote: args.quote,
3274
+ suffix: args.suffix,
3275
+ via: args.via
3276
+ });
3277
+ } catch (err) {
3278
+ printCliError(err, Boolean(args.json));
3279
+ process.exit(1);
3280
+ }
3281
+ }
3282
+ });
3283
+ var edit = defineCommand({
3284
+ args: {
3285
+ "api-key": {
3286
+ description: "Authorize with an account API key (default: stored login)",
3287
+ type: "string"
3288
+ },
3289
+ artifact: {
3290
+ description: "Act on an artifact ID instead of the linked folder",
3291
+ type: "string"
3292
+ },
3293
+ body: {
3294
+ description: "New comment text (omit to read from stdin)",
3295
+ type: "string"
3296
+ },
3297
+ dir: {
3298
+ default: ".",
3299
+ description: "Linked directory to authorize from (default: .)",
3300
+ type: "string"
3301
+ },
3302
+ json: { description: "Machine-readable output", type: "boolean" },
3303
+ message: {
3304
+ description: "Message id (from `comments --json`)",
3305
+ required: true,
3306
+ type: "positional"
3307
+ }
3308
+ },
3309
+ meta: { description: "Edit a comment you authored", name: "edit" },
3310
+ async run({ args }) {
3311
+ try {
3312
+ await editMessage({
3313
+ apiKey: args["api-key"],
3314
+ artifact: args.artifact,
3315
+ body: args.body,
3316
+ dir: args.dir,
3317
+ json: Boolean(args.json),
3318
+ message: args.message
3319
+ });
3320
+ } catch (err) {
3321
+ printCliError(err, Boolean(args.json));
3322
+ process.exit(1);
3323
+ }
3324
+ }
3325
+ });
3326
+ var del = defineCommand({
3327
+ args: {
3328
+ "api-key": {
3329
+ description: "Authorize with an account API key (default: stored login)",
3330
+ type: "string"
3331
+ },
3332
+ artifact: {
3333
+ description: "Act on an artifact ID instead of the linked folder",
3334
+ type: "string"
3335
+ },
3336
+ dir: {
3337
+ default: ".",
3338
+ description: "Linked directory to authorize from (default: .)",
3339
+ type: "string"
3340
+ },
3341
+ json: { description: "Machine-readable output", type: "boolean" },
3342
+ message: {
3343
+ description: "Message id (from `comments --json`)",
3344
+ required: true,
3345
+ type: "positional"
3346
+ }
3347
+ },
3348
+ meta: { description: "Delete a comment you authored", name: "delete" },
3349
+ async run({ args }) {
3350
+ try {
3351
+ await deleteMessage({
3352
+ apiKey: args["api-key"],
3353
+ artifact: args.artifact,
3354
+ dir: args.dir,
3355
+ json: Boolean(args.json),
3356
+ message: args.message
3357
+ });
3358
+ } catch (err) {
3359
+ printCliError(err, Boolean(args.json));
3360
+ process.exit(1);
3361
+ }
3362
+ }
3363
+ });
2654
3364
  var open = defineCommand({
2655
3365
  args: {
2656
3366
  "api-key": {
@@ -2720,8 +3430,12 @@ var main = defineCommand({
2720
3430
  await showUsage(cmd);
2721
3431
  },
2722
3432
  subCommands: {
3433
+ ack,
3434
+ comment: comment2,
2723
3435
  comments: comments2,
2724
3436
  dashboard,
3437
+ delete: del,
3438
+ edit,
2725
3439
  folders: folders2,
2726
3440
  link: link2,
2727
3441
  list: list2,