artifact-cafe 0.0.17 → 0.1.0

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 +1866 -1571
  2. package/package.json +10 -10
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ var TICK_TOP = " \u250C \u2510";
11
11
  var GLYPH = " \xE3";
12
12
  var TICK_BOTTOM = " \u2514 \u2518";
13
13
  var WORDMARK = " artifact.cafe";
14
- function printBanner() {
14
+ var printBanner = () => {
15
15
  const color = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
16
16
  const paint = (line, codes) => color ? `${codes}${line}${RESET}` : line;
17
17
  console.log();
@@ -21,10 +21,10 @@ function printBanner() {
21
21
  console.log();
22
22
  console.log(paint(WORDMARK, AMBER + BOLD));
23
23
  console.log();
24
- }
24
+ };
25
25
 
26
26
  // src/comment.ts
27
- import path3 from "path";
27
+ import path5 from "path";
28
28
 
29
29
  // ../shared/src/account.ts
30
30
  import { z } from "zod";
@@ -61,17 +61,37 @@ var meResponseSchema = z.object({
61
61
  keyName: z.string().optional(),
62
62
  userId: z.string()
63
63
  });
64
+ var sessionSummarySchema = z.object({
65
+ createdAt: z.string(),
66
+ current: z.boolean(),
67
+ id: z.string(),
68
+ ipAddress: z.string().nullable(),
69
+ lastActiveAt: z.string(),
70
+ userAgent: z.string().nullable()
71
+ });
72
+ var sessionListResponseSchema = z.object({
73
+ sessions: z.array(sessionSummarySchema)
74
+ });
75
+ var revokeSessionsResponseSchema = z.object({
76
+ revoked: z.number().int().nonnegative()
77
+ });
78
+ var deleteAccountSchema = z.object({
79
+ confirmEmail: z.string().trim().min(1).max(320)
80
+ });
81
+ var deleteAccountResponseSchema = z.object({
82
+ deleted: z.literal(true)
83
+ });
64
84
  var artifactListItemSchema = z.object({
65
- autoDeleteAt: z.string().datetime().nullable().optional(),
85
+ autoDeleteAt: z.iso.datetime().nullable().optional(),
66
86
  createdAt: z.string(),
67
87
  currentVersionNumber: z.number().int().positive().nullable(),
68
- expiresAt: z.string().datetime().nullable().optional(),
88
+ expiresAt: z.iso.datetime().nullable().optional(),
69
89
  id: z.string(),
70
90
  openCommentCount: z.number().int().nonnegative(),
71
91
  slug: z.string(),
72
92
  title: z.string(),
73
93
  updatedAt: z.string(),
74
- url: z.string().url(),
94
+ url: z.url(),
75
95
  versionCount: z.number().int().nonnegative()
76
96
  });
77
97
  var artifactListResponseSchema = z.object({
@@ -86,7 +106,7 @@ var createAgentTokenSchema = z2.object({
86
106
  ttlSeconds: z2.number().int().positive().max(3600)
87
107
  });
88
108
  var agentTokenResponseSchema = z2.object({
89
- expiresAt: z2.string().datetime(),
109
+ expiresAt: z2.iso.datetime(),
90
110
  token: z2.string(),
91
111
  workspaceId: z2.string()
92
112
  });
@@ -127,13 +147,13 @@ var AGENT_BRANDS = [
127
147
  },
128
148
  { id: "hermes", label: "Hermes", match: ["hermes"], short: "Hermes" }
129
149
  ];
130
- function normalizeAgent(raw) {
150
+ var normalizeAgent = (raw) => {
131
151
  const value = raw?.trim().toLowerCase();
132
152
  if (!value) {
133
153
  return null;
134
154
  }
135
155
  return AGENT_BRANDS.find((brand) => brand.id === value) ?? AGENT_BRANDS.find((brand) => brand.match.some((m) => value.includes(m))) ?? null;
136
- }
156
+ };
137
157
  var ENV_FINGERPRINTS = [
138
158
  { id: "claude-code", prefixes: ["CLAUDECODE", "CLAUDE_CODE_"] },
139
159
  { id: "codex", prefixes: ["CODEX_"] },
@@ -142,30 +162,24 @@ var ENV_FINGERPRINTS = [
142
162
  { id: "gemini-cli", prefixes: ["GEMINI_CLI", "GEMINI_SANDBOX"] },
143
163
  { id: "opencode", prefixes: ["OPENCODE"] }
144
164
  ];
145
- function detectAgentFromEnv(env) {
165
+ var detectAgentFromEnv = (env) => {
146
166
  const explicit = env.ARTIFACT_CAFE_AGENT?.trim();
147
167
  if (explicit) {
148
168
  return explicit;
149
169
  }
150
170
  const keys = Object.keys(env).filter((key) => env[key]);
151
171
  for (const { id, prefixes } of ENV_FINGERPRINTS) {
152
- const hit = keys.some(
153
- (key) => prefixes.some((prefix) => key.startsWith(prefix))
154
- );
172
+ const hit = keys.some((key) => prefixes.some((prefix) => key.startsWith(prefix)));
155
173
  if (hit) {
156
174
  return AGENT_BRANDS.find((brand) => brand.id === id)?.label;
157
175
  }
158
176
  }
159
177
  return normalizeAgent(env.AI_AGENT)?.label;
160
- }
178
+ };
161
179
 
162
180
  // ../shared/src/avatars.ts
163
181
  import { z as z3 } from "zod";
164
- var AVATAR_CONTENT_TYPES = [
165
- "image/png",
166
- "image/jpeg",
167
- "image/webp"
168
- ];
182
+ var AVATAR_CONTENT_TYPES = ["image/png", "image/jpeg", "image/webp"];
169
183
  var AVATAR_MAX_BYTES = 2 * 1024 * 1024;
170
184
  var avatarContentTypeSchema = z3.enum(AVATAR_CONTENT_TYPES);
171
185
  var userAvatarResponseSchema = z3.object({
@@ -181,10 +195,8 @@ import { z as z8 } from "zod";
181
195
  // ../shared/src/emoji.ts
182
196
  import { z as z4 } from "zod";
183
197
  var SINGLE_EMOJI = new RegExp("^\\p{RGI_Emoji}$", "v");
184
- var REDUNDANT_VS16 = new RegExp("(\\p{Emoji_Presentation})\\uFE0F", "gu");
185
- function canonicalizeEmoji(s) {
186
- return s.replace(REDUNDANT_VS16, "$1");
187
- }
198
+ var REDUNDANT_VS16 = new RegExp("(?<emoji>\\p{Emoji_Presentation})\\uFE0F", "gu");
199
+ var canonicalizeEmoji = (s) => s.replace(REDUNDANT_VS16, "$1");
188
200
  var iconEmojiSchema = z4.string().max(64).transform(canonicalizeEmoji).refine((s) => SINGLE_EMOJI.test(s), { message: "Must be a single emoji." }).nullable().describe("A single emoji shown as the icon, or null for the default.");
189
201
 
190
202
  // ../shared/src/publish.ts
@@ -198,20 +210,24 @@ var setExpirationSchema = z5.object({
198
210
  expiresInSeconds: expirationSecondsSchema.nullable()
199
211
  });
200
212
  var expirationResponseSchema = z5.object({
201
- autoDeleteAt: z5.string().datetime().nullable(),
202
- expiresAt: z5.string().datetime().nullable()
203
- });
204
- function parseArtifactDuration(value) {
205
- const match = /^([1-9]\d*)([mhd])$/.exec(value);
206
- const units = { d: 86400, h: 3600, m: 60 };
207
- const seconds = match ? Number(match[1]) * (units[match[2]] ?? 0) : 0;
213
+ autoDeleteAt: z5.iso.datetime().nullable(),
214
+ expiresAt: z5.iso.datetime().nullable()
215
+ });
216
+ var parseArtifactDuration = (value) => {
217
+ const match = /^(?<amount>[1-9]\d*)(?<unit>[mhd])$/u.exec(value);
218
+ const units = /* @__PURE__ */ new Map([
219
+ ["d", 86400],
220
+ ["h", 3600],
221
+ ["m", 60]
222
+ ]);
223
+ const seconds = match ? Number(match[1]) * (units.get(match[2] ?? "") ?? 0) : 0;
208
224
  if (!expirationSecondsSchema.safeParse(seconds).success) {
209
225
  throw new Error(
210
226
  `Duration must be a positive whole number followed by m, h, or d (for example 30m, 1h, 7d), up to ${MAX_EXPIRATION_SECONDS} seconds.`
211
227
  );
212
228
  }
213
229
  return seconds;
214
- }
230
+ };
215
231
 
216
232
  // ../shared/src/review-mode.ts
217
233
  import { z as z6 } from "zod";
@@ -224,7 +240,7 @@ var reviewModeResponseSchema = z6.object({
224
240
  });
225
241
 
226
242
  // ../shared/src/publish.ts
227
- function isSafeArtifactPath(p) {
243
+ var isSafeArtifactPath = (p) => {
228
244
  if (p.length === 0 || p.length > 1024) {
229
245
  return false;
230
246
  }
@@ -235,12 +251,12 @@ function isSafeArtifactPath(p) {
235
251
  return false;
236
252
  }
237
253
  return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
238
- }
254
+ };
239
255
  var CARD_DESCRIPTION_MAX = 300;
240
256
  var CARD_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
241
257
  var manifestFileSchema = z7.object({
242
- hash: z7.string().regex(/^sha256_[a-f0-9]{64}$/),
243
- mimeType: z7.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
258
+ hash: z7.string().regex(/^sha256_[a-f0-9]{64}$/u),
259
+ mimeType: z7.string().regex(/^[-\w.+]+\/[-\w.+]+$/u, { message: "invalid MIME type" }),
244
260
  path: z7.string().min(1).max(1024).refine(isSafeArtifactPath, {
245
261
  message: "path must be a safe relative path"
246
262
  }),
@@ -253,6 +269,7 @@ var publishSourceSchema = z7.object({
253
269
  });
254
270
  var createPublishSessionSchema = z7.object({
255
271
  artifactId: z7.string().nullable(),
272
+ baseVersion: z7.number().int().positive().optional(),
256
273
  // One-line share-card summary (`--description`). Overrides whatever the
257
274
  // entry HTML declares; leave it off and the API reads the document's own
258
275
  // `og:description`/`description` at finalize.
@@ -264,6 +281,7 @@ var createPublishSessionSchema = z7.object({
264
281
  // members; the API enforces membership and folder ownership. folderId
265
282
  // without workspaceId resolves against the account's personal workspace.
266
283
  folderId: z7.string().min(1).optional(),
284
+ force: z7.boolean().optional(),
267
285
  // Bullet-point changelog for this version, authored by the publisher.
268
286
  // Optional on every publish (incl. v1); stored immutably on the version.
269
287
  notes: z7.string().max(4e3).optional(),
@@ -279,7 +297,7 @@ var publishSessionResponseSchema = z7.object({
279
297
  z7.object({
280
298
  method: z7.literal("PUT"),
281
299
  path: z7.string(),
282
- uploadUrl: z7.string().url()
300
+ uploadUrl: z7.url()
283
301
  })
284
302
  ),
285
303
  publishSessionId: z7.string(),
@@ -288,15 +306,15 @@ var publishSessionResponseSchema = z7.object({
288
306
  });
289
307
  var finalizeResponseSchema = z7.object({
290
308
  artifactId: z7.string(),
291
- autoDeleteAt: z7.string().datetime().nullable().optional(),
309
+ autoDeleteAt: z7.iso.datetime().nullable().optional(),
310
+ // Returned only on first publish of a new artifact (one-time).
311
+ claimUrl: z7.url().optional(),
292
312
  // API-key publishes create the artifact already claimed (SPEC §5.4) —
293
313
  // the CLI uses this to tell "claim URL pending" from "already yours".
294
314
  claimed: z7.boolean(),
295
- // Returned only on first publish of a new artifact (one-time).
296
- claimUrl: z7.string().url().optional(),
297
- expiresAt: z7.string().datetime().nullable().optional(),
315
+ expiresAt: z7.iso.datetime().nullable().optional(),
298
316
  publishToken: z7.string().optional(),
299
- url: z7.string().url(),
317
+ url: z7.url(),
300
318
  versionId: z7.string(),
301
319
  versionNumber: z7.number().int().positive(),
302
320
  // SPEC-2 §5.2 — who can open the review URL. A publish into a folder
@@ -308,16 +326,16 @@ var finalizeResponseSchema = z7.object({
308
326
  workspaceId: z7.string().nullable().optional()
309
327
  });
310
328
  var artifactResponseSchema = z7.object({
311
- autoDeleteAt: z7.string().datetime().nullable().optional(),
329
+ autoDeleteAt: z7.iso.datetime().nullable().optional(),
312
330
  claimed: z7.boolean(),
313
331
  currentVersion: z7.object({
314
- entryUrl: z7.string().url(),
332
+ entryUrl: z7.url(),
315
333
  id: z7.string(),
316
334
  versionNumber: z7.number().int().positive()
317
335
  }).nullable(),
318
336
  // Custom icon, or null to render the default document glyph.
319
337
  emoji: z7.string().nullable(),
320
- expiresAt: z7.string().datetime().nullable().optional(),
338
+ expiresAt: z7.iso.datetime().nullable().optional(),
321
339
  // SPEC-2 §6.4 — placement + visibility surface on the artifact.
322
340
  folderId: z7.string().nullable(),
323
341
  id: z7.string(),
@@ -328,7 +346,7 @@ var artifactResponseSchema = z7.object({
328
346
  visibility: z7.enum(["public", "unlisted", "workspace", "private"]),
329
347
  workspaceId: z7.string().nullable()
330
348
  });
331
- function resolvePublishEntry(files, explicit) {
349
+ var resolvePublishEntry = (files, explicit) => {
332
350
  if (explicit !== void 0) {
333
351
  if (!files.some((file) => file.path === explicit)) {
334
352
  throw new Error(`Entry file "${explicit}" is not in the manifest.`);
@@ -347,7 +365,7 @@ function resolvePublishEntry(files, explicit) {
347
365
  );
348
366
  }
349
367
  return htmlFiles[0]?.path;
350
- }
368
+ };
351
369
 
352
370
  // ../shared/src/comments.ts
353
371
  var anchorPageSchema = z8.string().max(1024).refine(isSafeArtifactPath, {
@@ -533,17 +551,24 @@ var commentExportSchema = z8.object({
533
551
  var commentReadResponseSchema = z8.object({
534
552
  lastSeenAt: z8.string()
535
553
  });
536
- function elementLabel(el) {
554
+ var elementLabel = (el) => {
537
555
  if (el.id) {
538
556
  return `<${el.tag}#${el.id}>`;
539
557
  }
540
558
  const classes = el.classes ? el.classes.split(" ").slice(0, 2) : [];
541
559
  return classes.length ? `<${el.tag}.${classes.join(".")}>` : `<${el.tag}>`;
542
- }
560
+ };
543
561
 
544
562
  // ../shared/src/data.ts
563
+ var canParseUrl = (value) => {
564
+ try {
565
+ return Boolean(new URL(value));
566
+ } catch {
567
+ return false;
568
+ }
569
+ };
545
570
  var DATA_MANIFEST_PATH = ".artifact/data.json";
546
- var DATA_NAME_RE = /^[a-z][a-z0-9_]*$/;
571
+ var DATA_NAME_RE = /^[a-z][a-z0-9_]*$/u;
547
572
  var DATA_NAME_MAX = 64;
548
573
  var DATA_RESERVED_FIELDS = /* @__PURE__ */ new Set([
549
574
  "id",
@@ -573,37 +598,53 @@ var DATA_ACCESS_DEFAULTS = {
573
598
  read: "public",
574
599
  update: "owner"
575
600
  };
576
- var DATA_RATE_LIMIT_RE = /^([1-9]\d{0,5})\/(hour|minute)\/ip$/;
601
+ var DATA_RATE_LIMIT_RE = /^(?<count>[1-9]\d{0,5})\/(?<period>hour|minute)\/ip$/u;
577
602
  var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
578
- function validateFieldValue(name, field, value, errors) {
603
+ var validateStringField = (name, field, value, errors) => {
604
+ if (typeof value !== "string") {
605
+ errors.push(`${name}: expected a string`);
606
+ return value;
607
+ }
608
+ const s = field.trim ? value.trim() : value;
609
+ if (field.maxLength !== void 0 && s.length > field.maxLength) {
610
+ errors.push(`${name}: longer than maxLength ${field.maxLength}`);
611
+ }
612
+ return s;
613
+ };
614
+ var validateNumberField = (name, field, value, errors) => {
615
+ if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
616
+ errors.push(`${name}: expected a ${field.type}`);
617
+ return value;
618
+ }
619
+ if (field.type === "integer" && !Number.isInteger(value)) {
620
+ errors.push(`${name}: expected an integer`);
621
+ }
622
+ if (field.minimum !== void 0 && value < field.minimum) {
623
+ errors.push(`${name}: below minimum ${field.minimum}`);
624
+ }
625
+ if (field.maximum !== void 0 && value > field.maximum) {
626
+ errors.push(`${name}: above maximum ${field.maximum}`);
627
+ }
628
+ return value;
629
+ };
630
+ var validateArrayField = (name, field, value, errors) => {
631
+ if (!Array.isArray(value)) {
632
+ errors.push(`${name}: expected an array`);
633
+ return value;
634
+ }
635
+ if (field.maxLength !== void 0 && value.length > field.maxLength) {
636
+ errors.push(`${name}: more than maxLength ${field.maxLength} items`);
637
+ }
638
+ return value;
639
+ };
640
+ var validateFieldValue = (name, field, value, errors) => {
579
641
  switch (field.type) {
580
642
  case "string": {
581
- if (typeof value !== "string") {
582
- errors.push(`${name}: expected a string`);
583
- return value;
584
- }
585
- const s = field.trim ? value.trim() : value;
586
- if (field.maxLength !== void 0 && s.length > field.maxLength) {
587
- errors.push(`${name}: longer than maxLength ${field.maxLength}`);
588
- }
589
- return s;
643
+ return validateStringField(name, field, value, errors);
590
644
  }
591
645
  case "number":
592
646
  case "integer": {
593
- if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
594
- errors.push(`${name}: expected a ${field.type}`);
595
- return value;
596
- }
597
- if (field.type === "integer" && !Number.isInteger(value)) {
598
- errors.push(`${name}: expected an integer`);
599
- }
600
- if (field.minimum !== void 0 && value < field.minimum) {
601
- errors.push(`${name}: below minimum ${field.minimum}`);
602
- }
603
- if (field.maximum !== void 0 && value > field.maximum) {
604
- errors.push(`${name}: above maximum ${field.maximum}`);
605
- }
606
- return value;
647
+ return validateNumberField(name, field, value, errors);
607
648
  }
608
649
  case "boolean": {
609
650
  if (typeof value !== "boolean") {
@@ -624,20 +665,13 @@ function validateFieldValue(name, field, value, errors) {
624
665
  return value;
625
666
  }
626
667
  case "email": {
627
- if (typeof value !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
668
+ if (typeof value !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(value)) {
628
669
  errors.push(`${name}: expected an email address`);
629
670
  }
630
671
  return value;
631
672
  }
632
673
  case "array": {
633
- if (!Array.isArray(value)) {
634
- errors.push(`${name}: expected an array`);
635
- return value;
636
- }
637
- if (field.maxLength !== void 0 && value.length > field.maxLength) {
638
- errors.push(`${name}: more than maxLength ${field.maxLength} items`);
639
- }
640
- return value;
674
+ return validateArrayField(name, field, value, errors);
641
675
  }
642
676
  case "object": {
643
677
  if (!isPlainObject(value)) {
@@ -650,15 +684,8 @@ function validateFieldValue(name, field, value, errors) {
650
684
  return value;
651
685
  }
652
686
  }
653
- }
654
- function canParseUrl(value) {
655
- try {
656
- return Boolean(new URL(value));
657
- } catch {
658
- return false;
659
- }
660
- }
661
- function validateRecordInput(collection, input, opts = {}) {
687
+ };
688
+ var validateRecordInput = (collection, input, opts = {}) => {
662
689
  if (!isPlainObject(input)) {
663
690
  return { errors: ["record body must be a JSON object"], ok: false };
664
691
  }
@@ -706,7 +733,7 @@ function validateRecordInput(collection, input, opts = {}) {
706
733
  return { errors, ok: false };
707
734
  }
708
735
  return { data: data2, ok: true };
709
- }
736
+ };
710
737
 
711
738
  // ../shared/src/data-api.ts
712
739
  import { z as z9 } from "zod";
@@ -751,6 +778,18 @@ var dataClearResponseSchema = z9.object({
751
778
 
752
779
  // ../shared/src/data-manifest.ts
753
780
  import { z as z10 } from "zod";
781
+ var defaultMatchesType = (field) => {
782
+ if (field.required && field.default === null) {
783
+ return false;
784
+ }
785
+ return validateRecordInput(
786
+ {
787
+ access: DATA_ACCESS_DEFAULTS,
788
+ fields: { value: { ...field, default: void 0 } }
789
+ },
790
+ { value: field.default }
791
+ ).ok;
792
+ };
754
793
  var nameSchema = z10.string().max(DATA_NAME_MAX).regex(DATA_NAME_RE, "must match ^[a-z][a-z0-9_]*$");
755
794
  var fieldTypeSchema = z10.enum([
756
795
  "string",
@@ -765,8 +804,8 @@ var fieldTypeSchema = z10.enum([
765
804
  ]);
766
805
  var fieldSchema = z10.strictObject({
767
806
  default: z10.unknown().optional(),
768
- maximum: z10.number().optional(),
769
807
  maxLength: z10.number().int().positive().optional(),
808
+ maximum: z10.number().optional(),
770
809
  minimum: z10.number().optional(),
771
810
  required: z10.boolean().optional(),
772
811
  trim: z10.boolean().optional(),
@@ -783,18 +822,6 @@ var fieldSchema = z10.strictObject({
783
822
  });
784
823
  }
785
824
  });
786
- function defaultMatchesType(field) {
787
- if (field.required && field.default === null) {
788
- return false;
789
- }
790
- return validateRecordInput(
791
- {
792
- access: DATA_ACCESS_DEFAULTS,
793
- fields: { value: { ...field, default: void 0 } }
794
- },
795
- { value: field.default }
796
- ).ok;
797
- }
798
825
  var accessLevelSchema = z10.enum(["public", "creator", "owner", "none"]);
799
826
  var accessSchema = z10.strictObject({
800
827
  delete: accessLevelSchema.default("owner"),
@@ -846,7 +873,7 @@ var dataManifestSchema = z10.strictObject({
846
873
  });
847
874
  }
848
875
  });
849
- function parseDataManifest(text) {
876
+ var parseDataManifest = (text) => {
850
877
  if (new TextEncoder().encode(text).length > DATA_LIMITS.maxManifestBytes) {
851
878
  return {
852
879
  errors: [`manifest larger than ${DATA_LIMITS.maxManifestBytes} bytes`],
@@ -856,9 +883,9 @@ function parseDataManifest(text) {
856
883
  let raw;
857
884
  try {
858
885
  raw = JSON.parse(text);
859
- } catch (err) {
886
+ } catch (error) {
860
887
  return {
861
- errors: [`not valid JSON: ${err instanceof Error ? err.message : err}`],
888
+ errors: [`not valid JSON: ${error instanceof Error ? error.message : error}`],
862
889
  ok: false
863
890
  };
864
891
  }
@@ -872,13 +899,14 @@ function parseDataManifest(text) {
872
899
  };
873
900
  }
874
901
  return { config: parsed.data, ok: true };
875
- }
902
+ };
876
903
 
877
904
  // ../shared/src/errors.ts
878
905
  import { z as z11 } from "zod";
879
906
  var apiErrorSchema = z11.object({
880
907
  code: z11.string(),
881
- docsUrl: z11.string().url(),
908
+ currentVersion: z11.number().int().positive().nullable().optional(),
909
+ docsUrl: z11.url(),
882
910
  message: z11.string(),
883
911
  retryAfter: z11.number().int().positive().optional(),
884
912
  scope: z11.enum(["ip", "artifact"]).optional()
@@ -906,7 +934,7 @@ var createGrantSchema = z13.object({
906
934
  });
907
935
  var grantSchema = z13.object({
908
936
  createdAt: z13.string(),
909
- email: z13.string().email().nullable(),
937
+ email: z13.email().nullable(),
910
938
  expiresAt: z13.string().nullable(),
911
939
  id: z13.string(),
912
940
  // The subject's avatar once they have an account; null otherwise.
@@ -923,7 +951,7 @@ var grantSchema = z13.object({
923
951
  userId: z13.string().nullable()
924
952
  });
925
953
  var createdGrantSchema = grantSchema.extend({
926
- url: z13.string().url()
954
+ url: z13.url()
927
955
  });
928
956
  var grantListResponseSchema = z13.object({
929
957
  grants: z13.array(grantSchema),
@@ -948,7 +976,7 @@ var acceptGrantSchema = z13.object({
948
976
  var acceptGrantResponseSchema = z13.object({
949
977
  artifactId: z13.string(),
950
978
  // The review shell URL to land on, carrying ?invited=1 for the banner.
951
- artifactUrl: z13.string().url(),
979
+ artifactUrl: z13.url(),
952
980
  slug: z13.string(),
953
981
  title: z13.string()
954
982
  });
@@ -974,7 +1002,7 @@ var sharedWithMeArtifactSchema = z13.object({
974
1002
  artifactId: z13.string(),
975
1003
  slug: z13.string(),
976
1004
  title: z13.string(),
977
- url: z13.string().url(),
1005
+ url: z13.url(),
978
1006
  // Who shared it, for the "Shared with me" list.
979
1007
  workspaceName: z13.string()
980
1008
  });
@@ -984,13 +1012,105 @@ var sharedWithMeListResponseSchema = z13.object({
984
1012
 
985
1013
  // ../shared/src/html-meta.ts
986
1014
  var HEAD_SCAN_CHARS = 64 * 1024;
1015
+ var NAMED_ENTITIES = new Map(
1016
+ Object.entries({
1017
+ amp: "&",
1018
+ apos: "'",
1019
+ gt: ">",
1020
+ lt: "<",
1021
+ nbsp: " ",
1022
+ quot: '"'
1023
+ })
1024
+ );
987
1025
 
988
1026
  // ../shared/src/me.ts
989
1027
  import { z as z15 } from "zod";
990
1028
 
991
1029
  // ../shared/src/workspaces.ts
992
1030
  import { z as z14 } from "zod";
993
- var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/;
1031
+
1032
+ // ../shared/src/reserved.ts
1033
+ var VANITY_REDIRECTS = /* @__PURE__ */ new Map([
1034
+ ["bharath", "https://bharath-sd6csg.artifactcafe.dev/"],
1035
+ ["demo", "https://artifact-cafe-demo-vsuadu.artifactcafe.dev/"],
1036
+ ["wilson", "https://wilson-nxzuc4.artifactcafe.dev/"],
1037
+ ["yulong", "https://yulong-hoae20.artifactcafe.dev/"]
1038
+ ]);
1039
+ var RESERVED_SLUGS = /* @__PURE__ */ new Set([
1040
+ ...VANITY_REDIRECTS.keys(),
1041
+ "www",
1042
+ "api",
1043
+ "render",
1044
+ // Former auth-provider subdomains; kept reserved so a custom slug can't
1045
+ // shadow a future auth host.
1046
+ "clerk",
1047
+ "accounts",
1048
+ "claim",
1049
+ // The CLI key-management page lives at /cli (SPEC §6.14).
1050
+ "cli",
1051
+ "docs",
1052
+ "admin",
1053
+ "app",
1054
+ "auth",
1055
+ "login",
1056
+ "signin",
1057
+ "signup",
1058
+ "sign-in",
1059
+ "sign-up",
1060
+ "account",
1061
+ "settings",
1062
+ "dashboard",
1063
+ "workspace",
1064
+ "static",
1065
+ "assets",
1066
+ "cdn",
1067
+ "mail",
1068
+ "email",
1069
+ "status",
1070
+ "blog",
1071
+ "help",
1072
+ "support",
1073
+ "about",
1074
+ "pricing",
1075
+ "terms",
1076
+ "privacy",
1077
+ "examples",
1078
+ "new",
1079
+ "artifact",
1080
+ "artifacts",
1081
+ // Mail and DNS labels: a workspace holding one of these could receive
1082
+ // provider verification mail or shadow a record on the apex zone.
1083
+ "abuse",
1084
+ "autodiscover",
1085
+ "autoconfig",
1086
+ "ftp",
1087
+ "imap",
1088
+ "mx",
1089
+ "ns",
1090
+ "ns1",
1091
+ "ns2",
1092
+ "postmaster",
1093
+ "smtp",
1094
+ "webmail",
1095
+ // Security and legal contacts people expect to reach us at.
1096
+ "legal",
1097
+ "security",
1098
+ // Product routes and environment names a subdomain shouldn't shadow.
1099
+ "billing",
1100
+ "dev",
1101
+ "invite",
1102
+ "invites",
1103
+ "preview",
1104
+ "staging",
1105
+ "team",
1106
+ "teams",
1107
+ "test",
1108
+ "user",
1109
+ "users"
1110
+ ]);
1111
+
1112
+ // ../shared/src/workspaces.ts
1113
+ var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/u;
994
1114
  var workspaceHandleUnavailableReasonSchema = z14.enum([
995
1115
  "held",
996
1116
  "invalid",
@@ -1045,7 +1165,7 @@ var deleteWorkspaceResponseSchema = z14.object({
1045
1165
  });
1046
1166
  var workspaceMemberSchema = z14.object({
1047
1167
  createdAt: z14.string(),
1048
- email: z14.string().email(),
1168
+ email: z14.email(),
1049
1169
  image: z14.string().nullable(),
1050
1170
  name: z14.string(),
1051
1171
  role: workspaceRoleSchema,
@@ -1062,13 +1182,13 @@ var removeWorkspaceMemberResponseSchema = z14.object({
1062
1182
  });
1063
1183
  var createWorkspaceInviteSchema = z14.object({
1064
1184
  email: z14.string().trim().toLowerCase().email().optional(),
1065
- expiresAt: z14.string().datetime(),
1185
+ expiresAt: z14.iso.datetime(),
1066
1186
  maxUses: z14.number().int().positive().optional(),
1067
1187
  role: workspaceInviteRoleSchema
1068
1188
  });
1069
1189
  var workspaceInviteSchema = z14.object({
1070
1190
  createdAt: z14.string(),
1071
- email: z14.string().email().nullable(),
1191
+ email: z14.email().nullable(),
1072
1192
  expiresAt: z14.string(),
1073
1193
  id: z14.string(),
1074
1194
  maxUses: z14.number().int().positive().nullable(),
@@ -1077,7 +1197,7 @@ var workspaceInviteSchema = z14.object({
1077
1197
  useCount: z14.number().int().nonnegative()
1078
1198
  });
1079
1199
  var createdWorkspaceInviteSchema = workspaceInviteSchema.extend({
1080
- url: z14.string().url()
1200
+ url: z14.url()
1081
1201
  });
1082
1202
  var workspaceInviteListResponseSchema = z14.object({
1083
1203
  invites: z14.array(workspaceInviteSchema)
@@ -1093,12 +1213,7 @@ var acceptWorkspaceInviteResponseSchema = z14.object({
1093
1213
  workspace: workspaceSchema
1094
1214
  });
1095
1215
  var folderVisibilitySchema = z14.enum(["public", "workspace"]);
1096
- var artifactVisibilitySchema = z14.enum([
1097
- "public",
1098
- "unlisted",
1099
- "workspace",
1100
- "private"
1101
- ]);
1216
+ var artifactVisibilitySchema = z14.enum(["public", "unlisted", "workspace", "private"]);
1102
1217
  var createFolderSchema = z14.object({
1103
1218
  // Optional custom icon; omitted (or null) leaves the default glyph.
1104
1219
  emoji: iconEmojiSchema.optional(),
@@ -1142,12 +1257,12 @@ var deleteFolderResponseSchema = z14.object({
1142
1257
  deleted: z14.literal(true)
1143
1258
  });
1144
1259
  var workspaceArtifactSchema = z14.object({
1145
- autoDeleteAt: z14.string().datetime().nullable().optional(),
1260
+ autoDeleteAt: z14.iso.datetime().nullable().optional(),
1146
1261
  createdAt: z14.string(),
1147
1262
  currentVersionNumber: z14.number().int().nullable(),
1148
1263
  // Custom icon, or null to render the default document glyph.
1149
1264
  emoji: z14.string().nullable(),
1150
- expiresAt: z14.string().datetime().nullable().optional(),
1265
+ expiresAt: z14.iso.datetime().nullable().optional(),
1151
1266
  folderId: z14.string().nullable(),
1152
1267
  id: z14.string(),
1153
1268
  openCommentCount: z14.number().int().nonnegative(),
@@ -1155,7 +1270,7 @@ var workspaceArtifactSchema = z14.object({
1155
1270
  slug: z14.string(),
1156
1271
  title: z14.string(),
1157
1272
  updatedAt: z14.string(),
1158
- url: z14.string().url(),
1273
+ url: z14.url(),
1159
1274
  versionCount: z14.number().int().nonnegative(),
1160
1275
  visibility: artifactVisibilitySchema
1161
1276
  });
@@ -1177,7 +1292,7 @@ var personSchema = z15.object({
1177
1292
  name: z15.string()
1178
1293
  });
1179
1294
  var ownArtifactSchema = z15.object({
1180
- autoDeleteAt: z15.string().datetime().nullable().optional(),
1295
+ autoDeleteAt: z15.iso.datetime().nullable().optional(),
1181
1296
  // Everyone else who has published a version, oldest first. Any member may
1182
1297
  // publish a new one (SPEC-2 §5.1), so an artifact's byline is often more
1183
1298
  // than one person and the card stacks their faces rather than crediting
@@ -1248,12 +1363,12 @@ var revokePublishTokenResponseSchema = z15.object({
1248
1363
 
1249
1364
  // ../shared/src/owner.ts
1250
1365
  import { z as z16 } from "zod";
1251
- var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
1366
+ var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/u;
1252
1367
  var claimResponseSchema = z16.object({
1253
1368
  artifactId: z16.string(),
1254
1369
  slug: z16.string(),
1255
1370
  title: z16.string(),
1256
- url: z16.string().url()
1371
+ url: z16.url()
1257
1372
  });
1258
1373
  var updateArtifactSchema = z16.object({
1259
1374
  // Custom icon; explicit null clears it back to the default glyph.
@@ -1385,6 +1500,11 @@ var artifactStatsResponseSchema = z21.object({
1385
1500
 
1386
1501
  // ../shared/src/versions.ts
1387
1502
  import { z as z22 } from "zod";
1503
+ var versionFilesResponseSchema = z22.object({
1504
+ files: z22.array(manifestFileSchema.extend({ downloadUrl: z22.url() })),
1505
+ slug: z22.string(),
1506
+ versionNumber: z22.number().int().positive()
1507
+ });
1388
1508
  var versionSourceSchema = z22.object({
1389
1509
  agentName: z22.string().nullable(),
1390
1510
  type: z22.string()
@@ -1398,7 +1518,7 @@ var versionSummarySchema = z22.object({
1398
1518
  // Absent for the first version — there is no previous version to diff against.
1399
1519
  changes: versionChangesSchema.nullable(),
1400
1520
  createdAt: z22.string(),
1401
- entryUrl: z22.string().url(),
1521
+ entryUrl: z22.url(),
1402
1522
  fileCount: z22.number().int().nonnegative(),
1403
1523
  id: z22.string(),
1404
1524
  isCurrent: z22.boolean(),
@@ -1419,34 +1539,237 @@ var versionListResponseSchema = z22.object({
1419
1539
  });
1420
1540
 
1421
1541
  // src/agent-identity.ts
1422
- function agentLabel(explicit) {
1542
+ var agentLabel = (explicit) => {
1423
1543
  if (explicit !== void 0) {
1424
1544
  return explicit.trim() || void 0;
1425
1545
  }
1426
1546
  return detectAgentFromEnv(process.env);
1427
- }
1547
+ };
1548
+
1549
+ // src/install-id.ts
1550
+ import { randomUUID } from "crypto";
1551
+ import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
1552
+ import { homedir } from "os";
1553
+ import path from "path";
1554
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
1555
+ var resetInstallId = (file = path.join(homedir(), ".artifactcafe", "install-id")) => {
1556
+ const temporary = `${file}.${randomUUID()}`;
1557
+ try {
1558
+ mkdirSync(path.dirname(file), { mode: 448, recursive: true });
1559
+ writeFileSync(temporary, `${randomUUID()}
1560
+ `, { flag: "wx", mode: 384 });
1561
+ renameSync(temporary, file);
1562
+ } catch {
1563
+ } finally {
1564
+ try {
1565
+ unlinkSync(temporary);
1566
+ } catch {
1567
+ }
1568
+ }
1569
+ };
1570
+ var readInstallId = (file = path.join(homedir(), ".artifactcafe", "install-id")) => {
1571
+ try {
1572
+ mkdirSync(path.dirname(file), { mode: 448, recursive: true });
1573
+ try {
1574
+ writeFileSync(file, `${randomUUID()}
1575
+ `, { flag: "wx", mode: 384 });
1576
+ } catch (error) {
1577
+ if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) {
1578
+ return null;
1579
+ }
1580
+ }
1581
+ const id = readFileSync(file, "utf-8").trim();
1582
+ return UUID.test(id) ? id.toLowerCase() : null;
1583
+ } catch {
1584
+ return null;
1585
+ }
1586
+ };
1587
+
1588
+ // src/update.ts
1589
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1590
+ import { createRequire } from "module";
1591
+ import { homedir as homedir2 } from "os";
1592
+ import path2 from "path";
1593
+ import { z as z23 } from "zod";
1594
+ var REGISTRY_LATEST_URL = "https://registry.npmjs.org/artifact-cafe/latest";
1595
+ var SKILL_SOURCE_URL = "https://raw.githubusercontent.com/Artifact-Cafe/skill/main/artifact-cafe/SKILL.md";
1596
+ var SKILL_UPDATE_COMMAND = "npx skills add artifact-cafe/skill --skill artifact-cafe -g";
1597
+ var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
1598
+ var FETCH_TIMEOUT_MS = 2e3;
1599
+ var SKILL_VERSION_PATTERN = /\*\*Skill version:\s*(?<version>\d+\.\d+\.\d+)\*\*/u;
1600
+ var cacheSchema = z23.object({
1601
+ checkedAt: z23.number(),
1602
+ cliLatest: z23.string().nullable(),
1603
+ skillLatest: z23.string().nullable(),
1604
+ version: z23.literal(1)
1605
+ });
1606
+ var cliVersion = () => {
1607
+ const pkg = z23.object({ version: z23.string() }).parse(createRequire(import.meta.url)("../package.json"));
1608
+ return pkg.version;
1609
+ };
1610
+ var cachePath = () => path2.join(homedir2(), ".artifactcafe", "update-check.json");
1611
+ var readCache = () => {
1612
+ try {
1613
+ return cacheSchema.parse(JSON.parse(readFileSync2(cachePath(), "utf-8")));
1614
+ } catch {
1615
+ return null;
1616
+ }
1617
+ };
1618
+ var writeCache = (cache) => {
1619
+ try {
1620
+ mkdirSync2(path2.dirname(cachePath()), { mode: 448, recursive: true });
1621
+ writeFileSync2(cachePath(), `${JSON.stringify(cache, null, 2)}
1622
+ `);
1623
+ } catch {
1624
+ }
1625
+ };
1626
+ var parse = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
1627
+ var isNewerVersion = (candidate, current) => {
1628
+ const a = parse(candidate);
1629
+ const b = parse(current);
1630
+ for (let i = 0; i < 3; i += 1) {
1631
+ if ((a[i] ?? 0) !== (b[i] ?? 0)) {
1632
+ return (a[i] ?? 0) > (b[i] ?? 0);
1633
+ }
1634
+ }
1635
+ return false;
1636
+ };
1637
+ var fetchCliVersion = async () => {
1638
+ const url = REGISTRY_LATEST_URL;
1639
+ const res = await fetch(url, {
1640
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
1641
+ });
1642
+ if (!res.ok) {
1643
+ throw new Error(`GET ${url} \u2192 ${res.status}`);
1644
+ }
1645
+ return z23.object({ version: z23.string() }).parse(await res.json()).version;
1646
+ };
1647
+ var fetchText = async (url) => {
1648
+ const res = await fetch(url, {
1649
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
1650
+ });
1651
+ if (!res.ok) {
1652
+ throw new Error(`GET ${url} \u2192 ${res.status}`);
1653
+ }
1654
+ return res.text();
1655
+ };
1656
+ var fetchLatestVersions = async (previous) => {
1657
+ const [cli, skill] = await Promise.all([
1658
+ fetchCliVersion().catch(() => null),
1659
+ fetchText(SKILL_SOURCE_URL).then((body) => SKILL_VERSION_PATTERN.exec(body)?.[1] ?? null).catch(() => null)
1660
+ ]);
1661
+ return {
1662
+ checkedAt: Date.now(),
1663
+ cliLatest: cli ?? previous?.cliLatest ?? null,
1664
+ skillLatest: skill ?? previous?.skillLatest ?? null,
1665
+ version: 1
1666
+ };
1667
+ };
1668
+ var installedSkillVersions = () => {
1669
+ const roots = [homedir2(), process.cwd()];
1670
+ const dirs = [".agents", ".claude"];
1671
+ const versions = [];
1672
+ for (const root of roots) {
1673
+ for (const dir of dirs) {
1674
+ const file = path2.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
1675
+ if (!existsSync(file)) {
1676
+ continue;
1677
+ }
1678
+ try {
1679
+ const found = SKILL_VERSION_PATTERN.exec(readFileSync2(file, "utf-8"));
1680
+ if (found?.[1]) {
1681
+ versions.push(found[1]);
1682
+ }
1683
+ } catch {
1684
+ }
1685
+ }
1686
+ }
1687
+ return versions;
1688
+ };
1689
+ var updateChecksDisabled = () => Boolean(
1690
+ process.env.ARTIFACT_CAFE_NO_UPDATE_CHECK ?? process.env.NO_UPDATE_NOTIFIER ?? process.env.CI
1691
+ );
1692
+ var startUpdateCheck = () => {
1693
+ if (updateChecksDisabled()) {
1694
+ return () => Promise.resolve();
1695
+ }
1696
+ const pending = (async () => {
1697
+ try {
1698
+ const cached = readCache();
1699
+ if (cached && Date.now() - cached.checkedAt < CHECK_TTL_MS) {
1700
+ return cached;
1701
+ }
1702
+ const fresh = await fetchLatestVersions(cached);
1703
+ writeCache(fresh);
1704
+ return fresh;
1705
+ } catch {
1706
+ return null;
1707
+ }
1708
+ })();
1709
+ return async () => {
1710
+ const latest = await pending;
1711
+ if (!latest) {
1712
+ return;
1713
+ }
1714
+ const notices = [];
1715
+ const current = cliVersion();
1716
+ if (latest.cliLatest && isNewerVersion(latest.cliLatest, current)) {
1717
+ notices.push(
1718
+ `[artifact-cafe] CLI update available: ${current} \u2192 ${latest.cliLatest}. Run commands via \`npx artifact-cafe@latest\`; update a global install with \`npm i -g artifact-cafe@latest\`.`
1719
+ );
1720
+ }
1721
+ const { skillLatest } = latest;
1722
+ if (skillLatest) {
1723
+ const stale = installedSkillVersions().filter((version) => isNewerVersion(skillLatest, version)).sort((a, b) => isNewerVersion(a, b) ? 1 : -1);
1724
+ if (stale.length > 0) {
1725
+ notices.push(
1726
+ `[artifact-cafe] Skill update available: ${stale[0]} \u2192 ${latest.skillLatest}. Update: \`${SKILL_UPDATE_COMMAND}\` (drop -g for a repo-local copy), then re-read SKILL.md.`
1727
+ );
1728
+ }
1729
+ }
1730
+ if (notices.length > 0) {
1731
+ console.error(`
1732
+ ${notices.join("\n")}`);
1733
+ }
1734
+ };
1735
+ };
1428
1736
 
1429
1737
  // src/api.ts
1430
1738
  var ApiRequestError = class extends Error {
1431
- constructor(status2, code, message, retryAfter) {
1739
+ status;
1740
+ code;
1741
+ retryAfter;
1742
+ currentVersion;
1743
+ docsUrl;
1744
+ constructor(status2, code, message, retryAfter, details = void 0) {
1432
1745
  super(message);
1433
1746
  this.status = status2;
1434
1747
  this.code = code;
1435
1748
  this.retryAfter = retryAfter;
1749
+ this.currentVersion = details?.currentVersion;
1750
+ this.docsUrl = details?.docsUrl;
1436
1751
  this.name = "ApiRequestError";
1437
1752
  }
1438
- status;
1439
- code;
1440
- retryAfter;
1441
1753
  };
1442
- async function request(url, init, parse) {
1754
+ var accessHeaders = (env = process.env) => {
1755
+ const id = env.CF_ACCESS_CLIENT_ID;
1756
+ const secret = env.CF_ACCESS_CLIENT_SECRET;
1757
+ return id && secret ? { "cf-access-client-id": id, "cf-access-client-secret": secret } : {};
1758
+ };
1759
+ var request = async (url, init, parse2) => {
1443
1760
  let res;
1444
1761
  try {
1445
- res = await fetch(url, init);
1446
- } catch (err) {
1762
+ res = await fetch(url, {
1763
+ ...init,
1764
+ headers: {
1765
+ ...Object.fromEntries(new Headers(init.headers)),
1766
+ ...accessHeaders()
1767
+ }
1768
+ });
1769
+ } catch (error) {
1447
1770
  throw new Error(
1448
- `Could not reach ${new URL(url).origin} \u2014 is the API up? (${err instanceof Error ? err.message : err})`,
1449
- { cause: err }
1771
+ `Could not reach ${new URL(url).origin} \u2014 is the API up? (${error instanceof Error ? error.message : error})`,
1772
+ { cause: error }
1450
1773
  );
1451
1774
  }
1452
1775
  const body = await res.json().catch(() => null);
@@ -1457,24 +1780,26 @@ async function request(url, init, parse) {
1457
1780
  res.status,
1458
1781
  parsed.data.code,
1459
1782
  parsed.data.message,
1460
- parsed.data.retryAfter
1783
+ parsed.data.retryAfter,
1784
+ parsed.data
1461
1785
  );
1462
1786
  }
1463
- throw new ApiRequestError(
1464
- res.status,
1465
- "unknown_error",
1466
- `API returned ${res.status}`
1467
- );
1787
+ throw new ApiRequestError(res.status, "unknown_error", `API returned ${res.status}`);
1788
+ }
1789
+ return parse2(body);
1790
+ };
1791
+ var createApiClient = (apiUrl2, publishToken) => {
1792
+ const base = apiUrl2.replace(/\/$/u, "");
1793
+ const headers = new Headers({
1794
+ "content-type": "application/json",
1795
+ "x-artifact-cafe-cli-version": cliVersion()
1796
+ });
1797
+ const installId = readInstallId();
1798
+ if (installId) {
1799
+ headers.set("x-artifact-cafe-install-id", installId);
1468
1800
  }
1469
- return parse(body);
1470
- }
1471
- function createApiClient(apiUrl2, publishToken) {
1472
- const base = apiUrl2.replace(/\/$/, "");
1473
- const headers = {
1474
- "content-type": "application/json"
1475
- };
1476
1801
  if (publishToken) {
1477
- headers.authorization = `Bearer ${publishToken}`;
1802
+ headers.set("authorization", `Bearer ${publishToken}`);
1478
1803
  }
1479
1804
  return {
1480
1805
  // SPEC §6.19 — mark the agent as actively waiting for feedback.
@@ -1482,25 +1807,25 @@ function createApiClient(apiUrl2, publishToken) {
1482
1807
  // SPEC §6.6.1 — mark an open thread acknowledged ("seen, working on
1483
1808
  // it") without resolving it. Publish token or account key only —
1484
1809
  // the server rejects guests.
1485
- async acknowledgeThread(threadId) {
1810
+ acknowledgeThread(threadId) {
1486
1811
  return request(
1487
1812
  `${base}/api/v1/comment-threads/${threadId}/acknowledge`,
1488
1813
  { body: "{}", headers, method: "POST" },
1489
1814
  (d) => commentThreadSchema.parse(d)
1490
1815
  );
1491
1816
  },
1492
- async agentHeartbeat(artifactId, state) {
1817
+ agentHeartbeat(artifactId, state) {
1493
1818
  return request(
1494
1819
  `${base}/api/v1/artifacts/${artifactId}/agent-presence`,
1495
1820
  {
1496
- ...state ? { body: JSON.stringify({ state }) } : {},
1821
+ ...state ? { body: JSON.stringify({ state }) } : void 0,
1497
1822
  headers,
1498
1823
  method: "POST"
1499
1824
  },
1500
1825
  (d) => agentHeartbeatResponseSchema.parse(d)
1501
1826
  );
1502
1827
  },
1503
- async clearData(artifactId, collection) {
1828
+ clearData(artifactId, collection) {
1504
1829
  return request(
1505
1830
  `${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}`,
1506
1831
  { headers, method: "DELETE" },
@@ -1509,7 +1834,7 @@ function createApiClient(apiUrl2, publishToken) {
1509
1834
  },
1510
1835
  // SPEC-2 §6.3 — create a folder in a workspace. Account key required
1511
1836
  // (owner/admin; in a personal workspace that's always the user).
1512
- async createFolder(workspaceId, body) {
1837
+ createFolder(workspaceId, body) {
1513
1838
  return request(
1514
1839
  `${base}/api/v1/workspaces/${workspaceId}/folders`,
1515
1840
  { body: JSON.stringify(body), headers, method: "POST" },
@@ -1520,14 +1845,14 @@ function createApiClient(apiUrl2, publishToken) {
1520
1845
  // Authorized by this client's credential: the publish token (an agent
1521
1846
  // sharing a private draft) or an account key/session. The invitation
1522
1847
  // URL is returned exactly once, here.
1523
- async createGrant(artifactId, body) {
1848
+ createGrant(artifactId, body) {
1524
1849
  return request(
1525
1850
  `${base}/api/v1/artifacts/${artifactId}/grants`,
1526
1851
  { body: JSON.stringify(body), headers, method: "POST" },
1527
1852
  (d) => createdGrantSchema.parse(d)
1528
1853
  );
1529
1854
  },
1530
- async createSession(body) {
1855
+ createSession(body) {
1531
1856
  return request(
1532
1857
  `${base}/api/v1/publish`,
1533
1858
  { body: JSON.stringify(body), headers, method: "POST" },
@@ -1538,7 +1863,7 @@ function createApiClient(apiUrl2, publishToken) {
1538
1863
  // key belongs to (agent-originated review, §5.1 `comment`). The create
1539
1864
  // route rejects a bare publish token (agents respond, accounts
1540
1865
  // originate), so this must carry an account credential.
1541
- async createThread(artifactId, body) {
1866
+ createThread(artifactId, body) {
1542
1867
  return request(
1543
1868
  `${base}/api/v1/artifacts/${artifactId}/comments`,
1544
1869
  { body: JSON.stringify(body), headers, method: "POST" },
@@ -1552,51 +1877,56 @@ function createApiClient(apiUrl2, publishToken) {
1552
1877
  await request(
1553
1878
  `${base}/api/v1/comment-messages/${messageId}`,
1554
1879
  { body: "{}", headers, method: "DELETE" },
1555
- () => void 0
1880
+ () => {
1881
+ }
1556
1882
  );
1557
1883
  },
1558
1884
  // SPEC §6.7.1 — edit a message you authored (new body + "edited" badge).
1559
1885
  // Same credential rule as delete; returns the updated thread.
1560
- async editMessage(messageId, body) {
1886
+ editMessage(messageId, body) {
1561
1887
  return request(
1562
1888
  `${base}/api/v1/comment-messages/${messageId}`,
1563
1889
  { body: JSON.stringify({ body }), headers, method: "PATCH" },
1564
1890
  (d) => commentThreadSchema.parse(d)
1565
1891
  );
1566
1892
  },
1567
- async exportComments(artifactId, query) {
1893
+ exportComments(artifactId, query, { poll = false } = {}) {
1568
1894
  const params = new URLSearchParams({
1569
1895
  status: query.status,
1570
1896
  version: query.version
1571
1897
  });
1898
+ const exportHeaders = new Headers(headers);
1899
+ if (poll) {
1900
+ exportHeaders.set("x-artifact-cafe-poll", "1");
1901
+ }
1572
1902
  return request(
1573
1903
  `${base}/api/v1/artifacts/${artifactId}/comments/export?${params}`,
1574
- { headers, method: "GET" },
1904
+ { headers: exportHeaders, method: "GET" },
1575
1905
  (d) => commentExportSchema.parse(d)
1576
1906
  );
1577
1907
  },
1578
- async finalize(publishSessionId) {
1908
+ finalize(publishSessionId) {
1579
1909
  return request(
1580
1910
  `${base}/api/v1/publish/${publishSessionId}/finalize`,
1581
1911
  { headers, method: "POST" },
1582
1912
  (d) => finalizeResponseSchema.parse(d)
1583
1913
  );
1584
1914
  },
1585
- async getArtifact(artifactId) {
1915
+ getArtifact(artifactId) {
1586
1916
  return request(
1587
1917
  `${base}/api/v1/artifacts/${artifactId}`,
1588
1918
  { headers, method: "GET" },
1589
1919
  (d) => artifactResponseSchema.parse(d)
1590
1920
  );
1591
1921
  },
1592
- async getDataSummary(artifactId) {
1922
+ getDataSummary(artifactId) {
1593
1923
  return request(
1594
1924
  `${base}/api/v1/artifacts/${artifactId}/data`,
1595
1925
  { headers, method: "GET" },
1596
1926
  (d) => dataSummaryResponseSchema.parse(d)
1597
1927
  );
1598
1928
  },
1599
- async insertData(artifactId, collection, fields) {
1929
+ insertData(artifactId, collection, fields) {
1600
1930
  return request(
1601
1931
  `${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}`,
1602
1932
  { body: JSON.stringify(fields), headers, method: "POST" },
@@ -1604,7 +1934,7 @@ function createApiClient(apiUrl2, publishToken) {
1604
1934
  );
1605
1935
  },
1606
1936
  /** Owner-plane batch: up to DATA_LIMITS.maxInsertBatch, all-or-nothing. */
1607
- async insertDataBatch(artifactId, collection, records) {
1937
+ insertDataBatch(artifactId, collection, records) {
1608
1938
  return request(
1609
1939
  `${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}`,
1610
1940
  { body: JSON.stringify(records), headers, method: "POST" },
@@ -1613,14 +1943,14 @@ function createApiClient(apiUrl2, publishToken) {
1613
1943
  },
1614
1944
  // SPEC §6.18 — everything owned by the account this client's API
1615
1945
  // key (or session) belongs to.
1616
- async listArtifacts() {
1946
+ listArtifacts() {
1617
1947
  return request(
1618
1948
  `${base}/api/v1/artifacts`,
1619
1949
  { headers, method: "GET" },
1620
1950
  (d) => artifactListResponseSchema.parse(d)
1621
1951
  );
1622
1952
  },
1623
- async listData(artifactId, collection, query = {}) {
1953
+ listData(artifactId, collection, query = {}) {
1624
1954
  const params = new URLSearchParams();
1625
1955
  if (query.limit !== void 0) {
1626
1956
  params.set("limit", String(query.limit));
@@ -1635,7 +1965,7 @@ function createApiClient(apiUrl2, publishToken) {
1635
1965
  (d) => dataListResponseSchema.parse(d)
1636
1966
  );
1637
1967
  },
1638
- async listFolders(workspaceId) {
1968
+ listFolders(workspaceId) {
1639
1969
  return request(
1640
1970
  `${base}/api/v1/workspaces/${workspaceId}/folders`,
1641
1971
  { headers, method: "GET" },
@@ -1644,7 +1974,7 @@ function createApiClient(apiUrl2, publishToken) {
1644
1974
  },
1645
1975
  // SPEC-2 §6.6 — the artifact's grants (no plaintext tokens). Used by
1646
1976
  // `share --list` and to resolve an email → grant id for `unshare`.
1647
- async listGrants(artifactId) {
1977
+ listGrants(artifactId) {
1648
1978
  return request(
1649
1979
  `${base}/api/v1/artifacts/${artifactId}/grants`,
1650
1980
  { headers, method: "GET" },
@@ -1653,14 +1983,14 @@ function createApiClient(apiUrl2, publishToken) {
1653
1983
  },
1654
1984
  // SPEC-2 §6.4 — every artifact in a workspace (members see all, with
1655
1985
  // each artifact's folderId). The only folder-aware artifact listing.
1656
- async listWorkspaceArtifacts(workspaceId) {
1986
+ listWorkspaceArtifacts(workspaceId) {
1657
1987
  return request(
1658
1988
  `${base}/api/v1/workspaces/${workspaceId}/artifacts`,
1659
1989
  { headers, method: "GET" },
1660
1990
  (d) => workspaceArtifactListResponseSchema.parse(d)
1661
1991
  );
1662
1992
  },
1663
- async listWorkspaces() {
1993
+ listWorkspaces() {
1664
1994
  return request(
1665
1995
  `${base}/api/v1/workspaces`,
1666
1996
  { headers, method: "GET" },
@@ -1669,7 +1999,7 @@ function createApiClient(apiUrl2, publishToken) {
1669
1999
  },
1670
2000
  // SPEC §6.14 — who the presented credential belongs to. Used by
1671
2001
  // `login` to verify a pasted key and by `whoami`.
1672
- async me() {
2002
+ me() {
1673
2003
  return request(
1674
2004
  `${base}/api/v1/me`,
1675
2005
  { headers, method: "GET" },
@@ -1677,7 +2007,7 @@ function createApiClient(apiUrl2, publishToken) {
1677
2007
  );
1678
2008
  },
1679
2009
  // SPEC §6.7 — reopen a resolved thread. Same auth as resolve.
1680
- async reopenThread(threadId) {
2010
+ reopenThread(threadId) {
1681
2011
  return request(
1682
2012
  `${base}/api/v1/comment-threads/${threadId}/reopen`,
1683
2013
  { body: "{}", headers, method: "POST" },
@@ -1690,7 +2020,7 @@ function createApiClient(apiUrl2, publishToken) {
1690
2020
  // payload — the credential is the identity. `via` names the tool: it
1691
2021
  // becomes the agent's byline on a token reply, a "via" label on an
1692
2022
  // account one.
1693
- async replyThread(threadId, body, via) {
2023
+ replyThread(threadId, body, via) {
1694
2024
  return request(
1695
2025
  `${base}/api/v1/comment-threads/${threadId}/messages`,
1696
2026
  { body: JSON.stringify({ body, via }), headers, method: "POST" },
@@ -1699,7 +2029,7 @@ function createApiClient(apiUrl2, publishToken) {
1699
2029
  },
1700
2030
  // SPEC §6.6 — mark a thread resolved. Authorized by this client's
1701
2031
  // credential: the artifact's publish token, or the owner's account key.
1702
- async resolveThread(threadId) {
2032
+ resolveThread(threadId) {
1703
2033
  return request(
1704
2034
  `${base}/api/v1/comment-threads/${threadId}/resolve`,
1705
2035
  { body: "{}", headers, method: "POST" },
@@ -1711,10 +2041,11 @@ function createApiClient(apiUrl2, publishToken) {
1711
2041
  await request(
1712
2042
  `${base}/api/v1/artifacts/${artifactId}/grants/${grantId}`,
1713
2043
  { headers, method: "DELETE" },
1714
- () => void 0
2044
+ () => {
2045
+ }
1715
2046
  );
1716
2047
  },
1717
- async setExpiration(artifactId, expiresInSeconds) {
2048
+ setExpiration(artifactId, expiresInSeconds) {
1718
2049
  return request(
1719
2050
  `${base}/api/v1/artifacts/${artifactId}/expiration`,
1720
2051
  { body: JSON.stringify({ expiresInSeconds }), headers, method: "PUT" },
@@ -1723,7 +2054,7 @@ function createApiClient(apiUrl2, publishToken) {
1723
2054
  },
1724
2055
  // SPEC §6.16 — set a password (string) or clear it (null). Authorized
1725
2056
  // by the publish token this client was created with.
1726
- async setProtection(artifactId, password) {
2057
+ setProtection(artifactId, password) {
1727
2058
  return request(
1728
2059
  `${base}/api/v1/artifacts/${artifactId}/protection`,
1729
2060
  { body: JSON.stringify({ password }), headers, method: "PUT" },
@@ -1733,7 +2064,7 @@ function createApiClient(apiUrl2, publishToken) {
1733
2064
  // SPEC-2 §5.8 — what reviewers may do once they can open it. Same
1734
2065
  // dual-auth as protection, so the publish token this client holds is
1735
2066
  // enough (an agent can close review on its own artifact).
1736
- async setReviewMode(artifactId, mode) {
2067
+ setReviewMode(artifactId, mode) {
1737
2068
  return request(
1738
2069
  `${base}/api/v1/artifacts/${artifactId}/review-mode`,
1739
2070
  { body: JSON.stringify({ mode }), headers, method: "PUT" },
@@ -1743,16 +2074,23 @@ function createApiClient(apiUrl2, publishToken) {
1743
2074
  // SPEC.md §6.11 / SPEC-2 §6.4 — update an artifact (folder placement,
1744
2075
  // title, slug, visibility). Workspace-member action: account key only,
1745
2076
  // a publish token can't re-file.
1746
- async updateArtifact(artifactId, body) {
2077
+ updateArtifact(artifactId, body) {
1747
2078
  return request(
1748
2079
  `${base}/api/v1/artifacts/${artifactId}`,
1749
2080
  { body: JSON.stringify(body), headers, method: "PATCH" },
1750
2081
  (d) => artifactResponseSchema.parse(d)
1751
2082
  );
2083
+ },
2084
+ versionFiles(artifactId, version) {
2085
+ return request(
2086
+ `${base}/api/v1/artifacts/${encodeURIComponent(artifactId)}/versions/${version}/files`,
2087
+ { headers, method: "GET" },
2088
+ (data2) => versionFilesResponseSchema.parse(data2)
2089
+ );
1752
2090
  }
1753
2091
  };
1754
- }
1755
- async function uploadFile(uploadUrl, content, mimeType) {
2092
+ };
2093
+ var uploadFile = async (uploadUrl, content, mimeType) => {
1756
2094
  const res = await fetch(uploadUrl, {
1757
2095
  body: new Uint8Array(content),
1758
2096
  headers: {
@@ -1762,190 +2100,166 @@ async function uploadFile(uploadUrl, content, mimeType) {
1762
2100
  method: "PUT"
1763
2101
  });
1764
2102
  if (!res.ok) {
1765
- throw new Error(
1766
- `Upload failed (${res.status}): ${(await res.text()).slice(0, 200)}`
1767
- );
2103
+ const responseBody = await res.text();
2104
+ throw new Error(`Upload failed (${res.status}): ${responseBody.slice(0, 200)}`);
1768
2105
  }
1769
- }
2106
+ };
1770
2107
 
1771
2108
  // src/config.ts
1772
- import {
1773
- appendFileSync,
1774
- existsSync as existsSync2,
1775
- mkdirSync as mkdirSync2,
1776
- readFileSync as readFileSync2,
1777
- writeFileSync as writeFileSync2
1778
- } from "fs";
1779
- import path2 from "path";
1780
- import { z as z24 } from "zod";
2109
+ import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
2110
+ import path4 from "path";
2111
+ import { z as z25 } from "zod";
1781
2112
 
1782
2113
  // src/credentials.ts
1783
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1784
- import { homedir } from "os";
1785
- import path from "path";
1786
- import { z as z23 } from "zod";
2114
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
2115
+ import { homedir as homedir3 } from "os";
2116
+ import path3 from "path";
2117
+ import { z as z24 } from "zod";
1787
2118
  var DEFAULT_API_URL = "https://artifact.cafe";
1788
- var credentialsSchema = z23.object({
1789
- keys: z23.record(z23.string(), z23.string()),
1790
- version: z23.literal(1)
2119
+ var credentialsSchema = z24.object({
2120
+ keys: z24.record(z24.string(), z24.string()),
2121
+ version: z24.literal(1)
1791
2122
  });
1792
- function credentialsPath() {
1793
- return path.join(homedir(), ".artifactcafe", "credentials");
1794
- }
1795
- function apiOrigin(apiUrl2) {
1796
- return new URL(apiUrl2).origin;
1797
- }
1798
- function readCredentials() {
2123
+ var credentialsPath = () => path3.join(homedir3(), ".artifactcafe", "credentials");
2124
+ var apiOrigin = (apiUrl2) => new URL(apiUrl2).origin;
2125
+ var readCredentials = () => {
1799
2126
  const file = credentialsPath();
1800
- if (!existsSync(file)) {
2127
+ if (!existsSync2(file)) {
1801
2128
  return { keys: {}, version: 1 };
1802
2129
  }
1803
2130
  try {
1804
- return credentialsSchema.parse(JSON.parse(readFileSync(file, "utf8")));
2131
+ return credentialsSchema.parse(JSON.parse(readFileSync3(file, "utf-8")));
1805
2132
  } catch {
1806
2133
  return { keys: {}, version: 1 };
1807
2134
  }
1808
- }
1809
- function writeCredentials(credentials) {
2135
+ };
2136
+ var writeCredentials = (credentials) => {
1810
2137
  const file = credentialsPath();
1811
- mkdirSync(path.dirname(file), { mode: 448, recursive: true });
1812
- writeFileSync(file, `${JSON.stringify(credentials, null, 2)}
2138
+ mkdirSync3(path3.dirname(file), { mode: 448, recursive: true });
2139
+ writeFileSync3(file, `${JSON.stringify(credentials, null, 2)}
1813
2140
  `, {
1814
2141
  mode: 384
1815
2142
  });
1816
- }
1817
- function storedApiKey(apiUrl2) {
1818
- return readCredentials().keys[apiOrigin(apiUrl2)] ?? null;
1819
- }
1820
- function storeApiKey(apiUrl2, key) {
2143
+ };
2144
+ var storedApiKey = (apiUrl2) => readCredentials().keys[apiOrigin(apiUrl2)] ?? null;
2145
+ var storeApiKey = (apiUrl2, key) => {
1821
2146
  const credentials = readCredentials();
1822
2147
  credentials.keys[apiOrigin(apiUrl2)] = key;
1823
2148
  writeCredentials(credentials);
1824
- }
1825
- function removeApiKey(apiUrl2) {
2149
+ };
2150
+ var removeApiKey = (apiUrl2) => {
1826
2151
  const credentials = readCredentials();
1827
2152
  const origin = apiOrigin(apiUrl2);
1828
2153
  if (!(origin in credentials.keys)) {
1829
2154
  return false;
1830
2155
  }
1831
- delete credentials.keys[origin];
2156
+ credentials.keys = Object.fromEntries(
2157
+ Object.entries(credentials.keys).filter(([key]) => key !== origin)
2158
+ );
1832
2159
  writeCredentials(credentials);
1833
2160
  return true;
1834
- }
1835
- function resolveApiKey(apiUrl2, flag) {
1836
- return flag ?? process.env.ARTIFACT_CAFE_API_KEY ?? storedApiKey(apiUrl2) ?? void 0;
1837
- }
2161
+ };
2162
+ var resolveApiKey = (apiUrl2, flag) => flag ?? process.env.ARTIFACT_CAFE_API_KEY ?? storedApiKey(apiUrl2) ?? void 0;
1838
2163
 
1839
2164
  // src/config.ts
1840
- var configSchema = z24.object({
1841
- apiUrl: z24.string(),
1842
- artifactId: z24.string(),
1843
- folder: z24.string().optional(),
1844
- folderId: z24.string().optional(),
2165
+ var CONFIG_DIR = ".artifactcafe";
2166
+ var ensureGitignored = (dir) => {
2167
+ const gitignore = path4.join(dir, ".gitignore");
2168
+ if (!existsSync3(gitignore)) {
2169
+ return;
2170
+ }
2171
+ const lines = readFileSync4(gitignore, "utf-8").split(/\r?\n/u);
2172
+ const present = lines.some((l) => {
2173
+ const t = l.trim();
2174
+ return t === CONFIG_DIR || t === `${CONFIG_DIR}/` || t === `/${CONFIG_DIR}` || t === `/${CONFIG_DIR}/`;
2175
+ });
2176
+ if (!present) {
2177
+ appendFileSync(gitignore, `
2178
+ # artifact.cafe publish token
2179
+ ${CONFIG_DIR}/
2180
+ `);
2181
+ }
2182
+ };
2183
+ var configSchema = z25.object({
2184
+ apiUrl: z25.string(),
2185
+ artifactId: z25.string(),
2186
+ baseVersion: z25.number().int().positive().optional(),
2187
+ folder: z25.string().optional(),
2188
+ folderId: z25.string().optional(),
1845
2189
  // Optional: a folder linked by `link` to an artifact you own carries no
1846
2190
  // token — owner actions authorize via your stored login instead. A first
1847
2191
  // publish still writes the anonymous-mode token here.
1848
- publishToken: z24.string().optional(),
1849
- slug: z24.string(),
1850
- url: z24.string(),
1851
- workspace: z24.string().optional(),
1852
- workspaceId: z24.string().optional(),
1853
- workspaceKind: z24.enum(["personal", "workspace"]).optional()
1854
- });
1855
- var projectConfigSchema = z24.object({
1856
- folder: z24.string().trim().min(1).optional(),
1857
- workspace: z24.string().trim().min(1)
1858
- }).strict();
1859
- var CONFIG_DIR = ".artifactcafe";
2192
+ publishToken: z25.string().optional(),
2193
+ slug: z25.string(),
2194
+ url: z25.string(),
2195
+ workspace: z25.string().optional(),
2196
+ workspaceId: z25.string().optional(),
2197
+ workspaceKind: z25.enum(["personal", "workspace"]).optional()
2198
+ });
2199
+ var projectConfigSchema = z25.strictObject({
2200
+ folder: z25.string().trim().min(1).optional(),
2201
+ workspace: z25.string().trim().min(1)
2202
+ });
1860
2203
  var CONFIG_FILE = "config.json";
1861
2204
  var PROJECT_CONFIG_FILE = "artifact-cafe.json";
1862
- function readConfig(dir) {
1863
- const file = path2.join(dir, CONFIG_DIR, CONFIG_FILE);
1864
- if (!existsSync2(file)) {
2205
+ var readConfig = (dir) => {
2206
+ const file = path4.join(dir, CONFIG_DIR, CONFIG_FILE);
2207
+ if (!existsSync3(file)) {
1865
2208
  return null;
1866
2209
  }
1867
2210
  try {
1868
- return configSchema.parse(JSON.parse(readFileSync2(file, "utf8")));
2211
+ return configSchema.parse(JSON.parse(readFileSync4(file, "utf-8")));
1869
2212
  } catch {
1870
2213
  return null;
1871
2214
  }
1872
- }
1873
- function writeConfig(dir, config) {
1874
- mkdirSync2(path2.join(dir, CONFIG_DIR), { recursive: true });
1875
- writeFileSync2(
1876
- path2.join(dir, CONFIG_DIR, CONFIG_FILE),
1877
- `${JSON.stringify(config, null, 2)}
1878
- `,
1879
- { mode: 384 }
1880
- );
2215
+ };
2216
+ var writeConfig = (dir, config) => {
2217
+ mkdirSync4(path4.join(dir, CONFIG_DIR), { recursive: true });
2218
+ writeFileSync4(path4.join(dir, CONFIG_DIR, CONFIG_FILE), `${JSON.stringify(config, null, 2)}
2219
+ `, {
2220
+ mode: 384
2221
+ });
1881
2222
  ensureGitignored(dir);
1882
- }
1883
- function readProjectConfig(dir) {
1884
- let current = path2.resolve(dir);
2223
+ };
2224
+ var readProjectConfig = (dir) => {
2225
+ let current = path4.resolve(dir);
1885
2226
  while (true) {
1886
- const file = path2.join(current, PROJECT_CONFIG_FILE);
1887
- if (existsSync2(file)) {
2227
+ const file = path4.join(current, PROJECT_CONFIG_FILE);
2228
+ if (existsSync3(file)) {
1888
2229
  try {
1889
- return projectConfigSchema.parse(
1890
- JSON.parse(readFileSync2(file, "utf8"))
1891
- );
2230
+ return projectConfigSchema.parse(JSON.parse(readFileSync4(file, "utf-8")));
1892
2231
  } catch (error) {
1893
- const message = error instanceof z24.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
1894
- throw new Error(`Invalid ${file}: ${message}.`);
2232
+ const message = error instanceof z25.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
2233
+ throw new Error(`Invalid ${file}: ${message}.`, { cause: error });
1895
2234
  }
1896
2235
  }
1897
- if (existsSync2(path2.join(current, ".git"))) {
2236
+ if (existsSync3(path4.join(current, ".git"))) {
1898
2237
  return null;
1899
2238
  }
1900
- const parent = path2.dirname(current);
2239
+ const parent = path4.dirname(current);
1901
2240
  if (parent === current) {
1902
2241
  return null;
1903
2242
  }
1904
2243
  current = parent;
1905
2244
  }
1906
- }
1907
- function writeProjectConfig(dir, config) {
1908
- let root = path2.resolve(dir);
1909
- while (!existsSync2(path2.join(root, ".git"))) {
1910
- const parent = path2.dirname(root);
2245
+ };
2246
+ var writeProjectConfig = (dir, config) => {
2247
+ let root = path4.resolve(dir);
2248
+ while (!existsSync3(path4.join(root, ".git"))) {
2249
+ const parent = path4.dirname(root);
1911
2250
  if (parent === root) {
1912
- root = path2.resolve(dir);
2251
+ root = path4.resolve(dir);
1913
2252
  break;
1914
2253
  }
1915
2254
  root = parent;
1916
2255
  }
1917
- const file = path2.join(root, PROJECT_CONFIG_FILE);
1918
- writeFileSync2(
1919
- file,
1920
- `${JSON.stringify(projectConfigSchema.parse(config), null, 2)}
1921
- `
1922
- );
2256
+ const file = path4.join(root, PROJECT_CONFIG_FILE);
2257
+ writeFileSync4(file, `${JSON.stringify(projectConfigSchema.parse(config), null, 2)}
2258
+ `);
1923
2259
  return file;
1924
- }
1925
- function ensureGitignored(dir) {
1926
- const gitignore = path2.join(dir, ".gitignore");
1927
- if (!existsSync2(gitignore)) {
1928
- return;
1929
- }
1930
- const lines = readFileSync2(gitignore, "utf8").split(/\r?\n/);
1931
- const present = lines.some((l) => {
1932
- const t = l.trim();
1933
- return t === CONFIG_DIR || t === `${CONFIG_DIR}/` || t === `/${CONFIG_DIR}` || t === `/${CONFIG_DIR}/`;
1934
- });
1935
- if (!present) {
1936
- appendFileSync(
1937
- gitignore,
1938
- `
1939
- # artifact.cafe publish token
1940
- ${CONFIG_DIR}/
1941
- `
1942
- );
1943
- }
1944
- }
1945
- function reviewUrl(apiUrl2, slug) {
1946
- return `${apiUrl2.replace(/\/$/, "")}/a/${slug}`;
1947
- }
1948
- function resolveArtifactTarget(dir, opts = {}) {
2260
+ };
2261
+ var reviewUrl = (apiUrl2, slug) => `${apiUrl2.replace(/\/$/u, "")}/a/${slug}`;
2262
+ var resolveArtifactTarget = (dir, opts = {}) => {
1949
2263
  const config = readConfig(dir);
1950
2264
  const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
1951
2265
  const artifactId = opts.artifact ?? process.env.ARTIFACT_CAFE_ARTIFACT_ID ?? config?.artifactId ?? null;
@@ -1959,26 +2273,24 @@ function resolveArtifactTarget(dir, opts = {}) {
1959
2273
  const credential = (apiKey?.startsWith("aat_") ? apiKey : void 0) ?? folderToken ?? process.env.ARTIFACT_CAFE_TOKEN ?? apiKey;
1960
2274
  const url = artifactId === config?.artifactId ? config.url : void 0;
1961
2275
  return { apiUrl: apiUrl2, artifactId, credential, url };
1962
- }
2276
+ };
1963
2277
 
1964
2278
  // src/comment.ts
1965
- async function readStdin() {
2279
+ var readStdin = async () => {
1966
2280
  if (process.stdin.isTTY) {
1967
2281
  return "";
1968
2282
  }
1969
2283
  const chunks = [];
1970
2284
  for await (const chunk of process.stdin) {
1971
- chunks.push(chunk);
2285
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1972
2286
  }
1973
- return Buffer.concat(chunks).toString("utf8");
1974
- }
1975
- function buildAnchor(opts) {
2287
+ return Buffer.concat(chunks).toString("utf-8");
2288
+ };
2289
+ var buildAnchor = (opts) => {
1976
2290
  if (opts.quote !== void 0) {
1977
2291
  const quote = opts.quote.trim();
1978
2292
  if (!quote) {
1979
- throw new Error(
1980
- "--quote can't be empty. Omit it entirely for a page-level comment."
1981
- );
2293
+ throw new Error("--quote can't be empty. Omit it entirely for a page-level comment.");
1982
2294
  }
1983
2295
  const anchor2 = {
1984
2296
  elementPath: "body",
@@ -2002,16 +2314,14 @@ function buildAnchor(opts) {
2002
2314
  anchor.page = opts.page;
2003
2315
  }
2004
2316
  return anchor;
2005
- }
2006
- async function comment(opts) {
2317
+ };
2318
+ var comment = async (opts) => {
2007
2319
  const body = (opts.body ?? await readStdin()).trim();
2008
2320
  if (!body) {
2009
- throw new Error(
2010
- 'Nothing to post. Pass the comment with --body "\u2026", or pipe it on stdin.'
2011
- );
2321
+ throw new Error('Nothing to post. Pass the comment with --body "\u2026", or pipe it on stdin.');
2012
2322
  }
2013
2323
  const anchor = buildAnchor(opts);
2014
- const dir = path3.resolve(opts.dir);
2324
+ const dir = path5.resolve(opts.dir);
2015
2325
  const target = resolveArtifactTarget(dir, {
2016
2326
  apiKey: opts.apiKey,
2017
2327
  artifact: opts.artifact
@@ -2020,9 +2330,7 @@ async function comment(opts) {
2020
2330
  const client = createApiClient(target.apiUrl, credential);
2021
2331
  const artifact = await client.getArtifact(target.artifactId);
2022
2332
  if (!artifact.currentVersion) {
2023
- throw new Error(
2024
- "This artifact has no published version yet \u2014 publish before commenting."
2025
- );
2333
+ throw new Error("This artifact has no published version yet \u2014 publish before commenting.");
2026
2334
  }
2027
2335
  let thread;
2028
2336
  try {
@@ -2035,13 +2343,14 @@ async function comment(opts) {
2035
2343
  // commenting with your key still says which tool typed it.
2036
2344
  via: agentLabel(opts.as ?? opts.via)
2037
2345
  });
2038
- } catch (err) {
2039
- if (err instanceof ApiRequestError && err.status === 401) {
2346
+ } catch (error) {
2347
+ if (error instanceof ApiRequestError && error.status === 401) {
2040
2348
  throw new Error(
2041
- "Publish tokens can't open threads. Run `artifact-cafe login` to comment as your account."
2349
+ "Publish tokens can't open threads. Run `artifact-cafe login` to comment as your account.",
2350
+ { cause: error }
2042
2351
  );
2043
2352
  }
2044
- throw err;
2353
+ throw error;
2045
2354
  }
2046
2355
  if (opts.json) {
2047
2356
  console.log(
@@ -2061,64 +2370,19 @@ async function comment(opts) {
2061
2370
  const url = target.url ?? reviewUrl(target.apiUrl, artifact.slug);
2062
2371
  const where = anchor.type === "text" ? ` on \u201C${anchor.quote}\u201D` : " on the page";
2063
2372
  console.log(`\u2713 Opened thread ${thread.id}${where} \u2014 ${url}`);
2064
- }
2373
+ };
2065
2374
 
2066
2375
  // src/comments.ts
2067
- import path4 from "path";
2068
- var STATUSES = ["open", "resolved", "all"];
2069
- var VERSIONS = ["current", "all"];
2070
- var pollSeconds = () => Number(process.env.AC_WAIT_POLL_SECONDS ?? 5);
2071
- var settleSeconds = () => Number(process.env.AC_WAIT_SETTLE_SECONDS ?? 20);
2072
- var WAIT_TIMEOUT_EXIT = 2;
2073
- async function comments(opts) {
2074
- if (!STATUSES.includes(opts.status)) {
2075
- throw new Error(
2076
- `--status must be one of: ${STATUSES.join(", ")} (got "${opts.status}").`
2077
- );
2078
- }
2079
- if (!VERSIONS.includes(opts.version)) {
2080
- throw new Error(
2081
- `--version must be one of: ${VERSIONS.join(", ")} (got "${opts.version}").`
2082
- );
2083
- }
2084
- const status2 = opts.status;
2085
- const version = opts.version;
2086
- const dir = path4.resolve(opts.dir);
2087
- const target = resolveArtifactTarget(dir, {
2088
- apiKey: opts.apiKey,
2089
- artifact: opts.artifact
2090
- });
2091
- const client = createApiClient(target.apiUrl, target.credential);
2092
- const url = target.url ?? (opts.json ? "" : await deriveReviewUrl(client, target));
2093
- const ref = { artifactId: target.artifactId, url };
2094
- if (opts.wait) {
2095
- await waitForFeedback(client, ref, {
2096
- json: opts.json,
2097
- status: status2,
2098
- timeoutSeconds: opts.timeout,
2099
- version
2100
- });
2101
- return;
2102
- }
2103
- const data2 = await client.exportComments(target.artifactId, {
2104
- status: status2,
2105
- version
2106
- });
2107
- if (opts.json) {
2108
- console.log(JSON.stringify(data2, null, 2));
2109
- return;
2110
- }
2111
- printHuman(data2, url, status2, version);
2112
- }
2113
- async function deriveReviewUrl(client, target) {
2376
+ import path6 from "path";
2377
+ import { setTimeout as waitForTimeout } from "timers/promises";
2378
+ var deriveReviewUrl = async (client, target) => {
2114
2379
  const artifact = await client.getArtifact(target.artifactId);
2115
2380
  return reviewUrl(target.apiUrl, artifact.slug);
2116
- }
2117
- var sleep = (s) => new Promise((r) => setTimeout(r, s * 1e3));
2118
- function messageCounts(data2) {
2119
- return new Map(data2.comments.map((t) => [t.id, 1 + t.replies.length]));
2120
- }
2121
- function diffThreads(baseline, data2) {
2381
+ };
2382
+ var messageCounts = (data2) => new Map(data2.comments.map((t) => [t.id, 1 + t.replies.length]));
2383
+ var sleep = (s) => waitForTimeout(s * 1e3);
2384
+ var pollSeconds = () => Number(process.env.AC_WAIT_POLL_SECONDS ?? 5);
2385
+ var diffThreads = (baseline, data2) => {
2122
2386
  const changed = [];
2123
2387
  for (const t of data2.comments) {
2124
2388
  const before = baseline.get(t.id);
@@ -2129,8 +2393,68 @@ function diffThreads(baseline, data2) {
2129
2393
  }
2130
2394
  }
2131
2395
  return changed;
2132
- }
2133
- async function waitForFeedback(client, config, opts) {
2396
+ };
2397
+ var settleSeconds = () => Number(process.env.AC_WAIT_SETTLE_SECONDS ?? 20);
2398
+ var indent = (text, pad) => text.split("\n").map((line) => pad + line).join("\n");
2399
+ var printThread = (t) => {
2400
+ const marker = t.status === "resolved" ? "\u2713" : "\u25CF";
2401
+ const date = t.createdAt.slice(0, 10);
2402
+ const edited = t.editedAt && !t.deleted ? " \xB7 edited" : "";
2403
+ let state = "";
2404
+ if (t.status === "resolved" && t.resolvedBy) {
2405
+ state = ` \xB7 resolved by ${t.resolvedBy.displayName}`;
2406
+ } else if (t.status === "open" && t.acknowledgedAt) {
2407
+ state = " \xB7 acknowledged";
2408
+ }
2409
+ console.log(`${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}${edited}${state}`);
2410
+ if (t.anchor.type === "text") {
2411
+ console.log(` \u21B3 "${t.anchor.quote}"`);
2412
+ } else if (t.anchor.type === "point") {
2413
+ const el = t.anchor.element;
2414
+ if (el) {
2415
+ const text = el.text ? ` "${el.text}"` : "";
2416
+ console.log(` \u21B3 at ${elementLabel(el)}${text} \u2014 ${t.anchor.elementPath}`);
2417
+ } else {
2418
+ console.log(` \u21B3 at ${t.anchor.elementPath}`);
2419
+ }
2420
+ } else {
2421
+ console.log(t.anchor.page ? ` \u21B3 on ${t.anchor.page}` : " \u21B3 page comment");
2422
+ }
2423
+ console.log(indent(t.deleted ? "[deleted]" : t.body, " "));
2424
+ for (const reply2 of t.replies) {
2425
+ const replyEdited = reply2.editedAt && !reply2.deleted ? " (edited)" : "";
2426
+ const replyBody = reply2.deleted ? "[deleted]" : reply2.body;
2427
+ console.log(indent(`${reply2.author}: ${replyBody}${replyEdited}`, " "));
2428
+ }
2429
+ console.log("");
2430
+ };
2431
+ var printWaitResult = (config, data2, changed, json) => {
2432
+ if (json) {
2433
+ console.log(
2434
+ JSON.stringify(
2435
+ {
2436
+ artifactId: config.artifactId,
2437
+ threads: changed,
2438
+ timedOut: false,
2439
+ versionNumber: data2.versionNumber
2440
+ },
2441
+ null,
2442
+ 2
2443
+ )
2444
+ );
2445
+ return;
2446
+ }
2447
+ const plural = changed.length === 1 ? "" : "s";
2448
+ console.log(`
2449
+ ${changed.length} thread${plural} with new feedback \u2014 ${config.url}
2450
+ `);
2451
+ for (const thread of changed) {
2452
+ console.log(thread.change === "new" ? "NEW" : "UPDATED");
2453
+ printThread(thread);
2454
+ }
2455
+ };
2456
+ var WAIT_TIMEOUT_EXIT = 2;
2457
+ var waitForFeedback = async (client, config, opts) => {
2134
2458
  const query = { status: opts.status, version: opts.version };
2135
2459
  const baselineExport = await client.exportComments(config.artifactId, query);
2136
2460
  const baseline = messageCounts(baselineExport);
@@ -2150,11 +2474,11 @@ async function waitForFeedback(client, config, opts) {
2150
2474
  lastHeartbeat = Date.now();
2151
2475
  try {
2152
2476
  await client.agentHeartbeat(config.artifactId);
2153
- } catch (err) {
2477
+ } catch (error) {
2154
2478
  if (!(opts.json || heartbeatWarned)) {
2155
2479
  heartbeatWarned = true;
2156
2480
  console.error(
2157
- `(presence heartbeat failed \u2014 reviewers won't see "Agent is listening": ${err instanceof Error ? err.message : err})`
2481
+ `(presence heartbeat failed \u2014 reviewers won't see "Agent is listening": ${error instanceof Error ? error.message : error})`
2158
2482
  );
2159
2483
  }
2160
2484
  }
@@ -2163,7 +2487,7 @@ async function waitForFeedback(client, config, opts) {
2163
2487
  while (Date.now() < deadline) {
2164
2488
  await sleep(Math.min(pollSeconds(), (deadline - Date.now()) / 1e3));
2165
2489
  await heartbeat();
2166
- const data2 = await client.exportComments(config.artifactId, query);
2490
+ const data2 = await client.exportComments(config.artifactId, query, { poll: true });
2167
2491
  if (diffThreads(baseline, data2).length === 0) {
2168
2492
  continue;
2169
2493
  }
@@ -2196,35 +2520,8 @@ async function waitForFeedback(client, config, opts) {
2196
2520
  );
2197
2521
  }
2198
2522
  process.exit(WAIT_TIMEOUT_EXIT);
2199
- }
2200
- function printWaitResult(config, data2, changed, json) {
2201
- if (json) {
2202
- console.log(
2203
- JSON.stringify(
2204
- {
2205
- artifactId: config.artifactId,
2206
- threads: changed,
2207
- timedOut: false,
2208
- versionNumber: data2.versionNumber
2209
- },
2210
- null,
2211
- 2
2212
- )
2213
- );
2214
- return;
2215
- }
2216
- const plural = changed.length === 1 ? "" : "s";
2217
- console.log(
2218
- `
2219
- ${changed.length} thread${plural} with new feedback \u2014 ${config.url}
2220
- `
2221
- );
2222
- for (const thread of changed) {
2223
- console.log(thread.change === "new" ? "NEW" : "UPDATED");
2224
- printThread(thread);
2225
- }
2226
- }
2227
- function printHuman(data2, url, status2, version) {
2523
+ };
2524
+ var printHuman = (data2, url, status2, version) => {
2228
2525
  const statusLabel = status2 === "all" ? "" : `${status2} `;
2229
2526
  let scope = "across all versions";
2230
2527
  if (version === "current") {
@@ -2235,73 +2532,68 @@ function printHuman(data2, url, status2, version) {
2235
2532
  return;
2236
2533
  }
2237
2534
  const plural = data2.comments.length === 1 ? "" : "s";
2238
- console.log(
2239
- `${data2.comments.length} ${statusLabel}thread${plural} ${scope} \u2014 ${url}
2240
- `
2241
- );
2535
+ console.log(`${data2.comments.length} ${statusLabel}thread${plural} ${scope} \u2014 ${url}
2536
+ `);
2242
2537
  for (const thread of data2.comments) {
2243
2538
  printThread(thread);
2244
2539
  }
2245
- }
2246
- function printThread(t) {
2247
- const marker = t.status === "resolved" ? "\u2713" : "\u25CF";
2248
- const date = t.createdAt.slice(0, 10);
2249
- const edited = t.editedAt && !t.deleted ? " \xB7 edited" : "";
2250
- let state = "";
2251
- if (t.status === "resolved" && t.resolvedBy) {
2252
- state = ` \xB7 resolved by ${t.resolvedBy.displayName}`;
2253
- } else if (t.status === "open" && t.acknowledgedAt) {
2254
- state = " \xB7 acknowledged";
2540
+ };
2541
+ var STATUSES = ["open", "resolved", "all"];
2542
+ var VERSIONS = ["current", "all"];
2543
+ var comments = async (opts) => {
2544
+ const status2 = STATUSES.find((value) => value === opts.status);
2545
+ if (!status2) {
2546
+ throw new Error(`--status must be one of: ${STATUSES.join(", ")} (got "${opts.status}").`);
2255
2547
  }
2256
- console.log(
2257
- `${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}${edited}${state}`
2258
- );
2259
- if (t.anchor.type === "text") {
2260
- console.log(` \u21B3 "${t.anchor.quote}"`);
2261
- } else if (t.anchor.type === "point") {
2262
- const el = t.anchor.element;
2263
- if (el) {
2264
- const text = el.text ? ` "${el.text}"` : "";
2265
- console.log(
2266
- ` \u21B3 at ${elementLabel(el)}${text} \u2014 ${t.anchor.elementPath}`
2267
- );
2268
- } else {
2269
- console.log(` \u21B3 at ${t.anchor.elementPath}`);
2270
- }
2271
- } else {
2272
- console.log(t.anchor.page ? ` \u21B3 on ${t.anchor.page}` : " \u21B3 page comment");
2548
+ const version = VERSIONS.find((value) => value === opts.version);
2549
+ if (!version) {
2550
+ throw new Error(`--version must be one of: ${VERSIONS.join(", ")} (got "${opts.version}").`);
2273
2551
  }
2274
- console.log(indent(t.deleted ? "[deleted]" : t.body, " "));
2275
- for (const reply2 of t.replies) {
2276
- const replyEdited = reply2.editedAt && !reply2.deleted ? " (edited)" : "";
2277
- const replyBody = reply2.deleted ? "[deleted]" : reply2.body;
2278
- console.log(indent(`${reply2.author}: ${replyBody}${replyEdited}`, " "));
2552
+ const dir = path6.resolve(opts.dir);
2553
+ const target = resolveArtifactTarget(dir, {
2554
+ apiKey: opts.apiKey,
2555
+ artifact: opts.artifact
2556
+ });
2557
+ const client = createApiClient(target.apiUrl, target.credential);
2558
+ const url = target.url ?? (opts.json ? "" : await deriveReviewUrl(client, target));
2559
+ const ref = { artifactId: target.artifactId, url };
2560
+ if (opts.wait) {
2561
+ await waitForFeedback(client, ref, {
2562
+ json: opts.json,
2563
+ status: status2,
2564
+ timeoutSeconds: opts.timeout,
2565
+ version
2566
+ });
2567
+ return;
2279
2568
  }
2280
- console.log("");
2281
- }
2282
- function indent(text, pad) {
2283
- return text.split("\n").map((line) => pad + line).join("\n");
2284
- }
2569
+ const data2 = await client.exportComments(target.artifactId, {
2570
+ status: status2,
2571
+ version
2572
+ });
2573
+ if (opts.json) {
2574
+ console.log(JSON.stringify(data2, null, 2));
2575
+ return;
2576
+ }
2577
+ printHuman(data2, url, status2, version);
2578
+ };
2285
2579
 
2286
2580
  // src/dashboard.ts
2287
- import path8 from "path";
2581
+ import path10 from "path";
2288
2582
 
2289
2583
  // src/publish.ts
2290
2584
  import { exec } from "child_process";
2291
- import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
2292
- import path7 from "path";
2585
+ import { readFileSync as readFileSync7, statSync as statSync2 } from "fs";
2586
+ import path9 from "path";
2293
2587
  import { createInterface } from "readline";
2294
2588
 
2295
2589
  // src/data.ts
2296
- import { readFileSync as readFileSync3 } from "fs";
2297
- import path5 from "path";
2298
- function inspectDataManifest(files) {
2590
+ import { readFileSync as readFileSync5 } from "fs";
2591
+ import path7 from "path";
2592
+ var inspectDataManifest = (files) => {
2299
2593
  const info = { announcements: [] };
2300
2594
  const manifestFile = files.find((f) => f.path === DATA_MANIFEST_PATH);
2301
2595
  if (manifestFile) {
2302
- const parsed = parseDataManifest(
2303
- readFileSync3(manifestFile.absPath, "utf8")
2304
- );
2596
+ const parsed = parseDataManifest(readFileSync5(manifestFile.absPath, "utf-8"));
2305
2597
  if (!parsed.ok) {
2306
2598
  throw new Error(
2307
2599
  `${DATA_MANIFEST_PATH} is invalid:
@@ -2309,13 +2601,8 @@ ${(parsed.errors ?? []).map((e) => ` - ${e}`).join("\n")}`
2309
2601
  );
2310
2602
  }
2311
2603
  info.config = parsed.config;
2312
- for (const [name, collection] of Object.entries(
2313
- parsed.config?.collections ?? {}
2314
- )) {
2315
- const bits = [
2316
- `read: ${collection.access.read}`,
2317
- `insert: ${collection.access.insert}`
2318
- ];
2604
+ for (const [name, collection] of Object.entries(parsed.config?.collections ?? {})) {
2605
+ const bits = [`read: ${collection.access.read}`, `insert: ${collection.access.insert}`];
2319
2606
  if (collection.access.update !== "owner") {
2320
2607
  bits.push(`update: ${collection.access.update}`);
2321
2608
  }
@@ -2328,15 +2615,13 @@ ${(parsed.errors ?? []).map((e) => ` - ${e}`).join("\n")}`
2328
2615
  info.announcements.push(`\u26C1 Data enabled: ${name} (${bits.join(", ")})`);
2329
2616
  }
2330
2617
  if (info.announcements.length === 0) {
2331
- info.announcements.push(
2332
- "\u26C1 Data disabled (empty collections in .artifact/data.json)"
2333
- );
2618
+ info.announcements.push("\u26C1 Data disabled (empty collections in .artifact/data.json)");
2334
2619
  }
2335
2620
  }
2336
2621
  return info;
2337
- }
2338
- function requireLinkedArtifact(dir) {
2339
- const resolved = path5.resolve(dir);
2622
+ };
2623
+ var requireLinkedArtifact = (dir) => {
2624
+ const resolved = path7.resolve(dir);
2340
2625
  const config = readConfig(resolved);
2341
2626
  if (!config) {
2342
2627
  throw new Error(
@@ -2349,8 +2634,8 @@ function requireLinkedArtifact(dir) {
2349
2634
  publishToken: resolveApiKey(process.env.ARTIFACT_CAFE_URL ?? config.apiUrl) ?? config.publishToken ?? process.env.ARTIFACT_CAFE_TOKEN,
2350
2635
  url: config.url
2351
2636
  };
2352
- }
2353
- async function dataLs(opts) {
2637
+ };
2638
+ var dataLs = async (opts) => {
2354
2639
  const linked = requireLinkedArtifact(opts.dir);
2355
2640
  const client = createApiClient(linked.apiUrl, linked.publishToken);
2356
2641
  const records = [];
@@ -2365,9 +2650,7 @@ async function dataLs(opts) {
2365
2650
  } while (cursor && (opts.limit === void 0 || records.length < opts.limit));
2366
2651
  const shown = opts.limit === void 0 ? records : records.slice(0, opts.limit);
2367
2652
  if (opts.json) {
2368
- console.log(
2369
- JSON.stringify({ collection: opts.collection, records: shown }, null, 2)
2370
- );
2653
+ console.log(JSON.stringify({ collection: opts.collection, records: shown }, null, 2));
2371
2654
  return;
2372
2655
  }
2373
2656
  if (shown.length === 0) {
@@ -2375,40 +2658,40 @@ async function dataLs(opts) {
2375
2658
  return;
2376
2659
  }
2377
2660
  const plural = shown.length === 1 ? "" : "s";
2378
- console.log(
2379
- `${shown.length} record${plural} in "${opts.collection}" \u2014 ${linked.url}
2380
- `
2381
- );
2661
+ console.log(`${shown.length} record${plural} in "${opts.collection}" \u2014 ${linked.url}
2662
+ `);
2382
2663
  for (const record of shown) {
2383
2664
  const { created_at, created_by, id, updated_at: _u, ...fields } = record;
2384
2665
  const author = created_by ?? "anonymous";
2385
2666
  console.log(`\u25CF ${id} \xB7 ${String(created_at).slice(0, 10)} \xB7 ${author}`);
2386
2667
  console.log(` ${JSON.stringify(fields)}`);
2387
2668
  }
2388
- }
2389
- var RECORD_SHAPE_HINT = `a JSON object or array of objects, e.g. '{"name":"Ada"}'`;
2390
- function isRecord(value) {
2391
- return typeof value === "object" && value !== null && !Array.isArray(value);
2392
- }
2393
- async function dataInsert(opts) {
2394
- if (opts.record === void 0 === (opts.file === void 0)) {
2395
- throw new Error("Pass exactly one of --record '<json>' or --file <path>.");
2669
+ };
2670
+ var RECORD_INPUT_HINT = `a JSON object or array of objects, e.g. '{"name":"Ada"}'`;
2671
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2672
+ var dataInsert = async (opts) => {
2673
+ let text;
2674
+ if (opts.record === void 0) {
2675
+ if (opts.file === void 0) {
2676
+ throw new Error("Pass exactly one of --record '<json>' or --file <path>.");
2677
+ }
2678
+ text = readFileSync5(opts.file, "utf-8");
2679
+ } else {
2680
+ if (opts.file !== void 0) {
2681
+ throw new Error("Pass exactly one of --record '<json>' or --file <path>.");
2682
+ }
2683
+ text = opts.record;
2396
2684
  }
2397
- const text = opts.record ?? readFileSync3(opts.file, "utf8");
2398
2685
  let parsed;
2399
2686
  try {
2400
2687
  parsed = JSON.parse(text);
2401
2688
  } catch {
2402
- throw new Error(`Records must be ${RECORD_SHAPE_HINT}.`);
2689
+ throw new Error(`Records must be ${RECORD_INPUT_HINT}.`);
2403
2690
  }
2404
2691
  const linked = requireLinkedArtifact(opts.dir);
2405
2692
  const client = createApiClient(linked.apiUrl, linked.publishToken);
2406
2693
  if (isRecord(parsed)) {
2407
- const record = await client.insertData(
2408
- linked.artifactId,
2409
- opts.collection,
2410
- parsed
2411
- );
2694
+ const record = await client.insertData(linked.artifactId, opts.collection, parsed);
2412
2695
  if (opts.json) {
2413
2696
  console.log(JSON.stringify({ collection: opts.collection, record }));
2414
2697
  return;
@@ -2417,7 +2700,7 @@ async function dataInsert(opts) {
2417
2700
  return;
2418
2701
  }
2419
2702
  if (!(Array.isArray(parsed) && parsed.length > 0 && parsed.every(isRecord))) {
2420
- throw new Error(`Records must be ${RECORD_SHAPE_HINT}.`);
2703
+ throw new Error(`Records must be ${RECORD_INPUT_HINT}.`);
2421
2704
  }
2422
2705
  const records = [];
2423
2706
  for (let i = 0; i < parsed.length; i += DATA_LIMITS.maxInsertBatch) {
@@ -2428,22 +2711,22 @@ async function dataInsert(opts) {
2428
2711
  parsed.slice(i, i + DATA_LIMITS.maxInsertBatch)
2429
2712
  );
2430
2713
  records.push(...page.records);
2431
- } catch (err) {
2714
+ } catch (error) {
2432
2715
  if (records.length === 0) {
2433
- throw err;
2716
+ throw error;
2434
2717
  }
2435
2718
  const note = ` (the first ${records.length} records were inserted; records ${i}+ may not have been)`;
2436
- if (err instanceof ApiRequestError) {
2719
+ if (error instanceof ApiRequestError) {
2437
2720
  throw new ApiRequestError(
2438
- err.status,
2439
- err.code,
2440
- `${err.message}${note}`,
2441
- err.retryAfter
2721
+ error.status,
2722
+ error.code,
2723
+ `${error.message}${note}`,
2724
+ error.retryAfter
2442
2725
  );
2443
2726
  }
2444
- throw new Error(
2445
- `${err instanceof Error ? err.message : String(err)}${note}`
2446
- );
2727
+ throw new Error(`${error instanceof Error ? error.message : String(error)}${note}`, {
2728
+ cause: error
2729
+ });
2447
2730
  }
2448
2731
  }
2449
2732
  if (opts.json) {
@@ -2451,11 +2734,9 @@ async function dataInsert(opts) {
2451
2734
  return;
2452
2735
  }
2453
2736
  const plural = records.length === 1 ? "" : "s";
2454
- console.log(
2455
- `Inserted ${records.length} record${plural} into "${opts.collection}".`
2456
- );
2457
- }
2458
- async function dataExport(opts) {
2737
+ console.log(`Inserted ${records.length} record${plural} into "${opts.collection}".`);
2738
+ };
2739
+ var dataExport = async (opts) => {
2459
2740
  const linked = requireLinkedArtifact(opts.dir);
2460
2741
  const client = createApiClient(linked.apiUrl, linked.publishToken);
2461
2742
  const summary = await client.getDataSummary(linked.artifactId);
@@ -2485,8 +2766,8 @@ async function dataExport(opts) {
2485
2766
  2
2486
2767
  )
2487
2768
  );
2488
- }
2489
- async function dataClear(opts) {
2769
+ };
2770
+ var dataClear = async (opts) => {
2490
2771
  if (!opts.yes) {
2491
2772
  throw new Error(
2492
2773
  `Clearing "${opts.collection}" deletes every record in it. Re-run with --yes to confirm.`
@@ -2496,21 +2777,19 @@ async function dataClear(opts) {
2496
2777
  const client = createApiClient(linked.apiUrl, linked.publishToken);
2497
2778
  const result = await client.clearData(linked.artifactId, opts.collection);
2498
2779
  if (opts.json) {
2499
- console.log(
2500
- JSON.stringify({ cleared: result.cleared, collection: opts.collection })
2501
- );
2780
+ console.log(JSON.stringify({ cleared: result.cleared, collection: opts.collection }));
2502
2781
  return;
2503
2782
  }
2504
2783
  console.log(`Cleared ${result.cleared} record(s) from "${opts.collection}".`);
2505
- }
2784
+ };
2506
2785
 
2507
2786
  // src/manifest.ts
2508
2787
  import { createHash } from "crypto";
2509
- import { lstatSync, readdirSync, readFileSync as readFileSync4, statSync } from "fs";
2510
- import path6 from "path";
2788
+ import { lstatSync, readdirSync, readFileSync as readFileSync6, statSync } from "fs";
2789
+ import path8 from "path";
2511
2790
  var IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".artifactcafe"]);
2512
2791
  var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store", "artifact-cafe.json"]);
2513
- function isIgnoredFile(name) {
2792
+ var isIgnoredFile = (name) => {
2514
2793
  if (IGNORED_FILES.has(name)) {
2515
2794
  return true;
2516
2795
  }
@@ -2524,44 +2803,44 @@ function isIgnoredFile(name) {
2524
2803
  return true;
2525
2804
  }
2526
2805
  return false;
2527
- }
2528
- var MIME_TYPES = {
2529
- ".avif": "image/avif",
2530
- ".css": "text/css",
2531
- ".csv": "text/csv",
2532
- ".gif": "image/gif",
2533
- ".htm": "text/html",
2534
- ".html": "text/html",
2535
- ".ico": "image/x-icon",
2536
- ".jpeg": "image/jpeg",
2537
- ".jpg": "image/jpeg",
2538
- ".js": "text/javascript",
2539
- ".json": "application/json",
2540
- ".map": "application/json",
2541
- ".markdown": "text/markdown",
2542
- ".md": "text/markdown",
2543
- ".mjs": "text/javascript",
2544
- ".mp3": "audio/mpeg",
2545
- ".mp4": "video/mp4",
2546
- ".otf": "font/otf",
2547
- ".pdf": "application/pdf",
2548
- ".png": "image/png",
2549
- ".svg": "image/svg+xml",
2550
- ".ttf": "font/ttf",
2551
- ".txt": "text/plain",
2552
- ".wasm": "application/wasm",
2553
- ".wav": "audio/wav",
2554
- ".webm": "video/webm",
2555
- ".webp": "image/webp",
2556
- ".woff": "font/woff",
2557
- ".woff2": "font/woff2",
2558
- ".xml": "application/xml"
2559
2806
  };
2560
- function mimeFor(filePath) {
2561
- return MIME_TYPES[path6.extname(filePath).toLowerCase()] ?? "application/octet-stream";
2562
- }
2563
- function localFile(absPath, rel) {
2564
- if (isIgnoredFile(path6.basename(rel)) || !isSafeArtifactPath(rel)) {
2807
+ var MIME_TYPES = new Map(
2808
+ Object.entries({
2809
+ ".avif": "image/avif",
2810
+ ".css": "text/css",
2811
+ ".csv": "text/csv",
2812
+ ".gif": "image/gif",
2813
+ ".htm": "text/html",
2814
+ ".html": "text/html",
2815
+ ".ico": "image/x-icon",
2816
+ ".jpeg": "image/jpeg",
2817
+ ".jpg": "image/jpeg",
2818
+ ".js": "text/javascript",
2819
+ ".json": "application/json",
2820
+ ".map": "application/json",
2821
+ ".markdown": "text/markdown",
2822
+ ".md": "text/markdown",
2823
+ ".mjs": "text/javascript",
2824
+ ".mp3": "audio/mpeg",
2825
+ ".mp4": "video/mp4",
2826
+ ".otf": "font/otf",
2827
+ ".pdf": "application/pdf",
2828
+ ".png": "image/png",
2829
+ ".svg": "image/svg+xml",
2830
+ ".ttf": "font/ttf",
2831
+ ".txt": "text/plain",
2832
+ ".wasm": "application/wasm",
2833
+ ".wav": "audio/wav",
2834
+ ".webm": "video/webm",
2835
+ ".webp": "image/webp",
2836
+ ".woff": "font/woff",
2837
+ ".woff2": "font/woff2",
2838
+ ".xml": "application/xml"
2839
+ })
2840
+ );
2841
+ var mimeFor = (filePath) => MIME_TYPES.get(path8.extname(filePath).toLowerCase()) ?? "application/octet-stream";
2842
+ var localFile = (absPath, rel) => {
2843
+ if (isIgnoredFile(path8.basename(rel)) || !isSafeArtifactPath(rel)) {
2565
2844
  return null;
2566
2845
  }
2567
2846
  const { size } = statSync(absPath);
@@ -2570,28 +2849,19 @@ function localFile(absPath, rel) {
2570
2849
  }
2571
2850
  return {
2572
2851
  absPath,
2573
- hash: `sha256_${createHash("sha256").update(readFileSync4(absPath)).digest("hex")}`,
2852
+ hash: `sha256_${createHash("sha256").update(readFileSync6(absPath)).digest("hex")}`,
2574
2853
  mimeType: mimeFor(rel),
2575
2854
  path: rel,
2576
2855
  size
2577
2856
  };
2578
- }
2579
- function scanPath(target) {
2580
- const stat = lstatSync(target);
2581
- if (stat.isDirectory()) {
2582
- return scanDirectory(target);
2583
- }
2584
- const rel = path6.basename(target);
2585
- const file = stat.isFile() ? localFile(target, rel) : null;
2586
- return file ? { files: [file], skipped: [] } : { files: [], skipped: [rel] };
2587
- }
2588
- function scanDirectory(dir) {
2857
+ };
2858
+ var scanDirectory = (dir) => {
2589
2859
  const files = [];
2590
2860
  const skipped = [];
2591
- function walk(current, relPrefix) {
2861
+ const walk = (current, relPrefix) => {
2592
2862
  for (const entry of readdirSync(current, { withFileTypes: true })) {
2593
2863
  const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
2594
- const abs = path6.join(current, entry.name);
2864
+ const abs = path8.join(current, entry.name);
2595
2865
  if (entry.isSymbolicLink()) {
2596
2866
  skipped.push(rel);
2597
2867
  continue;
@@ -2614,14 +2884,21 @@ function scanDirectory(dir) {
2614
2884
  }
2615
2885
  files.push(file);
2616
2886
  }
2617
- }
2887
+ };
2618
2888
  walk(dir, "");
2619
2889
  files.sort((a, b) => a.path.localeCompare(b.path));
2620
2890
  return { files, skipped };
2621
- }
2622
- function resolveEntry(files, explicit) {
2623
- return resolvePublishEntry(files, explicit);
2624
- }
2891
+ };
2892
+ var scanPath = (target) => {
2893
+ const stat = lstatSync(target);
2894
+ if (stat.isDirectory()) {
2895
+ return scanDirectory(target);
2896
+ }
2897
+ const rel = path8.basename(target);
2898
+ const file = stat.isFile() ? localFile(target, rel) : null;
2899
+ return file ? { files: [file], skipped: [] } : { files: [], skipped: [rel] };
2900
+ };
2901
+ var resolveEntry = (files, explicit) => resolvePublishEntry(files, explicit);
2625
2902
 
2626
2903
  // src/spinner.ts
2627
2904
  var FRAMES = ["\xE3", "\xE4", "\xE5"];
@@ -2629,18 +2906,19 @@ var INTERVAL_MS = 420;
2629
2906
  var AMBER2 = "\x1B[38;2;214;138;57m";
2630
2907
  var RESET2 = "\x1B[0m";
2631
2908
  var CLEAR_LINE = "\r\x1B[2K";
2632
- function startBrewing(label, enabled) {
2909
+ var startBrewing = (label, enabled) => {
2633
2910
  const stream = process.stderr;
2634
2911
  if (!(enabled && stream.isTTY) || process.env.CI) {
2635
- return { stop: () => void 0 };
2912
+ return {
2913
+ stop: () => {
2914
+ }
2915
+ };
2636
2916
  }
2637
2917
  const color = !process.env.NO_COLOR;
2638
2918
  let frame = 0;
2639
2919
  const draw = () => {
2640
2920
  const glyph = FRAMES[frame % FRAMES.length];
2641
- stream.write(
2642
- `${CLEAR_LINE} ${color ? `${AMBER2}${glyph}${RESET2}` : glyph} ${label}`
2643
- );
2921
+ stream.write(`${CLEAR_LINE} ${color ? `${AMBER2}${glyph}${RESET2}` : glyph} ${label}`);
2644
2922
  frame += 1;
2645
2923
  };
2646
2924
  draw();
@@ -2651,199 +2929,392 @@ function startBrewing(label, enabled) {
2651
2929
  stream.write(CLEAR_LINE);
2652
2930
  }
2653
2931
  };
2654
- }
2932
+ };
2655
2933
 
2656
2934
  // src/publish.ts
2657
- var UPLOAD_CONCURRENCY = 6;
2658
- var REVIEW_LABEL = {
2659
- off: "off \u2014 plain artifact, no review tools",
2660
- open: "open \u2014 anyone with the link can comment",
2661
- readonly: "read-only \u2014 existing comments only"
2935
+ var accessNote = (visibility) => {
2936
+ if (visibility === "workspace") {
2937
+ return "Access: workspace members only \u2014 anyone else needs `artifact-cafe share <email>` to open it.";
2938
+ }
2939
+ if (visibility === "private") {
2940
+ return "Access: private \u2014 you, the workspace's admins, and anyone you `artifact-cafe share` it with.";
2941
+ }
2942
+ return null;
2662
2943
  };
2663
- async function publish(opts) {
2664
- const target = path7.resolve(opts.dir);
2665
- const dir = statSync2(target).isFile() ? path7.dirname(target) : target;
2666
- const config = readConfig(dir);
2667
- const expiresInSeconds = opts.expiresIn === void 0 ? void 0 : parseArtifactDuration(opts.expiresIn);
2668
- if (opts.password !== void 0) {
2669
- const parsed = gatePasswordSchema.safeParse(opts.password);
2670
- if (!parsed.success) {
2671
- throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
2944
+ var shouldPromptForWorkspace = (opts) => {
2945
+ if (opts.json || process.env.ARTIFACT_CAFE_AGENT) {
2946
+ return false;
2947
+ }
2948
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
2949
+ };
2950
+ var pickWorkspace = (workspaces3) => {
2951
+ const choices = [...workspaces3].sort((a, b) => {
2952
+ if (a.isPersonal !== b.isPersonal) {
2953
+ return a.isPersonal ? -1 : 1;
2672
2954
  }
2955
+ return a.name.localeCompare(b.name);
2956
+ });
2957
+ console.log("Where should this artifact live?\n");
2958
+ for (const [index, workspace] of choices.entries()) {
2959
+ const label = workspace.isPersonal ? "Personal" : workspace.name;
2960
+ const handle = workspace.isPersonal ? "" : ` \xB7 ${workspace.handle}`;
2961
+ console.log(` ${String(index + 1).padStart(2)}. ${label}${handle}`);
2673
2962
  }
2674
- let reviewMode;
2675
- if (opts.review !== void 0) {
2676
- const parsed = reviewModeSchema.safeParse(opts.review);
2677
- if (!parsed.success) {
2678
- throw new Error("--review must be one of: open, readonly, off.");
2963
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
2964
+ return new Promise((resolve2, reject) => {
2965
+ rl.question(`
2966
+ Publish where? [1-${choices.length}]: `, (answer) => {
2967
+ const index = Number(answer.trim()) - 1;
2968
+ const workspace = choices[index];
2969
+ if (!(Number.isInteger(index) && workspace)) {
2970
+ rl.close();
2971
+ reject(new Error(`Enter a number between 1 and ${choices.length}.`));
2972
+ return;
2973
+ }
2974
+ if (workspace.isPersonal) {
2975
+ rl.close();
2976
+ resolve2({ remember: false, workspace });
2977
+ return;
2978
+ }
2979
+ rl.question(
2980
+ `Remember ${workspace.name} for new artifacts in this project? [y/N]: `,
2981
+ (rememberAnswer) => {
2982
+ rl.close();
2983
+ resolve2({
2984
+ remember: /^y(?:es)?$/iu.test(rememberAnswer.trim()),
2985
+ workspace
2986
+ });
2987
+ }
2988
+ );
2989
+ });
2990
+ });
2991
+ };
2992
+ var agentPlacement = (apiKey, opts) => {
2993
+ if (apiKey?.startsWith("aat_")) {
2994
+ if (opts.workspace !== void 0 || opts.folder !== void 0) {
2995
+ throw new Error("An agent token selects its own workspace; omit --workspace and --folder.");
2679
2996
  }
2680
- reviewMode = parsed.data;
2997
+ return {
2998
+ destination: "workspace",
2999
+ destinationLabel: "Agent token's workspace",
3000
+ destinationSource: "agent-token"
3001
+ };
3002
+ }
3003
+ return null;
3004
+ };
3005
+ var storedPlacement = (config, workspaceId, apiKey, apiUrl2) => {
3006
+ const isPersonal = config.workspaceKind === "personal";
3007
+ if (!(apiKey || isPersonal)) {
3008
+ throw new Error(
3009
+ `Publishing into workspace ${config.workspace ?? workspaceId} needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
3010
+ );
3011
+ }
3012
+ return {
3013
+ destination: isPersonal ? "personal" : "workspace",
3014
+ destinationLabel: isPersonal ? "Personal" : config.workspace ?? workspaceId,
3015
+ destinationSource: "artifact-config",
3016
+ ...config.folder ? { folder: config.folder } : void 0,
3017
+ ...config.folderId ? { folderId: config.folderId } : void 0,
3018
+ ...config.workspace ? { workspace: config.workspace } : void 0,
3019
+ ...apiKey ? { publishWorkspaceId: workspaceId } : void 0,
3020
+ workspaceId
3021
+ };
3022
+ };
3023
+ var defaultPlacement = async (apiKey, apiUrl2, opts) => {
3024
+ if (!apiKey) {
3025
+ return {
3026
+ destination: "anonymous",
3027
+ destinationLabel: "Anonymous",
3028
+ destinationSource: "anonymous-default"
3029
+ };
3030
+ }
3031
+ if (shouldPromptForWorkspace(opts)) {
3032
+ const data2 = await createApiClient(apiUrl2, apiKey).listWorkspaces();
3033
+ if (data2.workspaces.length > 1) {
3034
+ const choice = await pickWorkspace(data2.workspaces);
3035
+ return {
3036
+ destination: choice.workspace.isPersonal ? "personal" : "workspace",
3037
+ destinationLabel: choice.workspace.isPersonal ? "Personal" : choice.workspace.name,
3038
+ destinationSource: "interactive",
3039
+ publishWorkspaceId: choice.workspace.id,
3040
+ ...choice.remember ? {
3041
+ rememberProjectDefault: {
3042
+ workspace: choice.workspace.handle
3043
+ }
3044
+ } : void 0,
3045
+ workspace: choice.workspace.handle,
3046
+ workspaceId: choice.workspace.id
3047
+ };
3048
+ }
3049
+ }
3050
+ return {
3051
+ destination: "personal",
3052
+ destinationLabel: "Personal",
3053
+ destinationSource: "personal-default"
3054
+ };
3055
+ };
3056
+ var getPublishWorkspace = (workspaces3, opts, config, projectConfig) => {
3057
+ const workspaceHandle = opts.workspace ?? config?.workspace ?? projectConfig?.workspace;
3058
+ let workspace;
3059
+ if (workspaceHandle) {
3060
+ workspace = workspaces3.find((candidate) => candidate.handle === workspaceHandle);
3061
+ } else if (config?.workspaceId) {
3062
+ workspace = workspaces3.find((candidate) => candidate.id === config.workspaceId);
3063
+ } else {
3064
+ workspace = workspaces3.find((candidate) => candidate.isPersonal);
3065
+ }
3066
+ if (!workspace) {
3067
+ const target = workspaceHandle ? `workspace with handle "${workspaceHandle}"` : "personal workspace";
3068
+ throw new Error(`No ${target} belongs to this account.`);
2681
3069
  }
3070
+ return workspace;
3071
+ };
3072
+ var publishingIdentity = (opts, config) => {
2682
3073
  const artifactId = opts.artifact ?? config?.artifactId ?? null;
2683
3074
  const token = (artifactId !== null && artifactId === config?.artifactId ? config.publishToken : void 0) ?? process.env.ARTIFACT_CAFE_TOKEN;
2684
3075
  const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
2685
3076
  const apiKey = resolveApiKey(apiUrl2, opts.apiKey);
2686
3077
  const usingAgentToken = apiKey?.startsWith("aat_") === true;
2687
- const placement = await resolvePlacement({
2688
- apiKey,
2689
- apiUrl: apiUrl2,
2690
- config,
2691
- isNewArtifact: artifactId === null,
2692
- opts: { ...opts, dir }
2693
- });
2694
- const { files, skipped } = scanPath(target);
2695
- if (files.length === 0) {
2696
- throw new Error(`Nothing to publish at ${target} (after ignore rules).`);
3078
+ return { apiKey, apiUrl: apiUrl2, artifactId, token, usingAgentToken };
3079
+ };
3080
+ var resolveRequestedPlacement = async ({
3081
+ apiKey,
3082
+ apiUrl: apiUrl2,
3083
+ config,
3084
+ isNewArtifact,
3085
+ opts,
3086
+ projectConfig,
3087
+ hasPlacementFlag
3088
+ }) => {
3089
+ if (!isNewArtifact && opts.folder !== void 0 && opts.workspace === void 0 && !(config?.workspace || config?.workspaceId)) {
3090
+ throw new Error("--folder requires --workspace or a stored workspace.");
2697
3091
  }
2698
- const entryPath = resolveEntry(files, opts.entry);
2699
- const dataInfo = inspectDataManifest(files);
2700
- const log = (msg) => {
2701
- if (!opts.json) {
2702
- console.log(msg);
2703
- }
3092
+ const client = createApiClient(apiUrl2, apiKey);
3093
+ const data2 = await client.listWorkspaces();
3094
+ const workspace = getPublishWorkspace(data2.workspaces, opts, config, projectConfig);
3095
+ const folderName = opts.workspace !== void 0 && opts.folder === void 0 ? void 0 : opts.folder ?? projectConfig?.folder;
3096
+ const destinationSource = hasPlacementFlag ? "explicit" : "project-default";
3097
+ const placement = {
3098
+ destination: workspace.isPersonal ? "personal" : "workspace",
3099
+ destinationLabel: workspace.isPersonal ? "Personal" : workspace.name,
3100
+ destinationSource,
3101
+ publishWorkspaceId: workspace.id,
3102
+ workspace: workspace.handle,
3103
+ workspaceId: workspace.id
2704
3104
  };
2705
- if (skipped.length) {
2706
- log(` ignoring ${skipped.length} item(s): ${preview(skipped)}`);
3105
+ if (!folderName) {
3106
+ return placement;
2707
3107
  }
2708
- const sessionBody = {
2709
- artifactId,
2710
- ...expiresInSeconds === void 0 ? {} : { expiresInSeconds },
2711
- // Overrides the entry HTML's own meta description on the share card.
2712
- // Omitted entirely when absent, so the document keeps speaking for itself.
2713
- ...opts.description?.trim() ? { description: opts.description.trim() } : {},
2714
- entryPath,
2715
- files: files.map(({ absPath: _absPath, ...manifest }) => manifest),
2716
- ...placement.folderId ? { folderId: placement.folderId } : {},
2717
- // Agents often pass a double-quoted string where "\n" is a literal
2718
- // backslash-n; turn those back into real newlines so bullets survive.
2719
- ...opts.notes?.trim() ? { notes: opts.notes.replace(/\\n/g, "\n").trim() } : {},
2720
- source: {
2721
- // ARTIFACT_CAFE_AGENT if it's set, else whichever agent's environment
2722
- // we recognize — this is what names the agent on every reply to a
2723
- // thread opened against this version (§6.5).
2724
- agent: agentLabel(),
2725
- runId: process.env.ARTIFACT_CAFE_RUN_ID,
2726
- type: "cli"
2727
- },
2728
- title: opts.title,
2729
- ...placement.publishWorkspaceId ? { workspaceId: placement.publishWorkspaceId } : {}
2730
- };
2731
- let credential = apiKey ?? token;
2732
- let client = createApiClient(apiUrl2, credential);
2733
- let session;
2734
- try {
2735
- session = await client.createSession(sessionBody);
2736
- } catch (err) {
2737
- const keyRefused = apiKey !== void 0 && !usingAgentToken && token !== void 0 && apiKey !== token && artifactId !== null && err instanceof ApiRequestError && (err.status === 401 || err.status === 403);
2738
- if (!keyRefused) {
2739
- throw err;
2740
- }
2741
- credential = token;
2742
- client = createApiClient(apiUrl2, credential);
2743
- session = await client.createSession(sessionBody);
3108
+ const folderData = await client.listFolders(workspace.id);
3109
+ const matches = folderData.folders.filter(
3110
+ (candidate) => candidate.name === folderName || candidate.id === folderName
3111
+ );
3112
+ if (matches.length === 0) {
3113
+ throw new Error(`No folder named "${folderName}" exists in workspace ${workspace.handle}.`);
2744
3114
  }
2745
- if (expiresInSeconds !== void 0 && session.expiresInSeconds !== expiresInSeconds) {
3115
+ if (matches.length > 1) {
2746
3116
  throw new Error(
2747
- "This API does not support temporary publishing. Nothing was uploaded or finalized; upgrade the API before using --expires-in."
3117
+ `More than one folder is named "${folderName}" in workspace ${workspace.handle}; pass the folder id instead.`
2748
3118
  );
2749
3119
  }
2750
- const byPath = new Map(files.map((f) => [f.path, f]));
2751
- log(
2752
- ` uploading ${session.filesToUpload.length} file(s)` + (session.skippedFiles.length ? `, ${session.skippedFiles.length} unchanged` : "")
2753
- );
2754
- const presence = startPublishingPresence(client, session.artifactId);
2755
- const brewing = startBrewing("brewing\u2026", !opts.json);
2756
- try {
2757
- await inBatches(
2758
- session.filesToUpload,
2759
- UPLOAD_CONCURRENCY,
2760
- async (upload) => {
2761
- const file = byPath.get(upload.path);
2762
- if (!file) {
2763
- throw new Error(`Server asked for unknown file ${upload.path}`);
2764
- }
2765
- await uploadFile(
2766
- upload.uploadUrl,
2767
- readFileSync5(file.absPath),
2768
- file.mimeType
2769
- );
2770
- }
2771
- );
2772
- } catch (error) {
2773
- brewing.stop();
2774
- throw error;
2775
- } finally {
2776
- presence.stop();
3120
+ const [folder] = matches;
3121
+ if (!folder) {
3122
+ throw new Error("The selected folder is unavailable.");
2777
3123
  }
2778
- let result;
2779
- try {
2780
- result = await client.finalize(session.publishSessionId);
2781
- } finally {
2782
- brewing.stop();
3124
+ return { ...placement, folder: folder.name, folderId: folder.id };
3125
+ };
3126
+ var resolvePlacement = async (params) => {
3127
+ const { apiKey, apiUrl: apiUrl2, config, isNewArtifact, opts } = params;
3128
+ const agent = agentPlacement(apiKey, opts);
3129
+ if (agent) {
3130
+ return agent;
2783
3131
  }
2784
- if (usingAgentToken && result.workspaceId) {
2785
- placement.workspaceId = result.workspaceId;
3132
+ const hasPlacementFlag = opts.workspace !== void 0 || opts.folder !== void 0;
3133
+ if (!hasPlacementFlag && config?.workspaceId) {
3134
+ return storedPlacement(config, config.workspaceId, apiKey, apiUrl2);
2786
3135
  }
2787
- const rememberedProjectFile = placement.rememberProjectDefault ? writeProjectConfig(dir, placement.rememberProjectDefault) : null;
2788
- const existingToken = !usingAgentToken && config?.artifactId === result.artifactId ? config.publishToken : void 0;
2789
- const shouldWriteConfig = usingAgentToken || result.publishToken !== void 0 || config?.artifactId === result.artifactId || opts.workspace !== void 0 || opts.folder !== void 0 || placement.workspaceId !== void 0;
2790
- if (shouldWriteConfig) {
2791
- writeConfig(dir, {
2792
- apiUrl: apiUrl2,
2793
- artifactId: result.artifactId,
2794
- ...placement.folder ? { folder: placement.folder } : {},
2795
- ...placement.folderId ? { folderId: placement.folderId } : {},
2796
- ...!usingAgentToken && (result.publishToken ?? existingToken) ? { publishToken: result.publishToken ?? existingToken } : {},
2797
- slug: slugFromUrl(result.url),
2798
- url: result.url,
2799
- ...placement.workspace ? { workspace: placement.workspace } : {},
2800
- ...placement.workspaceId ? { workspaceId: placement.workspaceId } : {},
2801
- ...placement.destination === "personal" || placement.destination === "workspace" ? { workspaceKind: placement.destination } : {}
2802
- });
3136
+ if (!(hasPlacementFlag || isNewArtifact)) {
3137
+ return {
3138
+ destination: "existing-artifact",
3139
+ destinationLabel: "Existing artifact",
3140
+ destinationSource: "artifact-config"
3141
+ };
2803
3142
  }
2804
- if (opts.password !== void 0) {
2805
- const protectClient = createApiClient(
2806
- apiUrl2,
2807
- credential ?? result.publishToken
3143
+ const projectConfig = isNewArtifact ? readProjectConfig(opts.dir) : null;
3144
+ const wantsWorkspace = opts.workspace !== void 0 || opts.folder !== void 0 || projectConfig !== null;
3145
+ if (wantsWorkspace && !apiKey) {
3146
+ throw new Error(
3147
+ "Publishing into a workspace needs an account \u2014 run `artifact-cafe login` first."
2808
3148
  );
2809
- await protectClient.setProtection(result.artifactId, opts.password);
2810
3149
  }
2811
- if (reviewMode !== void 0) {
2812
- const reviewClient = createApiClient(
2813
- apiUrl2,
2814
- credential ?? result.publishToken
2815
- );
2816
- await reviewClient.setReviewMode(result.artifactId, reviewMode);
3150
+ if (!(hasPlacementFlag || projectConfig)) {
3151
+ return defaultPlacement(apiKey, apiUrl2, opts);
2817
3152
  }
2818
- if (opts.json) {
2819
- console.log(
2820
- JSON.stringify(
2821
- {
2822
- artifactId: result.artifactId,
2823
- autoDeleteAt: result.autoDeleteAt ?? null,
2824
- expiresAt: result.expiresAt ?? null,
2825
- ...dataInfo.config ? { data: dataInfo.config } : {},
2826
- claimed: result.claimed,
2827
- destination: placement.destination,
2828
- destinationSource: placement.destinationSource,
2829
- folder: placement.folder ?? null,
2830
- folderId: placement.folderId ?? null,
2831
- url: result.url,
2832
- versionNumber: result.versionNumber,
2833
- visibility: result.visibility ?? null,
2834
- workspace: placement.workspace ?? null,
2835
- workspaceId: placement.workspaceId ?? null,
2836
- ...result.claimUrl ? { claimUrl: result.claimUrl } : {},
2837
- ...opts.password === void 0 ? {} : { protected: true },
2838
- ...reviewMode === void 0 ? {} : { reviewMode },
2839
- filesUploaded: session.filesToUpload.map((f) => f.path),
2840
- ignored: skipped,
2841
- skippedFiles: session.skippedFiles
2842
- },
2843
- null,
2844
- 2
2845
- )
2846
- );
3153
+ return await resolveRequestedPlacement({ ...params, hasPlacementFlag, projectConfig });
3154
+ };
3155
+ var startPublishingPresence = (client, artifactId) => {
3156
+ const beat = async () => {
3157
+ try {
3158
+ await client.agentHeartbeat(artifactId, "publishing");
3159
+ } catch {
3160
+ }
3161
+ };
3162
+ beat();
3163
+ const timer = setInterval(beat, AGENT_PRESENCE.heartbeatSeconds * 1e3);
3164
+ timer.unref();
3165
+ return { stop: () => clearInterval(timer) };
3166
+ };
3167
+ var inBatches = async (items, concurrency, fn) => {
3168
+ let next = 0;
3169
+ const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
3170
+ while (next < items.length) {
3171
+ const item = items[next];
3172
+ next += 1;
3173
+ if (item === void 0) {
3174
+ continue;
3175
+ }
3176
+ await fn(item);
3177
+ }
3178
+ });
3179
+ await Promise.all(workers);
3180
+ };
3181
+ var slugFromUrl = (url) => url.split("/a/")[1] ?? "";
3182
+ var preview = (items) => items.slice(0, 5).join(", ") + (items.length > 5 ? ", \u2026" : "");
3183
+ var openInBrowser = (url) => {
3184
+ let cmd = "xdg-open";
3185
+ if (process.platform === "darwin") {
3186
+ cmd = "open";
3187
+ } else if (process.platform === "win32") {
3188
+ cmd = "start";
3189
+ }
3190
+ exec(`${cmd} ${JSON.stringify(url)}`, () => {
3191
+ });
3192
+ };
3193
+ var UPLOAD_CONCURRENCY = 6;
3194
+ var REVIEW_LABEL = {
3195
+ off: "off \u2014 plain artifact, no review tools",
3196
+ open: "open \u2014 anyone with the link can comment",
3197
+ readonly: "read-only \u2014 existing comments only"
3198
+ };
3199
+ var createSessionBody = ({
3200
+ artifactId,
3201
+ baseVersion,
3202
+ entryPath,
3203
+ expiresInSeconds,
3204
+ files,
3205
+ opts,
3206
+ placement
3207
+ }) => ({
3208
+ artifactId,
3209
+ ...baseVersion === void 0 ? void 0 : { baseVersion },
3210
+ ...opts.force ? { force: true } : void 0,
3211
+ ...expiresInSeconds === void 0 ? void 0 : { expiresInSeconds },
3212
+ // Overrides the entry HTML's own meta description on the share card.
3213
+ // Omitted entirely when absent, so the document keeps speaking for itself.
3214
+ ...opts.description?.trim() ? { description: opts.description.trim() } : void 0,
3215
+ entryPath,
3216
+ files: files.map(({ absPath: _absPath, ...manifest }) => manifest),
3217
+ ...placement.folderId ? { folderId: placement.folderId } : void 0,
3218
+ // Agents often pass a double-quoted string where "\n" is a literal
3219
+ // backslash-n; turn those back into real newlines so bullets survive.
3220
+ ...opts.notes?.trim() ? { notes: opts.notes.replaceAll("\\n", "\n").trim() } : void 0,
3221
+ source: {
3222
+ // ARTIFACT_CAFE_AGENT if it's set, else whichever agent's environment
3223
+ // we recognize — this is what names the agent on every reply to a
3224
+ // thread opened against this version (§6.5).
3225
+ agent: agentLabel(),
3226
+ runId: process.env.ARTIFACT_CAFE_RUN_ID,
3227
+ type: "cli"
3228
+ },
3229
+ title: opts.title,
3230
+ ...placement.publishWorkspaceId ? { workspaceId: placement.publishWorkspaceId } : void 0
3231
+ });
3232
+ var savePublishedConfig = ({
3233
+ apiUrl: apiUrl2,
3234
+ config,
3235
+ dir,
3236
+ opts,
3237
+ placement,
3238
+ result,
3239
+ usingAgentToken
3240
+ }) => {
3241
+ const publishToken = usingAgentToken ? void 0 : result.publishToken ?? (config?.artifactId === result.artifactId ? config.publishToken : void 0);
3242
+ const shouldWriteConfig = usingAgentToken || result.publishToken !== void 0 || config?.artifactId === result.artifactId || opts.workspace !== void 0 || opts.folder !== void 0 || placement.workspaceId !== void 0;
3243
+ if (shouldWriteConfig) {
3244
+ writeConfig(dir, {
3245
+ apiUrl: apiUrl2,
3246
+ artifactId: result.artifactId,
3247
+ baseVersion: result.versionNumber,
3248
+ ...placement.folder ? { folder: placement.folder } : void 0,
3249
+ ...placement.folderId ? { folderId: placement.folderId } : void 0,
3250
+ ...publishToken ? { publishToken } : void 0,
3251
+ slug: slugFromUrl(result.url),
3252
+ url: result.url,
3253
+ ...placement.workspace ? { workspace: placement.workspace } : void 0,
3254
+ ...placement.workspaceId ? { workspaceId: placement.workspaceId } : void 0,
3255
+ ...placement.destination === "personal" || placement.destination === "workspace" ? { workspaceKind: placement.destination } : void 0
3256
+ });
3257
+ }
3258
+ };
3259
+ var printPublishJson = ({
3260
+ reviewMode,
3261
+ dataInfo,
3262
+ opts,
3263
+ placement,
3264
+ result,
3265
+ session,
3266
+ skipped
3267
+ }) => {
3268
+ console.log(
3269
+ JSON.stringify(
3270
+ {
3271
+ artifactId: result.artifactId,
3272
+ autoDeleteAt: result.autoDeleteAt ?? null,
3273
+ expiresAt: result.expiresAt ?? null,
3274
+ ...dataInfo.config ? { data: dataInfo.config } : void 0,
3275
+ claimed: result.claimed,
3276
+ destination: placement.destination,
3277
+ destinationSource: placement.destinationSource,
3278
+ folder: placement.folder ?? null,
3279
+ folderId: placement.folderId ?? null,
3280
+ url: result.url,
3281
+ versionNumber: result.versionNumber,
3282
+ visibility: result.visibility ?? null,
3283
+ workspace: placement.workspace ?? null,
3284
+ workspaceId: placement.workspaceId ?? null,
3285
+ ...result.claimUrl ? { claimUrl: result.claimUrl } : void 0,
3286
+ ...opts.password === void 0 ? void 0 : { protected: true },
3287
+ ...reviewMode === void 0 ? void 0 : { reviewMode },
3288
+ filesUploaded: session.filesToUpload.map((f) => f.path),
3289
+ ignored: skipped,
3290
+ skippedFiles: session.skippedFiles
3291
+ },
3292
+ null,
3293
+ 2
3294
+ )
3295
+ );
3296
+ };
3297
+ var printPublishResult = ({
3298
+ reviewMode,
3299
+ dataInfo,
3300
+ opts,
3301
+ placement,
3302
+ rememberedProjectFile,
3303
+ result,
3304
+ session,
3305
+ skipped
3306
+ }) => {
3307
+ if (opts.json) {
3308
+ printPublishJson({
3309
+ dataInfo,
3310
+ opts,
3311
+ placement,
3312
+ rememberedProjectFile,
3313
+ result,
3314
+ reviewMode,
3315
+ session,
3316
+ skipped
3317
+ });
2847
3318
  } else if (result.versionNumber === 1) {
2848
3319
  console.log("\n\u2713 Published to artifact.cafe\n");
2849
3320
  console.log(`Destination: ${placement.destinationLabel}
@@ -2861,11 +3332,9 @@ ${result.url}
2861
3332
  `);
2862
3333
  }
2863
3334
  if (result.claimUrl) {
2864
- console.log(
2865
- `Claim URL (shown once \u2014 claim to keep this artifact):
3335
+ console.log(`Claim URL (shown once \u2014 claim to keep this artifact):
2866
3336
  ${result.claimUrl}
2867
- `
2868
- );
3337
+ `);
2869
3338
  } else if (result.claimed && !result.expiresAt) {
2870
3339
  console.log("Claimed by your account \u2014 no expiry.\n");
2871
3340
  }
@@ -2912,269 +3381,172 @@ ${result.claimUrl}
2912
3381
  console.log(announcement);
2913
3382
  }
2914
3383
  }
2915
- if (opts.open && !opts.json && process.stdout.isTTY) {
2916
- openInBrowser(result.url);
2917
- }
2918
- }
2919
- function accessNote(visibility) {
2920
- if (visibility === "workspace") {
2921
- return "Access: workspace members only \u2014 anyone else needs `artifact-cafe share <email>` to open it.";
2922
- }
2923
- if (visibility === "private") {
2924
- return "Access: private \u2014 you, the workspace's admins, and anyone you `artifact-cafe share` it with.";
2925
- }
2926
- return null;
2927
- }
2928
- async function resolvePlacement(params) {
2929
- const { apiKey, apiUrl: apiUrl2, config, isNewArtifact, opts } = params;
2930
- if (apiKey?.startsWith("aat_")) {
2931
- if (opts.workspace !== void 0 || opts.folder !== void 0) {
2932
- throw new Error(
2933
- "An agent token selects its own workspace; omit --workspace and --folder."
2934
- );
3384
+ };
3385
+ var openPublishSession = async ({
3386
+ apiKey,
3387
+ token,
3388
+ apiUrl: apiUrl2,
3389
+ usingAgentToken,
3390
+ artifactId,
3391
+ sessionBody
3392
+ }) => {
3393
+ let credential = apiKey ?? token;
3394
+ let client = createApiClient(apiUrl2, credential);
3395
+ let session;
3396
+ try {
3397
+ session = await client.createSession(sessionBody);
3398
+ } catch (error) {
3399
+ const keyRefused = apiKey !== void 0 && !usingAgentToken && token !== void 0 && apiKey !== token && artifactId !== null && error instanceof ApiRequestError && (error.status === 401 || error.status === 403);
3400
+ if (!keyRefused) {
3401
+ throw error;
2935
3402
  }
2936
- return {
2937
- destination: "workspace",
2938
- destinationLabel: "Agent token's workspace",
2939
- destinationSource: "agent-token"
2940
- };
3403
+ credential = token;
3404
+ client = createApiClient(apiUrl2, credential);
3405
+ session = await client.createSession(sessionBody);
2941
3406
  }
2942
- const hasPlacementFlag = opts.workspace !== void 0 || opts.folder !== void 0;
2943
- if (!hasPlacementFlag && config?.workspaceId) {
2944
- const isPersonal = config.workspaceKind === "personal";
2945
- if (!(apiKey || isPersonal)) {
2946
- throw new Error(
2947
- `Publishing into workspace ${config.workspace ?? config.workspaceId} needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
2948
- );
3407
+ return { client, credential, session };
3408
+ };
3409
+ var validatePublishOptions = (opts) => {
3410
+ const expiresInSeconds = opts.expiresIn === void 0 ? void 0 : parseArtifactDuration(opts.expiresIn);
3411
+ if (opts.password !== void 0) {
3412
+ const parsed = gatePasswordSchema.safeParse(opts.password);
3413
+ if (!parsed.success) {
3414
+ throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
2949
3415
  }
2950
- return {
2951
- destination: isPersonal ? "personal" : "workspace",
2952
- destinationLabel: isPersonal ? "Personal" : config.workspace ?? config.workspaceId,
2953
- destinationSource: "artifact-config",
2954
- ...config.folder ? { folder: config.folder } : {},
2955
- ...config.folderId ? { folderId: config.folderId } : {},
2956
- ...config.workspace ? { workspace: config.workspace } : {},
2957
- ...apiKey ? { publishWorkspaceId: config.workspaceId } : {},
2958
- workspaceId: config.workspaceId
2959
- };
2960
3416
  }
2961
- if (!(hasPlacementFlag || isNewArtifact)) {
2962
- return {
2963
- destination: "existing-artifact",
2964
- destinationLabel: "Existing artifact",
2965
- destinationSource: "artifact-config"
2966
- };
3417
+ let reviewMode;
3418
+ if (opts.review !== void 0) {
3419
+ const parsed = reviewModeSchema.safeParse(opts.review);
3420
+ if (!parsed.success) {
3421
+ throw new Error("--review must be one of: open, readonly, off.");
3422
+ }
3423
+ reviewMode = parsed.data;
2967
3424
  }
2968
- const projectConfig = isNewArtifact ? readProjectConfig(opts.dir) : null;
2969
- const wantsWorkspace = opts.workspace !== void 0 || opts.folder !== void 0 || projectConfig !== null;
2970
- if (wantsWorkspace && !apiKey) {
2971
- throw new Error(
2972
- "Publishing into a workspace needs an account \u2014 run `artifact-cafe login` first."
2973
- );
3425
+ return { expiresInSeconds, reviewMode };
3426
+ };
3427
+ var publish = async (opts) => {
3428
+ const target = path9.resolve(opts.dir);
3429
+ const dir = statSync2(target).isFile() ? path9.dirname(target) : target;
3430
+ const config = readConfig(dir);
3431
+ const { expiresInSeconds, reviewMode } = validatePublishOptions(opts);
3432
+ const { artifactId, token, apiUrl: apiUrl2, apiKey, usingAgentToken } = publishingIdentity(opts, config);
3433
+ const placement = await resolvePlacement({
3434
+ apiKey,
3435
+ apiUrl: apiUrl2,
3436
+ config,
3437
+ isNewArtifact: artifactId === null,
3438
+ opts: { ...opts, dir }
3439
+ });
3440
+ const { files, skipped } = scanPath(target);
3441
+ if (files.length === 0) {
3442
+ throw new Error(`Nothing to publish at ${target} (after ignore rules).`);
2974
3443
  }
2975
- if (!(hasPlacementFlag || projectConfig)) {
2976
- if (!apiKey) {
2977
- return {
2978
- destination: "anonymous",
2979
- destinationLabel: "Anonymous",
2980
- destinationSource: "anonymous-default"
2981
- };
2982
- }
2983
- if (shouldPromptForWorkspace(opts)) {
2984
- const data3 = await createApiClient(apiUrl2, apiKey).listWorkspaces();
2985
- if (data3.workspaces.length > 1) {
2986
- const choice = await pickWorkspace(data3.workspaces);
2987
- return {
2988
- destination: choice.workspace.isPersonal ? "personal" : "workspace",
2989
- destinationLabel: choice.workspace.isPersonal ? "Personal" : choice.workspace.name,
2990
- destinationSource: "interactive",
2991
- publishWorkspaceId: choice.workspace.id,
2992
- ...choice.remember ? {
2993
- rememberProjectDefault: {
2994
- workspace: choice.workspace.handle
2995
- }
2996
- } : {},
2997
- workspace: choice.workspace.handle,
2998
- workspaceId: choice.workspace.id
2999
- };
3000
- }
3444
+ const entryPath = resolveEntry(files, opts.entry);
3445
+ const dataInfo = inspectDataManifest(files);
3446
+ const log = (msg) => {
3447
+ if (!opts.json) {
3448
+ console.log(msg);
3001
3449
  }
3002
- return {
3003
- destination: "personal",
3004
- destinationLabel: "Personal",
3005
- destinationSource: "personal-default"
3006
- };
3007
- }
3008
- if (!isNewArtifact && opts.folder !== void 0 && opts.workspace === void 0 && !(config?.workspace || config?.workspaceId)) {
3009
- throw new Error("--folder requires --workspace or a stored workspace.");
3450
+ };
3451
+ if (skipped.length) {
3452
+ log(` ignoring ${skipped.length} item(s): ${preview(skipped)}`);
3010
3453
  }
3011
- const client = createApiClient(apiUrl2, apiKey);
3012
- const data2 = await client.listWorkspaces();
3013
- const workspaceHandle = opts.workspace ?? config?.workspace ?? projectConfig?.workspace;
3014
- let workspace;
3015
- if (workspaceHandle) {
3016
- workspace = data2.workspaces.find(
3017
- (candidate) => candidate.handle === workspaceHandle
3018
- );
3019
- } else if (config?.workspaceId) {
3020
- workspace = data2.workspaces.find(
3021
- (candidate) => candidate.id === config.workspaceId
3454
+ const sessionBody = createSessionBody({
3455
+ artifactId,
3456
+ baseVersion: artifactId === config?.artifactId ? config.baseVersion : void 0,
3457
+ entryPath,
3458
+ expiresInSeconds,
3459
+ files,
3460
+ opts,
3461
+ placement
3462
+ });
3463
+ const { client, credential, session } = await openPublishSession({
3464
+ apiKey,
3465
+ apiUrl: apiUrl2,
3466
+ artifactId,
3467
+ sessionBody,
3468
+ token,
3469
+ usingAgentToken
3470
+ });
3471
+ if (expiresInSeconds !== void 0 && session.expiresInSeconds !== expiresInSeconds) {
3472
+ throw new Error(
3473
+ "This API does not support temporary publishing. Nothing was uploaded or finalized; upgrade the API before using --expires-in."
3022
3474
  );
3023
- } else {
3024
- workspace = data2.workspaces.find((candidate) => candidate.isPersonal);
3025
3475
  }
3026
- if (!workspace) {
3027
- const target = workspaceHandle ? `workspace with handle "${workspaceHandle}"` : "personal workspace";
3028
- throw new Error(`No ${target} belongs to this account.`);
3476
+ const byPath = new Map(files.map((f) => [f.path, f]));
3477
+ log(
3478
+ ` uploading ${session.filesToUpload.length} file(s)${session.skippedFiles.length ? `, ${session.skippedFiles.length} unchanged` : ""}`
3479
+ );
3480
+ const presence = startPublishingPresence(client, session.artifactId);
3481
+ const brewing = startBrewing("brewing\u2026", !opts.json);
3482
+ try {
3483
+ await inBatches(session.filesToUpload, UPLOAD_CONCURRENCY, async (upload) => {
3484
+ const file = byPath.get(upload.path);
3485
+ if (!file) {
3486
+ throw new Error(`Server asked for unknown file ${upload.path}`);
3487
+ }
3488
+ await uploadFile(upload.uploadUrl, readFileSync7(file.absPath), file.mimeType);
3489
+ });
3490
+ } catch (error) {
3491
+ brewing.stop();
3492
+ throw error;
3493
+ } finally {
3494
+ presence.stop();
3029
3495
  }
3030
- const folderName = opts.workspace !== void 0 && opts.folder === void 0 ? void 0 : opts.folder ?? projectConfig?.folder;
3031
- const destinationSource = hasPlacementFlag ? "explicit" : "project-default";
3032
- if (!folderName) {
3033
- return {
3034
- destination: workspace.isPersonal ? "personal" : "workspace",
3035
- destinationLabel: workspace.isPersonal ? "Personal" : workspace.name,
3036
- destinationSource,
3037
- publishWorkspaceId: workspace.id,
3038
- workspace: workspace.handle,
3039
- workspaceId: workspace.id
3040
- };
3496
+ let result;
3497
+ try {
3498
+ result = await client.finalize(session.publishSessionId);
3499
+ } finally {
3500
+ brewing.stop();
3041
3501
  }
3042
- const folderData = await client.listFolders(workspace.id);
3043
- const matches = folderData.folders.filter(
3044
- (candidate) => candidate.name === folderName || candidate.id === folderName
3045
- );
3046
- if (matches.length === 0) {
3047
- throw new Error(
3048
- `No folder named "${folderName}" exists in workspace ${workspace.handle}.`
3049
- );
3502
+ if (usingAgentToken && result.workspaceId) {
3503
+ placement.workspaceId = result.workspaceId;
3050
3504
  }
3051
- if (matches.length > 1) {
3052
- throw new Error(
3053
- `More than one folder is named "${folderName}" in workspace ${workspace.handle}; pass the folder id instead.`
3054
- );
3505
+ const rememberedProjectFile = placement.rememberProjectDefault ? writeProjectConfig(dir, placement.rememberProjectDefault) : null;
3506
+ savePublishedConfig({ apiUrl: apiUrl2, config, dir, opts, placement, result, usingAgentToken });
3507
+ if (opts.password !== void 0) {
3508
+ const protectClient = createApiClient(apiUrl2, credential ?? result.publishToken);
3509
+ await protectClient.setProtection(result.artifactId, opts.password);
3055
3510
  }
3056
- const folder = matches[0];
3057
- return {
3058
- destination: workspace.isPersonal ? "personal" : "workspace",
3059
- destinationLabel: workspace.isPersonal ? "Personal" : workspace.name,
3060
- destinationSource,
3061
- folder: folder.name,
3062
- folderId: folder.id,
3063
- publishWorkspaceId: workspace.id,
3064
- workspace: workspace.handle,
3065
- workspaceId: workspace.id
3066
- };
3067
- }
3068
- function shouldPromptForWorkspace(opts) {
3069
- if (opts.json || process.env.ARTIFACT_CAFE_AGENT) {
3070
- return false;
3511
+ if (reviewMode !== void 0) {
3512
+ const reviewClient = createApiClient(apiUrl2, credential ?? result.publishToken);
3513
+ await reviewClient.setReviewMode(result.artifactId, reviewMode);
3071
3514
  }
3072
- return Boolean(process.stdin.isTTY && process.stdout.isTTY);
3073
- }
3074
- function pickWorkspace(workspaces3) {
3075
- const choices = [...workspaces3].sort((a, b) => {
3076
- if (a.isPersonal !== b.isPersonal) {
3077
- return a.isPersonal ? -1 : 1;
3078
- }
3079
- return a.name.localeCompare(b.name);
3080
- });
3081
- console.log("Where should this artifact live?\n");
3082
- choices.forEach((workspace, index) => {
3083
- const label = workspace.isPersonal ? "Personal" : workspace.name;
3084
- const handle = workspace.isPersonal ? "" : ` \xB7 ${workspace.handle}`;
3085
- console.log(` ${String(index + 1).padStart(2)}. ${label}${handle}`);
3515
+ printPublishResult({
3516
+ dataInfo,
3517
+ opts,
3518
+ placement,
3519
+ rememberedProjectFile,
3520
+ result,
3521
+ reviewMode,
3522
+ session,
3523
+ skipped
3086
3524
  });
3087
- const rl = createInterface({ input: process.stdin, output: process.stdout });
3088
- return new Promise((resolve2, reject) => {
3089
- rl.question(`
3090
- Publish where? [1-${choices.length}]: `, (answer) => {
3091
- const index = Number(answer.trim()) - 1;
3092
- const workspace = choices[index];
3093
- if (!(Number.isInteger(index) && workspace)) {
3094
- rl.close();
3095
- reject(new Error(`Enter a number between 1 and ${choices.length}.`));
3096
- return;
3097
- }
3098
- if (workspace.isPersonal) {
3099
- rl.close();
3100
- resolve2({ remember: false, workspace });
3101
- return;
3102
- }
3103
- rl.question(
3104
- `Remember ${workspace.name} for new artifacts in this project? [y/N]: `,
3105
- (rememberAnswer) => {
3106
- rl.close();
3107
- resolve2({
3108
- remember: /^y(?:es)?$/i.test(rememberAnswer.trim()),
3109
- workspace
3110
- });
3111
- }
3112
- );
3113
- });
3114
- });
3115
- }
3116
- function startPublishingPresence(client, artifactId) {
3117
- const beat = () => {
3118
- client.agentHeartbeat(artifactId, "publishing").catch(() => {
3119
- });
3120
- };
3121
- beat();
3122
- const timer = setInterval(beat, AGENT_PRESENCE.heartbeatSeconds * 1e3);
3123
- timer.unref();
3124
- return { stop: () => clearInterval(timer) };
3125
- }
3126
- async function inBatches(items, concurrency, fn) {
3127
- let next = 0;
3128
- const workers = Array.from(
3129
- { length: Math.min(concurrency, items.length) },
3130
- async () => {
3131
- while (next < items.length) {
3132
- const item = items[next];
3133
- next += 1;
3134
- if (item === void 0) {
3135
- continue;
3136
- }
3137
- await fn(item);
3138
- }
3139
- }
3140
- );
3141
- await Promise.all(workers);
3142
- }
3143
- function slugFromUrl(url) {
3144
- return url.split("/a/")[1] ?? "";
3145
- }
3146
- function preview(items) {
3147
- return items.slice(0, 5).join(", ") + (items.length > 5 ? ", \u2026" : "");
3148
- }
3149
- function openInBrowser(url) {
3150
- let cmd = "xdg-open";
3151
- if (process.platform === "darwin") {
3152
- cmd = "open";
3153
- } else if (process.platform === "win32") {
3154
- cmd = "start";
3525
+ if (opts.open && !opts.json && process.stdout.isTTY) {
3526
+ openInBrowser(result.url);
3155
3527
  }
3156
- exec(`${cmd} ${JSON.stringify(url)}`, () => {
3157
- });
3158
- }
3528
+ };
3159
3529
 
3160
3530
  // src/dashboard.ts
3161
- function openDashboard(opts) {
3162
- const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path8.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
3163
- const url = `${base.replace(/\/$/, "")}/dashboard`;
3531
+ var openDashboard = (opts) => {
3532
+ const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path10.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
3533
+ const url = `${base.replace(/\/$/u, "")}/dashboard`;
3164
3534
  console.log(url);
3165
3535
  if (process.stdout.isTTY) {
3166
3536
  openInBrowser(url);
3167
3537
  }
3168
- }
3538
+ };
3169
3539
 
3170
3540
  // src/errors.ts
3171
- function printCliError(err, json) {
3541
+ var printCliError = (err, json) => {
3172
3542
  if (err instanceof ApiRequestError) {
3173
3543
  if (json) {
3174
3544
  console.log(
3175
3545
  JSON.stringify({
3176
3546
  error: {
3177
3547
  code: err.code,
3548
+ currentVersion: err.currentVersion,
3549
+ docsUrl: err.docsUrl,
3178
3550
  message: err.message,
3179
3551
  retryAfter: err.retryAfter
3180
3552
  }
@@ -3199,20 +3571,20 @@ function printCliError(err, json) {
3199
3571
  } else {
3200
3572
  console.error(`\u2717 ${message}`);
3201
3573
  }
3202
- }
3574
+ };
3203
3575
 
3204
3576
  // src/expiration.ts
3205
- import path9 from "path";
3206
- async function expiration(opts) {
3577
+ import path11 from "path";
3578
+ var expiration = async (opts) => {
3207
3579
  if (opts.duration !== void 0 === opts.keep) {
3208
3580
  throw new Error("Provide exactly one of --in <duration> or --keep.");
3209
3581
  }
3210
- const seconds = opts.keep ? null : parseArtifactDuration(opts.duration);
3211
- const target = resolveArtifactTarget(path9.resolve(opts.dir), opts);
3212
- const result = await createApiClient(
3213
- target.apiUrl,
3214
- target.credential
3215
- ).setExpiration(target.artifactId, seconds);
3582
+ const seconds = opts.duration === void 0 ? null : parseArtifactDuration(opts.duration);
3583
+ const target = resolveArtifactTarget(path11.resolve(opts.dir), opts);
3584
+ const result = await createApiClient(target.apiUrl, target.credential).setExpiration(
3585
+ target.artifactId,
3586
+ seconds
3587
+ );
3216
3588
  if (opts.json) {
3217
3589
  console.log(JSON.stringify(result, null, 2));
3218
3590
  return;
@@ -3228,10 +3600,41 @@ async function expiration(opts) {
3228
3600
  "Artifact kept \u2014 no automatic expiry. Its link, versions, and comments are preserved."
3229
3601
  );
3230
3602
  }
3231
- }
3603
+ };
3232
3604
 
3233
3605
  // src/folders.ts
3234
- async function folders(opts) {
3606
+ var createFolder = async (client, workspace, opts) => {
3607
+ const name = opts.name?.trim();
3608
+ if (!name) {
3609
+ throw new Error("--name is required when creating a folder.");
3610
+ }
3611
+ const visibility = folderVisibilitySchema.parse(opts.visibility ?? "workspace");
3612
+ const existing = await client.listFolders(workspace.id);
3613
+ const duplicate = existing.folders.find(
3614
+ (candidate) => candidate.name.toLowerCase() === name.toLowerCase()
3615
+ );
3616
+ if (duplicate) {
3617
+ throw new Error(
3618
+ `A folder named "${duplicate.name}" already exists in ${workspace.handle} (${duplicate.id}) \u2014 publish into it with --folder, or pick a different name.`
3619
+ );
3620
+ }
3621
+ const folder = await client.createFolder(workspace.id, { name, visibility });
3622
+ if (opts.json) {
3623
+ console.log(JSON.stringify(folder, null, 2));
3624
+ return;
3625
+ }
3626
+ console.log(`\u2713 Created folder "${folder.name}" (${folder.visibility}) in ${workspace.handle}`);
3627
+ console.log(` ${folder.id}`);
3628
+ if (folder.visibility === "public") {
3629
+ console.log(" Artifacts placed in it default to public \u2014 visible to anyone.");
3630
+ }
3631
+ };
3632
+ var printFolder = (folder) => {
3633
+ console.log(`${folder.name} (${folder.visibility})`);
3634
+ console.log(` ${folder.id}
3635
+ `);
3636
+ };
3637
+ var folders = async (opts) => {
3235
3638
  if (opts.action !== void 0 && opts.action !== "create") {
3236
3639
  throw new Error(
3237
3640
  `Unknown folders action "${opts.action}" \u2014 run \`folders\` to list, or \`folders create <name>\` to create one.`
@@ -3245,15 +3648,10 @@ async function folders(opts) {
3245
3648
  }
3246
3649
  const visibility = opts.visibility ?? "workspace";
3247
3650
  if (visibility !== "workspace" && visibility !== "public") {
3248
- throw new Error(
3249
- `--visibility must be "workspace" or "public" (got "${opts.visibility}").`
3250
- );
3651
+ throw new Error(`--visibility must be "workspace" or "public" (got "${opts.visibility}").`);
3251
3652
  }
3252
3653
  }
3253
- const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
3254
- /\/$/,
3255
- ""
3256
- );
3654
+ const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/u, "");
3257
3655
  const key = resolveApiKey(apiUrl2, opts.apiKey);
3258
3656
  if (!key) {
3259
3657
  throw new Error(
@@ -3266,9 +3664,7 @@ async function folders(opts) {
3266
3664
  (candidate) => candidate.handle === opts.workspace
3267
3665
  );
3268
3666
  if (!workspace) {
3269
- throw new Error(
3270
- `No workspace with handle "${opts.workspace}" belongs to this account.`
3271
- );
3667
+ throw new Error(`No workspace with handle "${opts.workspace}" belongs to this account.`);
3272
3668
  }
3273
3669
  if (opts.action === "create") {
3274
3670
  await createFolder(client, workspace, opts);
@@ -3284,68 +3680,107 @@ async function folders(opts) {
3284
3680
  return;
3285
3681
  }
3286
3682
  const plural = data2.folders.length === 1 ? "" : "s";
3287
- console.log(
3288
- `${data2.folders.length} folder${plural} in ${workspace.handle}
3289
- `
3290
- );
3683
+ console.log(`${data2.folders.length} folder${plural} in ${workspace.handle}
3684
+ `);
3291
3685
  for (const folder of data2.folders) {
3292
3686
  printFolder(folder);
3293
3687
  }
3294
- }
3295
- async function createFolder(client, workspace, opts) {
3296
- const name = opts.name.trim();
3297
- const visibility = opts.visibility ?? "workspace";
3298
- const existing = await client.listFolders(workspace.id);
3299
- const duplicate = existing.folders.find(
3300
- (candidate) => candidate.name.toLowerCase() === name.toLowerCase()
3688
+ };
3689
+
3690
+ // src/link.ts
3691
+ import path12 from "path";
3692
+ import { createInterface as createInterface2 } from "readline";
3693
+
3694
+ // src/list.ts
3695
+ var printWorkspaceArtifact = (a) => {
3696
+ const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
3697
+ const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
3698
+ const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
3699
+ console.log(` ${marker} ${a.title} \u2014 ${version} \xB7 ${open2} \xB7 ${a.visibility}`);
3700
+ console.log(` ${a.url}`);
3701
+ };
3702
+ var listWorkspace = async (client, opts) => {
3703
+ const workspaceData = await client.listWorkspaces();
3704
+ const workspace = workspaceData.workspaces.find(
3705
+ (candidate) => candidate.handle === opts.workspace
3301
3706
  );
3302
- if (duplicate) {
3303
- throw new Error(
3304
- `A folder named "${duplicate.name}" already exists in ${workspace.handle} (${duplicate.id}) \u2014 publish into it with --folder, or pick a different name.`
3305
- );
3707
+ if (!workspace) {
3708
+ throw new Error(`No workspace with handle "${opts.workspace}" belongs to this account.`);
3306
3709
  }
3307
- const folder = await client.createFolder(workspace.id, { name, visibility });
3710
+ const [artifactData, folderData] = await Promise.all([
3711
+ client.listWorkspaceArtifacts(workspace.id),
3712
+ client.listFolders(workspace.id)
3713
+ ]);
3308
3714
  if (opts.json) {
3309
- console.log(JSON.stringify(folder, null, 2));
3310
- return;
3311
- }
3312
- console.log(
3313
- `\u2713 Created folder "${folder.name}" (${folder.visibility}) in ${workspace.handle}`
3314
- );
3315
- console.log(` ${folder.id}`);
3316
- if (folder.visibility === "public") {
3317
3715
  console.log(
3318
- " Artifacts placed in it default to public \u2014 visible to anyone."
3716
+ JSON.stringify({ artifacts: artifactData.artifacts, folders: folderData.folders }, null, 2)
3319
3717
  );
3718
+ return;
3320
3719
  }
3321
- }
3322
- function printFolder(folder) {
3323
- console.log(`${folder.name} (${folder.visibility})`);
3324
- console.log(` ${folder.id}
3720
+ if (artifactData.artifacts.length === 0) {
3721
+ console.log(`No artifacts in workspace ${workspace.handle}.`);
3722
+ return;
3723
+ }
3724
+ const plural = artifactData.artifacts.length === 1 ? "" : "s";
3725
+ console.log(`${artifactData.artifacts.length} artifact${plural} in ${workspace.handle}
3325
3726
  `);
3326
- }
3327
-
3328
- // src/link.ts
3329
- import path10 from "path";
3330
- import { createInterface as createInterface2 } from "readline";
3331
-
3332
- // src/list.ts
3333
- async function teamGroups(client) {
3727
+ for (const folder of folderData.folders) {
3728
+ const inFolder = artifactData.artifacts.filter((artifact) => artifact.folderId === folder.id);
3729
+ if (inFolder.length === 0) {
3730
+ continue;
3731
+ }
3732
+ console.log(`${folder.name} (${folder.visibility})`);
3733
+ for (const artifact of inFolder) {
3734
+ printWorkspaceArtifact(artifact);
3735
+ }
3736
+ }
3737
+ const unfiled = artifactData.artifacts.filter((artifact) => artifact.folderId === null);
3738
+ if (unfiled.length > 0) {
3739
+ console.log("Unfiled");
3740
+ for (const artifact of unfiled) {
3741
+ printWorkspaceArtifact(artifact);
3742
+ }
3743
+ }
3744
+ };
3745
+ var relativeTime = (iso) => {
3746
+ const seconds = Math.max(0, (Date.now() - new Date(iso).getTime()) / 1e3);
3747
+ if (seconds < 60) {
3748
+ return "just now";
3749
+ }
3750
+ if (seconds < 3600) {
3751
+ return `${Math.floor(seconds / 60)}m ago`;
3752
+ }
3753
+ if (seconds < 86400) {
3754
+ return `${Math.floor(seconds / 3600)}h ago`;
3755
+ }
3756
+ if (seconds < 30 * 86400) {
3757
+ return `${Math.floor(seconds / 86400)}d ago`;
3758
+ }
3759
+ return iso.slice(0, 10);
3760
+ };
3761
+ var printArtifact = (a) => {
3762
+ const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
3763
+ const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
3764
+ const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
3765
+ console.log(`${marker} ${a.title}`);
3766
+ console.log(` ${version} \xB7 ${open2} \xB7 updated ${relativeTime(a.updatedAt)}`);
3767
+ console.log(` ${a.url}
3768
+ `);
3769
+ };
3770
+ var getWorkspaceGroup = async (client, workspace) => {
3771
+ const { artifacts } = await client.listWorkspaceArtifacts(workspace.id);
3772
+ return { artifacts, handle: workspace.handle, id: workspace.id, name: workspace.name };
3773
+ };
3774
+ var teamGroups = async (client) => {
3334
3775
  const { workspaces: workspaces3 } = await client.listWorkspaces();
3335
3776
  return await Promise.all(
3336
- workspaces3.filter((workspace) => !workspace.isPersonal).map(async (workspace) => ({
3337
- artifacts: (await client.listWorkspaceArtifacts(workspace.id)).artifacts,
3338
- handle: workspace.handle,
3339
- id: workspace.id,
3340
- name: workspace.name
3341
- }))
3777
+ workspaces3.flatMap(
3778
+ (workspace) => workspace.isPersonal ? [] : [getWorkspaceGroup(client, workspace)]
3779
+ )
3342
3780
  );
3343
- }
3344
- async function reachableArtifacts(client) {
3345
- const [own, groups] = await Promise.all([
3346
- client.listArtifacts(),
3347
- teamGroups(client)
3348
- ]);
3781
+ };
3782
+ var reachableArtifacts = async (client) => {
3783
+ const [own, groups] = await Promise.all([client.listArtifacts(), teamGroups(client)]);
3349
3784
  return [
3350
3785
  ...own.artifacts.map((artifact) => ({
3351
3786
  currentVersionNumber: artifact.currentVersionNumber,
@@ -3366,12 +3801,9 @@ async function reachableArtifacts(client) {
3366
3801
  }))
3367
3802
  )
3368
3803
  ];
3369
- }
3370
- async function list(opts) {
3371
- const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
3372
- /\/$/,
3373
- ""
3374
- );
3804
+ };
3805
+ var list = async (opts) => {
3806
+ const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/u, "");
3375
3807
  const key = resolveApiKey(apiUrl2, opts.apiKey);
3376
3808
  if (!key) {
3377
3809
  throw new Error(
@@ -3383,14 +3815,9 @@ async function list(opts) {
3383
3815
  await listWorkspace(client, opts);
3384
3816
  return;
3385
3817
  }
3386
- const [data2, groups] = await Promise.all([
3387
- client.listArtifacts(),
3388
- teamGroups(client)
3389
- ]);
3818
+ const [data2, groups] = await Promise.all([client.listArtifacts(), teamGroups(client)]);
3390
3819
  if (opts.json) {
3391
- console.log(
3392
- JSON.stringify({ artifacts: data2.artifacts, workspaces: groups }, null, 2)
3393
- );
3820
+ console.log(JSON.stringify({ artifacts: data2.artifacts, workspaces: groups }, null, 2));
3394
3821
  return;
3395
3822
  }
3396
3823
  const total = data2.artifacts.length + groups.reduce((sum, group) => sum + group.artifacts.length, 0);
@@ -3413,112 +3840,44 @@ async function list(opts) {
3413
3840
  continue;
3414
3841
  }
3415
3842
  const plural = group.artifacts.length === 1 ? "" : "s";
3416
- console.log(
3417
- `${group.artifacts.length} artifact${plural} in ${group.handle}
3418
- `
3419
- );
3843
+ console.log(`${group.artifacts.length} artifact${plural} in ${group.handle}
3844
+ `);
3420
3845
  for (const artifact of group.artifacts) {
3421
3846
  printWorkspaceArtifact(artifact);
3422
3847
  }
3423
3848
  console.log("");
3424
3849
  }
3425
- }
3426
- async function listWorkspace(client, opts) {
3427
- const workspaceData = await client.listWorkspaces();
3428
- const workspace = workspaceData.workspaces.find(
3429
- (candidate) => candidate.handle === opts.workspace
3430
- );
3431
- if (!workspace) {
3432
- throw new Error(
3433
- `No workspace with handle "${opts.workspace}" belongs to this account.`
3434
- );
3435
- }
3436
- const [artifactData, folderData] = await Promise.all([
3437
- client.listWorkspaceArtifacts(workspace.id),
3438
- client.listFolders(workspace.id)
3439
- ]);
3440
- if (opts.json) {
3441
- console.log(
3442
- JSON.stringify(
3443
- { artifacts: artifactData.artifacts, folders: folderData.folders },
3444
- null,
3445
- 2
3446
- )
3447
- );
3448
- return;
3449
- }
3450
- if (artifactData.artifacts.length === 0) {
3451
- console.log(`No artifacts in workspace ${workspace.handle}.`);
3452
- return;
3453
- }
3454
- const plural = artifactData.artifacts.length === 1 ? "" : "s";
3455
- console.log(
3456
- `${artifactData.artifacts.length} artifact${plural} in ${workspace.handle}
3457
- `
3458
- );
3459
- for (const folder of folderData.folders) {
3460
- const inFolder = artifactData.artifacts.filter(
3461
- (artifact) => artifact.folderId === folder.id
3462
- );
3463
- if (inFolder.length === 0) {
3464
- continue;
3465
- }
3466
- console.log(`${folder.name} (${folder.visibility})`);
3467
- for (const artifact of inFolder) {
3468
- printWorkspaceArtifact(artifact);
3469
- }
3470
- }
3471
- const unfiled = artifactData.artifacts.filter(
3472
- (artifact) => artifact.folderId === null
3473
- );
3474
- if (unfiled.length > 0) {
3475
- console.log("Unfiled");
3476
- for (const artifact of unfiled) {
3477
- printWorkspaceArtifact(artifact);
3478
- }
3479
- }
3480
- }
3481
- function printWorkspaceArtifact(a) {
3482
- const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
3483
- const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
3484
- const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
3485
- console.log(
3486
- ` ${marker} ${a.title} \u2014 ${version} \xB7 ${open2} \xB7 ${a.visibility}`
3487
- );
3488
- console.log(` ${a.url}`);
3489
- }
3490
- function printArtifact(a) {
3491
- const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
3492
- const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
3493
- const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
3494
- console.log(`${marker} ${a.title}`);
3495
- console.log(` ${version} \xB7 ${open2} \xB7 updated ${relativeTime(a.updatedAt)}`);
3496
- console.log(` ${a.url}
3497
- `);
3498
- }
3499
- function relativeTime(iso) {
3500
- const seconds = Math.max(0, (Date.now() - new Date(iso).getTime()) / 1e3);
3501
- if (seconds < 60) {
3502
- return "just now";
3503
- }
3504
- if (seconds < 3600) {
3505
- return `${Math.floor(seconds / 60)}m ago`;
3506
- }
3507
- if (seconds < 86400) {
3508
- return `${Math.floor(seconds / 3600)}h ago`;
3509
- }
3510
- if (seconds < 30 * 86400) {
3511
- return `${Math.floor(seconds / 86400)}d ago`;
3512
- }
3513
- return iso.slice(0, 10);
3514
- }
3850
+ };
3515
3851
 
3516
3852
  // src/link.ts
3517
- async function link(opts) {
3518
- const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
3519
- /\/$/,
3520
- ""
3521
- );
3853
+ var pickArtifact = (artifacts) => {
3854
+ console.log("Artifacts you can publish to:\n");
3855
+ for (const [i, a] of artifacts.entries()) {
3856
+ const v = a.currentVersionNumber ? `v${a.currentVersionNumber}` : "\u2014";
3857
+ const where = a.workspaceHandle ? ` \xB7 ${a.workspaceHandle}` : "";
3858
+ console.log(` ${String(i + 1).padStart(2)}. ${a.title} \xB7 ${v}${where} \xB7 ${a.url}`);
3859
+ }
3860
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
3861
+ return new Promise((resolve2, reject) => {
3862
+ rl.question(`
3863
+ Link which? [1-${artifacts.length}]: `, (answer) => {
3864
+ rl.close();
3865
+ const n = Number(answer.trim());
3866
+ if (!(Number.isInteger(n) && n >= 1 && n <= artifacts.length)) {
3867
+ reject(new Error(`Enter a number between 1 and ${artifacts.length}.`));
3868
+ return;
3869
+ }
3870
+ const artifact = artifacts[n - 1];
3871
+ if (!artifact) {
3872
+ reject(new Error("No artifact at that selection."));
3873
+ return;
3874
+ }
3875
+ resolve2(artifact);
3876
+ });
3877
+ });
3878
+ };
3879
+ var link = async (opts) => {
3880
+ const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/u, "");
3522
3881
  const key = resolveApiKey(apiUrl2, opts.apiKey);
3523
3882
  if (!key) {
3524
3883
  throw new Error(
@@ -3555,10 +3914,11 @@ async function link(opts) {
3555
3914
  }
3556
3915
  chosen = await pickArtifact(artifacts);
3557
3916
  }
3558
- const dir = path10.resolve(opts.dir);
3917
+ const dir = path12.resolve(opts.dir);
3559
3918
  writeConfig(dir, {
3560
3919
  apiUrl: apiUrl2,
3561
3920
  artifactId: chosen.id,
3921
+ baseVersion: chosen.currentVersionNumber ?? void 0,
3562
3922
  slug: chosen.slug,
3563
3923
  url: chosen.url
3564
3924
  // No publishToken — this folder authorizes via your login, not a secret.
@@ -3573,68 +3933,43 @@ async function link(opts) {
3573
3933
  url: chosen.url
3574
3934
  },
3575
3935
  null,
3576
- 2
3577
- )
3578
- );
3579
- return;
3580
- }
3581
- console.log(`
3582
- \u2713 Linked ${dir} \u2192 "${chosen.title}" \u2014 ${chosen.url}`);
3583
- console.log(
3584
- " Owner actions (publish, comments --wait, protect) use your login.\n"
3585
- );
3586
- }
3587
- function pickArtifact(artifacts) {
3588
- console.log("Artifacts you can publish to:\n");
3589
- artifacts.forEach((a, i) => {
3590
- const v = a.currentVersionNumber ? `v${a.currentVersionNumber}` : "\u2014";
3591
- const where = a.workspaceHandle ? ` \xB7 ${a.workspaceHandle}` : "";
3592
- console.log(
3593
- ` ${String(i + 1).padStart(2)}. ${a.title} \xB7 ${v}${where} \xB7 ${a.url}`
3594
- );
3595
- });
3596
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
3597
- return new Promise((resolve2, reject) => {
3598
- rl.question(`
3599
- Link which? [1-${artifacts.length}]: `, (answer) => {
3600
- rl.close();
3601
- const n = Number(answer.trim());
3602
- if (!(Number.isInteger(n) && n >= 1 && n <= artifacts.length)) {
3603
- reject(new Error(`Enter a number between 1 and ${artifacts.length}.`));
3604
- return;
3605
- }
3606
- resolve2(artifacts[n - 1]);
3607
- });
3608
- });
3609
- }
3936
+ 2
3937
+ )
3938
+ );
3939
+ return;
3940
+ }
3941
+ console.log(`
3942
+ \u2713 Linked ${dir} \u2192 "${chosen.title}" \u2014 ${chosen.url}`);
3943
+ console.log(" Owner actions (publish, comments --wait, protect) use your login.\n");
3944
+ };
3610
3945
 
3611
3946
  // src/login.ts
3612
3947
  import { createInterface as createInterface3 } from "readline";
3613
- function apiUrl() {
3614
- return (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/, "");
3615
- }
3616
- function promptSecret(question) {
3617
- return new Promise((resolve2) => {
3948
+ var apiUrl = () => (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/u, "");
3949
+ var promptSecret = (question) => (
3950
+ // oxlint-disable-next-line promise/avoid-new -- The readline callback owns terminal cleanup and cancellation.
3951
+ new Promise((resolve2) => {
3618
3952
  const rl = createInterface3({
3619
3953
  input: process.stdin,
3620
3954
  output: process.stdout
3621
3955
  });
3622
3956
  let muted = false;
3623
- const raw = rl;
3624
- raw._writeToOutput = (s) => {
3625
- if (!muted) {
3626
- process.stdout.write(s);
3957
+ Object.defineProperty(rl, "_writeToOutput", {
3958
+ value: (s) => {
3959
+ if (!muted) {
3960
+ process.stdout.write(s);
3961
+ }
3627
3962
  }
3628
- };
3963
+ });
3629
3964
  rl.question(question, (answer) => {
3630
3965
  rl.close();
3631
3966
  process.stdout.write("\n");
3632
3967
  resolve2(answer);
3633
3968
  });
3634
3969
  muted = true;
3635
- });
3636
- }
3637
- async function login(opts) {
3970
+ })
3971
+ );
3972
+ var login = async (opts) => {
3638
3973
  const base = apiUrl();
3639
3974
  const keysUrl = `${base}/dashboard?view=api-keys`;
3640
3975
  let key = opts.apiKey?.trim();
@@ -3647,26 +3982,25 @@ Opening ${keysUrl}`);
3647
3982
  if (process.stdout.isTTY) {
3648
3983
  openInBrowser(keysUrl);
3649
3984
  }
3650
- key = (await promptSecret("API key: ")).trim();
3985
+ const enteredKey = await promptSecret("API key: ");
3986
+ key = enteredKey.trim();
3651
3987
  }
3652
3988
  if (!key) {
3653
3989
  throw new Error("No key entered \u2014 nothing was saved.");
3654
3990
  }
3655
3991
  if (!key.startsWith("ack_")) {
3656
- throw new Error(
3657
- `That doesn't look like an artifact.cafe API key (they start with "ack_").`
3658
- );
3992
+ throw new Error(`That doesn't look like an artifact.cafe API key (they start with "ack_").`);
3993
+ }
3994
+ const previous = resolveApiKey(base);
3995
+ if (previous && previous !== key) {
3996
+ resetInstallId();
3659
3997
  }
3660
3998
  const me = await createApiClient(base, key).me();
3661
3999
  storeApiKey(base, key);
3662
4000
  const who = me.email ?? me.userId;
3663
4001
  if (opts.json) {
3664
4002
  console.log(
3665
- JSON.stringify(
3666
- { apiUrl: base, email: me.email, loggedIn: true, userId: me.userId },
3667
- null,
3668
- 2
3669
- )
4003
+ JSON.stringify({ apiUrl: base, email: me.email, loggedIn: true, userId: me.userId }, null, 2)
3670
4004
  );
3671
4005
  return;
3672
4006
  }
@@ -3675,14 +4009,12 @@ Opening ${keysUrl}`);
3675
4009
  console.log(
3676
4010
  "New publishes are claimed by your account automatically. Try `artifact-cafe list`.\n"
3677
4011
  );
3678
- }
3679
- async function whoami(opts) {
4012
+ };
4013
+ var whoami = async (opts) => {
3680
4014
  const base = apiUrl();
3681
4015
  const key = resolveApiKey(base);
3682
4016
  if (!key) {
3683
- throw new Error(
3684
- `Not logged in to ${apiOrigin(base)} \u2014 run \`artifact-cafe login\` first.`
3685
- );
4017
+ throw new Error(`Not logged in to ${apiOrigin(base)} \u2014 run \`artifact-cafe login\` first.`);
3686
4018
  }
3687
4019
  const me = await createApiClient(base, key).me();
3688
4020
  if (opts.json) {
@@ -3701,13 +4033,14 @@ async function whoami(opts) {
3701
4033
  return;
3702
4034
  }
3703
4035
  const keyLabel = me.keyName ? ` (key: ${me.keyName})` : "";
3704
- console.log(
3705
- `Logged in to ${apiOrigin(base)} as ${me.email ?? me.userId}${keyLabel}`
3706
- );
3707
- }
3708
- function logout(opts) {
4036
+ console.log(`Logged in to ${apiOrigin(base)} as ${me.email ?? me.userId}${keyLabel}`);
4037
+ };
4038
+ var logout = (opts) => {
3709
4039
  const base = apiUrl();
3710
4040
  const removed = removeApiKey(base);
4041
+ if (removed) {
4042
+ resetInstallId();
4043
+ }
3711
4044
  if (opts.json) {
3712
4045
  console.log(JSON.stringify({ apiUrl: base, loggedOut: removed }, null, 2));
3713
4046
  return;
@@ -3720,34 +4053,30 @@ function logout(opts) {
3720
4053
  console.log(
3721
4054
  `The key itself is still active \u2014 revoke it at ${base}/dashboard?view=api-keys if this machine is shared.`
3722
4055
  );
3723
- }
4056
+ };
3724
4057
 
3725
4058
  // src/message.ts
3726
- import path11 from "path";
3727
- async function readStdin2() {
4059
+ import path13 from "path";
4060
+ var readStdin2 = async () => {
3728
4061
  if (process.stdin.isTTY) {
3729
4062
  return "";
3730
4063
  }
3731
4064
  const chunks = [];
3732
4065
  for await (const chunk of process.stdin) {
3733
- chunks.push(chunk);
4066
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3734
4067
  }
3735
- return Buffer.concat(chunks).toString("utf8");
3736
- }
3737
- async function editMessage(opts) {
4068
+ return Buffer.concat(chunks).toString("utf-8");
4069
+ };
4070
+ var editMessage = async (opts) => {
3738
4071
  const messageId = opts.message?.trim();
3739
4072
  if (!messageId) {
3740
- throw new Error(
3741
- "Pass the message id to edit \u2014 get it from `artifact-cafe comments --json`."
3742
- );
4073
+ throw new Error("Pass the message id to edit \u2014 get it from `artifact-cafe comments --json`.");
3743
4074
  }
3744
4075
  const body = (opts.body ?? await readStdin2()).trim();
3745
4076
  if (!body) {
3746
- throw new Error(
3747
- 'Nothing to save. Pass the new text with --body "\u2026", or pipe it on stdin.'
3748
- );
4077
+ throw new Error('Nothing to save. Pass the new text with --body "\u2026", or pipe it on stdin.');
3749
4078
  }
3750
- const dir = path11.resolve(opts.dir);
4079
+ const dir = path13.resolve(opts.dir);
3751
4080
  const target = resolveArtifactTarget(dir, {
3752
4081
  apiKey: opts.apiKey,
3753
4082
  artifact: opts.artifact
@@ -3759,15 +4088,13 @@ async function editMessage(opts) {
3759
4088
  return;
3760
4089
  }
3761
4090
  console.log(`\u2713 Edited ${messageId}.`);
3762
- }
3763
- async function deleteMessage(opts) {
4091
+ };
4092
+ var deleteMessage = async (opts) => {
3764
4093
  const messageId = opts.message?.trim();
3765
4094
  if (!messageId) {
3766
- throw new Error(
3767
- "Pass the message id to delete \u2014 get it from `artifact-cafe comments --json`."
3768
- );
4095
+ throw new Error("Pass the message id to delete \u2014 get it from `artifact-cafe comments --json`.");
3769
4096
  }
3770
- const dir = path11.resolve(opts.dir);
4097
+ const dir = path13.resolve(opts.dir);
3771
4098
  const target = resolveArtifactTarget(dir, {
3772
4099
  apiKey: opts.apiKey,
3773
4100
  artifact: opts.artifact
@@ -3779,15 +4106,15 @@ async function deleteMessage(opts) {
3779
4106
  return;
3780
4107
  }
3781
4108
  console.log(`\u2713 Deleted ${messageId}.`);
3782
- }
4109
+ };
3783
4110
 
3784
4111
  // src/move.ts
3785
- import path12 from "path";
3786
- async function move(opts) {
4112
+ import path14 from "path";
4113
+ var move = async (opts) => {
3787
4114
  if (Boolean(opts.folder) === Boolean(opts.unfiled)) {
3788
4115
  throw new Error("Pass exactly one of --folder <name-or-id> or --unfiled.");
3789
4116
  }
3790
- const dir = path12.resolve(opts.dir);
4117
+ const dir = path14.resolve(opts.dir);
3791
4118
  const target = resolveArtifactTarget(dir, {
3792
4119
  apiKey: opts.apiKey,
3793
4120
  artifact: opts.artifact
@@ -3822,6 +4149,9 @@ async function move(opts) {
3822
4149
  `More than one folder is named "${opts.folder}"; pass the folder id instead.`
3823
4150
  );
3824
4151
  }
4152
+ if (matches[0] === null || matches[0] === void 0) {
4153
+ throw new Error("Expected matches[0] to be present.");
4154
+ }
3825
4155
  folderId = matches[0].id;
3826
4156
  folderName = matches[0].name;
3827
4157
  }
@@ -3867,54 +4197,51 @@ async function move(opts) {
3867
4197
  const destination = folderName ? `"${folderName}"` : "Unfiled";
3868
4198
  console.log(`\u2713 Moved "${updated.title}" \u2192 ${destination}`);
3869
4199
  if (updated.visibility !== artifact.visibility) {
3870
- console.log(
3871
- ` Visibility: ${artifact.visibility} \u2192 ${updated.visibility} (folder default)`
3872
- );
4200
+ console.log(` Visibility: ${artifact.visibility} \u2192 ${updated.visibility} (folder default)`);
3873
4201
  if (updated.visibility === "public") {
3874
4202
  console.log(" Now public \u2014 visible to anyone.");
3875
4203
  }
3876
4204
  }
3877
- }
4205
+ };
3878
4206
 
3879
4207
  // src/open.ts
3880
- import path13 from "path";
3881
- async function openArtifact(opts) {
3882
- const dir = path13.resolve(opts.dir);
4208
+ import path15 from "path";
4209
+ var openArtifact = async (opts) => {
4210
+ const dir = path15.resolve(opts.dir);
3883
4211
  const target = resolveArtifactTarget(dir, {
3884
4212
  apiKey: opts.apiKey,
3885
4213
  artifact: opts.artifact
3886
4214
  });
3887
- const url = target.url ?? reviewUrl(
3888
- target.apiUrl,
3889
- (await createApiClient(target.apiUrl, target.credential).getArtifact(
4215
+ let { url } = target;
4216
+ if (!url) {
4217
+ const artifact = await createApiClient(target.apiUrl, target.credential).getArtifact(
3890
4218
  target.artifactId
3891
- )).slug
3892
- );
4219
+ );
4220
+ url = reviewUrl(target.apiUrl, artifact.slug);
4221
+ }
3893
4222
  console.log(url);
3894
4223
  if (process.stdout.isTTY) {
3895
4224
  openInBrowser(url);
3896
4225
  }
3897
- }
4226
+ };
3898
4227
 
3899
4228
  // src/protect.ts
3900
- import path14 from "path";
3901
- async function protect(opts) {
4229
+ import path16 from "path";
4230
+ var protect = async (opts) => {
3902
4231
  if (opts.off && opts.password !== void 0) {
3903
4232
  throw new Error("Pass either --password or --off, not both.");
3904
4233
  }
3905
4234
  if (!opts.off && opts.password === void 0) {
3906
- throw new Error(
3907
- "Provide --password <password> to set a gate, or --off to remove it."
3908
- );
4235
+ throw new Error("Provide --password <password> to set a gate, or --off to remove it.");
3909
4236
  }
3910
- const password = opts.off ? null : opts.password;
4237
+ const password = opts.password === void 0 ? null : opts.password;
3911
4238
  if (password !== null) {
3912
4239
  const parsed = gatePasswordSchema.safeParse(password);
3913
4240
  if (!parsed.success) {
3914
4241
  throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
3915
4242
  }
3916
4243
  }
3917
- const dir = path14.resolve(opts.dir);
4244
+ const dir = path16.resolve(opts.dir);
3918
4245
  const target = resolveArtifactTarget(dir, {
3919
4246
  apiKey: opts.apiKey,
3920
4247
  artifact: opts.artifact
@@ -3925,10 +4252,11 @@ async function protect(opts) {
3925
4252
  console.log(JSON.stringify({ protected: res.protected }, null, 2));
3926
4253
  return;
3927
4254
  }
3928
- const url = target.url ?? reviewUrl(
3929
- target.apiUrl,
3930
- (await client.getArtifact(target.artifactId)).slug
3931
- );
4255
+ let { url } = target;
4256
+ if (!url) {
4257
+ const artifact = await client.getArtifact(target.artifactId);
4258
+ url = reviewUrl(target.apiUrl, artifact.slug);
4259
+ }
3932
4260
  console.log(
3933
4261
  res.protected ? `
3934
4262
  \u2713 Password protection on \u2014 ${url}
@@ -3936,21 +4264,119 @@ async function protect(opts) {
3936
4264
  \u2713 Password protection removed \u2014 ${url}
3937
4265
  `
3938
4266
  );
3939
- }
4267
+ };
4268
+
4269
+ // src/pull.ts
4270
+ import { createHash as createHash2 } from "crypto";
4271
+ import { existsSync as existsSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
4272
+ import path17 from "path";
4273
+ var rejectSymlinks = (dir, relative) => {
4274
+ let current = dir;
4275
+ for (const segment of ["", ...relative.split("/")]) {
4276
+ current = path17.join(current, segment);
4277
+ try {
4278
+ if (lstatSync2(current).isSymbolicLink()) {
4279
+ throw new Error(`Refusing to pull through a symbolic link: ${relative}`);
4280
+ }
4281
+ } catch (error) {
4282
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
4283
+ throw error;
4284
+ }
4285
+ }
4286
+ }
4287
+ };
4288
+ var warnExtraFiles = (extraFiles, versionNumber) => {
4289
+ if (extraFiles.length > 0) {
4290
+ console.warn(
4291
+ `Warning: Local files not in v${versionNumber} will be published unless removed: ${extraFiles.join(", ")}`
4292
+ );
4293
+ }
4294
+ };
4295
+ var pull = async (opts) => {
4296
+ const dir = path17.resolve(opts.dir);
4297
+ rejectSymlinks(dir, ".artifactcafe/config.json");
4298
+ const config = readConfig(dir);
4299
+ const target = resolveArtifactTarget(dir, opts);
4300
+ if (config && config.artifactId !== target.artifactId) {
4301
+ throw new Error("This directory is linked to another artifact. Pull into another directory.");
4302
+ }
4303
+ if (existsSync4(dir) && readdirSync2(dir).length > 0 && !opts.force) {
4304
+ throw new Error("Pull requires an empty directory. Use --force to overwrite matching files.");
4305
+ }
4306
+ let version = "current";
4307
+ if (opts.version !== void 0 && opts.version !== "current") {
4308
+ if (!/^[1-9]\d*$/u.test(opts.version) || !Number.isSafeInteger(Number(opts.version))) {
4309
+ throw new Error("--version must be a positive integer or current.");
4310
+ }
4311
+ version = Number(opts.version);
4312
+ }
4313
+ const result = await createApiClient(target.apiUrl, target.credential).versionFiles(
4314
+ target.artifactId,
4315
+ version
4316
+ );
4317
+ for (const file of result.files) {
4318
+ if (!isSafeArtifactPath(file.path) || path17.win32.isAbsolute(file.path) || file.path.split("/")[0] === ".artifactcafe") {
4319
+ throw new Error(`Unsafe download path: ${file.path}`);
4320
+ }
4321
+ rejectSymlinks(dir, file.path);
4322
+ }
4323
+ for (const file of result.files) {
4324
+ const response = await fetch(file.downloadUrl);
4325
+ if (!response.ok) {
4326
+ throw new Error(`Download failed for ${file.path} (HTTP ${response.status}).`);
4327
+ }
4328
+ const bytes = Buffer.from(await response.arrayBuffer());
4329
+ const hash = `sha256_${createHash2("sha256").update(bytes).digest("hex")}`;
4330
+ if (hash !== file.hash) {
4331
+ throw new Error(`Hash mismatch for ${file.path}.`);
4332
+ }
4333
+ if (bytes.length !== file.size) {
4334
+ throw new Error(`Size mismatch for ${file.path}.`);
4335
+ }
4336
+ const destination = path17.join(dir, file.path);
4337
+ mkdirSync5(path17.dirname(destination), { recursive: true });
4338
+ writeFileSync5(destination, bytes);
4339
+ }
4340
+ writeConfig(dir, {
4341
+ ...config ?? { apiUrl: target.apiUrl, artifactId: target.artifactId },
4342
+ baseVersion: result.versionNumber,
4343
+ slug: result.slug,
4344
+ url: reviewUrl(target.apiUrl, result.slug)
4345
+ });
4346
+ const files = result.files.map(({ downloadUrl: _downloadUrl, ...file }) => file);
4347
+ const pulledPaths = new Set(files.map((file) => file.path));
4348
+ const extraFiles = scanDirectory(dir).files.flatMap(
4349
+ (file) => pulledPaths.has(file.path) ? [] : [file.path]
4350
+ );
4351
+ if (opts.json) {
4352
+ console.log(
4353
+ JSON.stringify({
4354
+ artifactId: target.artifactId,
4355
+ dir,
4356
+ extraFiles,
4357
+ files,
4358
+ versionNumber: result.versionNumber
4359
+ })
4360
+ );
4361
+ return;
4362
+ }
4363
+ console.log(`\u2713 Pulled v${result.versionNumber} to ${dir} (${files.length} files).`);
4364
+ warnExtraFiles(extraFiles, result.versionNumber);
4365
+ };
3940
4366
 
3941
4367
  // src/reply.ts
3942
- import path15 from "path";
3943
- async function readStdin3() {
4368
+ import path18 from "path";
4369
+ var readStdin3 = async () => {
3944
4370
  if (process.stdin.isTTY) {
3945
4371
  return "";
3946
4372
  }
3947
4373
  const chunks = [];
3948
4374
  for await (const chunk of process.stdin) {
3949
- chunks.push(chunk);
4375
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3950
4376
  }
3951
- return Buffer.concat(chunks).toString("utf8");
3952
- }
3953
- async function replyToThread(opts) {
4377
+ return Buffer.concat(chunks).toString("utf-8");
4378
+ };
4379
+ var replyToThread = async (opts) => {
3954
4380
  const threadId = opts.thread?.trim();
3955
4381
  if (!threadId) {
3956
4382
  throw new Error(
@@ -3959,11 +4385,9 @@ async function replyToThread(opts) {
3959
4385
  }
3960
4386
  const body = (opts.body ?? await readStdin3()).trim();
3961
4387
  if (!body) {
3962
- throw new Error(
3963
- 'Nothing to post. Pass the reply with --body "\u2026", or pipe it on stdin.'
3964
- );
4388
+ throw new Error('Nothing to post. Pass the reply with --body "\u2026", or pipe it on stdin.');
3965
4389
  }
3966
- const dir = path15.resolve(opts.dir);
4390
+ const dir = path18.resolve(opts.dir);
3967
4391
  const target = resolveArtifactTarget(dir, {
3968
4392
  apiKey: opts.apiKey,
3969
4393
  artifact: opts.artifact
@@ -3989,18 +4413,18 @@ async function replyToThread(opts) {
3989
4413
  }
3990
4414
  const closed = opts.resolve ? " and resolved it" : "";
3991
4415
  console.log(`\u2713 Replied to ${thread.id}${closed} \u2014 now ${thread.status}.`);
3992
- }
4416
+ };
3993
4417
 
3994
4418
  // src/resolve.ts
3995
- import path16 from "path";
3996
- async function changeThreadStatus(opts) {
4419
+ import path19 from "path";
4420
+ var changeThreadStatus = async (opts) => {
3997
4421
  const threadId = opts.thread?.trim();
3998
4422
  if (!threadId) {
3999
4423
  throw new Error(
4000
4424
  `Pass the thread id to ${opts.action} \u2014 get it from \`artifact-cafe comments --json\`.`
4001
4425
  );
4002
4426
  }
4003
- const dir = path16.resolve(opts.dir);
4427
+ const dir = path19.resolve(opts.dir);
4004
4428
  const target = resolveArtifactTarget(dir, {
4005
4429
  apiKey: opts.apiKey,
4006
4430
  artifact: opts.artifact
@@ -4027,23 +4451,21 @@ async function changeThreadStatus(opts) {
4027
4451
  return;
4028
4452
  }
4029
4453
  if (opts.action === "ack") {
4030
- console.log(
4031
- `\u2713 Acknowledged ${thread.id} \u2014 reviewers now see it as being handled.`
4032
- );
4454
+ console.log(`\u2713 Acknowledged ${thread.id} \u2014 reviewers now see it as being handled.`);
4033
4455
  return;
4034
4456
  }
4035
4457
  const verb = opts.action === "resolve" ? "Resolved" : "Reopened";
4036
4458
  console.log(`\u2713 ${verb} ${thread.id} \u2014 now ${thread.status}.`);
4037
- }
4459
+ };
4038
4460
 
4039
4461
  // src/review.ts
4040
- import path17 from "path";
4462
+ import path20 from "path";
4041
4463
  var OUTCOME = {
4042
4464
  off: "Plain artifact \u2014 the review tools are hidden entirely",
4043
4465
  open: "Open for review \u2014 anyone who can open it can comment",
4044
4466
  readonly: "Read-only \u2014 earlier comments stay visible, no new ones"
4045
4467
  };
4046
- async function review(opts) {
4468
+ var review = async (opts) => {
4047
4469
  const parsed = reviewModeSchema.safeParse(opts.mode);
4048
4470
  if (!parsed.success) {
4049
4471
  throw new Error(
@@ -4051,7 +4473,7 @@ async function review(opts) {
4051
4473
  );
4052
4474
  }
4053
4475
  const mode = parsed.data;
4054
- const dir = path17.resolve(opts.dir);
4476
+ const dir = path20.resolve(opts.dir);
4055
4477
  const target = resolveArtifactTarget(dir, {
4056
4478
  apiKey: opts.apiKey,
4057
4479
  artifact: opts.artifact
@@ -4062,21 +4484,22 @@ async function review(opts) {
4062
4484
  console.log(JSON.stringify({ reviewMode: res.mode }, null, 2));
4063
4485
  return;
4064
4486
  }
4065
- const url = target.url ?? reviewUrl(
4066
- target.apiUrl,
4067
- (await client.getArtifact(target.artifactId)).slug
4068
- );
4487
+ let { url } = target;
4488
+ if (!url) {
4489
+ const artifact = await client.getArtifact(target.artifactId);
4490
+ url = reviewUrl(target.apiUrl, artifact.slug);
4491
+ }
4069
4492
  console.log(`
4070
4493
  \u2713 ${OUTCOME[res.mode]}
4071
4494
  ${url}
4072
4495
  `);
4073
- }
4496
+ };
4074
4497
 
4075
4498
  // src/share.ts
4076
- import path18 from "path";
4077
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4078
- async function share(opts) {
4079
- const dir = path18.resolve(opts.dir);
4499
+ import path21 from "path";
4500
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
4501
+ var share = async (opts) => {
4502
+ const dir = path21.resolve(opts.dir);
4080
4503
  const target = resolveArtifactTarget(dir, {
4081
4504
  apiKey: opts.apiKey,
4082
4505
  artifact: opts.artifact
@@ -4105,9 +4528,7 @@ General access: ${visibility}`);
4105
4528
  return;
4106
4529
  }
4107
4530
  if (opts.emails.length === 0) {
4108
- throw new Error(
4109
- "Provide at least one email to invite, or --list to see current grants."
4110
- );
4531
+ throw new Error("Provide at least one email to invite, or --list to see current grants.");
4111
4532
  }
4112
4533
  for (const email of opts.emails) {
4113
4534
  if (!EMAIL_RE.test(email)) {
@@ -4129,9 +4550,9 @@ General access: ${visibility}`);
4129
4550
  console.log(` ${url}`);
4130
4551
  }
4131
4552
  console.log("");
4132
- }
4133
- async function unshare(opts) {
4134
- const dir = path18.resolve(opts.dir);
4553
+ };
4554
+ var unshare = async (opts) => {
4555
+ const dir = path21.resolve(opts.dir);
4135
4556
  const target = resolveArtifactTarget(dir, {
4136
4557
  apiKey: opts.apiKey,
4137
4558
  artifact: opts.artifact
@@ -4151,18 +4572,16 @@ async function unshare(opts) {
4151
4572
  console.log(`
4152
4573
  \u2713 Revoked access for ${opts.email}
4153
4574
  `);
4154
- }
4575
+ };
4155
4576
 
4156
4577
  // src/status.ts
4157
- import path19 from "path";
4158
- async function status(opts) {
4159
- if (!AGENT_STATES.includes(opts.state)) {
4160
- throw new Error(
4161
- `State must be one of: ${AGENT_STATES.join(", ")} (got "${opts.state}").`
4162
- );
4578
+ import path22 from "path";
4579
+ var status = async (opts) => {
4580
+ const state = AGENT_STATES.find((candidate) => candidate === opts.state);
4581
+ if (!state) {
4582
+ throw new Error(`State must be one of: ${AGENT_STATES.join(", ")} (got "${opts.state}").`);
4163
4583
  }
4164
- const state = opts.state;
4165
- const dir = path19.resolve(opts.dir);
4584
+ const dir = path22.resolve(opts.dir);
4166
4585
  const target = resolveArtifactTarget(dir, {
4167
4586
  apiKey: opts.apiKey,
4168
4587
  artifact: opts.artifact
@@ -4185,163 +4604,16 @@ async function status(opts) {
4185
4604
  }
4186
4605
  const visible = res.ttlSeconds >= 120 ? `~${Math.round(res.ttlSeconds / 60)}m` : `~${res.ttlSeconds}s`;
4187
4606
  console.log(`\u2713 Presence: ${state} (visible ${visible})`);
4188
- }
4189
-
4190
- // src/update.ts
4191
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
4192
- import { createRequire } from "module";
4193
- import { homedir as homedir2 } from "os";
4194
- import path20 from "path";
4195
- import { z as z25 } from "zod";
4196
- var REGISTRY_LATEST_URL = "https://registry.npmjs.org/artifact-cafe/latest";
4197
- var SKILL_SOURCE_URL = "https://raw.githubusercontent.com/Artifact-Cafe/skill/main/artifact-cafe/SKILL.md";
4198
- var SKILL_UPDATE_COMMAND = "npx skills add artifact-cafe/skill --skill artifact-cafe -g";
4199
- var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
4200
- var FETCH_TIMEOUT_MS = 2e3;
4201
- var SKILL_VERSION_PATTERN = /\*\*Skill version:\s*(\d+\.\d+\.\d+)\*\*/;
4202
- var cacheSchema = z25.object({
4203
- checkedAt: z25.number(),
4204
- cliLatest: z25.string().nullable(),
4205
- skillLatest: z25.string().nullable(),
4206
- version: z25.literal(1)
4207
- });
4208
- function cliVersion() {
4209
- const pkg = createRequire(import.meta.url)("../package.json");
4210
- return pkg.version;
4211
- }
4212
- function cachePath() {
4213
- return path20.join(homedir2(), ".artifactcafe", "update-check.json");
4214
- }
4215
- function readCache() {
4216
- try {
4217
- return cacheSchema.parse(JSON.parse(readFileSync6(cachePath(), "utf8")));
4218
- } catch {
4219
- return null;
4220
- }
4221
- }
4222
- function writeCache(cache) {
4223
- try {
4224
- mkdirSync3(path20.dirname(cachePath()), { mode: 448, recursive: true });
4225
- writeFileSync3(cachePath(), `${JSON.stringify(cache, null, 2)}
4226
- `);
4227
- } catch {
4228
- }
4229
- }
4230
- function isNewerVersion(candidate, current) {
4231
- const parse = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
4232
- const a = parse(candidate);
4233
- const b = parse(current);
4234
- for (let i = 0; i < 3; i++) {
4235
- if ((a[i] ?? 0) !== (b[i] ?? 0)) {
4236
- return (a[i] ?? 0) > (b[i] ?? 0);
4237
- }
4238
- }
4239
- return false;
4240
- }
4241
- async function fetchJson(url) {
4242
- const res = await fetch(url, {
4243
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
4244
- });
4245
- if (!res.ok) {
4246
- throw new Error(`GET ${url} \u2192 ${res.status}`);
4247
- }
4248
- return res.json();
4249
- }
4250
- async function fetchText(url) {
4251
- const res = await fetch(url, {
4252
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
4253
- });
4254
- if (!res.ok) {
4255
- throw new Error(`GET ${url} \u2192 ${res.status}`);
4256
- }
4257
- return res.text();
4258
- }
4259
- async function fetchLatestVersions(previous) {
4260
- const [cli, skill] = await Promise.all([
4261
- fetchJson(REGISTRY_LATEST_URL).then((body) => z25.object({ version: z25.string() }).parse(body).version).catch(() => null),
4262
- fetchText(SKILL_SOURCE_URL).then((body) => SKILL_VERSION_PATTERN.exec(body)?.[1] ?? null).catch(() => null)
4263
- ]);
4264
- return {
4265
- checkedAt: Date.now(),
4266
- cliLatest: cli ?? previous?.cliLatest ?? null,
4267
- skillLatest: skill ?? previous?.skillLatest ?? null,
4268
- version: 1
4269
- };
4270
- }
4271
- function installedSkillVersions() {
4272
- const roots = [homedir2(), process.cwd()];
4273
- const dirs = [".agents", ".claude"];
4274
- const versions = [];
4275
- for (const root of roots) {
4276
- for (const dir of dirs) {
4277
- const file = path20.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
4278
- if (!existsSync3(file)) {
4279
- continue;
4280
- }
4281
- try {
4282
- const found = SKILL_VERSION_PATTERN.exec(readFileSync6(file, "utf8"));
4283
- if (found?.[1]) {
4284
- versions.push(found[1]);
4285
- }
4286
- } catch {
4287
- }
4288
- }
4289
- }
4290
- return versions;
4291
- }
4292
- function updateChecksDisabled() {
4293
- return Boolean(
4294
- process.env.ARTIFACT_CAFE_NO_UPDATE_CHECK ?? process.env.NO_UPDATE_NOTIFIER ?? process.env.CI
4295
- );
4296
- }
4297
- function startUpdateCheck() {
4298
- if (updateChecksDisabled()) {
4299
- return () => Promise.resolve();
4300
- }
4301
- const pending = (async () => {
4302
- const cached = readCache();
4303
- if (cached && Date.now() - cached.checkedAt < CHECK_TTL_MS) {
4304
- return cached;
4305
- }
4306
- const fresh = await fetchLatestVersions(cached);
4307
- writeCache(fresh);
4308
- return fresh;
4309
- })().catch(() => null);
4310
- return async () => {
4311
- const latest = await pending;
4312
- if (!latest) {
4313
- return;
4314
- }
4315
- const notices = [];
4316
- const current = cliVersion();
4317
- if (latest.cliLatest && isNewerVersion(latest.cliLatest, current)) {
4318
- notices.push(
4319
- `[artifact-cafe] CLI update available: ${current} \u2192 ${latest.cliLatest}. Run commands via \`npx artifact-cafe@latest\`; update a global install with \`npm i -g artifact-cafe@latest\`.`
4320
- );
4321
- }
4322
- if (latest.skillLatest) {
4323
- const stale = installedSkillVersions().filter(
4324
- (version) => isNewerVersion(latest.skillLatest, version)
4325
- ).sort((a, b) => isNewerVersion(a, b) ? 1 : -1);
4326
- if (stale.length > 0) {
4327
- notices.push(
4328
- `[artifact-cafe] Skill update available: ${stale[0]} \u2192 ${latest.skillLatest}. Update: \`${SKILL_UPDATE_COMMAND}\` (drop -g for a repo-local copy), then re-read SKILL.md.`
4329
- );
4330
- }
4331
- }
4332
- if (notices.length > 0) {
4333
- console.error(`
4334
- ${notices.join("\n")}`);
4335
- }
4336
- };
4337
- }
4607
+ };
4338
4608
 
4339
4609
  // src/workspaces.ts
4340
- async function workspaces(opts) {
4341
- const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
4342
- /\/$/,
4343
- ""
4344
- );
4610
+ var printWorkspace = (workspace) => {
4611
+ console.log(`${workspace.name} (${workspace.role ?? "non-member"})`);
4612
+ console.log(` ${workspace.handle} \xB7 ${workspace.id}
4613
+ `);
4614
+ };
4615
+ var workspaces = async (opts) => {
4616
+ const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/u, "");
4345
4617
  const key = resolveApiKey(apiUrl2, opts.apiKey);
4346
4618
  if (!key) {
4347
4619
  throw new Error(
@@ -4363,17 +4635,12 @@ async function workspaces(opts) {
4363
4635
  for (const workspace of data2.workspaces) {
4364
4636
  printWorkspace(workspace);
4365
4637
  }
4366
- }
4367
- function printWorkspace(workspace) {
4368
- console.log(`${workspace.name} (${workspace.role ?? "non-member"})`);
4369
- console.log(` ${workspace.handle} \xB7 ${workspace.id}
4370
- `);
4371
- }
4638
+ };
4372
4639
 
4373
4640
  // src/index.ts
4374
- function readStringArg(argv, flag) {
4641
+ var readStringArg = (argv, flag) => {
4375
4642
  const prefix = `${flag}=`;
4376
- for (let i = 0; i < argv.length; i++) {
4643
+ for (let i = 0; i < argv.length; i += 1) {
4377
4644
  const token = argv[i];
4378
4645
  if (token === void 0) {
4379
4646
  continue;
@@ -4385,7 +4652,36 @@ function readStringArg(argv, flag) {
4385
4652
  return token.slice(prefix.length);
4386
4653
  }
4387
4654
  }
4388
- }
4655
+ };
4656
+ var pull2 = defineCommand({
4657
+ args: {
4658
+ "api-key": {
4659
+ description: "Authorize with an account key or agent token (default: env or login)",
4660
+ type: "string"
4661
+ },
4662
+ artifact: { description: "Artifact ID (default: env or folder config)", type: "string" },
4663
+ dir: { default: ".", description: "Directory to download into", type: "positional" },
4664
+ force: { description: "Overwrite matching files in a non-empty directory", type: "boolean" },
4665
+ json: { description: "Machine-readable output", type: "boolean" },
4666
+ version: { description: "Version number (default: current)", type: "string" }
4667
+ },
4668
+ meta: { description: "Download all files of an artifact version", name: "pull" },
4669
+ async run({ args }) {
4670
+ try {
4671
+ await pull({
4672
+ apiKey: args["api-key"],
4673
+ artifact: args.artifact,
4674
+ dir: args.dir,
4675
+ force: Boolean(args.force),
4676
+ json: Boolean(args.json),
4677
+ version: args.version
4678
+ });
4679
+ } catch (error) {
4680
+ printCliError(error, Boolean(args.json));
4681
+ process.exit(1);
4682
+ }
4683
+ }
4684
+ });
4389
4685
  var publish2 = defineCommand({
4390
4686
  args: {
4391
4687
  "api-key": {
@@ -4414,6 +4710,7 @@ var publish2 = defineCommand({
4414
4710
  description: "Publish into this workspace folder name (or id)",
4415
4711
  type: "string"
4416
4712
  },
4713
+ force: { description: "Publish even if a newer version exists", type: "boolean" },
4417
4714
  json: { description: "Machine-readable output", type: "boolean" },
4418
4715
  "no-open": { description: "Don't open the review URL", type: "boolean" },
4419
4716
  notes: {
@@ -4448,6 +4745,7 @@ var publish2 = defineCommand({
4448
4745
  entry: args.entry,
4449
4746
  expiresIn: args["expires-in"],
4450
4747
  folder: args.folder,
4748
+ force: Boolean(args.force),
4451
4749
  json: Boolean(args.json),
4452
4750
  notes: readStringArg(process.argv.slice(2), "--notes"),
4453
4751
  open: !args["no-open"],
@@ -4456,8 +4754,8 @@ var publish2 = defineCommand({
4456
4754
  title: args.title,
4457
4755
  workspace: args.workspace
4458
4756
  });
4459
- } catch (err) {
4460
- printCliError(err, Boolean(args.json));
4757
+ } catch (error) {
4758
+ printCliError(error, Boolean(args.json));
4461
4759
  process.exit(1);
4462
4760
  }
4463
4761
  }
@@ -4477,8 +4775,8 @@ var login2 = defineCommand({
4477
4775
  async run({ args }) {
4478
4776
  try {
4479
4777
  await login({ apiKey: args["api-key"], json: Boolean(args.json) });
4480
- } catch (err) {
4481
- printCliError(err, Boolean(args.json));
4778
+ } catch (error) {
4779
+ printCliError(error, Boolean(args.json));
4482
4780
  process.exit(1);
4483
4781
  }
4484
4782
  }
@@ -4494,8 +4792,8 @@ var logout2 = defineCommand({
4494
4792
  run({ args }) {
4495
4793
  try {
4496
4794
  logout({ json: Boolean(args.json) });
4497
- } catch (err) {
4498
- printCliError(err, Boolean(args.json));
4795
+ } catch (error) {
4796
+ printCliError(error, Boolean(args.json));
4499
4797
  process.exit(1);
4500
4798
  }
4501
4799
  }
@@ -4511,8 +4809,8 @@ var whoami2 = defineCommand({
4511
4809
  async run({ args }) {
4512
4810
  try {
4513
4811
  await whoami({ json: Boolean(args.json) });
4514
- } catch (err) {
4515
- printCliError(err, Boolean(args.json));
4812
+ } catch (error) {
4813
+ printCliError(error, Boolean(args.json));
4516
4814
  process.exit(1);
4517
4815
  }
4518
4816
  }
@@ -4540,8 +4838,8 @@ var list2 = defineCommand({
4540
4838
  json: Boolean(args.json),
4541
4839
  workspace: args.workspace
4542
4840
  });
4543
- } catch (err) {
4544
- printCliError(err, Boolean(args.json));
4841
+ } catch (error) {
4842
+ printCliError(error, Boolean(args.json));
4545
4843
  process.exit(1);
4546
4844
  }
4547
4845
  }
@@ -4587,8 +4885,8 @@ var folders2 = defineCommand({
4587
4885
  visibility: args.visibility,
4588
4886
  workspace: args.workspace
4589
4887
  });
4590
- } catch (err) {
4591
- printCliError(err, Boolean(args.json));
4888
+ } catch (error) {
4889
+ printCliError(error, Boolean(args.json));
4592
4890
  process.exit(1);
4593
4891
  }
4594
4892
  }
@@ -4632,8 +4930,8 @@ var move2 = defineCommand({
4632
4930
  json: Boolean(args.json),
4633
4931
  unfiled: Boolean(args.unfiled)
4634
4932
  });
4635
- } catch (err) {
4636
- printCliError(err, Boolean(args.json));
4933
+ } catch (error) {
4934
+ printCliError(error, Boolean(args.json));
4637
4935
  process.exit(1);
4638
4936
  }
4639
4937
  }
@@ -4656,8 +4954,8 @@ var workspaces2 = defineCommand({
4656
4954
  apiKey: args["api-key"],
4657
4955
  json: Boolean(args.json)
4658
4956
  });
4659
- } catch (err) {
4660
- printCliError(err, Boolean(args.json));
4957
+ } catch (error) {
4958
+ printCliError(error, Boolean(args.json));
4661
4959
  process.exit(1);
4662
4960
  }
4663
4961
  }
@@ -4696,8 +4994,8 @@ var link2 = defineCommand({
4696
4994
  json: Boolean(args.json),
4697
4995
  slug: args.slug
4698
4996
  });
4699
- } catch (err) {
4700
- printCliError(err, Boolean(args.json));
4997
+ } catch (error) {
4998
+ printCliError(error, Boolean(args.json));
4701
4999
  process.exit(1);
4702
5000
  }
4703
5001
  }
@@ -4738,8 +5036,8 @@ var expiration2 = defineCommand({
4738
5036
  json: Boolean(args.json),
4739
5037
  keep: Boolean(args.keep)
4740
5038
  });
4741
- } catch (err) {
4742
- printCliError(err, Boolean(args.json));
5039
+ } catch (error) {
5040
+ printCliError(error, Boolean(args.json));
4743
5041
  process.exitCode = 1;
4744
5042
  }
4745
5043
  }
@@ -4788,9 +5086,7 @@ var comments2 = defineCommand({
4788
5086
  try {
4789
5087
  const timeout = Number(args.timeout);
4790
5088
  if (!Number.isFinite(timeout) || timeout <= 0) {
4791
- throw new Error(
4792
- `--timeout must be a positive number of seconds (got "${args.timeout}").`
4793
- );
5089
+ throw new Error(`--timeout must be a positive number of seconds (got "${args.timeout}").`);
4794
5090
  }
4795
5091
  await comments({
4796
5092
  apiKey: args["api-key"],
@@ -4802,8 +5098,8 @@ var comments2 = defineCommand({
4802
5098
  version: args.version,
4803
5099
  wait: Boolean(args.wait)
4804
5100
  });
4805
- } catch (err) {
4806
- printCliError(err, Boolean(args.json));
5101
+ } catch (error) {
5102
+ printCliError(error, Boolean(args.json));
4807
5103
  process.exit(1);
4808
5104
  }
4809
5105
  }
@@ -4848,9 +5144,7 @@ var listen = defineCommand({
4848
5144
  try {
4849
5145
  const timeout = Number(args.timeout);
4850
5146
  if (!Number.isFinite(timeout) || timeout <= 0) {
4851
- throw new Error(
4852
- `--timeout must be a positive number of seconds (got "${args.timeout}").`
4853
- );
5147
+ throw new Error(`--timeout must be a positive number of seconds (got "${args.timeout}").`);
4854
5148
  }
4855
5149
  await comments({
4856
5150
  apiKey: args["api-key"],
@@ -4862,8 +5156,8 @@ var listen = defineCommand({
4862
5156
  version: args.version,
4863
5157
  wait: true
4864
5158
  });
4865
- } catch (err) {
4866
- printCliError(err, Boolean(args.json));
5159
+ } catch (error) {
5160
+ printCliError(error, Boolean(args.json));
4867
5161
  process.exit(1);
4868
5162
  }
4869
5163
  }
@@ -4903,8 +5197,8 @@ var statusCmd = defineCommand({
4903
5197
  json: Boolean(args.json),
4904
5198
  state: args.state
4905
5199
  });
4906
- } catch (err) {
4907
- printCliError(err, Boolean(args.json));
5200
+ } catch (error) {
5201
+ printCliError(error, Boolean(args.json));
4908
5202
  process.exit(1);
4909
5203
  }
4910
5204
  }
@@ -4942,8 +5236,8 @@ var protect2 = defineCommand({
4942
5236
  off: Boolean(args.off),
4943
5237
  password: args.password
4944
5238
  });
4945
- } catch (err) {
4946
- printCliError(err, Boolean(args.json));
5239
+ } catch (error) {
5240
+ printCliError(error, Boolean(args.json));
4947
5241
  process.exit(1);
4948
5242
  }
4949
5243
  }
@@ -4983,8 +5277,8 @@ var review2 = defineCommand({
4983
5277
  json: Boolean(args.json),
4984
5278
  mode: args.mode
4985
5279
  });
4986
- } catch (err) {
4987
- printCliError(err, Boolean(args.json));
5280
+ } catch (error) {
5281
+ printCliError(error, Boolean(args.json));
4988
5282
  process.exit(1);
4989
5283
  }
4990
5284
  }
@@ -5029,8 +5323,8 @@ var threadStatusCommand = (action) => defineCommand({
5029
5323
  json: Boolean(args.json),
5030
5324
  thread: args.thread
5031
5325
  });
5032
- } catch (err) {
5033
- printCliError(err, Boolean(args.json));
5326
+ } catch (error) {
5327
+ printCliError(error, Boolean(args.json));
5034
5328
  process.exit(1);
5035
5329
  }
5036
5330
  }
@@ -5088,8 +5382,8 @@ var reply = defineCommand({
5088
5382
  resolve: Boolean(args.resolve),
5089
5383
  thread: args.thread
5090
5384
  });
5091
- } catch (err) {
5092
- printCliError(err, Boolean(args.json));
5385
+ } catch (error) {
5386
+ printCliError(error, Boolean(args.json));
5093
5387
  process.exit(1);
5094
5388
  }
5095
5389
  }
@@ -5158,8 +5452,8 @@ var comment2 = defineCommand({
5158
5452
  suffix: args.suffix,
5159
5453
  via: args.via
5160
5454
  });
5161
- } catch (err) {
5162
- printCliError(err, Boolean(args.json));
5455
+ } catch (error) {
5456
+ printCliError(error, Boolean(args.json));
5163
5457
  process.exit(1);
5164
5458
  }
5165
5459
  }
@@ -5201,8 +5495,8 @@ var edit = defineCommand({
5201
5495
  json: Boolean(args.json),
5202
5496
  message: args.message
5203
5497
  });
5204
- } catch (err) {
5205
- printCliError(err, Boolean(args.json));
5498
+ } catch (error) {
5499
+ printCliError(error, Boolean(args.json));
5206
5500
  process.exit(1);
5207
5501
  }
5208
5502
  }
@@ -5239,8 +5533,8 @@ var del = defineCommand({
5239
5533
  json: Boolean(args.json),
5240
5534
  message: args.message
5241
5535
  });
5242
- } catch (err) {
5243
- printCliError(err, Boolean(args.json));
5536
+ } catch (error) {
5537
+ printCliError(error, Boolean(args.json));
5244
5538
  process.exit(1);
5245
5539
  }
5246
5540
  }
@@ -5281,8 +5575,8 @@ var share2 = defineCommand({
5281
5575
  json: Boolean(args.json),
5282
5576
  list: Boolean(args.list)
5283
5577
  });
5284
- } catch (err) {
5285
- printCliError(err, Boolean(args.json));
5578
+ } catch (error) {
5579
+ printCliError(error, Boolean(args.json));
5286
5580
  process.exit(1);
5287
5581
  }
5288
5582
  }
@@ -5322,8 +5616,8 @@ var unshare2 = defineCommand({
5322
5616
  email: args.email,
5323
5617
  json: Boolean(args.json)
5324
5618
  });
5325
- } catch (err) {
5326
- printCliError(err, Boolean(args.json));
5619
+ } catch (error) {
5620
+ printCliError(error, Boolean(args.json));
5327
5621
  process.exit(1);
5328
5622
  }
5329
5623
  }
@@ -5352,8 +5646,8 @@ var open = defineCommand({
5352
5646
  artifact: args.artifact,
5353
5647
  dir: args.dir
5354
5648
  });
5355
- } catch (err) {
5356
- printCliError(err, false);
5649
+ } catch (error) {
5650
+ printCliError(error, false);
5357
5651
  process.exit(1);
5358
5652
  }
5359
5653
  }
@@ -5373,8 +5667,8 @@ var dashboard = defineCommand({
5373
5667
  run({ args }) {
5374
5668
  try {
5375
5669
  openDashboard({ dir: args.dir });
5376
- } catch (err) {
5377
- printCliError(err, false);
5670
+ } catch (error) {
5671
+ printCliError(error, false);
5378
5672
  process.exit(1);
5379
5673
  }
5380
5674
  }
@@ -5406,8 +5700,8 @@ var dataLsCommand = defineCommand({
5406
5700
  json: Boolean(args.json),
5407
5701
  limit: args.limit === void 0 ? void 0 : Number(args.limit)
5408
5702
  });
5409
- } catch (err) {
5410
- printCliError(err, Boolean(args.json));
5703
+ } catch (error) {
5704
+ printCliError(error, Boolean(args.json));
5411
5705
  process.exit(1);
5412
5706
  }
5413
5707
  }
@@ -5447,8 +5741,8 @@ var dataInsertCommand = defineCommand({
5447
5741
  json: Boolean(args.json),
5448
5742
  record: args.record
5449
5743
  });
5450
- } catch (err) {
5451
- printCliError(err, Boolean(args.json));
5744
+ } catch (error) {
5745
+ printCliError(error, Boolean(args.json));
5452
5746
  process.exit(1);
5453
5747
  }
5454
5748
  }
@@ -5474,8 +5768,8 @@ var dataExportCommand = defineCommand({
5474
5768
  async run({ args }) {
5475
5769
  try {
5476
5770
  await dataExport({ dir: args.dir });
5477
- } catch (err) {
5478
- printCliError(err, true);
5771
+ } catch (error) {
5772
+ printCliError(error, true);
5479
5773
  process.exit(1);
5480
5774
  }
5481
5775
  }
@@ -5507,8 +5801,8 @@ var dataClearCommand = defineCommand({
5507
5801
  json: Boolean(args.json),
5508
5802
  yes: Boolean(args.yes)
5509
5803
  });
5510
- } catch (err) {
5511
- printCliError(err, Boolean(args.json));
5804
+ } catch (error) {
5805
+ printCliError(error, Boolean(args.json));
5512
5806
  process.exit(1);
5513
5807
  }
5514
5808
  }
@@ -5561,6 +5855,7 @@ var main = defineCommand({
5561
5855
  open,
5562
5856
  protect: protect2,
5563
5857
  publish: publish2,
5858
+ pull: pull2,
5564
5859
  reopen,
5565
5860
  reply,
5566
5861
  resolve,