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