artifact-cafe 0.0.15 → 0.0.17
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.
- package/dist/index.js +1544 -616
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -7,24 +7,17 @@ import { defineCommand, runMain, showUsage } from "citty";
|
|
|
7
7
|
var AMBER = "\x1B[38;2;214;138;57m";
|
|
8
8
|
var BOLD = "\x1B[1m";
|
|
9
9
|
var RESET = "\x1B[0m";
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
|
|
14
|
-
" \u2502 \u251C\u2500\u256E",
|
|
15
|
-
" \u2502 \u2502 \u2502",
|
|
16
|
-
" \u2502 \u251C\u2500\u256F",
|
|
17
|
-
" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
|
|
18
|
-
" \u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E"
|
|
19
|
-
];
|
|
10
|
+
var TICK_TOP = " \u250C \u2510";
|
|
11
|
+
var GLYPH = " \xE3";
|
|
12
|
+
var TICK_BOTTOM = " \u2514 \u2518";
|
|
20
13
|
var WORDMARK = " artifact.cafe";
|
|
21
14
|
function printBanner() {
|
|
22
15
|
const color = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
23
16
|
const paint = (line, codes) => color ? `${codes}${line}${RESET}` : line;
|
|
24
17
|
console.log();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
console.log(paint(TICK_TOP, AMBER));
|
|
19
|
+
console.log(paint(GLYPH, BOLD));
|
|
20
|
+
console.log(paint(TICK_BOTTOM, AMBER));
|
|
28
21
|
console.log();
|
|
29
22
|
console.log(paint(WORDMARK, AMBER + BOLD));
|
|
30
23
|
console.log();
|
|
@@ -69,8 +62,10 @@ var meResponseSchema = z.object({
|
|
|
69
62
|
userId: z.string()
|
|
70
63
|
});
|
|
71
64
|
var artifactListItemSchema = z.object({
|
|
65
|
+
autoDeleteAt: z.string().datetime().nullable().optional(),
|
|
72
66
|
createdAt: z.string(),
|
|
73
67
|
currentVersionNumber: z.number().int().positive().nullable(),
|
|
68
|
+
expiresAt: z.string().datetime().nullable().optional(),
|
|
74
69
|
id: z.string(),
|
|
75
70
|
openCommentCount: z.number().int().nonnegative(),
|
|
76
71
|
slug: z.string(),
|
|
@@ -181,7 +176,7 @@ var workspaceAvatarResponseSchema = z3.object({
|
|
|
181
176
|
});
|
|
182
177
|
|
|
183
178
|
// ../shared/src/comments.ts
|
|
184
|
-
import { z as
|
|
179
|
+
import { z as z8 } from "zod";
|
|
185
180
|
|
|
186
181
|
// ../shared/src/emoji.ts
|
|
187
182
|
import { z as z4 } from "zod";
|
|
@@ -193,15 +188,38 @@ function canonicalizeEmoji(s) {
|
|
|
193
188
|
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.");
|
|
194
189
|
|
|
195
190
|
// ../shared/src/publish.ts
|
|
196
|
-
import { z as
|
|
191
|
+
import { z as z7 } from "zod";
|
|
197
192
|
|
|
198
|
-
// ../shared/src/
|
|
193
|
+
// ../shared/src/expiration.ts
|
|
199
194
|
import { z as z5 } from "zod";
|
|
200
|
-
var
|
|
201
|
-
var
|
|
195
|
+
var MAX_EXPIRATION_SECONDS = 2147483647;
|
|
196
|
+
var expirationSecondsSchema = z5.number().int().positive().max(MAX_EXPIRATION_SECONDS);
|
|
197
|
+
var setExpirationSchema = z5.object({
|
|
198
|
+
expiresInSeconds: expirationSecondsSchema.nullable()
|
|
199
|
+
});
|
|
200
|
+
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;
|
|
208
|
+
if (!expirationSecondsSchema.safeParse(seconds).success) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`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
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return seconds;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ../shared/src/review-mode.ts
|
|
217
|
+
import { z as z6 } from "zod";
|
|
218
|
+
var reviewModeSchema = z6.enum(["open", "readonly", "off"]);
|
|
219
|
+
var setReviewModeSchema = z6.object({
|
|
202
220
|
mode: reviewModeSchema
|
|
203
221
|
});
|
|
204
|
-
var reviewModeResponseSchema =
|
|
222
|
+
var reviewModeResponseSchema = z6.object({
|
|
205
223
|
mode: reviewModeSchema
|
|
206
224
|
});
|
|
207
225
|
|
|
@@ -220,274 +238,300 @@ function isSafeArtifactPath(p) {
|
|
|
220
238
|
}
|
|
221
239
|
var CARD_DESCRIPTION_MAX = 300;
|
|
222
240
|
var CARD_IMAGE_MAX_BYTES = 5 * 1024 * 1024;
|
|
223
|
-
var manifestFileSchema =
|
|
224
|
-
hash:
|
|
225
|
-
mimeType:
|
|
226
|
-
path:
|
|
241
|
+
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" }),
|
|
244
|
+
path: z7.string().min(1).max(1024).refine(isSafeArtifactPath, {
|
|
227
245
|
message: "path must be a safe relative path"
|
|
228
246
|
}),
|
|
229
|
-
size:
|
|
247
|
+
size: z7.number().int().positive()
|
|
230
248
|
});
|
|
231
|
-
var publishSourceSchema =
|
|
232
|
-
agent:
|
|
233
|
-
runId:
|
|
234
|
-
type:
|
|
249
|
+
var publishSourceSchema = z7.object({
|
|
250
|
+
agent: z7.string().optional(),
|
|
251
|
+
runId: z7.string().optional(),
|
|
252
|
+
type: z7.literal("cli")
|
|
235
253
|
});
|
|
236
|
-
var createPublishSessionSchema =
|
|
237
|
-
artifactId:
|
|
254
|
+
var createPublishSessionSchema = z7.object({
|
|
255
|
+
artifactId: z7.string().nullable(),
|
|
238
256
|
// One-line share-card summary (`--description`). Overrides whatever the
|
|
239
257
|
// entry HTML declares; leave it off and the API reads the document's own
|
|
240
258
|
// `og:description`/`description` at finalize.
|
|
241
|
-
description:
|
|
242
|
-
entryPath:
|
|
243
|
-
|
|
259
|
+
description: z7.string().max(CARD_DESCRIPTION_MAX).optional(),
|
|
260
|
+
entryPath: z7.string().optional(),
|
|
261
|
+
expiresInSeconds: expirationSecondsSchema.optional(),
|
|
262
|
+
files: z7.array(manifestFileSchema).min(1).max(1e3),
|
|
244
263
|
// SPEC-2 §6.4 — workspace placement is available only to authenticated
|
|
245
264
|
// members; the API enforces membership and folder ownership. folderId
|
|
246
265
|
// without workspaceId resolves against the account's personal workspace.
|
|
247
|
-
folderId:
|
|
266
|
+
folderId: z7.string().min(1).optional(),
|
|
248
267
|
// Bullet-point changelog for this version, authored by the publisher.
|
|
249
268
|
// Optional on every publish (incl. v1); stored immutably on the version.
|
|
250
|
-
notes:
|
|
269
|
+
notes: z7.string().max(4e3).optional(),
|
|
251
270
|
source: publishSourceSchema,
|
|
252
|
-
title:
|
|
253
|
-
workspaceId:
|
|
254
|
-
});
|
|
255
|
-
var publishSessionResponseSchema =
|
|
256
|
-
artifactId:
|
|
257
|
-
expiresAt:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
271
|
+
title: z7.string().max(200).optional(),
|
|
272
|
+
workspaceId: z7.string().min(1).optional()
|
|
273
|
+
});
|
|
274
|
+
var publishSessionResponseSchema = z7.object({
|
|
275
|
+
artifactId: z7.string(),
|
|
276
|
+
expiresAt: z7.string(),
|
|
277
|
+
expiresInSeconds: expirationSecondsSchema.nullable().optional(),
|
|
278
|
+
filesToUpload: z7.array(
|
|
279
|
+
z7.object({
|
|
280
|
+
method: z7.literal("PUT"),
|
|
281
|
+
path: z7.string(),
|
|
282
|
+
uploadUrl: z7.string().url()
|
|
263
283
|
})
|
|
264
284
|
),
|
|
265
|
-
publishSessionId:
|
|
266
|
-
skippedFiles:
|
|
267
|
-
versionId:
|
|
285
|
+
publishSessionId: z7.string(),
|
|
286
|
+
skippedFiles: z7.array(z7.string()),
|
|
287
|
+
versionId: z7.string()
|
|
268
288
|
});
|
|
269
|
-
var finalizeResponseSchema =
|
|
270
|
-
artifactId:
|
|
289
|
+
var finalizeResponseSchema = z7.object({
|
|
290
|
+
artifactId: z7.string(),
|
|
291
|
+
autoDeleteAt: z7.string().datetime().nullable().optional(),
|
|
271
292
|
// API-key publishes create the artifact already claimed (SPEC §5.4) —
|
|
272
293
|
// the CLI uses this to tell "claim URL pending" from "already yours".
|
|
273
|
-
claimed:
|
|
294
|
+
claimed: z7.boolean(),
|
|
274
295
|
// Returned only on first publish of a new artifact (one-time).
|
|
275
|
-
claimUrl:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
296
|
+
claimUrl: z7.string().url().optional(),
|
|
297
|
+
expiresAt: z7.string().datetime().nullable().optional(),
|
|
298
|
+
publishToken: z7.string().optional(),
|
|
299
|
+
url: z7.string().url(),
|
|
300
|
+
versionId: z7.string(),
|
|
301
|
+
versionNumber: z7.number().int().positive(),
|
|
280
302
|
// SPEC-2 §5.2 — who can open the review URL. A publish into a folder
|
|
281
303
|
// inherits that folder's visibility, so this is the only place the CLI
|
|
282
304
|
// learns that a link it just printed is members-only. Optional so a new
|
|
283
305
|
// CLI against an older API degrades to "unknown" instead of failing the
|
|
284
306
|
// publish over a field that only shapes the closing message.
|
|
285
|
-
visibility:
|
|
286
|
-
workspaceId:
|
|
287
|
-
});
|
|
288
|
-
var artifactResponseSchema =
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
307
|
+
visibility: z7.enum(["public", "unlisted", "workspace", "private"]).optional(),
|
|
308
|
+
workspaceId: z7.string().nullable().optional()
|
|
309
|
+
});
|
|
310
|
+
var artifactResponseSchema = z7.object({
|
|
311
|
+
autoDeleteAt: z7.string().datetime().nullable().optional(),
|
|
312
|
+
claimed: z7.boolean(),
|
|
313
|
+
currentVersion: z7.object({
|
|
314
|
+
entryUrl: z7.string().url(),
|
|
315
|
+
id: z7.string(),
|
|
316
|
+
versionNumber: z7.number().int().positive()
|
|
294
317
|
}).nullable(),
|
|
295
318
|
// Custom icon, or null to render the default document glyph.
|
|
296
|
-
emoji:
|
|
319
|
+
emoji: z7.string().nullable(),
|
|
320
|
+
expiresAt: z7.string().datetime().nullable().optional(),
|
|
297
321
|
// SPEC-2 §6.4 — placement + visibility surface on the artifact.
|
|
298
|
-
folderId:
|
|
299
|
-
id:
|
|
322
|
+
folderId: z7.string().nullable(),
|
|
323
|
+
id: z7.string(),
|
|
300
324
|
// SPEC-2 §5.8 — what a viewer may do once in (open / readonly / off).
|
|
301
325
|
reviewMode: reviewModeSchema,
|
|
302
|
-
slug:
|
|
303
|
-
title:
|
|
304
|
-
visibility:
|
|
305
|
-
workspaceId:
|
|
326
|
+
slug: z7.string(),
|
|
327
|
+
title: z7.string(),
|
|
328
|
+
visibility: z7.enum(["public", "unlisted", "workspace", "private"]),
|
|
329
|
+
workspaceId: z7.string().nullable()
|
|
306
330
|
});
|
|
331
|
+
function resolvePublishEntry(files, explicit) {
|
|
332
|
+
if (explicit !== void 0) {
|
|
333
|
+
if (!files.some((file) => file.path === explicit)) {
|
|
334
|
+
throw new Error(`Entry file "${explicit}" is not in the manifest.`);
|
|
335
|
+
}
|
|
336
|
+
return explicit;
|
|
337
|
+
}
|
|
338
|
+
const htmlFiles = files.filter(
|
|
339
|
+
(file) => file.mimeType === "text/html" && !file.path.includes("/")
|
|
340
|
+
);
|
|
341
|
+
if (htmlFiles.some((file) => file.path === "index.html")) {
|
|
342
|
+
return "index.html";
|
|
343
|
+
}
|
|
344
|
+
if (htmlFiles.length > 1) {
|
|
345
|
+
throw new Error(
|
|
346
|
+
`Multiple HTML files found (${htmlFiles.map((file) => file.path).join(", ")}). Specify an entry file.`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
return htmlFiles[0]?.path;
|
|
350
|
+
}
|
|
307
351
|
|
|
308
352
|
// ../shared/src/comments.ts
|
|
309
|
-
var anchorPageSchema =
|
|
353
|
+
var anchorPageSchema = z8.string().max(1024).refine(isSafeArtifactPath, {
|
|
310
354
|
message: "page must be a safe relative path"
|
|
311
355
|
}).optional();
|
|
312
|
-
var elementSnapshotSchema =
|
|
313
|
-
classes:
|
|
314
|
-
id:
|
|
315
|
-
tag:
|
|
316
|
-
text:
|
|
356
|
+
var elementSnapshotSchema = z8.object({
|
|
357
|
+
classes: z8.string().max(256).optional(),
|
|
358
|
+
id: z8.string().max(256).optional(),
|
|
359
|
+
tag: z8.string().min(1).max(128),
|
|
360
|
+
text: z8.string().max(200).optional()
|
|
317
361
|
});
|
|
318
|
-
var pointAnchorSchema =
|
|
362
|
+
var pointAnchorSchema = z8.object({
|
|
319
363
|
element: elementSnapshotSchema.optional(),
|
|
320
364
|
// elementPath is the primary anchor; x/y are offsets within the
|
|
321
365
|
// element's box, normalized 0..1 (SPEC.md §8.1).
|
|
322
|
-
elementPath:
|
|
366
|
+
elementPath: z8.string().max(2048),
|
|
323
367
|
page: anchorPageSchema,
|
|
324
|
-
type:
|
|
325
|
-
viewportHeight:
|
|
326
|
-
viewportWidth:
|
|
327
|
-
x:
|
|
328
|
-
y:
|
|
329
|
-
});
|
|
330
|
-
var textAnchorSchema =
|
|
331
|
-
elementPath:
|
|
332
|
-
endOffset:
|
|
368
|
+
type: z8.literal("point"),
|
|
369
|
+
viewportHeight: z8.number().int().positive().max(1e5),
|
|
370
|
+
viewportWidth: z8.number().int().positive().max(1e5),
|
|
371
|
+
x: z8.number().min(0).max(1),
|
|
372
|
+
y: z8.number().min(0).max(1)
|
|
373
|
+
});
|
|
374
|
+
var textAnchorSchema = z8.object({
|
|
375
|
+
elementPath: z8.string().max(2048),
|
|
376
|
+
endOffset: z8.number().int().nonnegative().max(1e8),
|
|
333
377
|
page: anchorPageSchema,
|
|
334
|
-
prefix:
|
|
335
|
-
quote:
|
|
336
|
-
startOffset:
|
|
337
|
-
suffix:
|
|
338
|
-
type:
|
|
378
|
+
prefix: z8.string().max(200).optional(),
|
|
379
|
+
quote: z8.string().min(1).max(1e3),
|
|
380
|
+
startOffset: z8.number().int().nonnegative().max(1e8),
|
|
381
|
+
suffix: z8.string().max(200).optional(),
|
|
382
|
+
type: z8.literal("text")
|
|
339
383
|
});
|
|
340
|
-
var pageAnchorSchema =
|
|
384
|
+
var pageAnchorSchema = z8.object({
|
|
341
385
|
page: anchorPageSchema,
|
|
342
|
-
type:
|
|
386
|
+
type: z8.literal("page")
|
|
343
387
|
});
|
|
344
|
-
var anchorSchema =
|
|
388
|
+
var anchorSchema = z8.discriminatedUnion("type", [
|
|
345
389
|
pointAnchorSchema,
|
|
346
390
|
textAnchorSchema,
|
|
347
391
|
pageAnchorSchema
|
|
348
392
|
]);
|
|
349
|
-
var displayNameSchema =
|
|
350
|
-
var guestSchema =
|
|
393
|
+
var displayNameSchema = z8.string().trim().min(1).max(64);
|
|
394
|
+
var guestSchema = z8.object({
|
|
351
395
|
displayName: displayNameSchema,
|
|
352
|
-
guestId:
|
|
396
|
+
guestId: z8.string().min(16)
|
|
353
397
|
});
|
|
354
|
-
var viaLabelSchema =
|
|
355
|
-
var createCommentThreadSchema =
|
|
398
|
+
var viaLabelSchema = z8.string().trim().min(1).max(64);
|
|
399
|
+
var createCommentThreadSchema = z8.object({
|
|
356
400
|
anchor: anchorSchema,
|
|
357
|
-
body:
|
|
401
|
+
body: z8.string().min(1).max(1e4),
|
|
358
402
|
guest: guestSchema.optional(),
|
|
359
|
-
versionId:
|
|
403
|
+
versionId: z8.string(),
|
|
360
404
|
via: viaLabelSchema.optional()
|
|
361
405
|
});
|
|
362
|
-
var createCommentMessageSchema =
|
|
363
|
-
body:
|
|
406
|
+
var createCommentMessageSchema = z8.object({
|
|
407
|
+
body: z8.string().min(1).max(1e4),
|
|
364
408
|
guest: guestSchema.optional(),
|
|
365
409
|
via: viaLabelSchema.optional()
|
|
366
410
|
});
|
|
367
|
-
var editCommentMessageSchema =
|
|
368
|
-
body:
|
|
411
|
+
var editCommentMessageSchema = z8.object({
|
|
412
|
+
body: z8.string().min(1).max(1e4),
|
|
369
413
|
guest: guestSchema.optional()
|
|
370
414
|
});
|
|
371
|
-
var threadActionSchema =
|
|
415
|
+
var threadActionSchema = z8.object({
|
|
372
416
|
guest: guestSchema.optional()
|
|
373
417
|
});
|
|
374
|
-
var reactionEmojiSchema =
|
|
375
|
-
var toggleReactionSchema =
|
|
418
|
+
var reactionEmojiSchema = z8.string().max(64).transform(canonicalizeEmoji).refine((s) => SINGLE_EMOJI.test(s), { message: "Must be a single emoji." }).describe("A single emoji, e.g. \u{1F44D}");
|
|
419
|
+
var toggleReactionSchema = z8.object({
|
|
376
420
|
emoji: reactionEmojiSchema,
|
|
377
421
|
guest: guestSchema.optional()
|
|
378
422
|
});
|
|
379
|
-
var commentAgentSchema =
|
|
380
|
-
id:
|
|
381
|
-
label:
|
|
423
|
+
var commentAgentSchema = z8.object({
|
|
424
|
+
id: z8.string().nullable(),
|
|
425
|
+
label: z8.string()
|
|
382
426
|
});
|
|
383
|
-
var commentAuthorSchema =
|
|
427
|
+
var commentAuthorSchema = z8.object({
|
|
384
428
|
agent: commentAgentSchema.nullable().default(null),
|
|
385
|
-
avatarUrl:
|
|
386
|
-
displayName:
|
|
387
|
-
guestId:
|
|
388
|
-
isViewer:
|
|
389
|
-
kind:
|
|
429
|
+
avatarUrl: z8.string().nullable(),
|
|
430
|
+
displayName: z8.string(),
|
|
431
|
+
guestId: z8.string().nullable(),
|
|
432
|
+
isViewer: z8.boolean(),
|
|
433
|
+
kind: z8.enum(["member", "guest"]),
|
|
390
434
|
// Optional "via" attribution (§6.4). Null for plain comments; a bounded
|
|
391
435
|
// label the shell appends after the display name. Defaulted for the same
|
|
392
436
|
// reason as `agent`: a server predating §6.4 omits the key entirely.
|
|
393
|
-
via:
|
|
437
|
+
via: z8.string().nullable().default(null)
|
|
394
438
|
});
|
|
395
|
-
var messageReactionSchema =
|
|
396
|
-
emoji:
|
|
397
|
-
reactors:
|
|
439
|
+
var messageReactionSchema = z8.object({
|
|
440
|
+
emoji: z8.string(),
|
|
441
|
+
reactors: z8.array(commentAuthorSchema)
|
|
398
442
|
});
|
|
399
|
-
var commentMessageSchema =
|
|
443
|
+
var commentMessageSchema = z8.object({
|
|
400
444
|
author: commentAuthorSchema,
|
|
401
445
|
// Empty string for a deleted message — the body is stripped server-side
|
|
402
446
|
// (§6.7.1 tombstone), never sent to the client and hidden there.
|
|
403
|
-
body:
|
|
404
|
-
createdAt:
|
|
447
|
+
body: z8.string(),
|
|
448
|
+
createdAt: z8.string(),
|
|
405
449
|
// Soft-deleted (§6.7.1). A tombstone keeps thread structure and replies;
|
|
406
450
|
// the shell renders "Comment deleted" instead of the (absent) body.
|
|
407
|
-
deleted:
|
|
451
|
+
deleted: z8.boolean(),
|
|
408
452
|
// Last edit time (§6.7.1), or null if never edited — drives the "edited"
|
|
409
453
|
// badge. No edit history is kept.
|
|
410
|
-
editedAt:
|
|
411
|
-
id:
|
|
454
|
+
editedAt: z8.string().nullable(),
|
|
455
|
+
id: z8.string(),
|
|
412
456
|
// Defaulted, not required: a new CLI parsing an older server's
|
|
413
457
|
// response must not fail on the missing key.
|
|
414
|
-
reactions:
|
|
458
|
+
reactions: z8.array(messageReactionSchema).default([])
|
|
415
459
|
});
|
|
416
|
-
var threadActorSchema =
|
|
417
|
-
displayName:
|
|
418
|
-
kind:
|
|
460
|
+
var threadActorSchema = z8.object({
|
|
461
|
+
displayName: z8.string(),
|
|
462
|
+
kind: z8.enum(["member", "guest", "agent"])
|
|
419
463
|
});
|
|
420
|
-
var commentThreadSchema =
|
|
464
|
+
var commentThreadSchema = z8.object({
|
|
421
465
|
// §6.6.1 — set while open to signal "seen, working on it"; cleared on
|
|
422
466
|
// reopen, kept (but superseded in display) once resolved. Defaulted so
|
|
423
467
|
// clients parsing an older server's response don't fail.
|
|
424
|
-
acknowledgedAt:
|
|
468
|
+
acknowledgedAt: z8.string().nullable().default(null),
|
|
425
469
|
acknowledgedBy: threadActorSchema.nullable().default(null),
|
|
426
470
|
anchor: anchorSchema,
|
|
427
|
-
artifactId:
|
|
428
|
-
createdAt:
|
|
471
|
+
artifactId: z8.string(),
|
|
472
|
+
createdAt: z8.string(),
|
|
429
473
|
createdBy: commentAuthorSchema,
|
|
430
|
-
id:
|
|
431
|
-
messages:
|
|
474
|
+
id: z8.string(),
|
|
475
|
+
messages: z8.array(commentMessageSchema),
|
|
432
476
|
// Null on threads resolved before actor recording existed, and always
|
|
433
477
|
// null while open.
|
|
434
478
|
resolvedBy: threadActorSchema.nullable().default(null),
|
|
435
|
-
status:
|
|
436
|
-
versionId:
|
|
479
|
+
status: z8.enum(["open", "resolved"]),
|
|
480
|
+
versionId: z8.string()
|
|
437
481
|
});
|
|
438
|
-
var commentThreadListSchema =
|
|
439
|
-
threads:
|
|
482
|
+
var commentThreadListSchema = z8.object({
|
|
483
|
+
threads: z8.array(commentThreadSchema)
|
|
440
484
|
});
|
|
441
|
-
var commentExportQuerySchema =
|
|
442
|
-
status:
|
|
443
|
-
version:
|
|
485
|
+
var commentExportQuerySchema = z8.object({
|
|
486
|
+
status: z8.enum(["open", "resolved", "all"]).default("open"),
|
|
487
|
+
version: z8.enum(["current", "all"]).default("current")
|
|
444
488
|
});
|
|
445
|
-
var exportReactionSchema =
|
|
446
|
-
count:
|
|
447
|
-
emoji:
|
|
489
|
+
var exportReactionSchema = z8.object({
|
|
490
|
+
count: z8.number().int().positive(),
|
|
491
|
+
emoji: z8.string()
|
|
448
492
|
});
|
|
449
|
-
var commentExportReplySchema =
|
|
450
|
-
author:
|
|
451
|
-
authorKind:
|
|
452
|
-
body:
|
|
453
|
-
createdAt:
|
|
493
|
+
var commentExportReplySchema = z8.object({
|
|
494
|
+
author: z8.string(),
|
|
495
|
+
authorKind: z8.enum(["member", "guest"]),
|
|
496
|
+
body: z8.string(),
|
|
497
|
+
createdAt: z8.string(),
|
|
454
498
|
// Soft-delete tombstone (§6.7.1): body is "" when true.
|
|
455
|
-
deleted:
|
|
456
|
-
editedAt:
|
|
499
|
+
deleted: z8.boolean(),
|
|
500
|
+
editedAt: z8.string().nullable(),
|
|
457
501
|
// The message id — what `edit`/`delete` target (§5.1).
|
|
458
|
-
id:
|
|
459
|
-
reactions:
|
|
502
|
+
id: z8.string(),
|
|
503
|
+
reactions: z8.array(exportReactionSchema).optional()
|
|
460
504
|
});
|
|
461
|
-
var commentExportThreadSchema =
|
|
505
|
+
var commentExportThreadSchema = z8.object({
|
|
462
506
|
// §6.6.1 — lets a polling agent skip threads it already acknowledged
|
|
463
507
|
// (acknowledged threads stay in status=open exports on purpose).
|
|
464
|
-
acknowledgedAt:
|
|
508
|
+
acknowledgedAt: z8.string().nullable().default(null),
|
|
465
509
|
anchor: anchorSchema,
|
|
466
|
-
author:
|
|
467
|
-
authorKind:
|
|
468
|
-
body:
|
|
469
|
-
createdAt:
|
|
470
|
-
deleted:
|
|
471
|
-
editedAt:
|
|
472
|
-
id:
|
|
510
|
+
author: z8.string(),
|
|
511
|
+
authorKind: z8.enum(["member", "guest"]),
|
|
512
|
+
body: z8.string(),
|
|
513
|
+
createdAt: z8.string(),
|
|
514
|
+
deleted: z8.boolean(),
|
|
515
|
+
editedAt: z8.string().nullable(),
|
|
516
|
+
id: z8.string(),
|
|
473
517
|
// The opening message's id (distinct from the thread `id`) — `edit`/
|
|
474
518
|
// `delete` operate on messages, so the CLI needs it to target the first
|
|
475
519
|
// comment, not just the replies.
|
|
476
|
-
messageId:
|
|
477
|
-
reactions:
|
|
478
|
-
replies:
|
|
520
|
+
messageId: z8.string(),
|
|
521
|
+
reactions: z8.array(exportReactionSchema).optional(),
|
|
522
|
+
replies: z8.array(commentExportReplySchema),
|
|
479
523
|
resolvedBy: threadActorSchema.nullable().default(null),
|
|
480
|
-
status:
|
|
481
|
-
versionNumber:
|
|
524
|
+
status: z8.enum(["open", "resolved"]),
|
|
525
|
+
versionNumber: z8.number().int().positive()
|
|
482
526
|
});
|
|
483
|
-
var commentExportSchema =
|
|
484
|
-
artifactId:
|
|
485
|
-
comments:
|
|
527
|
+
var commentExportSchema = z8.object({
|
|
528
|
+
artifactId: z8.string(),
|
|
529
|
+
comments: z8.array(commentExportThreadSchema),
|
|
486
530
|
// The artifact's current version (null before the first finalize).
|
|
487
|
-
versionNumber:
|
|
531
|
+
versionNumber: z8.number().int().positive().nullable()
|
|
488
532
|
});
|
|
489
|
-
var commentReadResponseSchema =
|
|
490
|
-
lastSeenAt:
|
|
533
|
+
var commentReadResponseSchema = z8.object({
|
|
534
|
+
lastSeenAt: z8.string()
|
|
491
535
|
});
|
|
492
536
|
function elementLabel(el) {
|
|
493
537
|
if (el.id) {
|
|
@@ -497,245 +541,579 @@ function elementLabel(el) {
|
|
|
497
541
|
return classes.length ? `<${el.tag}.${classes.join(".")}>` : `<${el.tag}>`;
|
|
498
542
|
}
|
|
499
543
|
|
|
544
|
+
// ../shared/src/data.ts
|
|
545
|
+
var DATA_MANIFEST_PATH = ".artifact/data.json";
|
|
546
|
+
var DATA_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
547
|
+
var DATA_NAME_MAX = 64;
|
|
548
|
+
var DATA_RESERVED_FIELDS = /* @__PURE__ */ new Set([
|
|
549
|
+
"id",
|
|
550
|
+
"created_at",
|
|
551
|
+
"updated_at",
|
|
552
|
+
"created_by",
|
|
553
|
+
"mine"
|
|
554
|
+
]);
|
|
555
|
+
var DATA_LIMITS = {
|
|
556
|
+
artifactRateWindowSeconds: 3600,
|
|
557
|
+
artifactReadHourlyLimit: 1e4,
|
|
558
|
+
artifactWriteHourlyLimit: 1e3,
|
|
559
|
+
defaultReadRate: "120/minute/ip",
|
|
560
|
+
defaultWriteRate: "10/minute/ip",
|
|
561
|
+
maxCollections: 10,
|
|
562
|
+
maxFieldsPerCollection: 50,
|
|
563
|
+
/** Owner-plane batch insert: records per request. */
|
|
564
|
+
maxInsertBatch: 100,
|
|
565
|
+
maxListLimit: 200,
|
|
566
|
+
maxManifestBytes: 64 * 1024,
|
|
567
|
+
maxRecordBytes: 16 * 1024,
|
|
568
|
+
maxRecordsPerArtifact: 1e4
|
|
569
|
+
};
|
|
570
|
+
var DATA_ACCESS_DEFAULTS = {
|
|
571
|
+
delete: "owner",
|
|
572
|
+
insert: "owner",
|
|
573
|
+
read: "public",
|
|
574
|
+
update: "owner"
|
|
575
|
+
};
|
|
576
|
+
var DATA_RATE_LIMIT_RE = /^([1-9]\d{0,5})\/(hour|minute)\/ip$/;
|
|
577
|
+
var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
578
|
+
function validateFieldValue(name, field, value, errors) {
|
|
579
|
+
switch (field.type) {
|
|
580
|
+
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;
|
|
590
|
+
}
|
|
591
|
+
case "number":
|
|
592
|
+
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;
|
|
607
|
+
}
|
|
608
|
+
case "boolean": {
|
|
609
|
+
if (typeof value !== "boolean") {
|
|
610
|
+
errors.push(`${name}: expected a boolean`);
|
|
611
|
+
}
|
|
612
|
+
return value;
|
|
613
|
+
}
|
|
614
|
+
case "datetime": {
|
|
615
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
616
|
+
errors.push(`${name}: expected an ISO 8601 datetime string`);
|
|
617
|
+
}
|
|
618
|
+
return value;
|
|
619
|
+
}
|
|
620
|
+
case "url": {
|
|
621
|
+
if (typeof value !== "string" || !canParseUrl(value)) {
|
|
622
|
+
errors.push(`${name}: expected a valid URL`);
|
|
623
|
+
}
|
|
624
|
+
return value;
|
|
625
|
+
}
|
|
626
|
+
case "email": {
|
|
627
|
+
if (typeof value !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
628
|
+
errors.push(`${name}: expected an email address`);
|
|
629
|
+
}
|
|
630
|
+
return value;
|
|
631
|
+
}
|
|
632
|
+
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;
|
|
641
|
+
}
|
|
642
|
+
case "object": {
|
|
643
|
+
if (!isPlainObject(value)) {
|
|
644
|
+
errors.push(`${name}: expected an object`);
|
|
645
|
+
}
|
|
646
|
+
return value;
|
|
647
|
+
}
|
|
648
|
+
default: {
|
|
649
|
+
errors.push(`${name}: unknown field type`);
|
|
650
|
+
return value;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
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 = {}) {
|
|
662
|
+
if (!isPlainObject(input)) {
|
|
663
|
+
return { errors: ["record body must be a JSON object"], ok: false };
|
|
664
|
+
}
|
|
665
|
+
const errors = [];
|
|
666
|
+
const data2 = {};
|
|
667
|
+
for (const key of Object.keys(input)) {
|
|
668
|
+
if (DATA_RESERVED_FIELDS.has(key)) {
|
|
669
|
+
errors.push(`${key}: reserved field (server-stamped)`);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const field = collection.fields[key];
|
|
673
|
+
if (!(Object.hasOwn(collection.fields, key) && field)) {
|
|
674
|
+
errors.push(`${key}: not a declared field`);
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
const value = input[key];
|
|
678
|
+
if (value === null || value === void 0) {
|
|
679
|
+
if (!field.required) {
|
|
680
|
+
data2[key] = null;
|
|
681
|
+
} else if (opts.partial) {
|
|
682
|
+
errors.push(`${key}: required (cannot be null)`);
|
|
683
|
+
}
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
data2[key] = validateFieldValue(key, field, value, errors);
|
|
687
|
+
}
|
|
688
|
+
if (opts.partial) {
|
|
689
|
+
if (Object.keys(data2).length === 0 && errors.length === 0) {
|
|
690
|
+
errors.push("no fields to update");
|
|
691
|
+
}
|
|
692
|
+
} else {
|
|
693
|
+
for (const [name, field] of Object.entries(collection.fields)) {
|
|
694
|
+
const present = data2[name] !== void 0 && data2[name] !== null;
|
|
695
|
+
if (present) {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (field.default !== void 0) {
|
|
699
|
+
data2[name] = field.default;
|
|
700
|
+
} else if (field.required) {
|
|
701
|
+
errors.push(`${name}: required`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (errors.length > 0) {
|
|
706
|
+
return { errors, ok: false };
|
|
707
|
+
}
|
|
708
|
+
return { data: data2, ok: true };
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// ../shared/src/data-api.ts
|
|
712
|
+
import { z as z9 } from "zod";
|
|
713
|
+
var dataRecordSchema = z9.object({
|
|
714
|
+
created_at: z9.string(),
|
|
715
|
+
created_by: z9.string().nullable(),
|
|
716
|
+
id: z9.string(),
|
|
717
|
+
updated_at: z9.string()
|
|
718
|
+
}).catchall(z9.unknown());
|
|
719
|
+
var dataPublicRecordSchema = z9.object({
|
|
720
|
+
created_at: z9.string(),
|
|
721
|
+
created_by: z9.never().optional(),
|
|
722
|
+
id: z9.string(),
|
|
723
|
+
mine: z9.boolean(),
|
|
724
|
+
updated_at: z9.string()
|
|
725
|
+
}).catchall(z9.unknown());
|
|
726
|
+
var dataPublicListResponseSchema = z9.object({
|
|
727
|
+
nextCursor: z9.string().nullable(),
|
|
728
|
+
records: z9.array(dataPublicRecordSchema)
|
|
729
|
+
});
|
|
730
|
+
var dataListResponseSchema = z9.object({
|
|
731
|
+
nextCursor: z9.string().nullable(),
|
|
732
|
+
records: z9.array(dataRecordSchema)
|
|
733
|
+
});
|
|
734
|
+
var dataRecordResponseSchema = z9.object({ record: dataRecordSchema });
|
|
735
|
+
var dataBatchInsertResponseSchema = z9.object({
|
|
736
|
+
records: z9.array(dataRecordSchema)
|
|
737
|
+
});
|
|
738
|
+
var dataCollectionSummarySchema = z9.object({
|
|
739
|
+
access: z9.record(z9.string(), z9.string()).optional(),
|
|
740
|
+
count: z9.number().int().nonnegative().optional(),
|
|
741
|
+
name: z9.string(),
|
|
742
|
+
rateLimit: z9.string().optional()
|
|
743
|
+
});
|
|
744
|
+
var dataSummaryResponseSchema = z9.object({
|
|
745
|
+
collections: z9.array(dataCollectionSummarySchema),
|
|
746
|
+
enabled: z9.boolean()
|
|
747
|
+
});
|
|
748
|
+
var dataClearResponseSchema = z9.object({
|
|
749
|
+
cleared: z9.number().int().nonnegative()
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// ../shared/src/data-manifest.ts
|
|
753
|
+
import { z as z10 } from "zod";
|
|
754
|
+
var nameSchema = z10.string().max(DATA_NAME_MAX).regex(DATA_NAME_RE, "must match ^[a-z][a-z0-9_]*$");
|
|
755
|
+
var fieldTypeSchema = z10.enum([
|
|
756
|
+
"string",
|
|
757
|
+
"number",
|
|
758
|
+
"integer",
|
|
759
|
+
"boolean",
|
|
760
|
+
"datetime",
|
|
761
|
+
"url",
|
|
762
|
+
"email",
|
|
763
|
+
"array",
|
|
764
|
+
"object"
|
|
765
|
+
]);
|
|
766
|
+
var fieldSchema = z10.strictObject({
|
|
767
|
+
default: z10.unknown().optional(),
|
|
768
|
+
maximum: z10.number().optional(),
|
|
769
|
+
maxLength: z10.number().int().positive().optional(),
|
|
770
|
+
minimum: z10.number().optional(),
|
|
771
|
+
required: z10.boolean().optional(),
|
|
772
|
+
trim: z10.boolean().optional(),
|
|
773
|
+
type: fieldTypeSchema
|
|
774
|
+
}).superRefine((field, ctx) => {
|
|
775
|
+
if (field.default === void 0) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (!defaultMatchesType(field)) {
|
|
779
|
+
ctx.addIssue({
|
|
780
|
+
code: "custom",
|
|
781
|
+
message: `default value does not match type "${field.type}"`,
|
|
782
|
+
path: ["default"]
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
});
|
|
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
|
+
var accessLevelSchema = z10.enum(["public", "creator", "owner", "none"]);
|
|
799
|
+
var accessSchema = z10.strictObject({
|
|
800
|
+
delete: accessLevelSchema.default("owner"),
|
|
801
|
+
insert: accessLevelSchema.default("owner"),
|
|
802
|
+
read: accessLevelSchema.default("public"),
|
|
803
|
+
update: accessLevelSchema.default("owner")
|
|
804
|
+
}).prefault({});
|
|
805
|
+
var collectionSchema = z10.strictObject({
|
|
806
|
+
access: accessSchema,
|
|
807
|
+
fields: z10.record(nameSchema, fieldSchema),
|
|
808
|
+
publicMutation: z10.literal("open").optional(),
|
|
809
|
+
rateLimit: z10.string().regex(DATA_RATE_LIMIT_RE, 'expected "N/hour/ip" or "N/minute/ip"').optional()
|
|
810
|
+
}).superRefine((collection, ctx) => {
|
|
811
|
+
const fieldNames = Object.keys(collection.fields);
|
|
812
|
+
if (fieldNames.length > DATA_LIMITS.maxFieldsPerCollection) {
|
|
813
|
+
ctx.addIssue({
|
|
814
|
+
code: "custom",
|
|
815
|
+
message: `at most ${DATA_LIMITS.maxFieldsPerCollection} fields per collection`,
|
|
816
|
+
path: ["fields"]
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
for (const name of fieldNames) {
|
|
820
|
+
if (DATA_RESERVED_FIELDS.has(name)) {
|
|
821
|
+
ctx.addIssue({
|
|
822
|
+
code: "custom",
|
|
823
|
+
message: `"${name}" is reserved (server-stamped)`,
|
|
824
|
+
path: ["fields", name]
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
for (const action of ["update", "delete"]) {
|
|
829
|
+
if (collection.access[action] === "public" && collection.publicMutation !== "open") {
|
|
830
|
+
ctx.addIssue({
|
|
831
|
+
code: "custom",
|
|
832
|
+
message: `access.${action} may only be "public" alongside "publicMutation": "open"`,
|
|
833
|
+
path: ["access", action]
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
var dataManifestSchema = z10.strictObject({
|
|
839
|
+
collections: z10.record(nameSchema, collectionSchema)
|
|
840
|
+
}).superRefine((manifest, ctx) => {
|
|
841
|
+
if (Object.keys(manifest.collections).length > DATA_LIMITS.maxCollections) {
|
|
842
|
+
ctx.addIssue({
|
|
843
|
+
code: "custom",
|
|
844
|
+
message: `at most ${DATA_LIMITS.maxCollections} collections`,
|
|
845
|
+
path: ["collections"]
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
function parseDataManifest(text) {
|
|
850
|
+
if (new TextEncoder().encode(text).length > DATA_LIMITS.maxManifestBytes) {
|
|
851
|
+
return {
|
|
852
|
+
errors: [`manifest larger than ${DATA_LIMITS.maxManifestBytes} bytes`],
|
|
853
|
+
ok: false
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
let raw;
|
|
857
|
+
try {
|
|
858
|
+
raw = JSON.parse(text);
|
|
859
|
+
} catch (err) {
|
|
860
|
+
return {
|
|
861
|
+
errors: [`not valid JSON: ${err instanceof Error ? err.message : err}`],
|
|
862
|
+
ok: false
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
const parsed = dataManifestSchema.safeParse(raw);
|
|
866
|
+
if (!parsed.success) {
|
|
867
|
+
return {
|
|
868
|
+
errors: parsed.error.issues.map(
|
|
869
|
+
(issue) => issue.path.length ? `${issue.path.join(".")}: ${issue.message}` : issue.message
|
|
870
|
+
),
|
|
871
|
+
ok: false
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
return { config: parsed.data, ok: true };
|
|
875
|
+
}
|
|
876
|
+
|
|
500
877
|
// ../shared/src/errors.ts
|
|
501
|
-
import { z as
|
|
502
|
-
var apiErrorSchema =
|
|
503
|
-
code:
|
|
504
|
-
docsUrl:
|
|
505
|
-
message:
|
|
506
|
-
retryAfter:
|
|
878
|
+
import { z as z11 } from "zod";
|
|
879
|
+
var apiErrorSchema = z11.object({
|
|
880
|
+
code: z11.string(),
|
|
881
|
+
docsUrl: z11.string().url(),
|
|
882
|
+
message: z11.string(),
|
|
883
|
+
retryAfter: z11.number().int().positive().optional(),
|
|
884
|
+
scope: z11.enum(["ip", "artifact"]).optional()
|
|
507
885
|
});
|
|
508
886
|
|
|
509
887
|
// ../shared/src/feedback.ts
|
|
510
|
-
import { z as
|
|
511
|
-
var feedbackRequestSchema =
|
|
512
|
-
email:
|
|
513
|
-
message:
|
|
888
|
+
import { z as z12 } from "zod";
|
|
889
|
+
var feedbackRequestSchema = z12.object({
|
|
890
|
+
email: z12.string().trim().toLowerCase().email().optional().or(z12.literal("")),
|
|
891
|
+
message: z12.string().trim().min(1).max(4e3)
|
|
514
892
|
});
|
|
515
|
-
var feedbackResponseSchema =
|
|
516
|
-
sent:
|
|
893
|
+
var feedbackResponseSchema = z12.object({
|
|
894
|
+
sent: z12.literal(true)
|
|
517
895
|
});
|
|
518
896
|
|
|
519
897
|
// ../shared/src/grants.ts
|
|
520
|
-
import { z as
|
|
521
|
-
var grantRoleSchema =
|
|
522
|
-
var createGrantSchema =
|
|
523
|
-
email:
|
|
898
|
+
import { z as z13 } from "zod";
|
|
899
|
+
var grantRoleSchema = z13.enum(["viewer"]);
|
|
900
|
+
var createGrantSchema = z13.object({
|
|
901
|
+
email: z13.string().trim().toLowerCase().email().optional(),
|
|
524
902
|
role: grantRoleSchema.default("viewer"),
|
|
525
|
-
userId:
|
|
903
|
+
userId: z13.string().min(1).optional()
|
|
526
904
|
}).refine((body) => Boolean(body.email) !== Boolean(body.userId), {
|
|
527
905
|
message: "provide exactly one of email or userId"
|
|
528
906
|
});
|
|
529
|
-
var grantSchema =
|
|
530
|
-
createdAt:
|
|
531
|
-
email:
|
|
532
|
-
expiresAt:
|
|
533
|
-
id:
|
|
907
|
+
var grantSchema = z13.object({
|
|
908
|
+
createdAt: z13.string(),
|
|
909
|
+
email: z13.string().email().nullable(),
|
|
910
|
+
expiresAt: z13.string().nullable(),
|
|
911
|
+
id: z13.string(),
|
|
534
912
|
// The subject's avatar once they have an account; null otherwise.
|
|
535
|
-
image:
|
|
913
|
+
image: z13.string().nullable(),
|
|
536
914
|
// Present on the invited account after redemption / when granting an
|
|
537
915
|
// existing user; null for a still-pending email invite.
|
|
538
|
-
name:
|
|
539
|
-
redeemedAt:
|
|
916
|
+
name: z13.string().nullable(),
|
|
917
|
+
redeemedAt: z13.string().nullable(),
|
|
540
918
|
role: grantRoleSchema,
|
|
541
919
|
// Shown as "· pending" beside the role until the link is redeemed.
|
|
542
|
-
status:
|
|
920
|
+
status: z13.enum(["pending", "active"]),
|
|
543
921
|
// Display fragment of the live invitation token, or null once redeemed.
|
|
544
|
-
tokenPrefix:
|
|
545
|
-
userId:
|
|
922
|
+
tokenPrefix: z13.string().nullable(),
|
|
923
|
+
userId: z13.string().nullable()
|
|
546
924
|
});
|
|
547
925
|
var createdGrantSchema = grantSchema.extend({
|
|
548
|
-
url:
|
|
926
|
+
url: z13.string().url()
|
|
549
927
|
});
|
|
550
|
-
var grantListResponseSchema =
|
|
551
|
-
grants:
|
|
928
|
+
var grantListResponseSchema = z13.object({
|
|
929
|
+
grants: z13.array(grantSchema),
|
|
552
930
|
// The artifact's current general-access value, so the share surface can
|
|
553
931
|
// render the "General access" line without a second read.
|
|
554
|
-
visibility:
|
|
932
|
+
visibility: z13.enum(["public", "unlisted", "workspace", "private"]),
|
|
555
933
|
// The owning workspace's logo, so the share modal can draw the workspace
|
|
556
934
|
// itself rather than a generic glyph. Null when none was uploaded.
|
|
557
|
-
workspaceLogo:
|
|
935
|
+
workspaceLogo: z13.string().nullable().optional(),
|
|
558
936
|
// The owning workspace's display name, so "People with access" can name the
|
|
559
937
|
// audience `workspace` visibility actually admits ("All Acme members") rather
|
|
560
938
|
// than leaving an empty list to imply nobody. Null for a workspace-less
|
|
561
939
|
// artifact; optional so an older server still parses.
|
|
562
|
-
workspaceName:
|
|
940
|
+
workspaceName: z13.string().nullable().optional()
|
|
563
941
|
});
|
|
564
|
-
var revokeGrantResponseSchema =
|
|
565
|
-
revoked:
|
|
942
|
+
var revokeGrantResponseSchema = z13.object({
|
|
943
|
+
revoked: z13.literal(true)
|
|
566
944
|
});
|
|
567
|
-
var acceptGrantSchema =
|
|
568
|
-
token:
|
|
945
|
+
var acceptGrantSchema = z13.object({
|
|
946
|
+
token: z13.string().min(1)
|
|
569
947
|
});
|
|
570
|
-
var acceptGrantResponseSchema =
|
|
571
|
-
artifactId:
|
|
948
|
+
var acceptGrantResponseSchema = z13.object({
|
|
949
|
+
artifactId: z13.string(),
|
|
572
950
|
// The review shell URL to land on, carrying ?invited=1 for the banner.
|
|
573
|
-
artifactUrl:
|
|
574
|
-
slug:
|
|
575
|
-
title:
|
|
576
|
-
});
|
|
577
|
-
var workspaceGuestSchema =
|
|
578
|
-
artifacts:
|
|
579
|
-
|
|
580
|
-
artifactId:
|
|
581
|
-
grantId:
|
|
582
|
-
slug:
|
|
583
|
-
status:
|
|
584
|
-
title:
|
|
951
|
+
artifactUrl: z13.string().url(),
|
|
952
|
+
slug: z13.string(),
|
|
953
|
+
title: z13.string()
|
|
954
|
+
});
|
|
955
|
+
var workspaceGuestSchema = z13.object({
|
|
956
|
+
artifacts: z13.array(
|
|
957
|
+
z13.object({
|
|
958
|
+
artifactId: z13.string(),
|
|
959
|
+
grantId: z13.string(),
|
|
960
|
+
slug: z13.string(),
|
|
961
|
+
status: z13.enum(["pending", "active"]),
|
|
962
|
+
title: z13.string()
|
|
585
963
|
})
|
|
586
964
|
),
|
|
587
|
-
email:
|
|
588
|
-
image:
|
|
589
|
-
name:
|
|
590
|
-
userId:
|
|
591
|
-
});
|
|
592
|
-
var workspaceGuestListResponseSchema =
|
|
593
|
-
guests:
|
|
594
|
-
});
|
|
595
|
-
var sharedWithMeArtifactSchema =
|
|
596
|
-
artifactId:
|
|
597
|
-
slug:
|
|
598
|
-
title:
|
|
599
|
-
url:
|
|
965
|
+
email: z13.string().nullable(),
|
|
966
|
+
image: z13.string().nullable(),
|
|
967
|
+
name: z13.string().nullable(),
|
|
968
|
+
userId: z13.string().nullable()
|
|
969
|
+
});
|
|
970
|
+
var workspaceGuestListResponseSchema = z13.object({
|
|
971
|
+
guests: z13.array(workspaceGuestSchema)
|
|
972
|
+
});
|
|
973
|
+
var sharedWithMeArtifactSchema = z13.object({
|
|
974
|
+
artifactId: z13.string(),
|
|
975
|
+
slug: z13.string(),
|
|
976
|
+
title: z13.string(),
|
|
977
|
+
url: z13.string().url(),
|
|
600
978
|
// Who shared it, for the "Shared with me" list.
|
|
601
|
-
workspaceName:
|
|
979
|
+
workspaceName: z13.string()
|
|
602
980
|
});
|
|
603
|
-
var sharedWithMeListResponseSchema =
|
|
604
|
-
artifacts:
|
|
981
|
+
var sharedWithMeListResponseSchema = z13.object({
|
|
982
|
+
artifacts: z13.array(sharedWithMeArtifactSchema)
|
|
605
983
|
});
|
|
606
984
|
|
|
607
985
|
// ../shared/src/html-meta.ts
|
|
608
986
|
var HEAD_SCAN_CHARS = 64 * 1024;
|
|
609
987
|
|
|
610
988
|
// ../shared/src/me.ts
|
|
611
|
-
import { z as
|
|
989
|
+
import { z as z15 } from "zod";
|
|
612
990
|
|
|
613
991
|
// ../shared/src/workspaces.ts
|
|
614
|
-
import { z as
|
|
992
|
+
import { z as z14 } from "zod";
|
|
615
993
|
var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/;
|
|
616
|
-
var workspaceHandleUnavailableReasonSchema =
|
|
994
|
+
var workspaceHandleUnavailableReasonSchema = z14.enum([
|
|
617
995
|
"held",
|
|
618
996
|
"invalid",
|
|
619
997
|
"reserved",
|
|
620
998
|
"taken"
|
|
621
999
|
]);
|
|
622
|
-
var workspaceHandleAvailabilitySchema =
|
|
623
|
-
available:
|
|
624
|
-
handle:
|
|
1000
|
+
var workspaceHandleAvailabilitySchema = z14.object({
|
|
1001
|
+
available: z14.boolean(),
|
|
1002
|
+
handle: z14.string(),
|
|
625
1003
|
// Human-readable sentence for the field hint; null when available.
|
|
626
|
-
message:
|
|
1004
|
+
message: z14.string().nullable(),
|
|
627
1005
|
reason: workspaceHandleUnavailableReasonSchema.nullable()
|
|
628
1006
|
});
|
|
629
|
-
var workspaceRoleSchema =
|
|
630
|
-
var workspaceInviteRoleSchema =
|
|
631
|
-
var workspaceHandleSchema =
|
|
1007
|
+
var workspaceRoleSchema = z14.enum(["owner", "admin", "member"]);
|
|
1008
|
+
var workspaceInviteRoleSchema = z14.enum(["admin", "member"]);
|
|
1009
|
+
var workspaceHandleSchema = z14.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
|
|
632
1010
|
message: "handle must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
633
1011
|
});
|
|
634
|
-
var createWorkspaceSchema =
|
|
1012
|
+
var createWorkspaceSchema = z14.object({
|
|
635
1013
|
handle: workspaceHandleSchema.optional(),
|
|
636
|
-
name:
|
|
1014
|
+
name: z14.string().trim().min(1).max(100)
|
|
637
1015
|
});
|
|
638
|
-
var updateWorkspaceSchema =
|
|
1016
|
+
var updateWorkspaceSchema = z14.object({
|
|
639
1017
|
handle: workspaceHandleSchema.optional(),
|
|
640
|
-
name:
|
|
1018
|
+
name: z14.string().trim().min(1).max(100).optional()
|
|
641
1019
|
}).refine((body) => body.handle !== void 0 || body.name !== void 0, {
|
|
642
1020
|
message: "provide at least one of name or handle"
|
|
643
1021
|
});
|
|
644
|
-
var workspaceSchema =
|
|
645
|
-
createdAt:
|
|
646
|
-
handle:
|
|
1022
|
+
var workspaceSchema = z14.object({
|
|
1023
|
+
createdAt: z14.string(),
|
|
1024
|
+
handle: z14.string(),
|
|
647
1025
|
// ISO timestamp when `{handle}.artifact.cafe` was provisioned, or null.
|
|
648
1026
|
// Set by the one-time, self-serve enable flow (§4.4); never auto at
|
|
649
1027
|
// create, and always null for personal workspaces.
|
|
650
|
-
hostnameProvisionedAt:
|
|
651
|
-
id:
|
|
1028
|
+
hostnameProvisionedAt: z14.string().nullable(),
|
|
1029
|
+
id: z14.string(),
|
|
652
1030
|
// Auto-provisioned solo workspace, one per user: non-deletable, takes
|
|
653
1031
|
// no members, renameable like any workspace.
|
|
654
|
-
isPersonal:
|
|
1032
|
+
isPersonal: z14.boolean(),
|
|
655
1033
|
// Uploaded logo URL, or null for the initials fallback. Set through the
|
|
656
1034
|
// dedicated /workspaces/:id/avatar route, never the generic update.
|
|
657
|
-
logo:
|
|
658
|
-
name:
|
|
1035
|
+
logo: z14.string().nullable(),
|
|
1036
|
+
name: z14.string(),
|
|
659
1037
|
role: workspaceRoleSchema.nullable(),
|
|
660
|
-
updatedAt:
|
|
1038
|
+
updatedAt: z14.string()
|
|
661
1039
|
});
|
|
662
|
-
var workspaceListResponseSchema =
|
|
663
|
-
workspaces:
|
|
1040
|
+
var workspaceListResponseSchema = z14.object({
|
|
1041
|
+
workspaces: z14.array(workspaceSchema)
|
|
664
1042
|
});
|
|
665
|
-
var deleteWorkspaceResponseSchema =
|
|
666
|
-
deleted:
|
|
1043
|
+
var deleteWorkspaceResponseSchema = z14.object({
|
|
1044
|
+
deleted: z14.literal(true)
|
|
667
1045
|
});
|
|
668
|
-
var workspaceMemberSchema =
|
|
669
|
-
createdAt:
|
|
670
|
-
email:
|
|
671
|
-
image:
|
|
672
|
-
name:
|
|
1046
|
+
var workspaceMemberSchema = z14.object({
|
|
1047
|
+
createdAt: z14.string(),
|
|
1048
|
+
email: z14.string().email(),
|
|
1049
|
+
image: z14.string().nullable(),
|
|
1050
|
+
name: z14.string(),
|
|
673
1051
|
role: workspaceRoleSchema,
|
|
674
|
-
userId:
|
|
1052
|
+
userId: z14.string()
|
|
675
1053
|
});
|
|
676
|
-
var workspaceMemberListResponseSchema =
|
|
677
|
-
members:
|
|
1054
|
+
var workspaceMemberListResponseSchema = z14.object({
|
|
1055
|
+
members: z14.array(workspaceMemberSchema)
|
|
678
1056
|
});
|
|
679
|
-
var updateWorkspaceMemberSchema =
|
|
1057
|
+
var updateWorkspaceMemberSchema = z14.object({
|
|
680
1058
|
role: workspaceRoleSchema
|
|
681
1059
|
});
|
|
682
|
-
var removeWorkspaceMemberResponseSchema =
|
|
683
|
-
removed:
|
|
1060
|
+
var removeWorkspaceMemberResponseSchema = z14.object({
|
|
1061
|
+
removed: z14.literal(true)
|
|
684
1062
|
});
|
|
685
|
-
var createWorkspaceInviteSchema =
|
|
686
|
-
email:
|
|
687
|
-
expiresAt:
|
|
688
|
-
maxUses:
|
|
1063
|
+
var createWorkspaceInviteSchema = z14.object({
|
|
1064
|
+
email: z14.string().trim().toLowerCase().email().optional(),
|
|
1065
|
+
expiresAt: z14.string().datetime(),
|
|
1066
|
+
maxUses: z14.number().int().positive().optional(),
|
|
689
1067
|
role: workspaceInviteRoleSchema
|
|
690
1068
|
});
|
|
691
|
-
var workspaceInviteSchema =
|
|
692
|
-
createdAt:
|
|
693
|
-
email:
|
|
694
|
-
expiresAt:
|
|
695
|
-
id:
|
|
696
|
-
maxUses:
|
|
1069
|
+
var workspaceInviteSchema = z14.object({
|
|
1070
|
+
createdAt: z14.string(),
|
|
1071
|
+
email: z14.string().email().nullable(),
|
|
1072
|
+
expiresAt: z14.string(),
|
|
1073
|
+
id: z14.string(),
|
|
1074
|
+
maxUses: z14.number().int().positive().nullable(),
|
|
697
1075
|
role: workspaceInviteRoleSchema,
|
|
698
|
-
tokenPrefix:
|
|
699
|
-
useCount:
|
|
1076
|
+
tokenPrefix: z14.string(),
|
|
1077
|
+
useCount: z14.number().int().nonnegative()
|
|
700
1078
|
});
|
|
701
1079
|
var createdWorkspaceInviteSchema = workspaceInviteSchema.extend({
|
|
702
|
-
url:
|
|
1080
|
+
url: z14.string().url()
|
|
703
1081
|
});
|
|
704
|
-
var workspaceInviteListResponseSchema =
|
|
705
|
-
invites:
|
|
1082
|
+
var workspaceInviteListResponseSchema = z14.object({
|
|
1083
|
+
invites: z14.array(workspaceInviteSchema)
|
|
706
1084
|
});
|
|
707
|
-
var revokeWorkspaceInviteResponseSchema =
|
|
708
|
-
revoked:
|
|
1085
|
+
var revokeWorkspaceInviteResponseSchema = z14.object({
|
|
1086
|
+
revoked: z14.literal(true)
|
|
709
1087
|
});
|
|
710
|
-
var acceptWorkspaceInviteSchema =
|
|
711
|
-
token:
|
|
1088
|
+
var acceptWorkspaceInviteSchema = z14.object({
|
|
1089
|
+
token: z14.string().min(1)
|
|
712
1090
|
});
|
|
713
|
-
var acceptWorkspaceInviteResponseSchema =
|
|
1091
|
+
var acceptWorkspaceInviteResponseSchema = z14.object({
|
|
714
1092
|
role: workspaceRoleSchema,
|
|
715
1093
|
workspace: workspaceSchema
|
|
716
1094
|
});
|
|
717
|
-
var folderVisibilitySchema =
|
|
718
|
-
var artifactVisibilitySchema =
|
|
1095
|
+
var folderVisibilitySchema = z14.enum(["public", "workspace"]);
|
|
1096
|
+
var artifactVisibilitySchema = z14.enum([
|
|
719
1097
|
"public",
|
|
720
1098
|
"unlisted",
|
|
721
1099
|
"workspace",
|
|
722
1100
|
"private"
|
|
723
1101
|
]);
|
|
724
|
-
var createFolderSchema =
|
|
1102
|
+
var createFolderSchema = z14.object({
|
|
725
1103
|
// Optional custom icon; omitted (or null) leaves the default glyph.
|
|
726
1104
|
emoji: iconEmojiSchema.optional(),
|
|
727
|
-
name:
|
|
1105
|
+
name: z14.string().trim().min(1).max(100),
|
|
728
1106
|
visibility: folderVisibilitySchema
|
|
729
1107
|
});
|
|
730
|
-
var updateFolderSchema =
|
|
1108
|
+
var updateFolderSchema = z14.object({
|
|
731
1109
|
// With `visibility`: bulk-update the folder's artifacts to match
|
|
732
1110
|
// (SPEC-2 §3.4 "Changing a folder's visibility offers a bulk update").
|
|
733
|
-
cascade:
|
|
1111
|
+
cascade: z14.boolean().optional(),
|
|
734
1112
|
// Explicit null clears the icon back to the default glyph, so this is
|
|
735
1113
|
// a field where "sent as null" and "omitted" mean different things.
|
|
736
1114
|
emoji: iconEmojiSchema.optional(),
|
|
737
|
-
name:
|
|
738
|
-
sortOrder:
|
|
1115
|
+
name: z14.string().trim().min(1).max(100).optional(),
|
|
1116
|
+
sortOrder: z14.number().int().min(0).optional(),
|
|
739
1117
|
visibility: folderVisibilitySchema.optional()
|
|
740
1118
|
}).refine(
|
|
741
1119
|
(body) => body.name !== void 0 || body.visibility !== void 0 || body.sortOrder !== void 0 || body.emoji !== void 0,
|
|
@@ -743,93 +1121,95 @@ var updateFolderSchema = z11.object({
|
|
|
743
1121
|
).refine((body) => !body.cascade || body.visibility !== void 0, {
|
|
744
1122
|
message: "cascade requires visibility"
|
|
745
1123
|
});
|
|
746
|
-
var folderSchema =
|
|
747
|
-
createdAt:
|
|
1124
|
+
var folderSchema = z14.object({
|
|
1125
|
+
createdAt: z14.string(),
|
|
748
1126
|
// Custom icon, or null to render the default folder glyph.
|
|
749
|
-
emoji:
|
|
750
|
-
id:
|
|
751
|
-
name:
|
|
1127
|
+
emoji: z14.string().nullable(),
|
|
1128
|
+
id: z14.string(),
|
|
1129
|
+
name: z14.string(),
|
|
752
1130
|
// Permalink segment for the public folder page (/w/{handle}/f/{slug}).
|
|
753
1131
|
// Server-derived from the name; renames regenerate it.
|
|
754
|
-
slug:
|
|
755
|
-
sortOrder:
|
|
756
|
-
updatedAt:
|
|
1132
|
+
slug: z14.string(),
|
|
1133
|
+
sortOrder: z14.number().int(),
|
|
1134
|
+
updatedAt: z14.string(),
|
|
757
1135
|
visibility: folderVisibilitySchema,
|
|
758
|
-
workspaceId:
|
|
1136
|
+
workspaceId: z14.string()
|
|
759
1137
|
});
|
|
760
|
-
var folderListResponseSchema =
|
|
761
|
-
folders:
|
|
1138
|
+
var folderListResponseSchema = z14.object({
|
|
1139
|
+
folders: z14.array(folderSchema)
|
|
762
1140
|
});
|
|
763
|
-
var deleteFolderResponseSchema =
|
|
764
|
-
deleted:
|
|
1141
|
+
var deleteFolderResponseSchema = z14.object({
|
|
1142
|
+
deleted: z14.literal(true)
|
|
765
1143
|
});
|
|
766
|
-
var workspaceArtifactSchema =
|
|
767
|
-
|
|
768
|
-
|
|
1144
|
+
var workspaceArtifactSchema = z14.object({
|
|
1145
|
+
autoDeleteAt: z14.string().datetime().nullable().optional(),
|
|
1146
|
+
createdAt: z14.string(),
|
|
1147
|
+
currentVersionNumber: z14.number().int().nullable(),
|
|
769
1148
|
// Custom icon, or null to render the default document glyph.
|
|
770
|
-
emoji:
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1149
|
+
emoji: z14.string().nullable(),
|
|
1150
|
+
expiresAt: z14.string().datetime().nullable().optional(),
|
|
1151
|
+
folderId: z14.string().nullable(),
|
|
1152
|
+
id: z14.string(),
|
|
1153
|
+
openCommentCount: z14.number().int().nonnegative(),
|
|
774
1154
|
reviewMode: reviewModeSchema,
|
|
775
|
-
slug:
|
|
776
|
-
title:
|
|
777
|
-
updatedAt:
|
|
778
|
-
url:
|
|
779
|
-
versionCount:
|
|
1155
|
+
slug: z14.string(),
|
|
1156
|
+
title: z14.string(),
|
|
1157
|
+
updatedAt: z14.string(),
|
|
1158
|
+
url: z14.string().url(),
|
|
1159
|
+
versionCount: z14.number().int().nonnegative(),
|
|
780
1160
|
visibility: artifactVisibilitySchema
|
|
781
1161
|
});
|
|
782
|
-
var workspaceArtifactListResponseSchema =
|
|
783
|
-
artifacts:
|
|
1162
|
+
var workspaceArtifactListResponseSchema = z14.object({
|
|
1163
|
+
artifacts: z14.array(workspaceArtifactSchema)
|
|
784
1164
|
});
|
|
785
|
-
var workspaceArtifactSearchResultSchema =
|
|
786
|
-
id:
|
|
787
|
-
slug:
|
|
788
|
-
title:
|
|
1165
|
+
var workspaceArtifactSearchResultSchema = z14.object({
|
|
1166
|
+
id: z14.string(),
|
|
1167
|
+
slug: z14.string(),
|
|
1168
|
+
title: z14.string()
|
|
789
1169
|
});
|
|
790
|
-
var workspaceArtifactSearchResponseSchema =
|
|
791
|
-
results:
|
|
1170
|
+
var workspaceArtifactSearchResponseSchema = z14.object({
|
|
1171
|
+
results: z14.array(workspaceArtifactSearchResultSchema)
|
|
792
1172
|
});
|
|
793
1173
|
|
|
794
1174
|
// ../shared/src/me.ts
|
|
795
|
-
var personSchema =
|
|
796
|
-
image:
|
|
797
|
-
name:
|
|
1175
|
+
var personSchema = z15.object({
|
|
1176
|
+
image: z15.string().nullable(),
|
|
1177
|
+
name: z15.string()
|
|
798
1178
|
});
|
|
799
|
-
var ownArtifactSchema =
|
|
1179
|
+
var ownArtifactSchema = z15.object({
|
|
1180
|
+
autoDeleteAt: z15.string().datetime().nullable().optional(),
|
|
800
1181
|
// Everyone else who has published a version, oldest first. Any member may
|
|
801
1182
|
// publish a new one (SPEC-2 §5.1), so an artifact's byline is often more
|
|
802
1183
|
// than one person and the card stacks their faces rather than crediting
|
|
803
1184
|
// whoever shipped last. Excludes `publishedBy`; empty on a solo artifact.
|
|
804
|
-
coPublishers:
|
|
805
|
-
createdAt:
|
|
1185
|
+
coPublishers: z15.array(personSchema),
|
|
1186
|
+
createdAt: z15.string(),
|
|
806
1187
|
// SPEC-2 §5.1 — who *made* the artifact: the author of its first
|
|
807
1188
|
// finalized version, the same row `viewerCanManage` is decided on. Not
|
|
808
1189
|
// the same person as `publishedBy` once an agent ships v2, which is why
|
|
809
1190
|
// the dashboard filters on both. Null for a token-only publish.
|
|
810
|
-
createdBy:
|
|
811
|
-
currentVersionNumber:
|
|
1191
|
+
createdBy: z15.object({ image: z15.string().nullable(), name: z15.string() }).nullable(),
|
|
1192
|
+
currentVersionNumber: z15.number().nullable(),
|
|
812
1193
|
// Custom icon, or null to render the default document glyph.
|
|
813
|
-
emoji:
|
|
814
|
-
//
|
|
815
|
-
|
|
816
|
-
expiresAt: z12.string().nullable(),
|
|
1194
|
+
emoji: z15.string().nullable(),
|
|
1195
|
+
// Effective expiry: explicit temporary lifetime or anonymous retention.
|
|
1196
|
+
expiresAt: z15.string().nullable(),
|
|
817
1197
|
// SPEC-2 §8.4 — the containing folder for workspace-owned artifacts;
|
|
818
1198
|
// both null for personal artifacts. `folderId` drives the dashboard's
|
|
819
1199
|
// move control and folder filter; `folderName` is the display label,
|
|
820
1200
|
// `folderEmoji` its icon (null = the default folder glyph).
|
|
821
|
-
folderEmoji:
|
|
822
|
-
folderId:
|
|
823
|
-
folderName:
|
|
1201
|
+
folderEmoji: z15.string().nullable(),
|
|
1202
|
+
folderId: z15.string().nullable(),
|
|
1203
|
+
folderName: z15.string().nullable(),
|
|
824
1204
|
// Whether a publish token is still active — revocable from the
|
|
825
1205
|
// dashboard (§6.18); the token itself is never returned.
|
|
826
|
-
hasPublishToken:
|
|
1206
|
+
hasPublishToken: z15.boolean(),
|
|
827
1207
|
// Owner preference: hide the version list from reviewers in the review
|
|
828
1208
|
// shell. Managers still see it.
|
|
829
|
-
hideVersions:
|
|
830
|
-
id:
|
|
831
|
-
openThreads:
|
|
832
|
-
protected:
|
|
1209
|
+
hideVersions: z15.boolean(),
|
|
1210
|
+
id: z15.string(),
|
|
1211
|
+
openThreads: z15.number(),
|
|
1212
|
+
protected: z15.boolean(),
|
|
833
1213
|
// Who published the current version (falling back to the claiming
|
|
834
1214
|
// owner) — the dashboard shows a name and avatar per artifact so a
|
|
835
1215
|
// shared workspace reads as people's work, not anonymous rows. Null
|
|
@@ -838,44 +1218,44 @@ var ownArtifactSchema = z12.object({
|
|
|
838
1218
|
// SPEC-2 §5.8 — the review dial, so the dashboard's settings dialog can
|
|
839
1219
|
// render current state without a second fetch.
|
|
840
1220
|
reviewMode: reviewModeSchema,
|
|
841
|
-
slug:
|
|
842
|
-
title:
|
|
843
|
-
totalViews:
|
|
1221
|
+
slug: z15.string(),
|
|
1222
|
+
title: z15.string(),
|
|
1223
|
+
totalViews: z15.number(),
|
|
844
1224
|
// SPEC §6.20 — messages by others newer than the viewer's last-seen
|
|
845
1225
|
// watermark. 0 for signed-out/API-key-less contexts.
|
|
846
|
-
unreadComments:
|
|
847
|
-
updatedAt:
|
|
848
|
-
url:
|
|
849
|
-
versionCount:
|
|
1226
|
+
unreadComments: z15.number(),
|
|
1227
|
+
updatedAt: z15.string(),
|
|
1228
|
+
url: z15.string(),
|
|
1229
|
+
versionCount: z15.number(),
|
|
850
1230
|
// SPEC-2 §5.1 — whether the caller clears the manage bar for this
|
|
851
1231
|
// artifact (workspace admin, or the person who first published it).
|
|
852
1232
|
// Only a manager's folder move inherits the folder's visibility, so the
|
|
853
1233
|
// dashboard warns exactly the people the change can actually affect, and
|
|
854
1234
|
// only a manager is offered the Share entry (§5.7).
|
|
855
|
-
viewerCanManage:
|
|
1235
|
+
viewerCanManage: z15.boolean(),
|
|
856
1236
|
// SPEC-2 §5.2 — the artifact's own visibility, the single enforcement
|
|
857
1237
|
// source. The dashboard needs it to tell whether a folder move would
|
|
858
1238
|
// widen exposure before it sends the PATCH (§3.4), and to render the
|
|
859
1239
|
// audience glyph — one per level — on rows and cards (§5.7).
|
|
860
1240
|
visibility: artifactVisibilitySchema
|
|
861
1241
|
});
|
|
862
|
-
var ownArtifactsResponseSchema =
|
|
863
|
-
artifacts:
|
|
1242
|
+
var ownArtifactsResponseSchema = z15.object({
|
|
1243
|
+
artifacts: z15.array(ownArtifactSchema)
|
|
864
1244
|
});
|
|
865
|
-
var revokePublishTokenResponseSchema =
|
|
866
|
-
revoked:
|
|
1245
|
+
var revokePublishTokenResponseSchema = z15.object({
|
|
1246
|
+
revoked: z15.boolean()
|
|
867
1247
|
});
|
|
868
1248
|
|
|
869
1249
|
// ../shared/src/owner.ts
|
|
870
|
-
import { z as
|
|
1250
|
+
import { z as z16 } from "zod";
|
|
871
1251
|
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
|
|
872
|
-
var claimResponseSchema =
|
|
873
|
-
artifactId:
|
|
874
|
-
slug:
|
|
875
|
-
title:
|
|
876
|
-
url:
|
|
1252
|
+
var claimResponseSchema = z16.object({
|
|
1253
|
+
artifactId: z16.string(),
|
|
1254
|
+
slug: z16.string(),
|
|
1255
|
+
title: z16.string(),
|
|
1256
|
+
url: z16.string().url()
|
|
877
1257
|
});
|
|
878
|
-
var updateArtifactSchema =
|
|
1258
|
+
var updateArtifactSchema = z16.object({
|
|
879
1259
|
// Custom icon; explicit null clears it back to the default glyph.
|
|
880
1260
|
// Part of what the artifact *is* (like the title), so it takes the
|
|
881
1261
|
// same manage rights as title/slug — not the member-level folder move.
|
|
@@ -883,159 +1263,159 @@ var updateArtifactSchema = z13.object({
|
|
|
883
1263
|
// SPEC-2 §6.4 — placement + visibility. `folderId: null` clears the
|
|
884
1264
|
// folder; `workspaceId: null` moves the artifact out of its workspace
|
|
885
1265
|
// (§5.5 ownership transfer).
|
|
886
|
-
folderId:
|
|
1266
|
+
folderId: z16.string().min(1).nullable().optional(),
|
|
887
1267
|
// Keep the version list out of the review shell. Presentation only:
|
|
888
1268
|
// versions stay immutable, and a `/vN/` URL still resolves.
|
|
889
|
-
hideVersions:
|
|
890
|
-
slug:
|
|
1269
|
+
hideVersions: z16.boolean().optional(),
|
|
1270
|
+
slug: z16.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
891
1271
|
message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
892
1272
|
}).optional(),
|
|
893
|
-
title:
|
|
1273
|
+
title: z16.string().min(1).max(200).optional(),
|
|
894
1274
|
visibility: artifactVisibilitySchema.optional(),
|
|
895
|
-
workspaceId:
|
|
1275
|
+
workspaceId: z16.string().min(1).nullable().optional()
|
|
896
1276
|
}).refine(
|
|
897
1277
|
(body) => body.title !== void 0 || body.slug !== void 0 || body.visibility !== void 0 || body.folderId !== void 0 || body.workspaceId !== void 0 || body.emoji !== void 0 || body.hideVersions !== void 0,
|
|
898
1278
|
{
|
|
899
1279
|
message: "provide at least one of title, slug, visibility, folderId, workspaceId, emoji, hideVersions"
|
|
900
1280
|
}
|
|
901
1281
|
);
|
|
902
|
-
var deleteArtifactResponseSchema =
|
|
903
|
-
deleted:
|
|
1282
|
+
var deleteArtifactResponseSchema = z16.object({
|
|
1283
|
+
deleted: z16.literal(true)
|
|
904
1284
|
});
|
|
905
1285
|
|
|
906
1286
|
// ../shared/src/presence.ts
|
|
907
|
-
import { z as
|
|
1287
|
+
import { z as z17 } from "zod";
|
|
908
1288
|
var AGENT_STATES = ["listening", "editing", "publishing"];
|
|
909
|
-
var agentStateSchema =
|
|
1289
|
+
var agentStateSchema = z17.enum(AGENT_STATES);
|
|
910
1290
|
var AGENT_PRESENCE = {
|
|
911
1291
|
editingTtlSeconds: 300,
|
|
912
1292
|
heartbeatSeconds: 15,
|
|
913
1293
|
shellPollSeconds: 15,
|
|
914
1294
|
ttlSeconds: 45
|
|
915
1295
|
};
|
|
916
|
-
var agentHeartbeatBodySchema =
|
|
1296
|
+
var agentHeartbeatBodySchema = z17.object({
|
|
917
1297
|
state: agentStateSchema.optional()
|
|
918
1298
|
});
|
|
919
|
-
var agentHeartbeatResponseSchema =
|
|
920
|
-
listening:
|
|
1299
|
+
var agentHeartbeatResponseSchema = z17.object({
|
|
1300
|
+
listening: z17.literal(true),
|
|
921
1301
|
state: agentStateSchema.optional(),
|
|
922
|
-
ttlSeconds:
|
|
1302
|
+
ttlSeconds: z17.number().int().positive()
|
|
923
1303
|
});
|
|
924
|
-
var agentPresenceSchema =
|
|
925
|
-
agent:
|
|
926
|
-
listening:
|
|
1304
|
+
var agentPresenceSchema = z17.object({
|
|
1305
|
+
agent: z17.object({ id: z17.string().nullable(), label: z17.string() }).nullable().default(null),
|
|
1306
|
+
listening: z17.boolean(),
|
|
927
1307
|
state: agentStateSchema.nullable().optional(),
|
|
928
|
-
versionNumber:
|
|
1308
|
+
versionNumber: z17.number().int().positive().nullable()
|
|
929
1309
|
});
|
|
930
1310
|
|
|
931
1311
|
// ../shared/src/protection.ts
|
|
932
|
-
import { z as
|
|
1312
|
+
import { z as z18 } from "zod";
|
|
933
1313
|
var PASSWORD_MIN = 6;
|
|
934
1314
|
var PASSWORD_MAX = 128;
|
|
935
|
-
var gatePasswordSchema =
|
|
936
|
-
var setProtectionSchema =
|
|
1315
|
+
var gatePasswordSchema = z18.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
|
|
1316
|
+
var setProtectionSchema = z18.object({
|
|
937
1317
|
password: gatePasswordSchema.nullable()
|
|
938
1318
|
});
|
|
939
|
-
var protectionResponseSchema =
|
|
940
|
-
protected:
|
|
1319
|
+
var protectionResponseSchema = z18.object({
|
|
1320
|
+
protected: z18.boolean()
|
|
941
1321
|
});
|
|
942
|
-
var unlockSchema =
|
|
943
|
-
password:
|
|
1322
|
+
var unlockSchema = z18.object({
|
|
1323
|
+
password: z18.string().min(1).max(1024)
|
|
944
1324
|
});
|
|
945
|
-
var unlockResponseSchema =
|
|
946
|
-
unlocked:
|
|
1325
|
+
var unlockResponseSchema = z18.object({
|
|
1326
|
+
unlocked: z18.literal(true)
|
|
947
1327
|
});
|
|
948
1328
|
|
|
949
1329
|
// ../shared/src/report.ts
|
|
950
|
-
import { z as
|
|
951
|
-
var reportRequestSchema =
|
|
952
|
-
reason:
|
|
1330
|
+
import { z as z19 } from "zod";
|
|
1331
|
+
var reportRequestSchema = z19.object({
|
|
1332
|
+
reason: z19.string().max(1e3).optional()
|
|
953
1333
|
});
|
|
954
|
-
var reportResponseSchema =
|
|
955
|
-
reported:
|
|
1334
|
+
var reportResponseSchema = z19.object({
|
|
1335
|
+
reported: z19.literal(true)
|
|
956
1336
|
});
|
|
957
1337
|
|
|
958
1338
|
// ../shared/src/reviewers.ts
|
|
959
|
-
import { z as
|
|
960
|
-
var artifactReviewerSchema =
|
|
961
|
-
avatarUrl:
|
|
962
|
-
displayName:
|
|
1339
|
+
import { z as z20 } from "zod";
|
|
1340
|
+
var artifactReviewerSchema = z20.object({
|
|
1341
|
+
avatarUrl: z20.string().nullable(),
|
|
1342
|
+
displayName: z20.string(),
|
|
963
1343
|
// Exactly one of userId/guestId is set, per `kind`.
|
|
964
|
-
guestId:
|
|
1344
|
+
guestId: z20.string().nullable(),
|
|
965
1345
|
// "account", not commentAuthorSchema's "member" — the whole point here is
|
|
966
1346
|
// that these people need not be workspace members. A guest can be named in
|
|
967
1347
|
// a comment but never notified: guest_identities carries a display name and
|
|
968
1348
|
// no email, so there is nowhere to send anything.
|
|
969
|
-
kind:
|
|
970
|
-
userId:
|
|
1349
|
+
kind: z20.enum(["account", "guest"]),
|
|
1350
|
+
userId: z20.string().nullable()
|
|
971
1351
|
});
|
|
972
|
-
var artifactReviewerListResponseSchema =
|
|
973
|
-
reviewers:
|
|
1352
|
+
var artifactReviewerListResponseSchema = z20.object({
|
|
1353
|
+
reviewers: z20.array(artifactReviewerSchema)
|
|
974
1354
|
});
|
|
975
1355
|
|
|
976
1356
|
// ../shared/src/stats.ts
|
|
977
|
-
import { z as
|
|
978
|
-
var avgViewSecondsSchema =
|
|
979
|
-
var versionViewStatsSchema =
|
|
1357
|
+
import { z as z21 } from "zod";
|
|
1358
|
+
var avgViewSecondsSchema = z21.number().int().nonnegative().nullable();
|
|
1359
|
+
var versionViewStatsSchema = z21.object({
|
|
980
1360
|
avgViewSeconds: avgViewSecondsSchema,
|
|
981
1361
|
// Views from inside the review shell's iframe vs. direct visits to
|
|
982
1362
|
// the render URL. views = embeddedViews + directViews.
|
|
983
|
-
directViews:
|
|
984
|
-
embeddedViews:
|
|
1363
|
+
directViews: z21.number().int().nonnegative(),
|
|
1364
|
+
embeddedViews: z21.number().int().nonnegative(),
|
|
985
1365
|
// Distinct hashed viewer IPs across the version's lifetime.
|
|
986
|
-
uniqueViewers:
|
|
987
|
-
versionNumber:
|
|
988
|
-
views:
|
|
1366
|
+
uniqueViewers: z21.number().int().nonnegative(),
|
|
1367
|
+
versionNumber: z21.number().int().positive(),
|
|
1368
|
+
views: z21.number().int().nonnegative()
|
|
989
1369
|
});
|
|
990
|
-
var dailyViewsSchema =
|
|
1370
|
+
var dailyViewsSchema = z21.object({
|
|
991
1371
|
avgViewSeconds: avgViewSecondsSchema,
|
|
992
1372
|
// UTC day, YYYY-MM-DD.
|
|
993
|
-
day:
|
|
994
|
-
uniqueViewers:
|
|
995
|
-
views:
|
|
1373
|
+
day: z21.string(),
|
|
1374
|
+
uniqueViewers: z21.number().int().nonnegative(),
|
|
1375
|
+
views: z21.number().int().nonnegative()
|
|
996
1376
|
});
|
|
997
|
-
var artifactStatsResponseSchema =
|
|
1377
|
+
var artifactStatsResponseSchema = z21.object({
|
|
998
1378
|
avgViewSeconds: avgViewSecondsSchema,
|
|
999
|
-
daily:
|
|
1379
|
+
daily: z21.array(dailyViewsSchema),
|
|
1000
1380
|
// Distinct hashed viewer IPs across all versions and days.
|
|
1001
|
-
totalUniqueViewers:
|
|
1002
|
-
totalViews:
|
|
1003
|
-
versions:
|
|
1381
|
+
totalUniqueViewers: z21.number().int().nonnegative(),
|
|
1382
|
+
totalViews: z21.number().int().nonnegative(),
|
|
1383
|
+
versions: z21.array(versionViewStatsSchema)
|
|
1004
1384
|
});
|
|
1005
1385
|
|
|
1006
1386
|
// ../shared/src/versions.ts
|
|
1007
|
-
import { z as
|
|
1008
|
-
var versionSourceSchema =
|
|
1009
|
-
agentName:
|
|
1010
|
-
type:
|
|
1387
|
+
import { z as z22 } from "zod";
|
|
1388
|
+
var versionSourceSchema = z22.object({
|
|
1389
|
+
agentName: z22.string().nullable(),
|
|
1390
|
+
type: z22.string()
|
|
1011
1391
|
});
|
|
1012
|
-
var versionChangesSchema =
|
|
1013
|
-
added:
|
|
1014
|
-
changed:
|
|
1015
|
-
removed:
|
|
1392
|
+
var versionChangesSchema = z22.object({
|
|
1393
|
+
added: z22.array(z22.string()),
|
|
1394
|
+
changed: z22.array(z22.string()),
|
|
1395
|
+
removed: z22.array(z22.string())
|
|
1016
1396
|
});
|
|
1017
|
-
var versionSummarySchema =
|
|
1397
|
+
var versionSummarySchema = z22.object({
|
|
1018
1398
|
// Absent for the first version — there is no previous version to diff against.
|
|
1019
1399
|
changes: versionChangesSchema.nullable(),
|
|
1020
|
-
createdAt:
|
|
1021
|
-
entryUrl:
|
|
1022
|
-
fileCount:
|
|
1023
|
-
id:
|
|
1024
|
-
isCurrent:
|
|
1400
|
+
createdAt: z22.string(),
|
|
1401
|
+
entryUrl: z22.string().url(),
|
|
1402
|
+
fileCount: z22.number().int().nonnegative(),
|
|
1403
|
+
id: z22.string(),
|
|
1404
|
+
isCurrent: z22.boolean(),
|
|
1025
1405
|
// Author-supplied bullet-point changelog; null when the publish omitted it.
|
|
1026
|
-
notes:
|
|
1406
|
+
notes: z22.string().nullable(),
|
|
1027
1407
|
// The account that published this version, with their own avatar where
|
|
1028
1408
|
// they've set one. Any workspace member can publish a new version
|
|
1029
1409
|
// (SPEC-2 §5.1), so "which teammate" outranks "which agent" — the agent
|
|
1030
1410
|
// is the tool, this is the person. Null for token-only publishes, which
|
|
1031
1411
|
// carry no account.
|
|
1032
|
-
publishedBy:
|
|
1412
|
+
publishedBy: z22.object({ image: z22.string().nullable(), name: z22.string() }).nullable(),
|
|
1033
1413
|
source: versionSourceSchema,
|
|
1034
|
-
totalSizeBytes:
|
|
1035
|
-
versionNumber:
|
|
1414
|
+
totalSizeBytes: z22.number().int().nonnegative(),
|
|
1415
|
+
versionNumber: z22.number().int().positive()
|
|
1036
1416
|
});
|
|
1037
|
-
var versionListResponseSchema =
|
|
1038
|
-
versions:
|
|
1417
|
+
var versionListResponseSchema = z22.object({
|
|
1418
|
+
versions: z22.array(versionSummarySchema)
|
|
1039
1419
|
});
|
|
1040
1420
|
|
|
1041
1421
|
// src/agent-identity.ts
|
|
@@ -1120,6 +1500,13 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
1120
1500
|
(d) => agentHeartbeatResponseSchema.parse(d)
|
|
1121
1501
|
);
|
|
1122
1502
|
},
|
|
1503
|
+
async clearData(artifactId, collection) {
|
|
1504
|
+
return request(
|
|
1505
|
+
`${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}`,
|
|
1506
|
+
{ headers, method: "DELETE" },
|
|
1507
|
+
(d) => dataClearResponseSchema.parse(d)
|
|
1508
|
+
);
|
|
1509
|
+
},
|
|
1123
1510
|
// SPEC-2 §6.3 — create a folder in a workspace. Account key required
|
|
1124
1511
|
// (owner/admin; in a personal workspace that's always the user).
|
|
1125
1512
|
async createFolder(workspaceId, body) {
|
|
@@ -1202,6 +1589,28 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
1202
1589
|
(d) => artifactResponseSchema.parse(d)
|
|
1203
1590
|
);
|
|
1204
1591
|
},
|
|
1592
|
+
async getDataSummary(artifactId) {
|
|
1593
|
+
return request(
|
|
1594
|
+
`${base}/api/v1/artifacts/${artifactId}/data`,
|
|
1595
|
+
{ headers, method: "GET" },
|
|
1596
|
+
(d) => dataSummaryResponseSchema.parse(d)
|
|
1597
|
+
);
|
|
1598
|
+
},
|
|
1599
|
+
async insertData(artifactId, collection, fields) {
|
|
1600
|
+
return request(
|
|
1601
|
+
`${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}`,
|
|
1602
|
+
{ body: JSON.stringify(fields), headers, method: "POST" },
|
|
1603
|
+
(d) => dataRecordResponseSchema.parse(d).record
|
|
1604
|
+
);
|
|
1605
|
+
},
|
|
1606
|
+
/** Owner-plane batch: up to DATA_LIMITS.maxInsertBatch, all-or-nothing. */
|
|
1607
|
+
async insertDataBatch(artifactId, collection, records) {
|
|
1608
|
+
return request(
|
|
1609
|
+
`${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}`,
|
|
1610
|
+
{ body: JSON.stringify(records), headers, method: "POST" },
|
|
1611
|
+
(d) => dataBatchInsertResponseSchema.parse(d)
|
|
1612
|
+
);
|
|
1613
|
+
},
|
|
1205
1614
|
// SPEC §6.18 — everything owned by the account this client's API
|
|
1206
1615
|
// key (or session) belongs to.
|
|
1207
1616
|
async listArtifacts() {
|
|
@@ -1211,6 +1620,21 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
1211
1620
|
(d) => artifactListResponseSchema.parse(d)
|
|
1212
1621
|
);
|
|
1213
1622
|
},
|
|
1623
|
+
async listData(artifactId, collection, query = {}) {
|
|
1624
|
+
const params = new URLSearchParams();
|
|
1625
|
+
if (query.limit !== void 0) {
|
|
1626
|
+
params.set("limit", String(query.limit));
|
|
1627
|
+
}
|
|
1628
|
+
if (query.cursor !== void 0) {
|
|
1629
|
+
params.set("cursor", query.cursor);
|
|
1630
|
+
}
|
|
1631
|
+
const qs = params.toString();
|
|
1632
|
+
return request(
|
|
1633
|
+
`${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}${qs ? `?${qs}` : ""}`,
|
|
1634
|
+
{ headers, method: "GET" },
|
|
1635
|
+
(d) => dataListResponseSchema.parse(d)
|
|
1636
|
+
);
|
|
1637
|
+
},
|
|
1214
1638
|
async listFolders(workspaceId) {
|
|
1215
1639
|
return request(
|
|
1216
1640
|
`${base}/api/v1/workspaces/${workspaceId}/folders`,
|
|
@@ -1290,6 +1714,13 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
1290
1714
|
() => void 0
|
|
1291
1715
|
);
|
|
1292
1716
|
},
|
|
1717
|
+
async setExpiration(artifactId, expiresInSeconds) {
|
|
1718
|
+
return request(
|
|
1719
|
+
`${base}/api/v1/artifacts/${artifactId}/expiration`,
|
|
1720
|
+
{ body: JSON.stringify({ expiresInSeconds }), headers, method: "PUT" },
|
|
1721
|
+
(d) => expirationResponseSchema.parse(d)
|
|
1722
|
+
);
|
|
1723
|
+
},
|
|
1293
1724
|
// SPEC §6.16 — set a password (string) or clear it (null). Authorized
|
|
1294
1725
|
// by the publish token this client was created with.
|
|
1295
1726
|
async setProtection(artifactId, password) {
|
|
@@ -1346,17 +1777,17 @@ import {
|
|
|
1346
1777
|
writeFileSync as writeFileSync2
|
|
1347
1778
|
} from "fs";
|
|
1348
1779
|
import path2 from "path";
|
|
1349
|
-
import { z as
|
|
1780
|
+
import { z as z24 } from "zod";
|
|
1350
1781
|
|
|
1351
1782
|
// src/credentials.ts
|
|
1352
1783
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1353
1784
|
import { homedir } from "os";
|
|
1354
1785
|
import path from "path";
|
|
1355
|
-
import { z as
|
|
1786
|
+
import { z as z23 } from "zod";
|
|
1356
1787
|
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
1357
|
-
var credentialsSchema =
|
|
1358
|
-
keys:
|
|
1359
|
-
version:
|
|
1788
|
+
var credentialsSchema = z23.object({
|
|
1789
|
+
keys: z23.record(z23.string(), z23.string()),
|
|
1790
|
+
version: z23.literal(1)
|
|
1360
1791
|
});
|
|
1361
1792
|
function credentialsPath() {
|
|
1362
1793
|
return path.join(homedir(), ".artifactcafe", "credentials");
|
|
@@ -1406,24 +1837,24 @@ function resolveApiKey(apiUrl2, flag) {
|
|
|
1406
1837
|
}
|
|
1407
1838
|
|
|
1408
1839
|
// src/config.ts
|
|
1409
|
-
var configSchema =
|
|
1410
|
-
apiUrl:
|
|
1411
|
-
artifactId:
|
|
1412
|
-
folder:
|
|
1413
|
-
folderId:
|
|
1840
|
+
var configSchema = z24.object({
|
|
1841
|
+
apiUrl: z24.string(),
|
|
1842
|
+
artifactId: z24.string(),
|
|
1843
|
+
folder: z24.string().optional(),
|
|
1844
|
+
folderId: z24.string().optional(),
|
|
1414
1845
|
// Optional: a folder linked by `link` to an artifact you own carries no
|
|
1415
1846
|
// token — owner actions authorize via your stored login instead. A first
|
|
1416
1847
|
// publish still writes the anonymous-mode token here.
|
|
1417
|
-
publishToken:
|
|
1418
|
-
slug:
|
|
1419
|
-
url:
|
|
1420
|
-
workspace:
|
|
1421
|
-
workspaceId:
|
|
1422
|
-
workspaceKind:
|
|
1423
|
-
});
|
|
1424
|
-
var projectConfigSchema =
|
|
1425
|
-
folder:
|
|
1426
|
-
workspace:
|
|
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)
|
|
1427
1858
|
}).strict();
|
|
1428
1859
|
var CONFIG_DIR = ".artifactcafe";
|
|
1429
1860
|
var CONFIG_FILE = "config.json";
|
|
@@ -1459,7 +1890,7 @@ function readProjectConfig(dir) {
|
|
|
1459
1890
|
JSON.parse(readFileSync2(file, "utf8"))
|
|
1460
1891
|
);
|
|
1461
1892
|
} catch (error) {
|
|
1462
|
-
const message = error instanceof
|
|
1893
|
+
const message = error instanceof z24.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
|
|
1463
1894
|
throw new Error(`Invalid ${file}: ${message}.`);
|
|
1464
1895
|
}
|
|
1465
1896
|
}
|
|
@@ -1669,27 +2100,27 @@ async function comments(opts) {
|
|
|
1669
2100
|
});
|
|
1670
2101
|
return;
|
|
1671
2102
|
}
|
|
1672
|
-
const
|
|
2103
|
+
const data2 = await client.exportComments(target.artifactId, {
|
|
1673
2104
|
status: status2,
|
|
1674
2105
|
version
|
|
1675
2106
|
});
|
|
1676
2107
|
if (opts.json) {
|
|
1677
|
-
console.log(JSON.stringify(
|
|
2108
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
1678
2109
|
return;
|
|
1679
2110
|
}
|
|
1680
|
-
printHuman(
|
|
2111
|
+
printHuman(data2, url, status2, version);
|
|
1681
2112
|
}
|
|
1682
2113
|
async function deriveReviewUrl(client, target) {
|
|
1683
2114
|
const artifact = await client.getArtifact(target.artifactId);
|
|
1684
2115
|
return reviewUrl(target.apiUrl, artifact.slug);
|
|
1685
2116
|
}
|
|
1686
2117
|
var sleep = (s) => new Promise((r) => setTimeout(r, s * 1e3));
|
|
1687
|
-
function messageCounts(
|
|
1688
|
-
return new Map(
|
|
2118
|
+
function messageCounts(data2) {
|
|
2119
|
+
return new Map(data2.comments.map((t) => [t.id, 1 + t.replies.length]));
|
|
1689
2120
|
}
|
|
1690
|
-
function diffThreads(baseline,
|
|
2121
|
+
function diffThreads(baseline, data2) {
|
|
1691
2122
|
const changed = [];
|
|
1692
|
-
for (const t of
|
|
2123
|
+
for (const t of data2.comments) {
|
|
1693
2124
|
const before = baseline.get(t.id);
|
|
1694
2125
|
if (before === void 0) {
|
|
1695
2126
|
changed.push({ ...t, change: "new" });
|
|
@@ -1732,8 +2163,8 @@ async function waitForFeedback(client, config, opts) {
|
|
|
1732
2163
|
while (Date.now() < deadline) {
|
|
1733
2164
|
await sleep(Math.min(pollSeconds(), (deadline - Date.now()) / 1e3));
|
|
1734
2165
|
await heartbeat();
|
|
1735
|
-
const
|
|
1736
|
-
if (diffThreads(baseline,
|
|
2166
|
+
const data2 = await client.exportComments(config.artifactId, query);
|
|
2167
|
+
if (diffThreads(baseline, data2).length === 0) {
|
|
1737
2168
|
continue;
|
|
1738
2169
|
}
|
|
1739
2170
|
await sleep(settleSeconds());
|
|
@@ -1766,7 +2197,7 @@ async function waitForFeedback(client, config, opts) {
|
|
|
1766
2197
|
}
|
|
1767
2198
|
process.exit(WAIT_TIMEOUT_EXIT);
|
|
1768
2199
|
}
|
|
1769
|
-
function printWaitResult(config,
|
|
2200
|
+
function printWaitResult(config, data2, changed, json) {
|
|
1770
2201
|
if (json) {
|
|
1771
2202
|
console.log(
|
|
1772
2203
|
JSON.stringify(
|
|
@@ -1774,7 +2205,7 @@ function printWaitResult(config, data, changed, json) {
|
|
|
1774
2205
|
artifactId: config.artifactId,
|
|
1775
2206
|
threads: changed,
|
|
1776
2207
|
timedOut: false,
|
|
1777
|
-
versionNumber:
|
|
2208
|
+
versionNumber: data2.versionNumber
|
|
1778
2209
|
},
|
|
1779
2210
|
null,
|
|
1780
2211
|
2
|
|
@@ -1793,22 +2224,22 @@ ${changed.length} thread${plural} with new feedback \u2014 ${config.url}
|
|
|
1793
2224
|
printThread(thread);
|
|
1794
2225
|
}
|
|
1795
2226
|
}
|
|
1796
|
-
function printHuman(
|
|
2227
|
+
function printHuman(data2, url, status2, version) {
|
|
1797
2228
|
const statusLabel = status2 === "all" ? "" : `${status2} `;
|
|
1798
2229
|
let scope = "across all versions";
|
|
1799
2230
|
if (version === "current") {
|
|
1800
|
-
scope =
|
|
2231
|
+
scope = data2.versionNumber === null ? "on the current version" : `on v${data2.versionNumber}`;
|
|
1801
2232
|
}
|
|
1802
|
-
if (
|
|
2233
|
+
if (data2.comments.length === 0) {
|
|
1803
2234
|
console.log(`No ${statusLabel}comments ${scope}.`);
|
|
1804
2235
|
return;
|
|
1805
2236
|
}
|
|
1806
|
-
const plural =
|
|
2237
|
+
const plural = data2.comments.length === 1 ? "" : "s";
|
|
1807
2238
|
console.log(
|
|
1808
|
-
`${
|
|
2239
|
+
`${data2.comments.length} ${statusLabel}thread${plural} ${scope} \u2014 ${url}
|
|
1809
2240
|
`
|
|
1810
2241
|
);
|
|
1811
|
-
for (const thread of
|
|
2242
|
+
for (const thread of data2.comments) {
|
|
1812
2243
|
printThread(thread);
|
|
1813
2244
|
}
|
|
1814
2245
|
}
|
|
@@ -1853,18 +2284,230 @@ function indent(text, pad) {
|
|
|
1853
2284
|
}
|
|
1854
2285
|
|
|
1855
2286
|
// src/dashboard.ts
|
|
1856
|
-
import
|
|
2287
|
+
import path8 from "path";
|
|
1857
2288
|
|
|
1858
2289
|
// src/publish.ts
|
|
1859
2290
|
import { exec } from "child_process";
|
|
1860
|
-
import { readFileSync as
|
|
1861
|
-
import
|
|
2291
|
+
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
2292
|
+
import path7 from "path";
|
|
1862
2293
|
import { createInterface } from "readline";
|
|
1863
2294
|
|
|
2295
|
+
// src/data.ts
|
|
2296
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2297
|
+
import path5 from "path";
|
|
2298
|
+
function inspectDataManifest(files) {
|
|
2299
|
+
const info = { announcements: [] };
|
|
2300
|
+
const manifestFile = files.find((f) => f.path === DATA_MANIFEST_PATH);
|
|
2301
|
+
if (manifestFile) {
|
|
2302
|
+
const parsed = parseDataManifest(
|
|
2303
|
+
readFileSync3(manifestFile.absPath, "utf8")
|
|
2304
|
+
);
|
|
2305
|
+
if (!parsed.ok) {
|
|
2306
|
+
throw new Error(
|
|
2307
|
+
`${DATA_MANIFEST_PATH} is invalid:
|
|
2308
|
+
${(parsed.errors ?? []).map((e) => ` - ${e}`).join("\n")}`
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
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
|
+
];
|
|
2319
|
+
if (collection.access.update !== "owner") {
|
|
2320
|
+
bits.push(`update: ${collection.access.update}`);
|
|
2321
|
+
}
|
|
2322
|
+
if (collection.access.delete !== "owner") {
|
|
2323
|
+
bits.push(`delete: ${collection.access.delete}`);
|
|
2324
|
+
}
|
|
2325
|
+
if (collection.rateLimit) {
|
|
2326
|
+
bits.push(collection.rateLimit);
|
|
2327
|
+
}
|
|
2328
|
+
info.announcements.push(`\u26C1 Data enabled: ${name} (${bits.join(", ")})`);
|
|
2329
|
+
}
|
|
2330
|
+
if (info.announcements.length === 0) {
|
|
2331
|
+
info.announcements.push(
|
|
2332
|
+
"\u26C1 Data disabled (empty collections in .artifact/data.json)"
|
|
2333
|
+
);
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
return info;
|
|
2337
|
+
}
|
|
2338
|
+
function requireLinkedArtifact(dir) {
|
|
2339
|
+
const resolved = path5.resolve(dir);
|
|
2340
|
+
const config = readConfig(resolved);
|
|
2341
|
+
if (!config) {
|
|
2342
|
+
throw new Error(
|
|
2343
|
+
`No artifact linked in ${resolved} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
return {
|
|
2347
|
+
apiUrl: process.env.ARTIFACT_CAFE_URL ?? config.apiUrl,
|
|
2348
|
+
artifactId: config.artifactId,
|
|
2349
|
+
publishToken: resolveApiKey(process.env.ARTIFACT_CAFE_URL ?? config.apiUrl) ?? config.publishToken ?? process.env.ARTIFACT_CAFE_TOKEN,
|
|
2350
|
+
url: config.url
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
async function dataLs(opts) {
|
|
2354
|
+
const linked = requireLinkedArtifact(opts.dir);
|
|
2355
|
+
const client = createApiClient(linked.apiUrl, linked.publishToken);
|
|
2356
|
+
const records = [];
|
|
2357
|
+
let cursor;
|
|
2358
|
+
do {
|
|
2359
|
+
const page = await client.listData(linked.artifactId, opts.collection, {
|
|
2360
|
+
cursor,
|
|
2361
|
+
limit: 200
|
|
2362
|
+
});
|
|
2363
|
+
records.push(...page.records);
|
|
2364
|
+
cursor = page.nextCursor ?? void 0;
|
|
2365
|
+
} while (cursor && (opts.limit === void 0 || records.length < opts.limit));
|
|
2366
|
+
const shown = opts.limit === void 0 ? records : records.slice(0, opts.limit);
|
|
2367
|
+
if (opts.json) {
|
|
2368
|
+
console.log(
|
|
2369
|
+
JSON.stringify({ collection: opts.collection, records: shown }, null, 2)
|
|
2370
|
+
);
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2373
|
+
if (shown.length === 0) {
|
|
2374
|
+
console.log(`No records in "${opts.collection}".`);
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
const plural = shown.length === 1 ? "" : "s";
|
|
2378
|
+
console.log(
|
|
2379
|
+
`${shown.length} record${plural} in "${opts.collection}" \u2014 ${linked.url}
|
|
2380
|
+
`
|
|
2381
|
+
);
|
|
2382
|
+
for (const record of shown) {
|
|
2383
|
+
const { created_at, created_by, id, updated_at: _u, ...fields } = record;
|
|
2384
|
+
const author = created_by ?? "anonymous";
|
|
2385
|
+
console.log(`\u25CF ${id} \xB7 ${String(created_at).slice(0, 10)} \xB7 ${author}`);
|
|
2386
|
+
console.log(` ${JSON.stringify(fields)}`);
|
|
2387
|
+
}
|
|
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>.");
|
|
2396
|
+
}
|
|
2397
|
+
const text = opts.record ?? readFileSync3(opts.file, "utf8");
|
|
2398
|
+
let parsed;
|
|
2399
|
+
try {
|
|
2400
|
+
parsed = JSON.parse(text);
|
|
2401
|
+
} catch {
|
|
2402
|
+
throw new Error(`Records must be ${RECORD_SHAPE_HINT}.`);
|
|
2403
|
+
}
|
|
2404
|
+
const linked = requireLinkedArtifact(opts.dir);
|
|
2405
|
+
const client = createApiClient(linked.apiUrl, linked.publishToken);
|
|
2406
|
+
if (isRecord(parsed)) {
|
|
2407
|
+
const record = await client.insertData(
|
|
2408
|
+
linked.artifactId,
|
|
2409
|
+
opts.collection,
|
|
2410
|
+
parsed
|
|
2411
|
+
);
|
|
2412
|
+
if (opts.json) {
|
|
2413
|
+
console.log(JSON.stringify({ collection: opts.collection, record }));
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
console.log(`Inserted ${record.id} into "${opts.collection}".`);
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
if (!(Array.isArray(parsed) && parsed.length > 0 && parsed.every(isRecord))) {
|
|
2420
|
+
throw new Error(`Records must be ${RECORD_SHAPE_HINT}.`);
|
|
2421
|
+
}
|
|
2422
|
+
const records = [];
|
|
2423
|
+
for (let i = 0; i < parsed.length; i += DATA_LIMITS.maxInsertBatch) {
|
|
2424
|
+
try {
|
|
2425
|
+
const page = await client.insertDataBatch(
|
|
2426
|
+
linked.artifactId,
|
|
2427
|
+
opts.collection,
|
|
2428
|
+
parsed.slice(i, i + DATA_LIMITS.maxInsertBatch)
|
|
2429
|
+
);
|
|
2430
|
+
records.push(...page.records);
|
|
2431
|
+
} catch (err) {
|
|
2432
|
+
if (records.length === 0) {
|
|
2433
|
+
throw err;
|
|
2434
|
+
}
|
|
2435
|
+
const note = ` (the first ${records.length} records were inserted; records ${i}+ may not have been)`;
|
|
2436
|
+
if (err instanceof ApiRequestError) {
|
|
2437
|
+
throw new ApiRequestError(
|
|
2438
|
+
err.status,
|
|
2439
|
+
err.code,
|
|
2440
|
+
`${err.message}${note}`,
|
|
2441
|
+
err.retryAfter
|
|
2442
|
+
);
|
|
2443
|
+
}
|
|
2444
|
+
throw new Error(
|
|
2445
|
+
`${err instanceof Error ? err.message : String(err)}${note}`
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
if (opts.json) {
|
|
2450
|
+
console.log(JSON.stringify({ collection: opts.collection, records }));
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
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) {
|
|
2459
|
+
const linked = requireLinkedArtifact(opts.dir);
|
|
2460
|
+
const client = createApiClient(linked.apiUrl, linked.publishToken);
|
|
2461
|
+
const summary = await client.getDataSummary(linked.artifactId);
|
|
2462
|
+
const collections = {};
|
|
2463
|
+
for (const { name } of summary.collections) {
|
|
2464
|
+
const records = [];
|
|
2465
|
+
let cursor;
|
|
2466
|
+
do {
|
|
2467
|
+
const page = await client.listData(linked.artifactId, name, {
|
|
2468
|
+
cursor,
|
|
2469
|
+
limit: 200
|
|
2470
|
+
});
|
|
2471
|
+
records.push(...page.records);
|
|
2472
|
+
cursor = page.nextCursor ?? void 0;
|
|
2473
|
+
} while (cursor);
|
|
2474
|
+
collections[name] = records;
|
|
2475
|
+
}
|
|
2476
|
+
console.log(
|
|
2477
|
+
JSON.stringify(
|
|
2478
|
+
{
|
|
2479
|
+
artifactId: linked.artifactId,
|
|
2480
|
+
collections,
|
|
2481
|
+
enabled: summary.enabled,
|
|
2482
|
+
url: linked.url
|
|
2483
|
+
},
|
|
2484
|
+
null,
|
|
2485
|
+
2
|
|
2486
|
+
)
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
async function dataClear(opts) {
|
|
2490
|
+
if (!opts.yes) {
|
|
2491
|
+
throw new Error(
|
|
2492
|
+
`Clearing "${opts.collection}" deletes every record in it. Re-run with --yes to confirm.`
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
const linked = requireLinkedArtifact(opts.dir);
|
|
2496
|
+
const client = createApiClient(linked.apiUrl, linked.publishToken);
|
|
2497
|
+
const result = await client.clearData(linked.artifactId, opts.collection);
|
|
2498
|
+
if (opts.json) {
|
|
2499
|
+
console.log(
|
|
2500
|
+
JSON.stringify({ cleared: result.cleared, collection: opts.collection })
|
|
2501
|
+
);
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
console.log(`Cleared ${result.cleared} record(s) from "${opts.collection}".`);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
1864
2507
|
// src/manifest.ts
|
|
1865
2508
|
import { createHash } from "crypto";
|
|
1866
|
-
import { readdirSync, readFileSync as
|
|
1867
|
-
import
|
|
2509
|
+
import { lstatSync, readdirSync, readFileSync as readFileSync4, statSync } from "fs";
|
|
2510
|
+
import path6 from "path";
|
|
1868
2511
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".artifactcafe"]);
|
|
1869
2512
|
var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store", "artifact-cafe.json"]);
|
|
1870
2513
|
function isIgnoredFile(name) {
|
|
@@ -1885,6 +2528,7 @@ function isIgnoredFile(name) {
|
|
|
1885
2528
|
var MIME_TYPES = {
|
|
1886
2529
|
".avif": "image/avif",
|
|
1887
2530
|
".css": "text/css",
|
|
2531
|
+
".csv": "text/csv",
|
|
1888
2532
|
".gif": "image/gif",
|
|
1889
2533
|
".htm": "text/html",
|
|
1890
2534
|
".html": "text/html",
|
|
@@ -1894,6 +2538,7 @@ var MIME_TYPES = {
|
|
|
1894
2538
|
".js": "text/javascript",
|
|
1895
2539
|
".json": "application/json",
|
|
1896
2540
|
".map": "application/json",
|
|
2541
|
+
".markdown": "text/markdown",
|
|
1897
2542
|
".md": "text/markdown",
|
|
1898
2543
|
".mjs": "text/javascript",
|
|
1899
2544
|
".mp3": "audio/mpeg",
|
|
@@ -1913,7 +2558,32 @@ var MIME_TYPES = {
|
|
|
1913
2558
|
".xml": "application/xml"
|
|
1914
2559
|
};
|
|
1915
2560
|
function mimeFor(filePath) {
|
|
1916
|
-
return MIME_TYPES[
|
|
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)) {
|
|
2565
|
+
return null;
|
|
2566
|
+
}
|
|
2567
|
+
const { size } = statSync(absPath);
|
|
2568
|
+
if (size === 0) {
|
|
2569
|
+
return null;
|
|
2570
|
+
}
|
|
2571
|
+
return {
|
|
2572
|
+
absPath,
|
|
2573
|
+
hash: `sha256_${createHash("sha256").update(readFileSync4(absPath)).digest("hex")}`,
|
|
2574
|
+
mimeType: mimeFor(rel),
|
|
2575
|
+
path: rel,
|
|
2576
|
+
size
|
|
2577
|
+
};
|
|
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] };
|
|
1917
2587
|
}
|
|
1918
2588
|
function scanDirectory(dir) {
|
|
1919
2589
|
const files = [];
|
|
@@ -1921,7 +2591,7 @@ function scanDirectory(dir) {
|
|
|
1921
2591
|
function walk(current, relPrefix) {
|
|
1922
2592
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
1923
2593
|
const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
|
|
1924
|
-
const abs =
|
|
2594
|
+
const abs = path6.join(current, entry.name);
|
|
1925
2595
|
if (entry.isSymbolicLink()) {
|
|
1926
2596
|
skipped.push(rel);
|
|
1927
2597
|
continue;
|
|
@@ -1937,23 +2607,12 @@ function scanDirectory(dir) {
|
|
|
1937
2607
|
if (!entry.isFile()) {
|
|
1938
2608
|
continue;
|
|
1939
2609
|
}
|
|
1940
|
-
|
|
2610
|
+
const file = localFile(abs, rel);
|
|
2611
|
+
if (!file) {
|
|
1941
2612
|
skipped.push(rel);
|
|
1942
2613
|
continue;
|
|
1943
2614
|
}
|
|
1944
|
-
|
|
1945
|
-
if (size === 0) {
|
|
1946
|
-
skipped.push(rel);
|
|
1947
|
-
continue;
|
|
1948
|
-
}
|
|
1949
|
-
const content = readFileSync3(abs);
|
|
1950
|
-
files.push({
|
|
1951
|
-
absPath: abs,
|
|
1952
|
-
hash: `sha256_${createHash("sha256").update(content).digest("hex")}`,
|
|
1953
|
-
mimeType: mimeFor(rel),
|
|
1954
|
-
path: rel,
|
|
1955
|
-
size
|
|
1956
|
-
});
|
|
2615
|
+
files.push(file);
|
|
1957
2616
|
}
|
|
1958
2617
|
}
|
|
1959
2618
|
walk(dir, "");
|
|
@@ -1961,27 +2620,37 @@ function scanDirectory(dir) {
|
|
|
1961
2620
|
return { files, skipped };
|
|
1962
2621
|
}
|
|
1963
2622
|
function resolveEntry(files, explicit) {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2623
|
+
return resolvePublishEntry(files, explicit);
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
// src/spinner.ts
|
|
2627
|
+
var FRAMES = ["\xE3", "\xE4", "\xE5"];
|
|
2628
|
+
var INTERVAL_MS = 420;
|
|
2629
|
+
var AMBER2 = "\x1B[38;2;214;138;57m";
|
|
2630
|
+
var RESET2 = "\x1B[0m";
|
|
2631
|
+
var CLEAR_LINE = "\r\x1B[2K";
|
|
2632
|
+
function startBrewing(label, enabled) {
|
|
2633
|
+
const stream = process.stderr;
|
|
2634
|
+
if (!(enabled && stream.isTTY) || process.env.CI) {
|
|
2635
|
+
return { stop: () => void 0 };
|
|
2636
|
+
}
|
|
2637
|
+
const color = !process.env.NO_COLOR;
|
|
2638
|
+
let frame = 0;
|
|
2639
|
+
const draw = () => {
|
|
2640
|
+
const glyph = FRAMES[frame % FRAMES.length];
|
|
2641
|
+
stream.write(
|
|
2642
|
+
`${CLEAR_LINE} ${color ? `${AMBER2}${glyph}${RESET2}` : glyph} ${label}`
|
|
2643
|
+
);
|
|
2644
|
+
frame += 1;
|
|
2645
|
+
};
|
|
2646
|
+
draw();
|
|
2647
|
+
const timer = setInterval(draw, INTERVAL_MS);
|
|
2648
|
+
return {
|
|
2649
|
+
stop() {
|
|
2650
|
+
clearInterval(timer);
|
|
2651
|
+
stream.write(CLEAR_LINE);
|
|
1969
2652
|
}
|
|
1970
|
-
|
|
1971
|
-
}
|
|
1972
|
-
if (files.some((f) => f.path === "index.html")) {
|
|
1973
|
-
return "index.html";
|
|
1974
|
-
}
|
|
1975
|
-
const htmlFiles = files.filter(
|
|
1976
|
-
(f) => f.path.endsWith(".html") && !f.path.includes("/")
|
|
1977
|
-
);
|
|
1978
|
-
const [onlyHtmlFile] = htmlFiles;
|
|
1979
|
-
if (onlyHtmlFile && htmlFiles.length === 1) {
|
|
1980
|
-
return onlyHtmlFile.path;
|
|
1981
|
-
}
|
|
1982
|
-
throw new Error(
|
|
1983
|
-
htmlFiles.length === 0 ? "No entry file found. Add an index.html or pass --entry <file>." : `Multiple HTML files found (${htmlFiles.map((f) => f.path).join(", ")}). Pass --entry <file>.`
|
|
1984
|
-
);
|
|
2653
|
+
};
|
|
1985
2654
|
}
|
|
1986
2655
|
|
|
1987
2656
|
// src/publish.ts
|
|
@@ -1992,8 +2661,10 @@ var REVIEW_LABEL = {
|
|
|
1992
2661
|
readonly: "read-only \u2014 existing comments only"
|
|
1993
2662
|
};
|
|
1994
2663
|
async function publish(opts) {
|
|
1995
|
-
const
|
|
2664
|
+
const target = path7.resolve(opts.dir);
|
|
2665
|
+
const dir = statSync2(target).isFile() ? path7.dirname(target) : target;
|
|
1996
2666
|
const config = readConfig(dir);
|
|
2667
|
+
const expiresInSeconds = opts.expiresIn === void 0 ? void 0 : parseArtifactDuration(opts.expiresIn);
|
|
1997
2668
|
if (opts.password !== void 0) {
|
|
1998
2669
|
const parsed = gatePasswordSchema.safeParse(opts.password);
|
|
1999
2670
|
if (!parsed.success) {
|
|
@@ -2018,13 +2689,14 @@ async function publish(opts) {
|
|
|
2018
2689
|
apiUrl: apiUrl2,
|
|
2019
2690
|
config,
|
|
2020
2691
|
isNewArtifact: artifactId === null,
|
|
2021
|
-
opts
|
|
2692
|
+
opts: { ...opts, dir }
|
|
2022
2693
|
});
|
|
2023
|
-
const { files, skipped } =
|
|
2694
|
+
const { files, skipped } = scanPath(target);
|
|
2024
2695
|
if (files.length === 0) {
|
|
2025
|
-
throw new Error(`Nothing to publish
|
|
2696
|
+
throw new Error(`Nothing to publish at ${target} (after ignore rules).`);
|
|
2026
2697
|
}
|
|
2027
2698
|
const entryPath = resolveEntry(files, opts.entry);
|
|
2699
|
+
const dataInfo = inspectDataManifest(files);
|
|
2028
2700
|
const log = (msg) => {
|
|
2029
2701
|
if (!opts.json) {
|
|
2030
2702
|
console.log(msg);
|
|
@@ -2035,6 +2707,7 @@ async function publish(opts) {
|
|
|
2035
2707
|
}
|
|
2036
2708
|
const sessionBody = {
|
|
2037
2709
|
artifactId,
|
|
2710
|
+
...expiresInSeconds === void 0 ? {} : { expiresInSeconds },
|
|
2038
2711
|
// Overrides the entry HTML's own meta description on the share card.
|
|
2039
2712
|
// Omitted entirely when absent, so the document keeps speaking for itself.
|
|
2040
2713
|
...opts.description?.trim() ? { description: opts.description.trim() } : {},
|
|
@@ -2069,11 +2742,17 @@ async function publish(opts) {
|
|
|
2069
2742
|
client = createApiClient(apiUrl2, credential);
|
|
2070
2743
|
session = await client.createSession(sessionBody);
|
|
2071
2744
|
}
|
|
2745
|
+
if (expiresInSeconds !== void 0 && session.expiresInSeconds !== expiresInSeconds) {
|
|
2746
|
+
throw new Error(
|
|
2747
|
+
"This API does not support temporary publishing. Nothing was uploaded or finalized; upgrade the API before using --expires-in."
|
|
2748
|
+
);
|
|
2749
|
+
}
|
|
2072
2750
|
const byPath = new Map(files.map((f) => [f.path, f]));
|
|
2073
2751
|
log(
|
|
2074
2752
|
` uploading ${session.filesToUpload.length} file(s)` + (session.skippedFiles.length ? `, ${session.skippedFiles.length} unchanged` : "")
|
|
2075
2753
|
);
|
|
2076
2754
|
const presence = startPublishingPresence(client, session.artifactId);
|
|
2755
|
+
const brewing = startBrewing("brewing\u2026", !opts.json);
|
|
2077
2756
|
try {
|
|
2078
2757
|
await inBatches(
|
|
2079
2758
|
session.filesToUpload,
|
|
@@ -2085,15 +2764,23 @@ async function publish(opts) {
|
|
|
2085
2764
|
}
|
|
2086
2765
|
await uploadFile(
|
|
2087
2766
|
upload.uploadUrl,
|
|
2088
|
-
|
|
2767
|
+
readFileSync5(file.absPath),
|
|
2089
2768
|
file.mimeType
|
|
2090
2769
|
);
|
|
2091
2770
|
}
|
|
2092
2771
|
);
|
|
2772
|
+
} catch (error) {
|
|
2773
|
+
brewing.stop();
|
|
2774
|
+
throw error;
|
|
2093
2775
|
} finally {
|
|
2094
2776
|
presence.stop();
|
|
2095
2777
|
}
|
|
2096
|
-
|
|
2778
|
+
let result;
|
|
2779
|
+
try {
|
|
2780
|
+
result = await client.finalize(session.publishSessionId);
|
|
2781
|
+
} finally {
|
|
2782
|
+
brewing.stop();
|
|
2783
|
+
}
|
|
2097
2784
|
if (usingAgentToken && result.workspaceId) {
|
|
2098
2785
|
placement.workspaceId = result.workspaceId;
|
|
2099
2786
|
}
|
|
@@ -2133,6 +2820,9 @@ async function publish(opts) {
|
|
|
2133
2820
|
JSON.stringify(
|
|
2134
2821
|
{
|
|
2135
2822
|
artifactId: result.artifactId,
|
|
2823
|
+
autoDeleteAt: result.autoDeleteAt ?? null,
|
|
2824
|
+
expiresAt: result.expiresAt ?? null,
|
|
2825
|
+
...dataInfo.config ? { data: dataInfo.config } : {},
|
|
2136
2826
|
claimed: result.claimed,
|
|
2137
2827
|
destination: placement.destination,
|
|
2138
2828
|
destinationSource: placement.destinationSource,
|
|
@@ -2176,7 +2866,7 @@ ${result.url}
|
|
|
2176
2866
|
${result.claimUrl}
|
|
2177
2867
|
`
|
|
2178
2868
|
);
|
|
2179
|
-
} else if (result.claimed) {
|
|
2869
|
+
} else if (result.claimed && !result.expiresAt) {
|
|
2180
2870
|
console.log("Claimed by your account \u2014 no expiry.\n");
|
|
2181
2871
|
}
|
|
2182
2872
|
if (opts.password !== void 0) {
|
|
@@ -2211,6 +2901,17 @@ ${result.claimUrl}
|
|
|
2211
2901
|
`);
|
|
2212
2902
|
}
|
|
2213
2903
|
}
|
|
2904
|
+
if (!opts.json) {
|
|
2905
|
+
if (result.expiresAt) {
|
|
2906
|
+
console.log(
|
|
2907
|
+
`${result.autoDeleteAt ? "Temporary artifact" : "Anonymous artifact"} \u2014 expires at ${result.expiresAt}. All versions, comments, files, and artifact data will be automatically deleted.
|
|
2908
|
+
`
|
|
2909
|
+
);
|
|
2910
|
+
}
|
|
2911
|
+
for (const announcement of dataInfo.announcements) {
|
|
2912
|
+
console.log(announcement);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2214
2915
|
if (opts.open && !opts.json && process.stdout.isTTY) {
|
|
2215
2916
|
openInBrowser(result.url);
|
|
2216
2917
|
}
|
|
@@ -2280,9 +2981,9 @@ async function resolvePlacement(params) {
|
|
|
2280
2981
|
};
|
|
2281
2982
|
}
|
|
2282
2983
|
if (shouldPromptForWorkspace(opts)) {
|
|
2283
|
-
const
|
|
2284
|
-
if (
|
|
2285
|
-
const choice = await pickWorkspace(
|
|
2984
|
+
const data3 = await createApiClient(apiUrl2, apiKey).listWorkspaces();
|
|
2985
|
+
if (data3.workspaces.length > 1) {
|
|
2986
|
+
const choice = await pickWorkspace(data3.workspaces);
|
|
2286
2987
|
return {
|
|
2287
2988
|
destination: choice.workspace.isPersonal ? "personal" : "workspace",
|
|
2288
2989
|
destinationLabel: choice.workspace.isPersonal ? "Personal" : choice.workspace.name,
|
|
@@ -2308,19 +3009,19 @@ async function resolvePlacement(params) {
|
|
|
2308
3009
|
throw new Error("--folder requires --workspace or a stored workspace.");
|
|
2309
3010
|
}
|
|
2310
3011
|
const client = createApiClient(apiUrl2, apiKey);
|
|
2311
|
-
const
|
|
3012
|
+
const data2 = await client.listWorkspaces();
|
|
2312
3013
|
const workspaceHandle = opts.workspace ?? config?.workspace ?? projectConfig?.workspace;
|
|
2313
3014
|
let workspace;
|
|
2314
3015
|
if (workspaceHandle) {
|
|
2315
|
-
workspace =
|
|
3016
|
+
workspace = data2.workspaces.find(
|
|
2316
3017
|
(candidate) => candidate.handle === workspaceHandle
|
|
2317
3018
|
);
|
|
2318
3019
|
} else if (config?.workspaceId) {
|
|
2319
|
-
workspace =
|
|
3020
|
+
workspace = data2.workspaces.find(
|
|
2320
3021
|
(candidate) => candidate.id === config.workspaceId
|
|
2321
3022
|
);
|
|
2322
3023
|
} else {
|
|
2323
|
-
workspace =
|
|
3024
|
+
workspace = data2.workspaces.find((candidate) => candidate.isPersonal);
|
|
2324
3025
|
}
|
|
2325
3026
|
if (!workspace) {
|
|
2326
3027
|
const target = workspaceHandle ? `workspace with handle "${workspaceHandle}"` : "personal workspace";
|
|
@@ -2458,7 +3159,7 @@ function openInBrowser(url) {
|
|
|
2458
3159
|
|
|
2459
3160
|
// src/dashboard.ts
|
|
2460
3161
|
function openDashboard(opts) {
|
|
2461
|
-
const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(
|
|
3162
|
+
const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path8.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
|
|
2462
3163
|
const url = `${base.replace(/\/$/, "")}/dashboard`;
|
|
2463
3164
|
console.log(url);
|
|
2464
3165
|
if (process.stdout.isTTY) {
|
|
@@ -2500,6 +3201,35 @@ function printCliError(err, json) {
|
|
|
2500
3201
|
}
|
|
2501
3202
|
}
|
|
2502
3203
|
|
|
3204
|
+
// src/expiration.ts
|
|
3205
|
+
import path9 from "path";
|
|
3206
|
+
async function expiration(opts) {
|
|
3207
|
+
if (opts.duration !== void 0 === opts.keep) {
|
|
3208
|
+
throw new Error("Provide exactly one of --in <duration> or --keep.");
|
|
3209
|
+
}
|
|
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);
|
|
3216
|
+
if (opts.json) {
|
|
3217
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3220
|
+
if (result.autoDeleteAt) {
|
|
3221
|
+
console.log(`Temporary artifact \u2014 expires at ${result.expiresAt}.`);
|
|
3222
|
+
} else if (result.expiresAt) {
|
|
3223
|
+
console.log(
|
|
3224
|
+
`Temporary deadline cleared. Anonymous retention still expires at ${result.expiresAt}; claim the artifact to keep it.`
|
|
3225
|
+
);
|
|
3226
|
+
} else {
|
|
3227
|
+
console.log(
|
|
3228
|
+
"Artifact kept \u2014 no automatic expiry. Its link, versions, and comments are preserved."
|
|
3229
|
+
);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
|
|
2503
3233
|
// src/folders.ts
|
|
2504
3234
|
async function folders(opts) {
|
|
2505
3235
|
if (opts.action !== void 0 && opts.action !== "create") {
|
|
@@ -2544,21 +3274,21 @@ async function folders(opts) {
|
|
|
2544
3274
|
await createFolder(client, workspace, opts);
|
|
2545
3275
|
return;
|
|
2546
3276
|
}
|
|
2547
|
-
const
|
|
3277
|
+
const data2 = await client.listFolders(workspace.id);
|
|
2548
3278
|
if (opts.json) {
|
|
2549
|
-
console.log(JSON.stringify(
|
|
3279
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
2550
3280
|
return;
|
|
2551
3281
|
}
|
|
2552
|
-
if (
|
|
3282
|
+
if (data2.folders.length === 0) {
|
|
2553
3283
|
console.log(`No folders in workspace ${workspace.handle}.`);
|
|
2554
3284
|
return;
|
|
2555
3285
|
}
|
|
2556
|
-
const plural =
|
|
3286
|
+
const plural = data2.folders.length === 1 ? "" : "s";
|
|
2557
3287
|
console.log(
|
|
2558
|
-
`${
|
|
3288
|
+
`${data2.folders.length} folder${plural} in ${workspace.handle}
|
|
2559
3289
|
`
|
|
2560
3290
|
);
|
|
2561
|
-
for (const folder of
|
|
3291
|
+
for (const folder of data2.folders) {
|
|
2562
3292
|
printFolder(folder);
|
|
2563
3293
|
}
|
|
2564
3294
|
}
|
|
@@ -2596,7 +3326,7 @@ function printFolder(folder) {
|
|
|
2596
3326
|
}
|
|
2597
3327
|
|
|
2598
3328
|
// src/link.ts
|
|
2599
|
-
import
|
|
3329
|
+
import path10 from "path";
|
|
2600
3330
|
import { createInterface as createInterface2 } from "readline";
|
|
2601
3331
|
|
|
2602
3332
|
// src/list.ts
|
|
@@ -2653,28 +3383,28 @@ async function list(opts) {
|
|
|
2653
3383
|
await listWorkspace(client, opts);
|
|
2654
3384
|
return;
|
|
2655
3385
|
}
|
|
2656
|
-
const [
|
|
3386
|
+
const [data2, groups] = await Promise.all([
|
|
2657
3387
|
client.listArtifacts(),
|
|
2658
3388
|
teamGroups(client)
|
|
2659
3389
|
]);
|
|
2660
3390
|
if (opts.json) {
|
|
2661
3391
|
console.log(
|
|
2662
|
-
JSON.stringify({ artifacts:
|
|
3392
|
+
JSON.stringify({ artifacts: data2.artifacts, workspaces: groups }, null, 2)
|
|
2663
3393
|
);
|
|
2664
3394
|
return;
|
|
2665
3395
|
}
|
|
2666
|
-
const total =
|
|
3396
|
+
const total = data2.artifacts.length + groups.reduce((sum, group) => sum + group.artifacts.length, 0);
|
|
2667
3397
|
if (total === 0) {
|
|
2668
3398
|
console.log(
|
|
2669
3399
|
"No artifacts you can publish to yet \u2014 publish while logged in, or claim one via its claim link."
|
|
2670
3400
|
);
|
|
2671
3401
|
return;
|
|
2672
3402
|
}
|
|
2673
|
-
if (
|
|
2674
|
-
const plural =
|
|
2675
|
-
console.log(`${
|
|
3403
|
+
if (data2.artifacts.length > 0) {
|
|
3404
|
+
const plural = data2.artifacts.length === 1 ? "" : "s";
|
|
3405
|
+
console.log(`${data2.artifacts.length} artifact${plural}
|
|
2676
3406
|
`);
|
|
2677
|
-
for (const artifact of
|
|
3407
|
+
for (const artifact of data2.artifacts) {
|
|
2678
3408
|
printArtifact(artifact);
|
|
2679
3409
|
}
|
|
2680
3410
|
}
|
|
@@ -2825,7 +3555,7 @@ async function link(opts) {
|
|
|
2825
3555
|
}
|
|
2826
3556
|
chosen = await pickArtifact(artifacts);
|
|
2827
3557
|
}
|
|
2828
|
-
const dir =
|
|
3558
|
+
const dir = path10.resolve(opts.dir);
|
|
2829
3559
|
writeConfig(dir, {
|
|
2830
3560
|
apiUrl: apiUrl2,
|
|
2831
3561
|
artifactId: chosen.id,
|
|
@@ -2993,7 +3723,7 @@ function logout(opts) {
|
|
|
2993
3723
|
}
|
|
2994
3724
|
|
|
2995
3725
|
// src/message.ts
|
|
2996
|
-
import
|
|
3726
|
+
import path11 from "path";
|
|
2997
3727
|
async function readStdin2() {
|
|
2998
3728
|
if (process.stdin.isTTY) {
|
|
2999
3729
|
return "";
|
|
@@ -3017,7 +3747,7 @@ async function editMessage(opts) {
|
|
|
3017
3747
|
'Nothing to save. Pass the new text with --body "\u2026", or pipe it on stdin.'
|
|
3018
3748
|
);
|
|
3019
3749
|
}
|
|
3020
|
-
const dir =
|
|
3750
|
+
const dir = path11.resolve(opts.dir);
|
|
3021
3751
|
const target = resolveArtifactTarget(dir, {
|
|
3022
3752
|
apiKey: opts.apiKey,
|
|
3023
3753
|
artifact: opts.artifact
|
|
@@ -3037,7 +3767,7 @@ async function deleteMessage(opts) {
|
|
|
3037
3767
|
"Pass the message id to delete \u2014 get it from `artifact-cafe comments --json`."
|
|
3038
3768
|
);
|
|
3039
3769
|
}
|
|
3040
|
-
const dir =
|
|
3770
|
+
const dir = path11.resolve(opts.dir);
|
|
3041
3771
|
const target = resolveArtifactTarget(dir, {
|
|
3042
3772
|
apiKey: opts.apiKey,
|
|
3043
3773
|
artifact: opts.artifact
|
|
@@ -3052,12 +3782,12 @@ async function deleteMessage(opts) {
|
|
|
3052
3782
|
}
|
|
3053
3783
|
|
|
3054
3784
|
// src/move.ts
|
|
3055
|
-
import
|
|
3785
|
+
import path12 from "path";
|
|
3056
3786
|
async function move(opts) {
|
|
3057
3787
|
if (Boolean(opts.folder) === Boolean(opts.unfiled)) {
|
|
3058
3788
|
throw new Error("Pass exactly one of --folder <name-or-id> or --unfiled.");
|
|
3059
3789
|
}
|
|
3060
|
-
const dir =
|
|
3790
|
+
const dir = path12.resolve(opts.dir);
|
|
3061
3791
|
const target = resolveArtifactTarget(dir, {
|
|
3062
3792
|
apiKey: opts.apiKey,
|
|
3063
3793
|
artifact: opts.artifact
|
|
@@ -3078,8 +3808,8 @@ async function move(opts) {
|
|
|
3078
3808
|
let folderId = null;
|
|
3079
3809
|
let folderName = null;
|
|
3080
3810
|
if (opts.folder) {
|
|
3081
|
-
const
|
|
3082
|
-
const matches =
|
|
3811
|
+
const data2 = await client.listFolders(artifact.workspaceId);
|
|
3812
|
+
const matches = data2.folders.filter(
|
|
3083
3813
|
(candidate) => candidate.name === opts.folder || candidate.id === opts.folder
|
|
3084
3814
|
);
|
|
3085
3815
|
if (matches.length === 0) {
|
|
@@ -3147,9 +3877,9 @@ async function move(opts) {
|
|
|
3147
3877
|
}
|
|
3148
3878
|
|
|
3149
3879
|
// src/open.ts
|
|
3150
|
-
import
|
|
3880
|
+
import path13 from "path";
|
|
3151
3881
|
async function openArtifact(opts) {
|
|
3152
|
-
const dir =
|
|
3882
|
+
const dir = path13.resolve(opts.dir);
|
|
3153
3883
|
const target = resolveArtifactTarget(dir, {
|
|
3154
3884
|
apiKey: opts.apiKey,
|
|
3155
3885
|
artifact: opts.artifact
|
|
@@ -3167,7 +3897,7 @@ async function openArtifact(opts) {
|
|
|
3167
3897
|
}
|
|
3168
3898
|
|
|
3169
3899
|
// src/protect.ts
|
|
3170
|
-
import
|
|
3900
|
+
import path14 from "path";
|
|
3171
3901
|
async function protect(opts) {
|
|
3172
3902
|
if (opts.off && opts.password !== void 0) {
|
|
3173
3903
|
throw new Error("Pass either --password or --off, not both.");
|
|
@@ -3184,7 +3914,7 @@ async function protect(opts) {
|
|
|
3184
3914
|
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
3185
3915
|
}
|
|
3186
3916
|
}
|
|
3187
|
-
const dir =
|
|
3917
|
+
const dir = path14.resolve(opts.dir);
|
|
3188
3918
|
const target = resolveArtifactTarget(dir, {
|
|
3189
3919
|
apiKey: opts.apiKey,
|
|
3190
3920
|
artifact: opts.artifact
|
|
@@ -3209,7 +3939,7 @@ async function protect(opts) {
|
|
|
3209
3939
|
}
|
|
3210
3940
|
|
|
3211
3941
|
// src/reply.ts
|
|
3212
|
-
import
|
|
3942
|
+
import path15 from "path";
|
|
3213
3943
|
async function readStdin3() {
|
|
3214
3944
|
if (process.stdin.isTTY) {
|
|
3215
3945
|
return "";
|
|
@@ -3233,7 +3963,7 @@ async function replyToThread(opts) {
|
|
|
3233
3963
|
'Nothing to post. Pass the reply with --body "\u2026", or pipe it on stdin.'
|
|
3234
3964
|
);
|
|
3235
3965
|
}
|
|
3236
|
-
const dir =
|
|
3966
|
+
const dir = path15.resolve(opts.dir);
|
|
3237
3967
|
const target = resolveArtifactTarget(dir, {
|
|
3238
3968
|
apiKey: opts.apiKey,
|
|
3239
3969
|
artifact: opts.artifact
|
|
@@ -3262,7 +3992,7 @@ async function replyToThread(opts) {
|
|
|
3262
3992
|
}
|
|
3263
3993
|
|
|
3264
3994
|
// src/resolve.ts
|
|
3265
|
-
import
|
|
3995
|
+
import path16 from "path";
|
|
3266
3996
|
async function changeThreadStatus(opts) {
|
|
3267
3997
|
const threadId = opts.thread?.trim();
|
|
3268
3998
|
if (!threadId) {
|
|
@@ -3270,7 +4000,7 @@ async function changeThreadStatus(opts) {
|
|
|
3270
4000
|
`Pass the thread id to ${opts.action} \u2014 get it from \`artifact-cafe comments --json\`.`
|
|
3271
4001
|
);
|
|
3272
4002
|
}
|
|
3273
|
-
const dir =
|
|
4003
|
+
const dir = path16.resolve(opts.dir);
|
|
3274
4004
|
const target = resolveArtifactTarget(dir, {
|
|
3275
4005
|
apiKey: opts.apiKey,
|
|
3276
4006
|
artifact: opts.artifact
|
|
@@ -3307,7 +4037,7 @@ async function changeThreadStatus(opts) {
|
|
|
3307
4037
|
}
|
|
3308
4038
|
|
|
3309
4039
|
// src/review.ts
|
|
3310
|
-
import
|
|
4040
|
+
import path17 from "path";
|
|
3311
4041
|
var OUTCOME = {
|
|
3312
4042
|
off: "Plain artifact \u2014 the review tools are hidden entirely",
|
|
3313
4043
|
open: "Open for review \u2014 anyone who can open it can comment",
|
|
@@ -3321,7 +4051,7 @@ async function review(opts) {
|
|
|
3321
4051
|
);
|
|
3322
4052
|
}
|
|
3323
4053
|
const mode = parsed.data;
|
|
3324
|
-
const dir =
|
|
4054
|
+
const dir = path17.resolve(opts.dir);
|
|
3325
4055
|
const target = resolveArtifactTarget(dir, {
|
|
3326
4056
|
apiKey: opts.apiKey,
|
|
3327
4057
|
artifact: opts.artifact
|
|
@@ -3343,10 +4073,10 @@ async function review(opts) {
|
|
|
3343
4073
|
}
|
|
3344
4074
|
|
|
3345
4075
|
// src/share.ts
|
|
3346
|
-
import
|
|
4076
|
+
import path18 from "path";
|
|
3347
4077
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
3348
4078
|
async function share(opts) {
|
|
3349
|
-
const dir =
|
|
4079
|
+
const dir = path18.resolve(opts.dir);
|
|
3350
4080
|
const target = resolveArtifactTarget(dir, {
|
|
3351
4081
|
apiKey: opts.apiKey,
|
|
3352
4082
|
artifact: opts.artifact
|
|
@@ -3401,7 +4131,7 @@ General access: ${visibility}`);
|
|
|
3401
4131
|
console.log("");
|
|
3402
4132
|
}
|
|
3403
4133
|
async function unshare(opts) {
|
|
3404
|
-
const dir =
|
|
4134
|
+
const dir = path18.resolve(opts.dir);
|
|
3405
4135
|
const target = resolveArtifactTarget(dir, {
|
|
3406
4136
|
apiKey: opts.apiKey,
|
|
3407
4137
|
artifact: opts.artifact
|
|
@@ -3424,7 +4154,7 @@ async function unshare(opts) {
|
|
|
3424
4154
|
}
|
|
3425
4155
|
|
|
3426
4156
|
// src/status.ts
|
|
3427
|
-
import
|
|
4157
|
+
import path19 from "path";
|
|
3428
4158
|
async function status(opts) {
|
|
3429
4159
|
if (!AGENT_STATES.includes(opts.state)) {
|
|
3430
4160
|
throw new Error(
|
|
@@ -3432,7 +4162,7 @@ async function status(opts) {
|
|
|
3432
4162
|
);
|
|
3433
4163
|
}
|
|
3434
4164
|
const state = opts.state;
|
|
3435
|
-
const dir =
|
|
4165
|
+
const dir = path19.resolve(opts.dir);
|
|
3436
4166
|
const target = resolveArtifactTarget(dir, {
|
|
3437
4167
|
apiKey: opts.apiKey,
|
|
3438
4168
|
artifact: opts.artifact
|
|
@@ -3458,40 +4188,40 @@ async function status(opts) {
|
|
|
3458
4188
|
}
|
|
3459
4189
|
|
|
3460
4190
|
// src/update.ts
|
|
3461
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as
|
|
4191
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
3462
4192
|
import { createRequire } from "module";
|
|
3463
4193
|
import { homedir as homedir2 } from "os";
|
|
3464
|
-
import
|
|
3465
|
-
import { z as
|
|
4194
|
+
import path20 from "path";
|
|
4195
|
+
import { z as z25 } from "zod";
|
|
3466
4196
|
var REGISTRY_LATEST_URL = "https://registry.npmjs.org/artifact-cafe/latest";
|
|
3467
4197
|
var SKILL_SOURCE_URL = "https://raw.githubusercontent.com/Artifact-Cafe/skill/main/artifact-cafe/SKILL.md";
|
|
3468
4198
|
var SKILL_UPDATE_COMMAND = "npx skills add artifact-cafe/skill --skill artifact-cafe -g";
|
|
3469
4199
|
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3470
4200
|
var FETCH_TIMEOUT_MS = 2e3;
|
|
3471
4201
|
var SKILL_VERSION_PATTERN = /\*\*Skill version:\s*(\d+\.\d+\.\d+)\*\*/;
|
|
3472
|
-
var cacheSchema =
|
|
3473
|
-
checkedAt:
|
|
3474
|
-
cliLatest:
|
|
3475
|
-
skillLatest:
|
|
3476
|
-
version:
|
|
4202
|
+
var cacheSchema = z25.object({
|
|
4203
|
+
checkedAt: z25.number(),
|
|
4204
|
+
cliLatest: z25.string().nullable(),
|
|
4205
|
+
skillLatest: z25.string().nullable(),
|
|
4206
|
+
version: z25.literal(1)
|
|
3477
4207
|
});
|
|
3478
4208
|
function cliVersion() {
|
|
3479
4209
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
3480
4210
|
return pkg.version;
|
|
3481
4211
|
}
|
|
3482
4212
|
function cachePath() {
|
|
3483
|
-
return
|
|
4213
|
+
return path20.join(homedir2(), ".artifactcafe", "update-check.json");
|
|
3484
4214
|
}
|
|
3485
4215
|
function readCache() {
|
|
3486
4216
|
try {
|
|
3487
|
-
return cacheSchema.parse(JSON.parse(
|
|
4217
|
+
return cacheSchema.parse(JSON.parse(readFileSync6(cachePath(), "utf8")));
|
|
3488
4218
|
} catch {
|
|
3489
4219
|
return null;
|
|
3490
4220
|
}
|
|
3491
4221
|
}
|
|
3492
4222
|
function writeCache(cache) {
|
|
3493
4223
|
try {
|
|
3494
|
-
mkdirSync3(
|
|
4224
|
+
mkdirSync3(path20.dirname(cachePath()), { mode: 448, recursive: true });
|
|
3495
4225
|
writeFileSync3(cachePath(), `${JSON.stringify(cache, null, 2)}
|
|
3496
4226
|
`);
|
|
3497
4227
|
} catch {
|
|
@@ -3528,7 +4258,7 @@ async function fetchText(url) {
|
|
|
3528
4258
|
}
|
|
3529
4259
|
async function fetchLatestVersions(previous) {
|
|
3530
4260
|
const [cli, skill] = await Promise.all([
|
|
3531
|
-
fetchJson(REGISTRY_LATEST_URL).then((body) =>
|
|
4261
|
+
fetchJson(REGISTRY_LATEST_URL).then((body) => z25.object({ version: z25.string() }).parse(body).version).catch(() => null),
|
|
3532
4262
|
fetchText(SKILL_SOURCE_URL).then((body) => SKILL_VERSION_PATTERN.exec(body)?.[1] ?? null).catch(() => null)
|
|
3533
4263
|
]);
|
|
3534
4264
|
return {
|
|
@@ -3544,12 +4274,12 @@ function installedSkillVersions() {
|
|
|
3544
4274
|
const versions = [];
|
|
3545
4275
|
for (const root of roots) {
|
|
3546
4276
|
for (const dir of dirs) {
|
|
3547
|
-
const file =
|
|
4277
|
+
const file = path20.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
|
|
3548
4278
|
if (!existsSync3(file)) {
|
|
3549
4279
|
continue;
|
|
3550
4280
|
}
|
|
3551
4281
|
try {
|
|
3552
|
-
const found = SKILL_VERSION_PATTERN.exec(
|
|
4282
|
+
const found = SKILL_VERSION_PATTERN.exec(readFileSync6(file, "utf8"));
|
|
3553
4283
|
if (found?.[1]) {
|
|
3554
4284
|
versions.push(found[1]);
|
|
3555
4285
|
}
|
|
@@ -3618,19 +4348,19 @@ async function workspaces(opts) {
|
|
|
3618
4348
|
`Listing workspaces needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
3619
4349
|
);
|
|
3620
4350
|
}
|
|
3621
|
-
const
|
|
4351
|
+
const data2 = await createApiClient(apiUrl2, key).listWorkspaces();
|
|
3622
4352
|
if (opts.json) {
|
|
3623
|
-
console.log(JSON.stringify(
|
|
4353
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
3624
4354
|
return;
|
|
3625
4355
|
}
|
|
3626
|
-
if (
|
|
4356
|
+
if (data2.workspaces.length === 0) {
|
|
3627
4357
|
console.log("No workspaces yet \u2014 create one from the dashboard.");
|
|
3628
4358
|
return;
|
|
3629
4359
|
}
|
|
3630
|
-
const plural =
|
|
3631
|
-
console.log(`${
|
|
4360
|
+
const plural = data2.workspaces.length === 1 ? "" : "s";
|
|
4361
|
+
console.log(`${data2.workspaces.length} workspace${plural}
|
|
3632
4362
|
`);
|
|
3633
|
-
for (const workspace of
|
|
4363
|
+
for (const workspace of data2.workspaces) {
|
|
3634
4364
|
printWorkspace(workspace);
|
|
3635
4365
|
}
|
|
3636
4366
|
}
|
|
@@ -3672,10 +4402,14 @@ var publish2 = defineCommand({
|
|
|
3672
4402
|
},
|
|
3673
4403
|
dir: {
|
|
3674
4404
|
default: ".",
|
|
3675
|
-
description: "
|
|
4405
|
+
description: "File or directory to publish",
|
|
3676
4406
|
type: "positional"
|
|
3677
4407
|
},
|
|
3678
4408
|
entry: { description: "Entry file (default: auto-detect)", type: "string" },
|
|
4409
|
+
"expires-in": {
|
|
4410
|
+
description: "Temporary lifetime: 30m, 1h, 7d (ordinary republishes preserve it)",
|
|
4411
|
+
type: "string"
|
|
4412
|
+
},
|
|
3679
4413
|
folder: {
|
|
3680
4414
|
description: "Publish into this workspace folder name (or id)",
|
|
3681
4415
|
type: "string"
|
|
@@ -3700,7 +4434,10 @@ var publish2 = defineCommand({
|
|
|
3700
4434
|
type: "string"
|
|
3701
4435
|
}
|
|
3702
4436
|
},
|
|
3703
|
-
meta: {
|
|
4437
|
+
meta: {
|
|
4438
|
+
description: "Publish a file or folder as an artifact",
|
|
4439
|
+
name: "publish"
|
|
4440
|
+
},
|
|
3704
4441
|
async run({ args }) {
|
|
3705
4442
|
try {
|
|
3706
4443
|
await publish({
|
|
@@ -3709,6 +4446,7 @@ var publish2 = defineCommand({
|
|
|
3709
4446
|
description: readStringArg(process.argv.slice(2), "--description"),
|
|
3710
4447
|
dir: args.dir,
|
|
3711
4448
|
entry: args.entry,
|
|
4449
|
+
expiresIn: args["expires-in"],
|
|
3712
4450
|
folder: args.folder,
|
|
3713
4451
|
json: Boolean(args.json),
|
|
3714
4452
|
notes: readStringArg(process.argv.slice(2), "--notes"),
|
|
@@ -3964,6 +4702,48 @@ var link2 = defineCommand({
|
|
|
3964
4702
|
}
|
|
3965
4703
|
}
|
|
3966
4704
|
});
|
|
4705
|
+
var expiration2 = defineCommand({
|
|
4706
|
+
args: {
|
|
4707
|
+
"api-key": {
|
|
4708
|
+
description: "Account or agent token (default: env or login)",
|
|
4709
|
+
type: "string"
|
|
4710
|
+
},
|
|
4711
|
+
artifact: {
|
|
4712
|
+
description: "Artifact ID instead of the linked folder",
|
|
4713
|
+
type: "string"
|
|
4714
|
+
},
|
|
4715
|
+
dir: {
|
|
4716
|
+
default: ".",
|
|
4717
|
+
description: "Published directory",
|
|
4718
|
+
type: "positional"
|
|
4719
|
+
},
|
|
4720
|
+
in: { description: "Reset lifetime from now: 30m, 1h, 7d", type: "string" },
|
|
4721
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
4722
|
+
keep: {
|
|
4723
|
+
description: "Clear the explicit temporary deadline",
|
|
4724
|
+
type: "boolean"
|
|
4725
|
+
}
|
|
4726
|
+
},
|
|
4727
|
+
meta: {
|
|
4728
|
+
description: "Extend a temporary artifact or keep it",
|
|
4729
|
+
name: "expiration"
|
|
4730
|
+
},
|
|
4731
|
+
async run({ args }) {
|
|
4732
|
+
try {
|
|
4733
|
+
await expiration({
|
|
4734
|
+
apiKey: args["api-key"],
|
|
4735
|
+
artifact: args.artifact,
|
|
4736
|
+
dir: args.dir,
|
|
4737
|
+
duration: args.in,
|
|
4738
|
+
json: Boolean(args.json),
|
|
4739
|
+
keep: Boolean(args.keep)
|
|
4740
|
+
});
|
|
4741
|
+
} catch (err) {
|
|
4742
|
+
printCliError(err, Boolean(args.json));
|
|
4743
|
+
process.exitCode = 1;
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
});
|
|
3967
4747
|
var comments2 = defineCommand({
|
|
3968
4748
|
args: {
|
|
3969
4749
|
"api-key": {
|
|
@@ -4599,6 +5379,152 @@ var dashboard = defineCommand({
|
|
|
4599
5379
|
}
|
|
4600
5380
|
}
|
|
4601
5381
|
});
|
|
5382
|
+
var dataLsCommand = defineCommand({
|
|
5383
|
+
args: {
|
|
5384
|
+
collection: {
|
|
5385
|
+
description: "Collection name",
|
|
5386
|
+
required: true,
|
|
5387
|
+
type: "positional"
|
|
5388
|
+
},
|
|
5389
|
+
dir: {
|
|
5390
|
+
default: ".",
|
|
5391
|
+
description: "Published directory",
|
|
5392
|
+
type: "positional"
|
|
5393
|
+
},
|
|
5394
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
5395
|
+
limit: { description: "Max records to show", type: "string" }
|
|
5396
|
+
},
|
|
5397
|
+
meta: {
|
|
5398
|
+
description: "List records in a collection",
|
|
5399
|
+
name: "ls"
|
|
5400
|
+
},
|
|
5401
|
+
async run({ args }) {
|
|
5402
|
+
try {
|
|
5403
|
+
await dataLs({
|
|
5404
|
+
collection: args.collection,
|
|
5405
|
+
dir: args.dir,
|
|
5406
|
+
json: Boolean(args.json),
|
|
5407
|
+
limit: args.limit === void 0 ? void 0 : Number(args.limit)
|
|
5408
|
+
});
|
|
5409
|
+
} catch (err) {
|
|
5410
|
+
printCliError(err, Boolean(args.json));
|
|
5411
|
+
process.exit(1);
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
});
|
|
5415
|
+
var dataInsertCommand = defineCommand({
|
|
5416
|
+
args: {
|
|
5417
|
+
collection: {
|
|
5418
|
+
description: "Collection name",
|
|
5419
|
+
required: true,
|
|
5420
|
+
type: "positional"
|
|
5421
|
+
},
|
|
5422
|
+
dir: {
|
|
5423
|
+
default: ".",
|
|
5424
|
+
description: "Published directory",
|
|
5425
|
+
type: "positional"
|
|
5426
|
+
},
|
|
5427
|
+
file: {
|
|
5428
|
+
description: "Read the record(s) from a JSON file instead of --record",
|
|
5429
|
+
type: "string"
|
|
5430
|
+
},
|
|
5431
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
5432
|
+
record: {
|
|
5433
|
+
description: "A JSON object, or an array of objects for a batch",
|
|
5434
|
+
type: "string"
|
|
5435
|
+
}
|
|
5436
|
+
},
|
|
5437
|
+
meta: {
|
|
5438
|
+
description: "Insert one or many records into a collection",
|
|
5439
|
+
name: "insert"
|
|
5440
|
+
},
|
|
5441
|
+
async run({ args }) {
|
|
5442
|
+
try {
|
|
5443
|
+
await dataInsert({
|
|
5444
|
+
collection: args.collection,
|
|
5445
|
+
dir: args.dir,
|
|
5446
|
+
file: args.file,
|
|
5447
|
+
json: Boolean(args.json),
|
|
5448
|
+
record: args.record
|
|
5449
|
+
});
|
|
5450
|
+
} catch (err) {
|
|
5451
|
+
printCliError(err, Boolean(args.json));
|
|
5452
|
+
process.exit(1);
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
5455
|
+
});
|
|
5456
|
+
var dataExportCommand = defineCommand({
|
|
5457
|
+
args: {
|
|
5458
|
+
dir: {
|
|
5459
|
+
default: ".",
|
|
5460
|
+
description: "Published directory",
|
|
5461
|
+
type: "positional"
|
|
5462
|
+
},
|
|
5463
|
+
// Output is always JSON; accepted so --json works uniformly across
|
|
5464
|
+
// every command (SPEC-2 milestone 20 → CLI integration).
|
|
5465
|
+
json: {
|
|
5466
|
+
description: "Machine-readable output (always on)",
|
|
5467
|
+
type: "boolean"
|
|
5468
|
+
}
|
|
5469
|
+
},
|
|
5470
|
+
meta: {
|
|
5471
|
+
description: "Export every collection's records as JSON",
|
|
5472
|
+
name: "export"
|
|
5473
|
+
},
|
|
5474
|
+
async run({ args }) {
|
|
5475
|
+
try {
|
|
5476
|
+
await dataExport({ dir: args.dir });
|
|
5477
|
+
} catch (err) {
|
|
5478
|
+
printCliError(err, true);
|
|
5479
|
+
process.exit(1);
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
});
|
|
5483
|
+
var dataClearCommand = defineCommand({
|
|
5484
|
+
args: {
|
|
5485
|
+
collection: {
|
|
5486
|
+
description: "Collection name",
|
|
5487
|
+
required: true,
|
|
5488
|
+
type: "positional"
|
|
5489
|
+
},
|
|
5490
|
+
dir: {
|
|
5491
|
+
default: ".",
|
|
5492
|
+
description: "Published directory",
|
|
5493
|
+
type: "positional"
|
|
5494
|
+
},
|
|
5495
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
5496
|
+
yes: { description: "Confirm deleting every record", type: "boolean" }
|
|
5497
|
+
},
|
|
5498
|
+
meta: {
|
|
5499
|
+
description: "Delete every record in a collection",
|
|
5500
|
+
name: "clear"
|
|
5501
|
+
},
|
|
5502
|
+
async run({ args }) {
|
|
5503
|
+
try {
|
|
5504
|
+
await dataClear({
|
|
5505
|
+
collection: args.collection,
|
|
5506
|
+
dir: args.dir,
|
|
5507
|
+
json: Boolean(args.json),
|
|
5508
|
+
yes: Boolean(args.yes)
|
|
5509
|
+
});
|
|
5510
|
+
} catch (err) {
|
|
5511
|
+
printCliError(err, Boolean(args.json));
|
|
5512
|
+
process.exit(1);
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
});
|
|
5516
|
+
var data = defineCommand({
|
|
5517
|
+
meta: {
|
|
5518
|
+
description: "Read, insert, or clear the artifact's shared records",
|
|
5519
|
+
name: "data"
|
|
5520
|
+
},
|
|
5521
|
+
subCommands: {
|
|
5522
|
+
clear: dataClearCommand,
|
|
5523
|
+
export: dataExportCommand,
|
|
5524
|
+
insert: dataInsertCommand,
|
|
5525
|
+
ls: dataLsCommand
|
|
5526
|
+
}
|
|
5527
|
+
});
|
|
4602
5528
|
var main = defineCommand({
|
|
4603
5529
|
meta: {
|
|
4604
5530
|
description: "Publish AI-generated artifacts for review \u2014 https://artifact.cafe",
|
|
@@ -4621,8 +5547,10 @@ var main = defineCommand({
|
|
|
4621
5547
|
comment: comment2,
|
|
4622
5548
|
comments: comments2,
|
|
4623
5549
|
dashboard,
|
|
5550
|
+
data,
|
|
4624
5551
|
delete: del,
|
|
4625
5552
|
edit,
|
|
5553
|
+
expiration: expiration2,
|
|
4626
5554
|
folders: folders2,
|
|
4627
5555
|
link: link2,
|
|
4628
5556
|
list: list2,
|