artifact-cafe 0.0.11 → 0.0.13
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 +961 -475
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -100,10 +100,22 @@ var workspaceAvatarResponseSchema = z2.object({
|
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
// ../shared/src/comments.ts
|
|
103
|
-
import { z as
|
|
103
|
+
import { z as z5 } from "zod";
|
|
104
104
|
|
|
105
105
|
// ../shared/src/publish.ts
|
|
106
|
+
import { z as z4 } from "zod";
|
|
107
|
+
|
|
108
|
+
// ../shared/src/review-mode.ts
|
|
106
109
|
import { z as z3 } from "zod";
|
|
110
|
+
var reviewModeSchema = z3.enum(["open", "readonly", "off"]);
|
|
111
|
+
var setReviewModeSchema = z3.object({
|
|
112
|
+
mode: reviewModeSchema
|
|
113
|
+
});
|
|
114
|
+
var reviewModeResponseSchema = z3.object({
|
|
115
|
+
mode: reviewModeSchema
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ../shared/src/publish.ts
|
|
107
119
|
function isSafeArtifactPath(p) {
|
|
108
120
|
if (p.length === 0 || p.length > 1024) {
|
|
109
121
|
return false;
|
|
@@ -116,140 +128,148 @@ function isSafeArtifactPath(p) {
|
|
|
116
128
|
}
|
|
117
129
|
return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
|
|
118
130
|
}
|
|
119
|
-
var manifestFileSchema =
|
|
120
|
-
hash:
|
|
121
|
-
mimeType:
|
|
122
|
-
path:
|
|
131
|
+
var manifestFileSchema = z4.object({
|
|
132
|
+
hash: z4.string().regex(/^sha256_[a-f0-9]{64}$/),
|
|
133
|
+
mimeType: z4.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
|
|
134
|
+
path: z4.string().min(1).max(1024).refine(isSafeArtifactPath, {
|
|
123
135
|
message: "path must be a safe relative path"
|
|
124
136
|
}),
|
|
125
|
-
size:
|
|
137
|
+
size: z4.number().int().positive()
|
|
126
138
|
});
|
|
127
|
-
var publishSourceSchema =
|
|
128
|
-
agent:
|
|
129
|
-
runId:
|
|
130
|
-
type:
|
|
139
|
+
var publishSourceSchema = z4.object({
|
|
140
|
+
agent: z4.string().optional(),
|
|
141
|
+
runId: z4.string().optional(),
|
|
142
|
+
type: z4.literal("cli")
|
|
131
143
|
});
|
|
132
|
-
var createPublishSessionSchema =
|
|
133
|
-
artifactId:
|
|
134
|
-
entryPath:
|
|
135
|
-
files:
|
|
144
|
+
var createPublishSessionSchema = z4.object({
|
|
145
|
+
artifactId: z4.string().nullable(),
|
|
146
|
+
entryPath: z4.string().optional(),
|
|
147
|
+
files: z4.array(manifestFileSchema).min(1).max(1e3),
|
|
136
148
|
// SPEC-2 §6.4 — workspace placement is available only to authenticated
|
|
137
149
|
// members; the API enforces membership and folder ownership. folderId
|
|
138
150
|
// without workspaceId resolves against the account's personal workspace.
|
|
139
|
-
folderId:
|
|
151
|
+
folderId: z4.string().min(1).optional(),
|
|
140
152
|
// Bullet-point changelog for this version, authored by the publisher.
|
|
141
153
|
// Optional on every publish (incl. v1); stored immutably on the version.
|
|
142
|
-
notes:
|
|
154
|
+
notes: z4.string().max(4e3).optional(),
|
|
143
155
|
source: publishSourceSchema,
|
|
144
|
-
title:
|
|
145
|
-
workspaceId:
|
|
146
|
-
});
|
|
147
|
-
var publishSessionResponseSchema =
|
|
148
|
-
artifactId:
|
|
149
|
-
expiresAt:
|
|
150
|
-
filesToUpload:
|
|
151
|
-
|
|
152
|
-
method:
|
|
153
|
-
path:
|
|
154
|
-
uploadUrl:
|
|
156
|
+
title: z4.string().max(200).optional(),
|
|
157
|
+
workspaceId: z4.string().min(1).optional()
|
|
158
|
+
});
|
|
159
|
+
var publishSessionResponseSchema = z4.object({
|
|
160
|
+
artifactId: z4.string(),
|
|
161
|
+
expiresAt: z4.string(),
|
|
162
|
+
filesToUpload: z4.array(
|
|
163
|
+
z4.object({
|
|
164
|
+
method: z4.literal("PUT"),
|
|
165
|
+
path: z4.string(),
|
|
166
|
+
uploadUrl: z4.string().url()
|
|
155
167
|
})
|
|
156
168
|
),
|
|
157
|
-
publishSessionId:
|
|
158
|
-
skippedFiles:
|
|
159
|
-
versionId:
|
|
169
|
+
publishSessionId: z4.string(),
|
|
170
|
+
skippedFiles: z4.array(z4.string()),
|
|
171
|
+
versionId: z4.string()
|
|
160
172
|
});
|
|
161
|
-
var finalizeResponseSchema =
|
|
162
|
-
artifactId:
|
|
173
|
+
var finalizeResponseSchema = z4.object({
|
|
174
|
+
artifactId: z4.string(),
|
|
163
175
|
// API-key publishes create the artifact already claimed (SPEC §5.4) —
|
|
164
176
|
// the CLI uses this to tell "claim URL pending" from "already yours".
|
|
165
|
-
claimed:
|
|
177
|
+
claimed: z4.boolean(),
|
|
166
178
|
// Returned only on first publish of a new artifact (one-time).
|
|
167
|
-
claimUrl:
|
|
168
|
-
publishToken:
|
|
169
|
-
url:
|
|
170
|
-
versionId:
|
|
171
|
-
versionNumber:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
+
claimUrl: z4.string().url().optional(),
|
|
180
|
+
publishToken: z4.string().optional(),
|
|
181
|
+
url: z4.string().url(),
|
|
182
|
+
versionId: z4.string(),
|
|
183
|
+
versionNumber: z4.number().int().positive(),
|
|
184
|
+
// SPEC-2 §5.2 — who can open the review URL. A publish into a folder
|
|
185
|
+
// inherits that folder's visibility, so this is the only place the CLI
|
|
186
|
+
// learns that a link it just printed is members-only. Optional so a new
|
|
187
|
+
// CLI against an older API degrades to "unknown" instead of failing the
|
|
188
|
+
// publish over a field that only shapes the closing message.
|
|
189
|
+
visibility: z4.enum(["public", "unlisted", "workspace", "private"]).optional()
|
|
190
|
+
});
|
|
191
|
+
var artifactResponseSchema = z4.object({
|
|
192
|
+
claimed: z4.boolean(),
|
|
193
|
+
currentVersion: z4.object({
|
|
194
|
+
entryUrl: z4.string().url(),
|
|
195
|
+
id: z4.string(),
|
|
196
|
+
versionNumber: z4.number().int().positive()
|
|
179
197
|
}).nullable(),
|
|
180
198
|
// SPEC-2 §6.4 — placement + visibility surface on the artifact.
|
|
181
|
-
folderId:
|
|
182
|
-
id:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
199
|
+
folderId: z4.string().nullable(),
|
|
200
|
+
id: z4.string(),
|
|
201
|
+
// SPEC-2 §5.8 — what a viewer may do once in (open / readonly / off).
|
|
202
|
+
reviewMode: reviewModeSchema,
|
|
203
|
+
slug: z4.string(),
|
|
204
|
+
title: z4.string(),
|
|
205
|
+
visibility: z4.enum(["public", "unlisted", "workspace", "private"]),
|
|
206
|
+
workspaceId: z4.string().nullable()
|
|
187
207
|
});
|
|
188
208
|
|
|
189
209
|
// ../shared/src/comments.ts
|
|
190
|
-
var anchorPageSchema =
|
|
210
|
+
var anchorPageSchema = z5.string().max(1024).refine(isSafeArtifactPath, {
|
|
191
211
|
message: "page must be a safe relative path"
|
|
192
212
|
}).optional();
|
|
193
|
-
var elementSnapshotSchema =
|
|
194
|
-
classes:
|
|
195
|
-
id:
|
|
196
|
-
tag:
|
|
197
|
-
text:
|
|
213
|
+
var elementSnapshotSchema = z5.object({
|
|
214
|
+
classes: z5.string().max(256).optional(),
|
|
215
|
+
id: z5.string().max(256).optional(),
|
|
216
|
+
tag: z5.string().min(1).max(128),
|
|
217
|
+
text: z5.string().max(200).optional()
|
|
198
218
|
});
|
|
199
|
-
var pointAnchorSchema =
|
|
219
|
+
var pointAnchorSchema = z5.object({
|
|
200
220
|
element: elementSnapshotSchema.optional(),
|
|
201
221
|
// elementPath is the primary anchor; x/y are offsets within the
|
|
202
222
|
// element's box, normalized 0..1 (SPEC.md §8.1).
|
|
203
|
-
elementPath:
|
|
223
|
+
elementPath: z5.string().max(2048),
|
|
204
224
|
page: anchorPageSchema,
|
|
205
|
-
type:
|
|
206
|
-
viewportHeight:
|
|
207
|
-
viewportWidth:
|
|
208
|
-
x:
|
|
209
|
-
y:
|
|
210
|
-
});
|
|
211
|
-
var textAnchorSchema =
|
|
212
|
-
elementPath:
|
|
213
|
-
endOffset:
|
|
225
|
+
type: z5.literal("point"),
|
|
226
|
+
viewportHeight: z5.number().int().positive().max(1e5),
|
|
227
|
+
viewportWidth: z5.number().int().positive().max(1e5),
|
|
228
|
+
x: z5.number().min(0).max(1),
|
|
229
|
+
y: z5.number().min(0).max(1)
|
|
230
|
+
});
|
|
231
|
+
var textAnchorSchema = z5.object({
|
|
232
|
+
elementPath: z5.string().max(2048),
|
|
233
|
+
endOffset: z5.number().int().nonnegative().max(1e8),
|
|
214
234
|
page: anchorPageSchema,
|
|
215
|
-
prefix:
|
|
216
|
-
quote:
|
|
217
|
-
startOffset:
|
|
218
|
-
suffix:
|
|
219
|
-
type:
|
|
235
|
+
prefix: z5.string().max(200).optional(),
|
|
236
|
+
quote: z5.string().min(1).max(1e3),
|
|
237
|
+
startOffset: z5.number().int().nonnegative().max(1e8),
|
|
238
|
+
suffix: z5.string().max(200).optional(),
|
|
239
|
+
type: z5.literal("text")
|
|
220
240
|
});
|
|
221
|
-
var pageAnchorSchema =
|
|
241
|
+
var pageAnchorSchema = z5.object({
|
|
222
242
|
page: anchorPageSchema,
|
|
223
|
-
type:
|
|
243
|
+
type: z5.literal("page")
|
|
224
244
|
});
|
|
225
|
-
var anchorSchema =
|
|
245
|
+
var anchorSchema = z5.discriminatedUnion("type", [
|
|
226
246
|
pointAnchorSchema,
|
|
227
247
|
textAnchorSchema,
|
|
228
248
|
pageAnchorSchema
|
|
229
249
|
]);
|
|
230
|
-
var displayNameSchema =
|
|
231
|
-
var guestSchema =
|
|
250
|
+
var displayNameSchema = z5.string().trim().min(1).max(64);
|
|
251
|
+
var guestSchema = z5.object({
|
|
232
252
|
displayName: displayNameSchema,
|
|
233
|
-
guestId:
|
|
253
|
+
guestId: z5.string().min(16)
|
|
234
254
|
});
|
|
235
|
-
var viaLabelSchema =
|
|
236
|
-
var createCommentThreadSchema =
|
|
255
|
+
var viaLabelSchema = z5.string().trim().min(1).max(64);
|
|
256
|
+
var createCommentThreadSchema = z5.object({
|
|
237
257
|
anchor: anchorSchema,
|
|
238
|
-
body:
|
|
258
|
+
body: z5.string().min(1).max(1e4),
|
|
239
259
|
guest: guestSchema.optional(),
|
|
240
|
-
versionId:
|
|
260
|
+
versionId: z5.string(),
|
|
241
261
|
via: viaLabelSchema.optional()
|
|
242
262
|
});
|
|
243
|
-
var createCommentMessageSchema =
|
|
244
|
-
body:
|
|
263
|
+
var createCommentMessageSchema = z5.object({
|
|
264
|
+
body: z5.string().min(1).max(1e4),
|
|
245
265
|
guest: guestSchema.optional(),
|
|
246
266
|
via: viaLabelSchema.optional()
|
|
247
267
|
});
|
|
248
|
-
var editCommentMessageSchema =
|
|
249
|
-
body:
|
|
268
|
+
var editCommentMessageSchema = z5.object({
|
|
269
|
+
body: z5.string().min(1).max(1e4),
|
|
250
270
|
guest: guestSchema.optional()
|
|
251
271
|
});
|
|
252
|
-
var threadActionSchema =
|
|
272
|
+
var threadActionSchema = z5.object({
|
|
253
273
|
guest: guestSchema.optional()
|
|
254
274
|
});
|
|
255
275
|
var SINGLE_EMOJI = new RegExp("^\\p{RGI_Emoji}$", "v");
|
|
@@ -257,117 +277,117 @@ var REDUNDANT_VS16 = new RegExp("(\\p{Emoji_Presentation})\\uFE0F", "gu");
|
|
|
257
277
|
function canonicalizeEmoji(s) {
|
|
258
278
|
return s.replace(REDUNDANT_VS16, "$1");
|
|
259
279
|
}
|
|
260
|
-
var reactionEmojiSchema =
|
|
261
|
-
var toggleReactionSchema =
|
|
280
|
+
var reactionEmojiSchema = z5.string().max(64).transform(canonicalizeEmoji).refine((s) => SINGLE_EMOJI.test(s), { message: "Must be a single emoji." }).describe("A single emoji, e.g. \u{1F44D}");
|
|
281
|
+
var toggleReactionSchema = z5.object({
|
|
262
282
|
emoji: reactionEmojiSchema,
|
|
263
283
|
guest: guestSchema.optional()
|
|
264
284
|
});
|
|
265
|
-
var commentAuthorSchema =
|
|
266
|
-
avatarUrl:
|
|
267
|
-
displayName:
|
|
268
|
-
guestId:
|
|
269
|
-
isViewer:
|
|
270
|
-
kind:
|
|
285
|
+
var commentAuthorSchema = z5.object({
|
|
286
|
+
avatarUrl: z5.string().nullable(),
|
|
287
|
+
displayName: z5.string(),
|
|
288
|
+
guestId: z5.string().nullable(),
|
|
289
|
+
isViewer: z5.boolean(),
|
|
290
|
+
kind: z5.enum(["member", "guest"]),
|
|
271
291
|
// Optional "via" attribution (§6.4). Null for plain comments; a bounded
|
|
272
292
|
// label the shell appends after the display name.
|
|
273
|
-
via:
|
|
293
|
+
via: z5.string().nullable()
|
|
274
294
|
});
|
|
275
|
-
var messageReactionSchema =
|
|
276
|
-
emoji:
|
|
277
|
-
reactors:
|
|
295
|
+
var messageReactionSchema = z5.object({
|
|
296
|
+
emoji: z5.string(),
|
|
297
|
+
reactors: z5.array(commentAuthorSchema)
|
|
278
298
|
});
|
|
279
|
-
var commentMessageSchema =
|
|
299
|
+
var commentMessageSchema = z5.object({
|
|
280
300
|
author: commentAuthorSchema,
|
|
281
301
|
// Empty string for a deleted message — the body is stripped server-side
|
|
282
302
|
// (§6.7.1 tombstone), never sent to the client and hidden there.
|
|
283
|
-
body:
|
|
284
|
-
createdAt:
|
|
303
|
+
body: z5.string(),
|
|
304
|
+
createdAt: z5.string(),
|
|
285
305
|
// Soft-deleted (§6.7.1). A tombstone keeps thread structure and replies;
|
|
286
306
|
// the shell renders "Comment deleted" instead of the (absent) body.
|
|
287
|
-
deleted:
|
|
307
|
+
deleted: z5.boolean(),
|
|
288
308
|
// Last edit time (§6.7.1), or null if never edited — drives the "edited"
|
|
289
309
|
// badge. No edit history is kept.
|
|
290
|
-
editedAt:
|
|
291
|
-
id:
|
|
310
|
+
editedAt: z5.string().nullable(),
|
|
311
|
+
id: z5.string(),
|
|
292
312
|
// Defaulted, not required: a new CLI parsing an older server's
|
|
293
313
|
// response must not fail on the missing key.
|
|
294
|
-
reactions:
|
|
314
|
+
reactions: z5.array(messageReactionSchema).default([])
|
|
295
315
|
});
|
|
296
|
-
var threadActorSchema =
|
|
297
|
-
displayName:
|
|
298
|
-
kind:
|
|
316
|
+
var threadActorSchema = z5.object({
|
|
317
|
+
displayName: z5.string(),
|
|
318
|
+
kind: z5.enum(["member", "guest", "agent"])
|
|
299
319
|
});
|
|
300
|
-
var commentThreadSchema =
|
|
320
|
+
var commentThreadSchema = z5.object({
|
|
301
321
|
// §6.6.1 — set while open to signal "seen, working on it"; cleared on
|
|
302
322
|
// reopen, kept (but superseded in display) once resolved. Defaulted so
|
|
303
323
|
// clients parsing an older server's response don't fail.
|
|
304
|
-
acknowledgedAt:
|
|
324
|
+
acknowledgedAt: z5.string().nullable().default(null),
|
|
305
325
|
acknowledgedBy: threadActorSchema.nullable().default(null),
|
|
306
326
|
anchor: anchorSchema,
|
|
307
|
-
artifactId:
|
|
308
|
-
createdAt:
|
|
327
|
+
artifactId: z5.string(),
|
|
328
|
+
createdAt: z5.string(),
|
|
309
329
|
createdBy: commentAuthorSchema,
|
|
310
|
-
id:
|
|
311
|
-
messages:
|
|
330
|
+
id: z5.string(),
|
|
331
|
+
messages: z5.array(commentMessageSchema),
|
|
312
332
|
// Null on threads resolved before actor recording existed, and always
|
|
313
333
|
// null while open.
|
|
314
334
|
resolvedBy: threadActorSchema.nullable().default(null),
|
|
315
|
-
status:
|
|
316
|
-
versionId:
|
|
335
|
+
status: z5.enum(["open", "resolved"]),
|
|
336
|
+
versionId: z5.string()
|
|
317
337
|
});
|
|
318
|
-
var commentThreadListSchema =
|
|
319
|
-
threads:
|
|
338
|
+
var commentThreadListSchema = z5.object({
|
|
339
|
+
threads: z5.array(commentThreadSchema)
|
|
320
340
|
});
|
|
321
|
-
var commentExportQuerySchema =
|
|
322
|
-
status:
|
|
323
|
-
version:
|
|
341
|
+
var commentExportQuerySchema = z5.object({
|
|
342
|
+
status: z5.enum(["open", "resolved", "all"]).default("open"),
|
|
343
|
+
version: z5.enum(["current", "all"]).default("current")
|
|
324
344
|
});
|
|
325
|
-
var exportReactionSchema =
|
|
326
|
-
count:
|
|
327
|
-
emoji:
|
|
345
|
+
var exportReactionSchema = z5.object({
|
|
346
|
+
count: z5.number().int().positive(),
|
|
347
|
+
emoji: z5.string()
|
|
328
348
|
});
|
|
329
|
-
var commentExportReplySchema =
|
|
330
|
-
author:
|
|
331
|
-
authorKind:
|
|
332
|
-
body:
|
|
333
|
-
createdAt:
|
|
349
|
+
var commentExportReplySchema = z5.object({
|
|
350
|
+
author: z5.string(),
|
|
351
|
+
authorKind: z5.enum(["member", "guest"]),
|
|
352
|
+
body: z5.string(),
|
|
353
|
+
createdAt: z5.string(),
|
|
334
354
|
// Soft-delete tombstone (§6.7.1): body is "" when true.
|
|
335
|
-
deleted:
|
|
336
|
-
editedAt:
|
|
355
|
+
deleted: z5.boolean(),
|
|
356
|
+
editedAt: z5.string().nullable(),
|
|
337
357
|
// The message id — what `edit`/`delete` target (§5.1).
|
|
338
|
-
id:
|
|
339
|
-
reactions:
|
|
358
|
+
id: z5.string(),
|
|
359
|
+
reactions: z5.array(exportReactionSchema).optional()
|
|
340
360
|
});
|
|
341
|
-
var commentExportThreadSchema =
|
|
361
|
+
var commentExportThreadSchema = z5.object({
|
|
342
362
|
// §6.6.1 — lets a polling agent skip threads it already acknowledged
|
|
343
363
|
// (acknowledged threads stay in status=open exports on purpose).
|
|
344
|
-
acknowledgedAt:
|
|
364
|
+
acknowledgedAt: z5.string().nullable().default(null),
|
|
345
365
|
anchor: anchorSchema,
|
|
346
|
-
author:
|
|
347
|
-
authorKind:
|
|
348
|
-
body:
|
|
349
|
-
createdAt:
|
|
350
|
-
deleted:
|
|
351
|
-
editedAt:
|
|
352
|
-
id:
|
|
366
|
+
author: z5.string(),
|
|
367
|
+
authorKind: z5.enum(["member", "guest"]),
|
|
368
|
+
body: z5.string(),
|
|
369
|
+
createdAt: z5.string(),
|
|
370
|
+
deleted: z5.boolean(),
|
|
371
|
+
editedAt: z5.string().nullable(),
|
|
372
|
+
id: z5.string(),
|
|
353
373
|
// The opening message's id (distinct from the thread `id`) — `edit`/
|
|
354
374
|
// `delete` operate on messages, so the CLI needs it to target the first
|
|
355
375
|
// comment, not just the replies.
|
|
356
|
-
messageId:
|
|
357
|
-
reactions:
|
|
358
|
-
replies:
|
|
376
|
+
messageId: z5.string(),
|
|
377
|
+
reactions: z5.array(exportReactionSchema).optional(),
|
|
378
|
+
replies: z5.array(commentExportReplySchema),
|
|
359
379
|
resolvedBy: threadActorSchema.nullable().default(null),
|
|
360
|
-
status:
|
|
361
|
-
versionNumber:
|
|
380
|
+
status: z5.enum(["open", "resolved"]),
|
|
381
|
+
versionNumber: z5.number().int().positive()
|
|
362
382
|
});
|
|
363
|
-
var commentExportSchema =
|
|
364
|
-
artifactId:
|
|
365
|
-
comments:
|
|
383
|
+
var commentExportSchema = z5.object({
|
|
384
|
+
artifactId: z5.string(),
|
|
385
|
+
comments: z5.array(commentExportThreadSchema),
|
|
366
386
|
// The artifact's current version (null before the first finalize).
|
|
367
|
-
versionNumber:
|
|
387
|
+
versionNumber: z5.number().int().positive().nullable()
|
|
368
388
|
});
|
|
369
|
-
var commentReadResponseSchema =
|
|
370
|
-
lastSeenAt:
|
|
389
|
+
var commentReadResponseSchema = z5.object({
|
|
390
|
+
lastSeenAt: z5.string()
|
|
371
391
|
});
|
|
372
392
|
function elementLabel(el) {
|
|
373
393
|
if (el.id) {
|
|
@@ -378,228 +398,236 @@ function elementLabel(el) {
|
|
|
378
398
|
}
|
|
379
399
|
|
|
380
400
|
// ../shared/src/errors.ts
|
|
381
|
-
import { z as
|
|
382
|
-
var apiErrorSchema =
|
|
383
|
-
code:
|
|
384
|
-
docsUrl:
|
|
385
|
-
message:
|
|
386
|
-
retryAfter:
|
|
401
|
+
import { z as z6 } from "zod";
|
|
402
|
+
var apiErrorSchema = z6.object({
|
|
403
|
+
code: z6.string(),
|
|
404
|
+
docsUrl: z6.string().url(),
|
|
405
|
+
message: z6.string(),
|
|
406
|
+
retryAfter: z6.number().int().positive().optional()
|
|
387
407
|
});
|
|
388
408
|
|
|
389
409
|
// ../shared/src/feedback.ts
|
|
390
|
-
import { z as
|
|
391
|
-
var feedbackRequestSchema =
|
|
392
|
-
email:
|
|
393
|
-
message:
|
|
410
|
+
import { z as z7 } from "zod";
|
|
411
|
+
var feedbackRequestSchema = z7.object({
|
|
412
|
+
email: z7.string().trim().toLowerCase().email().optional().or(z7.literal("")),
|
|
413
|
+
message: z7.string().trim().min(1).max(4e3)
|
|
394
414
|
});
|
|
395
|
-
var feedbackResponseSchema =
|
|
396
|
-
sent:
|
|
415
|
+
var feedbackResponseSchema = z7.object({
|
|
416
|
+
sent: z7.literal(true)
|
|
397
417
|
});
|
|
398
418
|
|
|
399
419
|
// ../shared/src/grants.ts
|
|
400
|
-
import { z as
|
|
401
|
-
var grantRoleSchema =
|
|
402
|
-
var createGrantSchema =
|
|
403
|
-
email:
|
|
420
|
+
import { z as z8 } from "zod";
|
|
421
|
+
var grantRoleSchema = z8.enum(["viewer"]);
|
|
422
|
+
var createGrantSchema = z8.object({
|
|
423
|
+
email: z8.string().trim().toLowerCase().email().optional(),
|
|
404
424
|
role: grantRoleSchema.default("viewer"),
|
|
405
|
-
userId:
|
|
425
|
+
userId: z8.string().min(1).optional()
|
|
406
426
|
}).refine((body) => Boolean(body.email) !== Boolean(body.userId), {
|
|
407
427
|
message: "provide exactly one of email or userId"
|
|
408
428
|
});
|
|
409
|
-
var grantSchema =
|
|
410
|
-
createdAt:
|
|
411
|
-
email:
|
|
412
|
-
expiresAt:
|
|
413
|
-
id:
|
|
429
|
+
var grantSchema = z8.object({
|
|
430
|
+
createdAt: z8.string(),
|
|
431
|
+
email: z8.string().email().nullable(),
|
|
432
|
+
expiresAt: z8.string().nullable(),
|
|
433
|
+
id: z8.string(),
|
|
414
434
|
// The subject's avatar once they have an account; null otherwise.
|
|
415
|
-
image:
|
|
435
|
+
image: z8.string().nullable(),
|
|
416
436
|
// Present on the invited account after redemption / when granting an
|
|
417
437
|
// existing user; null for a still-pending email invite.
|
|
418
|
-
name:
|
|
419
|
-
redeemedAt:
|
|
438
|
+
name: z8.string().nullable(),
|
|
439
|
+
redeemedAt: z8.string().nullable(),
|
|
420
440
|
role: grantRoleSchema,
|
|
421
441
|
// Shown as "· pending" beside the role until the link is redeemed.
|
|
422
|
-
status:
|
|
442
|
+
status: z8.enum(["pending", "active"]),
|
|
423
443
|
// Display fragment of the live invitation token, or null once redeemed.
|
|
424
|
-
tokenPrefix:
|
|
425
|
-
userId:
|
|
444
|
+
tokenPrefix: z8.string().nullable(),
|
|
445
|
+
userId: z8.string().nullable()
|
|
426
446
|
});
|
|
427
447
|
var createdGrantSchema = grantSchema.extend({
|
|
428
|
-
url:
|
|
448
|
+
url: z8.string().url()
|
|
429
449
|
});
|
|
430
|
-
var grantListResponseSchema =
|
|
431
|
-
grants:
|
|
450
|
+
var grantListResponseSchema = z8.object({
|
|
451
|
+
grants: z8.array(grantSchema),
|
|
432
452
|
// The artifact's current general-access value, so the share surface can
|
|
433
453
|
// render the "General access" line without a second read.
|
|
434
|
-
visibility:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
454
|
+
visibility: z8.enum(["public", "unlisted", "workspace", "private"]),
|
|
455
|
+
// The owning workspace's logo, so the share modal can draw the workspace
|
|
456
|
+
// itself rather than a generic glyph. Null when none was uploaded.
|
|
457
|
+
workspaceLogo: z8.string().nullable().optional(),
|
|
458
|
+
// The owning workspace's display name, so "People with access" can name the
|
|
459
|
+
// audience `workspace` visibility actually admits ("All Acme members") rather
|
|
460
|
+
// than leaving an empty list to imply nobody. Null for a workspace-less
|
|
461
|
+
// artifact; optional so an older server still parses.
|
|
462
|
+
workspaceName: z8.string().nullable().optional()
|
|
463
|
+
});
|
|
464
|
+
var revokeGrantResponseSchema = z8.object({
|
|
465
|
+
revoked: z8.literal(true)
|
|
438
466
|
});
|
|
439
|
-
var acceptGrantSchema =
|
|
440
|
-
token:
|
|
467
|
+
var acceptGrantSchema = z8.object({
|
|
468
|
+
token: z8.string().min(1)
|
|
441
469
|
});
|
|
442
|
-
var acceptGrantResponseSchema =
|
|
443
|
-
artifactId:
|
|
470
|
+
var acceptGrantResponseSchema = z8.object({
|
|
471
|
+
artifactId: z8.string(),
|
|
444
472
|
// The review shell URL to land on, carrying ?invited=1 for the banner.
|
|
445
|
-
artifactUrl:
|
|
446
|
-
slug:
|
|
447
|
-
title:
|
|
448
|
-
});
|
|
449
|
-
var workspaceGuestSchema =
|
|
450
|
-
artifacts:
|
|
451
|
-
|
|
452
|
-
artifactId:
|
|
453
|
-
grantId:
|
|
454
|
-
slug:
|
|
455
|
-
status:
|
|
456
|
-
title:
|
|
473
|
+
artifactUrl: z8.string().url(),
|
|
474
|
+
slug: z8.string(),
|
|
475
|
+
title: z8.string()
|
|
476
|
+
});
|
|
477
|
+
var workspaceGuestSchema = z8.object({
|
|
478
|
+
artifacts: z8.array(
|
|
479
|
+
z8.object({
|
|
480
|
+
artifactId: z8.string(),
|
|
481
|
+
grantId: z8.string(),
|
|
482
|
+
slug: z8.string(),
|
|
483
|
+
status: z8.enum(["pending", "active"]),
|
|
484
|
+
title: z8.string()
|
|
457
485
|
})
|
|
458
486
|
),
|
|
459
|
-
email:
|
|
460
|
-
image:
|
|
461
|
-
name:
|
|
462
|
-
userId:
|
|
463
|
-
});
|
|
464
|
-
var workspaceGuestListResponseSchema =
|
|
465
|
-
guests:
|
|
466
|
-
});
|
|
467
|
-
var sharedWithMeArtifactSchema =
|
|
468
|
-
artifactId:
|
|
469
|
-
slug:
|
|
470
|
-
title:
|
|
471
|
-
url:
|
|
487
|
+
email: z8.string().nullable(),
|
|
488
|
+
image: z8.string().nullable(),
|
|
489
|
+
name: z8.string().nullable(),
|
|
490
|
+
userId: z8.string().nullable()
|
|
491
|
+
});
|
|
492
|
+
var workspaceGuestListResponseSchema = z8.object({
|
|
493
|
+
guests: z8.array(workspaceGuestSchema)
|
|
494
|
+
});
|
|
495
|
+
var sharedWithMeArtifactSchema = z8.object({
|
|
496
|
+
artifactId: z8.string(),
|
|
497
|
+
slug: z8.string(),
|
|
498
|
+
title: z8.string(),
|
|
499
|
+
url: z8.string().url(),
|
|
472
500
|
// Who shared it, for the "Shared with me" list.
|
|
473
|
-
workspaceName:
|
|
501
|
+
workspaceName: z8.string()
|
|
474
502
|
});
|
|
475
|
-
var sharedWithMeListResponseSchema =
|
|
476
|
-
artifacts:
|
|
503
|
+
var sharedWithMeListResponseSchema = z8.object({
|
|
504
|
+
artifacts: z8.array(sharedWithMeArtifactSchema)
|
|
477
505
|
});
|
|
478
506
|
|
|
479
507
|
// ../shared/src/me.ts
|
|
480
|
-
import { z as
|
|
508
|
+
import { z as z10 } from "zod";
|
|
481
509
|
|
|
482
510
|
// ../shared/src/workspaces.ts
|
|
483
|
-
import { z as
|
|
511
|
+
import { z as z9 } from "zod";
|
|
484
512
|
var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/;
|
|
485
|
-
var workspaceHandleUnavailableReasonSchema =
|
|
513
|
+
var workspaceHandleUnavailableReasonSchema = z9.enum([
|
|
486
514
|
"held",
|
|
487
515
|
"invalid",
|
|
488
516
|
"reserved",
|
|
489
517
|
"taken"
|
|
490
518
|
]);
|
|
491
|
-
var workspaceHandleAvailabilitySchema =
|
|
492
|
-
available:
|
|
493
|
-
handle:
|
|
519
|
+
var workspaceHandleAvailabilitySchema = z9.object({
|
|
520
|
+
available: z9.boolean(),
|
|
521
|
+
handle: z9.string(),
|
|
494
522
|
// Human-readable sentence for the field hint; null when available.
|
|
495
|
-
message:
|
|
523
|
+
message: z9.string().nullable(),
|
|
496
524
|
reason: workspaceHandleUnavailableReasonSchema.nullable()
|
|
497
525
|
});
|
|
498
|
-
var workspaceRoleSchema =
|
|
499
|
-
var workspaceInviteRoleSchema =
|
|
500
|
-
var workspaceHandleSchema =
|
|
526
|
+
var workspaceRoleSchema = z9.enum(["owner", "admin", "member"]);
|
|
527
|
+
var workspaceInviteRoleSchema = z9.enum(["admin", "member"]);
|
|
528
|
+
var workspaceHandleSchema = z9.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
|
|
501
529
|
message: "handle must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
502
530
|
});
|
|
503
|
-
var createWorkspaceSchema =
|
|
531
|
+
var createWorkspaceSchema = z9.object({
|
|
504
532
|
handle: workspaceHandleSchema.optional(),
|
|
505
|
-
name:
|
|
533
|
+
name: z9.string().trim().min(1).max(100)
|
|
506
534
|
});
|
|
507
|
-
var updateWorkspaceSchema =
|
|
535
|
+
var updateWorkspaceSchema = z9.object({
|
|
508
536
|
handle: workspaceHandleSchema.optional(),
|
|
509
|
-
name:
|
|
537
|
+
name: z9.string().trim().min(1).max(100).optional()
|
|
510
538
|
}).refine((body) => body.handle !== void 0 || body.name !== void 0, {
|
|
511
539
|
message: "provide at least one of name or handle"
|
|
512
540
|
});
|
|
513
|
-
var workspaceSchema =
|
|
514
|
-
createdAt:
|
|
515
|
-
handle:
|
|
541
|
+
var workspaceSchema = z9.object({
|
|
542
|
+
createdAt: z9.string(),
|
|
543
|
+
handle: z9.string(),
|
|
516
544
|
// ISO timestamp when `{handle}.artifact.cafe` was provisioned, or null.
|
|
517
545
|
// Set by the one-time, self-serve enable flow (§4.4); never auto at
|
|
518
546
|
// create, and always null for personal workspaces.
|
|
519
|
-
hostnameProvisionedAt:
|
|
520
|
-
id:
|
|
547
|
+
hostnameProvisionedAt: z9.string().nullable(),
|
|
548
|
+
id: z9.string(),
|
|
521
549
|
// Auto-provisioned solo workspace, one per user: non-deletable, takes
|
|
522
550
|
// no members, renameable like any workspace.
|
|
523
|
-
isPersonal:
|
|
551
|
+
isPersonal: z9.boolean(),
|
|
524
552
|
// Uploaded logo URL, or null for the initials fallback. Set through the
|
|
525
553
|
// dedicated /workspaces/:id/avatar route, never the generic update.
|
|
526
|
-
logo:
|
|
527
|
-
name:
|
|
554
|
+
logo: z9.string().nullable(),
|
|
555
|
+
name: z9.string(),
|
|
528
556
|
role: workspaceRoleSchema.nullable(),
|
|
529
|
-
updatedAt:
|
|
557
|
+
updatedAt: z9.string()
|
|
530
558
|
});
|
|
531
|
-
var workspaceListResponseSchema =
|
|
532
|
-
workspaces:
|
|
559
|
+
var workspaceListResponseSchema = z9.object({
|
|
560
|
+
workspaces: z9.array(workspaceSchema)
|
|
533
561
|
});
|
|
534
|
-
var deleteWorkspaceResponseSchema =
|
|
535
|
-
deleted:
|
|
562
|
+
var deleteWorkspaceResponseSchema = z9.object({
|
|
563
|
+
deleted: z9.literal(true)
|
|
536
564
|
});
|
|
537
|
-
var workspaceMemberSchema =
|
|
538
|
-
createdAt:
|
|
539
|
-
email:
|
|
540
|
-
image:
|
|
541
|
-
name:
|
|
565
|
+
var workspaceMemberSchema = z9.object({
|
|
566
|
+
createdAt: z9.string(),
|
|
567
|
+
email: z9.string().email(),
|
|
568
|
+
image: z9.string().nullable(),
|
|
569
|
+
name: z9.string(),
|
|
542
570
|
role: workspaceRoleSchema,
|
|
543
|
-
userId:
|
|
571
|
+
userId: z9.string()
|
|
544
572
|
});
|
|
545
|
-
var workspaceMemberListResponseSchema =
|
|
546
|
-
members:
|
|
573
|
+
var workspaceMemberListResponseSchema = z9.object({
|
|
574
|
+
members: z9.array(workspaceMemberSchema)
|
|
547
575
|
});
|
|
548
|
-
var updateWorkspaceMemberSchema =
|
|
576
|
+
var updateWorkspaceMemberSchema = z9.object({
|
|
549
577
|
role: workspaceRoleSchema
|
|
550
578
|
});
|
|
551
|
-
var removeWorkspaceMemberResponseSchema =
|
|
552
|
-
removed:
|
|
579
|
+
var removeWorkspaceMemberResponseSchema = z9.object({
|
|
580
|
+
removed: z9.literal(true)
|
|
553
581
|
});
|
|
554
|
-
var createWorkspaceInviteSchema =
|
|
555
|
-
email:
|
|
556
|
-
expiresAt:
|
|
557
|
-
maxUses:
|
|
582
|
+
var createWorkspaceInviteSchema = z9.object({
|
|
583
|
+
email: z9.string().trim().toLowerCase().email().optional(),
|
|
584
|
+
expiresAt: z9.string().datetime(),
|
|
585
|
+
maxUses: z9.number().int().positive().optional(),
|
|
558
586
|
role: workspaceInviteRoleSchema
|
|
559
587
|
});
|
|
560
|
-
var workspaceInviteSchema =
|
|
561
|
-
createdAt:
|
|
562
|
-
email:
|
|
563
|
-
expiresAt:
|
|
564
|
-
id:
|
|
565
|
-
maxUses:
|
|
588
|
+
var workspaceInviteSchema = z9.object({
|
|
589
|
+
createdAt: z9.string(),
|
|
590
|
+
email: z9.string().email().nullable(),
|
|
591
|
+
expiresAt: z9.string(),
|
|
592
|
+
id: z9.string(),
|
|
593
|
+
maxUses: z9.number().int().positive().nullable(),
|
|
566
594
|
role: workspaceInviteRoleSchema,
|
|
567
|
-
tokenPrefix:
|
|
568
|
-
useCount:
|
|
595
|
+
tokenPrefix: z9.string(),
|
|
596
|
+
useCount: z9.number().int().nonnegative()
|
|
569
597
|
});
|
|
570
598
|
var createdWorkspaceInviteSchema = workspaceInviteSchema.extend({
|
|
571
|
-
url:
|
|
599
|
+
url: z9.string().url()
|
|
572
600
|
});
|
|
573
|
-
var workspaceInviteListResponseSchema =
|
|
574
|
-
invites:
|
|
601
|
+
var workspaceInviteListResponseSchema = z9.object({
|
|
602
|
+
invites: z9.array(workspaceInviteSchema)
|
|
575
603
|
});
|
|
576
|
-
var revokeWorkspaceInviteResponseSchema =
|
|
577
|
-
revoked:
|
|
604
|
+
var revokeWorkspaceInviteResponseSchema = z9.object({
|
|
605
|
+
revoked: z9.literal(true)
|
|
578
606
|
});
|
|
579
|
-
var acceptWorkspaceInviteSchema =
|
|
580
|
-
token:
|
|
607
|
+
var acceptWorkspaceInviteSchema = z9.object({
|
|
608
|
+
token: z9.string().min(1)
|
|
581
609
|
});
|
|
582
|
-
var acceptWorkspaceInviteResponseSchema =
|
|
610
|
+
var acceptWorkspaceInviteResponseSchema = z9.object({
|
|
583
611
|
role: workspaceRoleSchema,
|
|
584
612
|
workspace: workspaceSchema
|
|
585
613
|
});
|
|
586
|
-
var folderVisibilitySchema =
|
|
587
|
-
var artifactVisibilitySchema =
|
|
614
|
+
var folderVisibilitySchema = z9.enum(["public", "workspace"]);
|
|
615
|
+
var artifactVisibilitySchema = z9.enum([
|
|
588
616
|
"public",
|
|
589
617
|
"unlisted",
|
|
590
618
|
"workspace",
|
|
591
619
|
"private"
|
|
592
620
|
]);
|
|
593
|
-
var createFolderSchema =
|
|
594
|
-
name:
|
|
621
|
+
var createFolderSchema = z9.object({
|
|
622
|
+
name: z9.string().trim().min(1).max(100),
|
|
595
623
|
visibility: folderVisibilitySchema
|
|
596
624
|
});
|
|
597
|
-
var updateFolderSchema =
|
|
625
|
+
var updateFolderSchema = z9.object({
|
|
598
626
|
// With `visibility`: bulk-update the folder's artifacts to match
|
|
599
627
|
// (SPEC-2 §3.4 "Changing a folder's visibility offers a bulk update").
|
|
600
|
-
cascade:
|
|
601
|
-
name:
|
|
602
|
-
sortOrder:
|
|
628
|
+
cascade: z9.boolean().optional(),
|
|
629
|
+
name: z9.string().trim().min(1).max(100).optional(),
|
|
630
|
+
sortOrder: z9.number().int().min(0).optional(),
|
|
603
631
|
visibility: folderVisibilitySchema.optional()
|
|
604
632
|
}).refine(
|
|
605
633
|
(body) => body.name !== void 0 || body.visibility !== void 0 || body.sortOrder !== void 0,
|
|
@@ -607,256 +635,260 @@ var updateFolderSchema = z8.object({
|
|
|
607
635
|
).refine((body) => !body.cascade || body.visibility !== void 0, {
|
|
608
636
|
message: "cascade requires visibility"
|
|
609
637
|
});
|
|
610
|
-
var folderSchema =
|
|
611
|
-
createdAt:
|
|
612
|
-
id:
|
|
613
|
-
name:
|
|
638
|
+
var folderSchema = z9.object({
|
|
639
|
+
createdAt: z9.string(),
|
|
640
|
+
id: z9.string(),
|
|
641
|
+
name: z9.string(),
|
|
614
642
|
// Permalink segment for the public folder page (/w/{handle}/f/{slug}).
|
|
615
643
|
// Server-derived from the name; renames regenerate it.
|
|
616
|
-
slug:
|
|
617
|
-
sortOrder:
|
|
618
|
-
updatedAt:
|
|
644
|
+
slug: z9.string(),
|
|
645
|
+
sortOrder: z9.number().int(),
|
|
646
|
+
updatedAt: z9.string(),
|
|
619
647
|
visibility: folderVisibilitySchema,
|
|
620
|
-
workspaceId:
|
|
648
|
+
workspaceId: z9.string()
|
|
621
649
|
});
|
|
622
|
-
var folderListResponseSchema =
|
|
623
|
-
folders:
|
|
650
|
+
var folderListResponseSchema = z9.object({
|
|
651
|
+
folders: z9.array(folderSchema)
|
|
624
652
|
});
|
|
625
|
-
var deleteFolderResponseSchema =
|
|
626
|
-
deleted:
|
|
653
|
+
var deleteFolderResponseSchema = z9.object({
|
|
654
|
+
deleted: z9.literal(true)
|
|
627
655
|
});
|
|
628
|
-
var workspaceArtifactSchema =
|
|
629
|
-
createdAt:
|
|
630
|
-
currentVersionNumber:
|
|
631
|
-
folderId:
|
|
632
|
-
id:
|
|
633
|
-
openCommentCount:
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
656
|
+
var workspaceArtifactSchema = z9.object({
|
|
657
|
+
createdAt: z9.string(),
|
|
658
|
+
currentVersionNumber: z9.number().int().nullable(),
|
|
659
|
+
folderId: z9.string().nullable(),
|
|
660
|
+
id: z9.string(),
|
|
661
|
+
openCommentCount: z9.number().int().nonnegative(),
|
|
662
|
+
reviewMode: reviewModeSchema,
|
|
663
|
+
slug: z9.string(),
|
|
664
|
+
title: z9.string(),
|
|
665
|
+
updatedAt: z9.string(),
|
|
666
|
+
url: z9.string().url(),
|
|
667
|
+
versionCount: z9.number().int().nonnegative(),
|
|
639
668
|
visibility: artifactVisibilitySchema
|
|
640
669
|
});
|
|
641
|
-
var workspaceArtifactListResponseSchema =
|
|
642
|
-
artifacts:
|
|
670
|
+
var workspaceArtifactListResponseSchema = z9.object({
|
|
671
|
+
artifacts: z9.array(workspaceArtifactSchema)
|
|
643
672
|
});
|
|
644
|
-
var workspaceArtifactSearchResultSchema =
|
|
645
|
-
id:
|
|
646
|
-
slug:
|
|
647
|
-
title:
|
|
673
|
+
var workspaceArtifactSearchResultSchema = z9.object({
|
|
674
|
+
id: z9.string(),
|
|
675
|
+
slug: z9.string(),
|
|
676
|
+
title: z9.string()
|
|
648
677
|
});
|
|
649
|
-
var workspaceArtifactSearchResponseSchema =
|
|
650
|
-
results:
|
|
678
|
+
var workspaceArtifactSearchResponseSchema = z9.object({
|
|
679
|
+
results: z9.array(workspaceArtifactSearchResultSchema)
|
|
651
680
|
});
|
|
652
681
|
|
|
653
682
|
// ../shared/src/me.ts
|
|
654
|
-
var ownArtifactSchema =
|
|
655
|
-
createdAt:
|
|
656
|
-
currentVersionNumber:
|
|
683
|
+
var ownArtifactSchema = z10.object({
|
|
684
|
+
createdAt: z10.string(),
|
|
685
|
+
currentVersionNumber: z10.number().nullable(),
|
|
657
686
|
// Owned artifacts normally never expire; non-null only while an
|
|
658
687
|
// expiry is still set (e.g. an unclaimed publish surfaced here later).
|
|
659
|
-
expiresAt:
|
|
688
|
+
expiresAt: z10.string().nullable(),
|
|
660
689
|
// SPEC-2 §8.4 — the containing folder for workspace-owned artifacts;
|
|
661
690
|
// both null for personal artifacts. `folderId` drives the dashboard's
|
|
662
691
|
// move control and folder filter; `folderName` is the display label.
|
|
663
|
-
folderId:
|
|
664
|
-
folderName:
|
|
692
|
+
folderId: z10.string().nullable(),
|
|
693
|
+
folderName: z10.string().nullable(),
|
|
665
694
|
// Whether a publish token is still active — revocable from the
|
|
666
695
|
// dashboard (§6.18); the token itself is never returned.
|
|
667
|
-
hasPublishToken:
|
|
668
|
-
id:
|
|
669
|
-
openThreads:
|
|
670
|
-
protected:
|
|
696
|
+
hasPublishToken: z10.boolean(),
|
|
697
|
+
id: z10.string(),
|
|
698
|
+
openThreads: z10.number(),
|
|
699
|
+
protected: z10.boolean(),
|
|
671
700
|
// Who published the current version (falling back to the claiming
|
|
672
701
|
// owner) — the dashboard shows a name and avatar per artifact so a
|
|
673
702
|
// shared workspace reads as people's work, not anonymous rows. Null
|
|
674
703
|
// when the publish was token-only and the artifact is unclaimed.
|
|
675
|
-
publishedBy:
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
704
|
+
publishedBy: z10.object({ image: z10.string().nullable(), name: z10.string() }).nullable(),
|
|
705
|
+
// SPEC-2 §5.8 — the review dial, so the dashboard's settings dialog can
|
|
706
|
+
// render current state without a second fetch.
|
|
707
|
+
reviewMode: reviewModeSchema,
|
|
708
|
+
slug: z10.string(),
|
|
709
|
+
title: z10.string(),
|
|
710
|
+
totalViews: z10.number(),
|
|
679
711
|
// SPEC §6.20 — messages by others newer than the viewer's last-seen
|
|
680
712
|
// watermark. 0 for signed-out/API-key-less contexts.
|
|
681
|
-
unreadComments:
|
|
682
|
-
updatedAt:
|
|
683
|
-
url:
|
|
684
|
-
versionCount:
|
|
713
|
+
unreadComments: z10.number(),
|
|
714
|
+
updatedAt: z10.string(),
|
|
715
|
+
url: z10.string(),
|
|
716
|
+
versionCount: z10.number(),
|
|
685
717
|
// SPEC-2 §5.1 — whether the caller clears the manage bar for this
|
|
686
718
|
// artifact (workspace admin, or the person who first published it).
|
|
687
719
|
// Only a manager's folder move inherits the folder's visibility, so the
|
|
688
720
|
// dashboard warns exactly the people the change can actually affect, and
|
|
689
721
|
// only a manager is offered the Share entry (§5.7).
|
|
690
|
-
viewerCanManage:
|
|
722
|
+
viewerCanManage: z10.boolean(),
|
|
691
723
|
// SPEC-2 §5.2 — the artifact's own visibility, the single enforcement
|
|
692
724
|
// source. The dashboard needs it to tell whether a folder move would
|
|
693
725
|
// widen exposure before it sends the PATCH (§3.4), and to render the lock
|
|
694
726
|
// glyph on `private` rows and cards (§5.7).
|
|
695
727
|
visibility: artifactVisibilitySchema
|
|
696
728
|
});
|
|
697
|
-
var ownArtifactsResponseSchema =
|
|
698
|
-
artifacts:
|
|
729
|
+
var ownArtifactsResponseSchema = z10.object({
|
|
730
|
+
artifacts: z10.array(ownArtifactSchema)
|
|
699
731
|
});
|
|
700
|
-
var revokePublishTokenResponseSchema =
|
|
701
|
-
revoked:
|
|
732
|
+
var revokePublishTokenResponseSchema = z10.object({
|
|
733
|
+
revoked: z10.boolean()
|
|
702
734
|
});
|
|
703
735
|
|
|
704
736
|
// ../shared/src/owner.ts
|
|
705
|
-
import { z as
|
|
737
|
+
import { z as z11 } from "zod";
|
|
706
738
|
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
|
|
707
|
-
var claimResponseSchema =
|
|
708
|
-
artifactId:
|
|
709
|
-
slug:
|
|
710
|
-
title:
|
|
711
|
-
url:
|
|
739
|
+
var claimResponseSchema = z11.object({
|
|
740
|
+
artifactId: z11.string(),
|
|
741
|
+
slug: z11.string(),
|
|
742
|
+
title: z11.string(),
|
|
743
|
+
url: z11.string().url()
|
|
712
744
|
});
|
|
713
|
-
var updateArtifactSchema =
|
|
745
|
+
var updateArtifactSchema = z11.object({
|
|
714
746
|
// SPEC-2 §6.4 — placement + visibility. `folderId: null` clears the
|
|
715
747
|
// folder; `workspaceId: null` moves the artifact out of its workspace
|
|
716
748
|
// (§5.5 ownership transfer).
|
|
717
|
-
folderId:
|
|
718
|
-
slug:
|
|
749
|
+
folderId: z11.string().min(1).nullable().optional(),
|
|
750
|
+
slug: z11.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
719
751
|
message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
720
752
|
}).optional(),
|
|
721
|
-
title:
|
|
753
|
+
title: z11.string().min(1).max(200).optional(),
|
|
722
754
|
visibility: artifactVisibilitySchema.optional(),
|
|
723
|
-
workspaceId:
|
|
755
|
+
workspaceId: z11.string().min(1).nullable().optional()
|
|
724
756
|
}).refine(
|
|
725
757
|
(body) => body.title !== void 0 || body.slug !== void 0 || body.visibility !== void 0 || body.folderId !== void 0 || body.workspaceId !== void 0,
|
|
726
758
|
{
|
|
727
759
|
message: "provide at least one of title, slug, visibility, folderId, workspaceId"
|
|
728
760
|
}
|
|
729
761
|
);
|
|
730
|
-
var deleteArtifactResponseSchema =
|
|
731
|
-
deleted:
|
|
762
|
+
var deleteArtifactResponseSchema = z11.object({
|
|
763
|
+
deleted: z11.literal(true)
|
|
732
764
|
});
|
|
733
765
|
|
|
734
766
|
// ../shared/src/presence.ts
|
|
735
|
-
import { z as
|
|
767
|
+
import { z as z12 } from "zod";
|
|
736
768
|
var AGENT_STATES = ["listening", "editing", "publishing"];
|
|
737
|
-
var agentStateSchema =
|
|
769
|
+
var agentStateSchema = z12.enum(AGENT_STATES);
|
|
738
770
|
var AGENT_PRESENCE = {
|
|
739
771
|
editingTtlSeconds: 300,
|
|
740
772
|
heartbeatSeconds: 15,
|
|
741
773
|
shellPollSeconds: 15,
|
|
742
774
|
ttlSeconds: 45
|
|
743
775
|
};
|
|
744
|
-
var agentHeartbeatBodySchema =
|
|
776
|
+
var agentHeartbeatBodySchema = z12.object({
|
|
745
777
|
state: agentStateSchema.optional()
|
|
746
778
|
});
|
|
747
|
-
var agentHeartbeatResponseSchema =
|
|
748
|
-
listening:
|
|
779
|
+
var agentHeartbeatResponseSchema = z12.object({
|
|
780
|
+
listening: z12.literal(true),
|
|
749
781
|
state: agentStateSchema.optional(),
|
|
750
|
-
ttlSeconds:
|
|
782
|
+
ttlSeconds: z12.number().int().positive()
|
|
751
783
|
});
|
|
752
|
-
var agentPresenceSchema =
|
|
753
|
-
listening:
|
|
784
|
+
var agentPresenceSchema = z12.object({
|
|
785
|
+
listening: z12.boolean(),
|
|
754
786
|
state: agentStateSchema.nullable().optional(),
|
|
755
|
-
versionNumber:
|
|
787
|
+
versionNumber: z12.number().int().positive().nullable()
|
|
756
788
|
});
|
|
757
789
|
|
|
758
790
|
// ../shared/src/protection.ts
|
|
759
|
-
import { z as
|
|
791
|
+
import { z as z13 } from "zod";
|
|
760
792
|
var PASSWORD_MIN = 6;
|
|
761
793
|
var PASSWORD_MAX = 128;
|
|
762
|
-
var gatePasswordSchema =
|
|
763
|
-
var setProtectionSchema =
|
|
794
|
+
var gatePasswordSchema = z13.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
|
|
795
|
+
var setProtectionSchema = z13.object({
|
|
764
796
|
password: gatePasswordSchema.nullable()
|
|
765
797
|
});
|
|
766
|
-
var protectionResponseSchema =
|
|
767
|
-
protected:
|
|
798
|
+
var protectionResponseSchema = z13.object({
|
|
799
|
+
protected: z13.boolean()
|
|
768
800
|
});
|
|
769
|
-
var unlockSchema =
|
|
770
|
-
password:
|
|
801
|
+
var unlockSchema = z13.object({
|
|
802
|
+
password: z13.string().min(1).max(1024)
|
|
771
803
|
});
|
|
772
|
-
var unlockResponseSchema =
|
|
773
|
-
unlocked:
|
|
804
|
+
var unlockResponseSchema = z13.object({
|
|
805
|
+
unlocked: z13.literal(true)
|
|
774
806
|
});
|
|
775
807
|
|
|
776
808
|
// ../shared/src/report.ts
|
|
777
|
-
import { z as
|
|
778
|
-
var reportRequestSchema =
|
|
779
|
-
reason:
|
|
809
|
+
import { z as z14 } from "zod";
|
|
810
|
+
var reportRequestSchema = z14.object({
|
|
811
|
+
reason: z14.string().max(1e3).optional()
|
|
780
812
|
});
|
|
781
|
-
var reportResponseSchema =
|
|
782
|
-
reported:
|
|
813
|
+
var reportResponseSchema = z14.object({
|
|
814
|
+
reported: z14.literal(true)
|
|
783
815
|
});
|
|
784
816
|
|
|
785
817
|
// ../shared/src/reviewers.ts
|
|
786
|
-
import { z as
|
|
787
|
-
var artifactReviewerSchema =
|
|
788
|
-
avatarUrl:
|
|
789
|
-
displayName:
|
|
818
|
+
import { z as z15 } from "zod";
|
|
819
|
+
var artifactReviewerSchema = z15.object({
|
|
820
|
+
avatarUrl: z15.string().nullable(),
|
|
821
|
+
displayName: z15.string(),
|
|
790
822
|
// Exactly one of userId/guestId is set, per `kind`.
|
|
791
|
-
guestId:
|
|
823
|
+
guestId: z15.string().nullable(),
|
|
792
824
|
// "account", not commentAuthorSchema's "member" — the whole point here is
|
|
793
825
|
// that these people need not be workspace members. A guest can be named in
|
|
794
826
|
// a comment but never notified: guest_identities carries a display name and
|
|
795
827
|
// no email, so there is nowhere to send anything.
|
|
796
|
-
kind:
|
|
797
|
-
userId:
|
|
828
|
+
kind: z15.enum(["account", "guest"]),
|
|
829
|
+
userId: z15.string().nullable()
|
|
798
830
|
});
|
|
799
|
-
var artifactReviewerListResponseSchema =
|
|
800
|
-
reviewers:
|
|
831
|
+
var artifactReviewerListResponseSchema = z15.object({
|
|
832
|
+
reviewers: z15.array(artifactReviewerSchema)
|
|
801
833
|
});
|
|
802
834
|
|
|
803
835
|
// ../shared/src/stats.ts
|
|
804
|
-
import { z as
|
|
805
|
-
var avgViewSecondsSchema =
|
|
806
|
-
var versionViewStatsSchema =
|
|
836
|
+
import { z as z16 } from "zod";
|
|
837
|
+
var avgViewSecondsSchema = z16.number().int().nonnegative().nullable();
|
|
838
|
+
var versionViewStatsSchema = z16.object({
|
|
807
839
|
avgViewSeconds: avgViewSecondsSchema,
|
|
808
840
|
// Views from inside the review shell's iframe vs. direct visits to
|
|
809
841
|
// the render URL. views = embeddedViews + directViews.
|
|
810
|
-
directViews:
|
|
811
|
-
embeddedViews:
|
|
842
|
+
directViews: z16.number().int().nonnegative(),
|
|
843
|
+
embeddedViews: z16.number().int().nonnegative(),
|
|
812
844
|
// Distinct hashed viewer IPs across the version's lifetime.
|
|
813
|
-
uniqueViewers:
|
|
814
|
-
versionNumber:
|
|
815
|
-
views:
|
|
845
|
+
uniqueViewers: z16.number().int().nonnegative(),
|
|
846
|
+
versionNumber: z16.number().int().positive(),
|
|
847
|
+
views: z16.number().int().nonnegative()
|
|
816
848
|
});
|
|
817
|
-
var dailyViewsSchema =
|
|
849
|
+
var dailyViewsSchema = z16.object({
|
|
818
850
|
avgViewSeconds: avgViewSecondsSchema,
|
|
819
851
|
// UTC day, YYYY-MM-DD.
|
|
820
|
-
day:
|
|
821
|
-
uniqueViewers:
|
|
822
|
-
views:
|
|
852
|
+
day: z16.string(),
|
|
853
|
+
uniqueViewers: z16.number().int().nonnegative(),
|
|
854
|
+
views: z16.number().int().nonnegative()
|
|
823
855
|
});
|
|
824
|
-
var artifactStatsResponseSchema =
|
|
856
|
+
var artifactStatsResponseSchema = z16.object({
|
|
825
857
|
avgViewSeconds: avgViewSecondsSchema,
|
|
826
|
-
daily:
|
|
858
|
+
daily: z16.array(dailyViewsSchema),
|
|
827
859
|
// Distinct hashed viewer IPs across all versions and days.
|
|
828
|
-
totalUniqueViewers:
|
|
829
|
-
totalViews:
|
|
830
|
-
versions:
|
|
860
|
+
totalUniqueViewers: z16.number().int().nonnegative(),
|
|
861
|
+
totalViews: z16.number().int().nonnegative(),
|
|
862
|
+
versions: z16.array(versionViewStatsSchema)
|
|
831
863
|
});
|
|
832
864
|
|
|
833
865
|
// ../shared/src/versions.ts
|
|
834
|
-
import { z as
|
|
835
|
-
var versionSourceSchema =
|
|
836
|
-
agentName:
|
|
837
|
-
type:
|
|
866
|
+
import { z as z17 } from "zod";
|
|
867
|
+
var versionSourceSchema = z17.object({
|
|
868
|
+
agentName: z17.string().nullable(),
|
|
869
|
+
type: z17.string()
|
|
838
870
|
});
|
|
839
|
-
var versionChangesSchema =
|
|
840
|
-
added:
|
|
841
|
-
changed:
|
|
842
|
-
removed:
|
|
871
|
+
var versionChangesSchema = z17.object({
|
|
872
|
+
added: z17.array(z17.string()),
|
|
873
|
+
changed: z17.array(z17.string()),
|
|
874
|
+
removed: z17.array(z17.string())
|
|
843
875
|
});
|
|
844
|
-
var versionSummarySchema =
|
|
876
|
+
var versionSummarySchema = z17.object({
|
|
845
877
|
// Absent for the first version — there is no previous version to diff against.
|
|
846
878
|
changes: versionChangesSchema.nullable(),
|
|
847
|
-
createdAt:
|
|
848
|
-
entryUrl:
|
|
849
|
-
fileCount:
|
|
850
|
-
id:
|
|
851
|
-
isCurrent:
|
|
879
|
+
createdAt: z17.string(),
|
|
880
|
+
entryUrl: z17.string().url(),
|
|
881
|
+
fileCount: z17.number().int().nonnegative(),
|
|
882
|
+
id: z17.string(),
|
|
883
|
+
isCurrent: z17.boolean(),
|
|
852
884
|
// Author-supplied bullet-point changelog; null when the publish omitted it.
|
|
853
|
-
notes:
|
|
885
|
+
notes: z17.string().nullable(),
|
|
854
886
|
source: versionSourceSchema,
|
|
855
|
-
totalSizeBytes:
|
|
856
|
-
versionNumber:
|
|
887
|
+
totalSizeBytes: z17.number().int().nonnegative(),
|
|
888
|
+
versionNumber: z17.number().int().positive()
|
|
857
889
|
});
|
|
858
|
-
var versionListResponseSchema =
|
|
859
|
-
versions:
|
|
890
|
+
var versionListResponseSchema = z17.object({
|
|
891
|
+
versions: z17.array(versionSummarySchema)
|
|
860
892
|
});
|
|
861
893
|
|
|
862
894
|
// src/api.ts
|
|
@@ -933,6 +965,15 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
933
965
|
(d) => agentHeartbeatResponseSchema.parse(d)
|
|
934
966
|
);
|
|
935
967
|
},
|
|
968
|
+
// SPEC-2 §6.3 — create a folder in a workspace. Account key required
|
|
969
|
+
// (owner/admin; in a personal workspace that's always the user).
|
|
970
|
+
async createFolder(workspaceId, body) {
|
|
971
|
+
return request(
|
|
972
|
+
`${base}/api/v1/workspaces/${workspaceId}/folders`,
|
|
973
|
+
{ body: JSON.stringify(body), headers, method: "POST" },
|
|
974
|
+
(d) => folderSchema.parse(d)
|
|
975
|
+
);
|
|
976
|
+
},
|
|
936
977
|
// SPEC-2 §6.6 — share an artifact with a person (email or user id).
|
|
937
978
|
// Authorized by this client's credential: the publish token (an agent
|
|
938
979
|
// sharing a private draft) or an account key/session. The invitation
|
|
@@ -1031,6 +1072,15 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
1031
1072
|
(d) => grantListResponseSchema.parse(d)
|
|
1032
1073
|
);
|
|
1033
1074
|
},
|
|
1075
|
+
// SPEC-2 §6.4 — every artifact in a workspace (members see all, with
|
|
1076
|
+
// each artifact's folderId). The only folder-aware artifact listing.
|
|
1077
|
+
async listWorkspaceArtifacts(workspaceId) {
|
|
1078
|
+
return request(
|
|
1079
|
+
`${base}/api/v1/workspaces/${workspaceId}/artifacts`,
|
|
1080
|
+
{ headers, method: "GET" },
|
|
1081
|
+
(d) => workspaceArtifactListResponseSchema.parse(d)
|
|
1082
|
+
);
|
|
1083
|
+
},
|
|
1034
1084
|
async listWorkspaces() {
|
|
1035
1085
|
return request(
|
|
1036
1086
|
`${base}/api/v1/workspaces`,
|
|
@@ -1091,6 +1141,26 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
1091
1141
|
{ body: JSON.stringify({ password }), headers, method: "PUT" },
|
|
1092
1142
|
(d) => protectionResponseSchema.parse(d)
|
|
1093
1143
|
);
|
|
1144
|
+
},
|
|
1145
|
+
// SPEC-2 §5.8 — what reviewers may do once they can open it. Same
|
|
1146
|
+
// dual-auth as protection, so the publish token this client holds is
|
|
1147
|
+
// enough (an agent can close review on its own artifact).
|
|
1148
|
+
async setReviewMode(artifactId, mode) {
|
|
1149
|
+
return request(
|
|
1150
|
+
`${base}/api/v1/artifacts/${artifactId}/review-mode`,
|
|
1151
|
+
{ body: JSON.stringify({ mode }), headers, method: "PUT" },
|
|
1152
|
+
(d) => reviewModeResponseSchema.parse(d)
|
|
1153
|
+
);
|
|
1154
|
+
},
|
|
1155
|
+
// SPEC.md §6.11 / SPEC-2 §6.4 — update an artifact (folder placement,
|
|
1156
|
+
// title, slug, visibility). Workspace-member action: account key only,
|
|
1157
|
+
// a publish token can't re-file.
|
|
1158
|
+
async updateArtifact(artifactId, body) {
|
|
1159
|
+
return request(
|
|
1160
|
+
`${base}/api/v1/artifacts/${artifactId}`,
|
|
1161
|
+
{ body: JSON.stringify(body), headers, method: "PATCH" },
|
|
1162
|
+
(d) => artifactResponseSchema.parse(d)
|
|
1163
|
+
);
|
|
1094
1164
|
}
|
|
1095
1165
|
};
|
|
1096
1166
|
}
|
|
@@ -1119,17 +1189,17 @@ import {
|
|
|
1119
1189
|
writeFileSync as writeFileSync2
|
|
1120
1190
|
} from "fs";
|
|
1121
1191
|
import path2 from "path";
|
|
1122
|
-
import { z as
|
|
1192
|
+
import { z as z19 } from "zod";
|
|
1123
1193
|
|
|
1124
1194
|
// src/credentials.ts
|
|
1125
1195
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1126
1196
|
import { homedir } from "os";
|
|
1127
1197
|
import path from "path";
|
|
1128
|
-
import { z as
|
|
1198
|
+
import { z as z18 } from "zod";
|
|
1129
1199
|
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
1130
|
-
var credentialsSchema =
|
|
1131
|
-
keys:
|
|
1132
|
-
version:
|
|
1200
|
+
var credentialsSchema = z18.object({
|
|
1201
|
+
keys: z18.record(z18.string(), z18.string()),
|
|
1202
|
+
version: z18.literal(1)
|
|
1133
1203
|
});
|
|
1134
1204
|
function credentialsPath() {
|
|
1135
1205
|
return path.join(homedir(), ".artifactcafe", "credentials");
|
|
@@ -1179,24 +1249,24 @@ function resolveApiKey(apiUrl2, flag) {
|
|
|
1179
1249
|
}
|
|
1180
1250
|
|
|
1181
1251
|
// src/config.ts
|
|
1182
|
-
var configSchema =
|
|
1183
|
-
apiUrl:
|
|
1184
|
-
artifactId:
|
|
1185
|
-
folder:
|
|
1186
|
-
folderId:
|
|
1252
|
+
var configSchema = z19.object({
|
|
1253
|
+
apiUrl: z19.string(),
|
|
1254
|
+
artifactId: z19.string(),
|
|
1255
|
+
folder: z19.string().optional(),
|
|
1256
|
+
folderId: z19.string().optional(),
|
|
1187
1257
|
// Optional: a folder linked by `link` to an artifact you own carries no
|
|
1188
1258
|
// token — owner actions authorize via your stored login instead. A first
|
|
1189
1259
|
// publish still writes the anonymous-mode token here.
|
|
1190
|
-
publishToken:
|
|
1191
|
-
slug:
|
|
1192
|
-
url:
|
|
1193
|
-
workspace:
|
|
1194
|
-
workspaceId:
|
|
1195
|
-
workspaceKind:
|
|
1196
|
-
});
|
|
1197
|
-
var projectConfigSchema =
|
|
1198
|
-
folder:
|
|
1199
|
-
workspace:
|
|
1260
|
+
publishToken: z19.string().optional(),
|
|
1261
|
+
slug: z19.string(),
|
|
1262
|
+
url: z19.string(),
|
|
1263
|
+
workspace: z19.string().optional(),
|
|
1264
|
+
workspaceId: z19.string().optional(),
|
|
1265
|
+
workspaceKind: z19.enum(["personal", "workspace"]).optional()
|
|
1266
|
+
});
|
|
1267
|
+
var projectConfigSchema = z19.object({
|
|
1268
|
+
folder: z19.string().trim().min(1).optional(),
|
|
1269
|
+
workspace: z19.string().trim().min(1)
|
|
1200
1270
|
}).strict();
|
|
1201
1271
|
var CONFIG_DIR = ".artifactcafe";
|
|
1202
1272
|
var CONFIG_FILE = "config.json";
|
|
@@ -1232,7 +1302,7 @@ function readProjectConfig(dir) {
|
|
|
1232
1302
|
JSON.parse(readFileSync2(file, "utf8"))
|
|
1233
1303
|
);
|
|
1234
1304
|
} catch (error) {
|
|
1235
|
-
const message = error instanceof
|
|
1305
|
+
const message = error instanceof z19.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
|
|
1236
1306
|
throw new Error(`Invalid ${file}: ${message}.`);
|
|
1237
1307
|
}
|
|
1238
1308
|
}
|
|
@@ -1755,6 +1825,11 @@ function resolveEntry(files, explicit) {
|
|
|
1755
1825
|
|
|
1756
1826
|
// src/publish.ts
|
|
1757
1827
|
var UPLOAD_CONCURRENCY = 6;
|
|
1828
|
+
var REVIEW_LABEL = {
|
|
1829
|
+
off: "off \u2014 plain artifact, no review tools",
|
|
1830
|
+
open: "open \u2014 anyone with the link can comment",
|
|
1831
|
+
readonly: "read-only \u2014 existing comments only"
|
|
1832
|
+
};
|
|
1758
1833
|
async function publish(opts) {
|
|
1759
1834
|
const dir = path6.resolve(opts.dir);
|
|
1760
1835
|
const config = readConfig(dir);
|
|
@@ -1764,6 +1839,14 @@ async function publish(opts) {
|
|
|
1764
1839
|
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
1765
1840
|
}
|
|
1766
1841
|
}
|
|
1842
|
+
let reviewMode;
|
|
1843
|
+
if (opts.review !== void 0) {
|
|
1844
|
+
const parsed = reviewModeSchema.safeParse(opts.review);
|
|
1845
|
+
if (!parsed.success) {
|
|
1846
|
+
throw new Error("--review must be one of: open, readonly, off.");
|
|
1847
|
+
}
|
|
1848
|
+
reviewMode = parsed.data;
|
|
1849
|
+
}
|
|
1767
1850
|
const artifactId = opts.artifact ?? config?.artifactId ?? null;
|
|
1768
1851
|
const token = (artifactId !== null && artifactId === config?.artifactId ? config.publishToken : void 0) ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
1769
1852
|
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
|
|
@@ -1867,6 +1950,13 @@ async function publish(opts) {
|
|
|
1867
1950
|
);
|
|
1868
1951
|
await protectClient.setProtection(result.artifactId, opts.password);
|
|
1869
1952
|
}
|
|
1953
|
+
if (reviewMode !== void 0) {
|
|
1954
|
+
const reviewClient = createApiClient(
|
|
1955
|
+
apiUrl2,
|
|
1956
|
+
credential ?? result.publishToken
|
|
1957
|
+
);
|
|
1958
|
+
await reviewClient.setReviewMode(result.artifactId, reviewMode);
|
|
1959
|
+
}
|
|
1870
1960
|
if (opts.json) {
|
|
1871
1961
|
console.log(
|
|
1872
1962
|
JSON.stringify(
|
|
@@ -1879,10 +1969,12 @@ async function publish(opts) {
|
|
|
1879
1969
|
folderId: placement.folderId ?? null,
|
|
1880
1970
|
url: result.url,
|
|
1881
1971
|
versionNumber: result.versionNumber,
|
|
1972
|
+
visibility: result.visibility ?? null,
|
|
1882
1973
|
workspace: placement.workspace ?? null,
|
|
1883
1974
|
workspaceId: placement.workspaceId ?? null,
|
|
1884
1975
|
...result.claimUrl ? { claimUrl: result.claimUrl } : {},
|
|
1885
1976
|
...opts.password === void 0 ? {} : { protected: true },
|
|
1977
|
+
...reviewMode === void 0 ? {} : { reviewMode },
|
|
1886
1978
|
filesUploaded: session.filesToUpload.map((f) => f.path),
|
|
1887
1979
|
ignored: skipped,
|
|
1888
1980
|
skippedFiles: session.skippedFiles
|
|
@@ -1902,6 +1994,11 @@ async function publish(opts) {
|
|
|
1902
1994
|
console.log(`Review URL:
|
|
1903
1995
|
${result.url}
|
|
1904
1996
|
`);
|
|
1997
|
+
const access = accessNote(result.visibility);
|
|
1998
|
+
if (access) {
|
|
1999
|
+
console.log(`${access}
|
|
2000
|
+
`);
|
|
2001
|
+
}
|
|
1905
2002
|
if (result.claimUrl) {
|
|
1906
2003
|
console.log(
|
|
1907
2004
|
`Claim URL (shown once \u2014 claim to keep this artifact):
|
|
@@ -1914,6 +2011,10 @@ ${result.claimUrl}
|
|
|
1914
2011
|
if (opts.password !== void 0) {
|
|
1915
2012
|
console.log("Password protection: on\n");
|
|
1916
2013
|
}
|
|
2014
|
+
if (reviewMode !== void 0) {
|
|
2015
|
+
console.log(`Review: ${REVIEW_LABEL[reviewMode]}
|
|
2016
|
+
`);
|
|
2017
|
+
}
|
|
1917
2018
|
} else {
|
|
1918
2019
|
console.log(`
|
|
1919
2020
|
\u2713 Published v${result.versionNumber}
|
|
@@ -1926,14 +2027,32 @@ ${result.claimUrl}
|
|
|
1926
2027
|
}
|
|
1927
2028
|
console.log(`${result.url}
|
|
1928
2029
|
`);
|
|
2030
|
+
const access = accessNote(result.visibility);
|
|
2031
|
+
if (access) {
|
|
2032
|
+
console.log(`${access}
|
|
2033
|
+
`);
|
|
2034
|
+
}
|
|
1929
2035
|
if (opts.password !== void 0) {
|
|
1930
2036
|
console.log("Password protection: on\n");
|
|
1931
2037
|
}
|
|
2038
|
+
if (reviewMode !== void 0) {
|
|
2039
|
+
console.log(`Review: ${REVIEW_LABEL[reviewMode]}
|
|
2040
|
+
`);
|
|
2041
|
+
}
|
|
1932
2042
|
}
|
|
1933
2043
|
if (opts.open && !opts.json && process.stdout.isTTY) {
|
|
1934
2044
|
openInBrowser(result.url);
|
|
1935
2045
|
}
|
|
1936
2046
|
}
|
|
2047
|
+
function accessNote(visibility) {
|
|
2048
|
+
if (visibility === "workspace") {
|
|
2049
|
+
return "Access: workspace members only \u2014 anyone else needs `artifact-cafe share <email>` to open it.";
|
|
2050
|
+
}
|
|
2051
|
+
if (visibility === "private") {
|
|
2052
|
+
return "Access: private \u2014 you, the workspace's admins, and anyone you `artifact-cafe share` it with.";
|
|
2053
|
+
}
|
|
2054
|
+
return null;
|
|
2055
|
+
}
|
|
1937
2056
|
async function resolvePlacement(params) {
|
|
1938
2057
|
const { apiKey, apiUrl: apiUrl2, config, isNewArtifact, opts } = params;
|
|
1939
2058
|
const hasPlacementFlag = opts.workspace !== void 0 || opts.folder !== void 0;
|
|
@@ -2200,6 +2319,24 @@ function printCliError(err, json) {
|
|
|
2200
2319
|
|
|
2201
2320
|
// src/folders.ts
|
|
2202
2321
|
async function folders(opts) {
|
|
2322
|
+
if (opts.action !== void 0 && opts.action !== "create") {
|
|
2323
|
+
throw new Error(
|
|
2324
|
+
`Unknown folders action "${opts.action}" \u2014 run \`folders\` to list, or \`folders create <name>\` to create one.`
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
if (opts.action === "create") {
|
|
2328
|
+
if (!opts.name?.trim()) {
|
|
2329
|
+
throw new Error(
|
|
2330
|
+
`Pass the folder name \u2014 e.g. \`artifact-cafe folders create "Design specs" --workspace ${opts.workspace}\`.`
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
const visibility = opts.visibility ?? "workspace";
|
|
2334
|
+
if (visibility !== "workspace" && visibility !== "public") {
|
|
2335
|
+
throw new Error(
|
|
2336
|
+
`--visibility must be "workspace" or "public" (got "${opts.visibility}").`
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2203
2340
|
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
2204
2341
|
/\/$/,
|
|
2205
2342
|
""
|
|
@@ -2207,7 +2344,7 @@ async function folders(opts) {
|
|
|
2207
2344
|
const key = resolveApiKey(apiUrl2, opts.apiKey);
|
|
2208
2345
|
if (!key) {
|
|
2209
2346
|
throw new Error(
|
|
2210
|
-
`
|
|
2347
|
+
`Folder commands need an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
2211
2348
|
);
|
|
2212
2349
|
}
|
|
2213
2350
|
const client = createApiClient(apiUrl2, key);
|
|
@@ -2220,6 +2357,10 @@ async function folders(opts) {
|
|
|
2220
2357
|
`No workspace with handle "${opts.workspace}" belongs to this account.`
|
|
2221
2358
|
);
|
|
2222
2359
|
}
|
|
2360
|
+
if (opts.action === "create") {
|
|
2361
|
+
await createFolder(client, workspace, opts);
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2223
2364
|
const data = await client.listFolders(workspace.id);
|
|
2224
2365
|
if (opts.json) {
|
|
2225
2366
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -2238,6 +2379,33 @@ async function folders(opts) {
|
|
|
2238
2379
|
printFolder(folder);
|
|
2239
2380
|
}
|
|
2240
2381
|
}
|
|
2382
|
+
async function createFolder(client, workspace, opts) {
|
|
2383
|
+
const name = opts.name.trim();
|
|
2384
|
+
const visibility = opts.visibility ?? "workspace";
|
|
2385
|
+
const existing = await client.listFolders(workspace.id);
|
|
2386
|
+
const duplicate = existing.folders.find(
|
|
2387
|
+
(candidate) => candidate.name.toLowerCase() === name.toLowerCase()
|
|
2388
|
+
);
|
|
2389
|
+
if (duplicate) {
|
|
2390
|
+
throw new Error(
|
|
2391
|
+
`A folder named "${duplicate.name}" already exists in ${workspace.handle} (${duplicate.id}) \u2014 publish into it with --folder, or pick a different name.`
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
const folder = await client.createFolder(workspace.id, { name, visibility });
|
|
2395
|
+
if (opts.json) {
|
|
2396
|
+
console.log(JSON.stringify(folder, null, 2));
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
console.log(
|
|
2400
|
+
`\u2713 Created folder "${folder.name}" (${folder.visibility}) in ${workspace.handle}`
|
|
2401
|
+
);
|
|
2402
|
+
console.log(` ${folder.id}`);
|
|
2403
|
+
if (folder.visibility === "public") {
|
|
2404
|
+
console.log(
|
|
2405
|
+
" Artifacts placed in it default to public \u2014 visible to anyone."
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2241
2409
|
function printFolder(folder) {
|
|
2242
2410
|
console.log(`${folder.name} (${folder.visibility})`);
|
|
2243
2411
|
console.log(` ${folder.id}
|
|
@@ -2352,7 +2520,12 @@ async function list(opts) {
|
|
|
2352
2520
|
`Listing your artifacts needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
2353
2521
|
);
|
|
2354
2522
|
}
|
|
2355
|
-
const
|
|
2523
|
+
const client = createApiClient(apiUrl2, key);
|
|
2524
|
+
if (opts.workspace !== void 0) {
|
|
2525
|
+
await listWorkspace(client, opts);
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
const data = await client.listArtifacts();
|
|
2356
2529
|
if (opts.json) {
|
|
2357
2530
|
console.log(JSON.stringify(data, null, 2));
|
|
2358
2531
|
return;
|
|
@@ -2370,6 +2543,70 @@ async function list(opts) {
|
|
|
2370
2543
|
printArtifact(artifact);
|
|
2371
2544
|
}
|
|
2372
2545
|
}
|
|
2546
|
+
async function listWorkspace(client, opts) {
|
|
2547
|
+
const workspaceData = await client.listWorkspaces();
|
|
2548
|
+
const workspace = workspaceData.workspaces.find(
|
|
2549
|
+
(candidate) => candidate.handle === opts.workspace
|
|
2550
|
+
);
|
|
2551
|
+
if (!workspace) {
|
|
2552
|
+
throw new Error(
|
|
2553
|
+
`No workspace with handle "${opts.workspace}" belongs to this account.`
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
const [artifactData, folderData] = await Promise.all([
|
|
2557
|
+
client.listWorkspaceArtifacts(workspace.id),
|
|
2558
|
+
client.listFolders(workspace.id)
|
|
2559
|
+
]);
|
|
2560
|
+
if (opts.json) {
|
|
2561
|
+
console.log(
|
|
2562
|
+
JSON.stringify(
|
|
2563
|
+
{ artifacts: artifactData.artifacts, folders: folderData.folders },
|
|
2564
|
+
null,
|
|
2565
|
+
2
|
|
2566
|
+
)
|
|
2567
|
+
);
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
if (artifactData.artifacts.length === 0) {
|
|
2571
|
+
console.log(`No artifacts in workspace ${workspace.handle}.`);
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
const plural = artifactData.artifacts.length === 1 ? "" : "s";
|
|
2575
|
+
console.log(
|
|
2576
|
+
`${artifactData.artifacts.length} artifact${plural} in ${workspace.handle}
|
|
2577
|
+
`
|
|
2578
|
+
);
|
|
2579
|
+
for (const folder of folderData.folders) {
|
|
2580
|
+
const inFolder = artifactData.artifacts.filter(
|
|
2581
|
+
(artifact) => artifact.folderId === folder.id
|
|
2582
|
+
);
|
|
2583
|
+
if (inFolder.length === 0) {
|
|
2584
|
+
continue;
|
|
2585
|
+
}
|
|
2586
|
+
console.log(`${folder.name} (${folder.visibility})`);
|
|
2587
|
+
for (const artifact of inFolder) {
|
|
2588
|
+
printWorkspaceArtifact(artifact);
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
const unfiled = artifactData.artifacts.filter(
|
|
2592
|
+
(artifact) => artifact.folderId === null
|
|
2593
|
+
);
|
|
2594
|
+
if (unfiled.length > 0) {
|
|
2595
|
+
console.log("Unfiled");
|
|
2596
|
+
for (const artifact of unfiled) {
|
|
2597
|
+
printWorkspaceArtifact(artifact);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
function printWorkspaceArtifact(a) {
|
|
2602
|
+
const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
|
|
2603
|
+
const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
|
|
2604
|
+
const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
|
|
2605
|
+
console.log(
|
|
2606
|
+
` ${marker} ${a.title} \u2014 ${version} \xB7 ${open2} \xB7 ${a.visibility}`
|
|
2607
|
+
);
|
|
2608
|
+
console.log(` ${a.url}`);
|
|
2609
|
+
}
|
|
2373
2610
|
function printArtifact(a) {
|
|
2374
2611
|
const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
|
|
2375
2612
|
const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
|
|
@@ -2569,14 +2806,109 @@ async function deleteMessage(opts) {
|
|
|
2569
2806
|
console.log(`\u2713 Deleted ${messageId}.`);
|
|
2570
2807
|
}
|
|
2571
2808
|
|
|
2572
|
-
// src/
|
|
2809
|
+
// src/move.ts
|
|
2573
2810
|
import path10 from "path";
|
|
2574
|
-
async function
|
|
2811
|
+
async function move(opts) {
|
|
2812
|
+
if (Boolean(opts.folder) === Boolean(opts.unfiled)) {
|
|
2813
|
+
throw new Error("Pass exactly one of --folder <name-or-id> or --unfiled.");
|
|
2814
|
+
}
|
|
2575
2815
|
const dir = path10.resolve(opts.dir);
|
|
2576
2816
|
const target = resolveArtifactTarget(dir, {
|
|
2577
2817
|
apiKey: opts.apiKey,
|
|
2578
2818
|
artifact: opts.artifact
|
|
2579
2819
|
});
|
|
2820
|
+
const key = resolveApiKey(target.apiUrl, opts.apiKey);
|
|
2821
|
+
if (!key) {
|
|
2822
|
+
throw new Error(
|
|
2823
|
+
`Moving an artifact needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(target.apiUrl)}).`
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
const client = createApiClient(target.apiUrl, key);
|
|
2827
|
+
const artifact = await client.getArtifact(target.artifactId);
|
|
2828
|
+
if (!artifact.workspaceId) {
|
|
2829
|
+
throw new Error(
|
|
2830
|
+
"This artifact isn't in a workspace \u2014 folders live in workspaces. Claim it (or publish it while logged in) first."
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
let folderId = null;
|
|
2834
|
+
let folderName = null;
|
|
2835
|
+
if (opts.folder) {
|
|
2836
|
+
const data = await client.listFolders(artifact.workspaceId);
|
|
2837
|
+
const matches = data.folders.filter(
|
|
2838
|
+
(candidate) => candidate.name === opts.folder || candidate.id === opts.folder
|
|
2839
|
+
);
|
|
2840
|
+
if (matches.length === 0) {
|
|
2841
|
+
throw new Error(
|
|
2842
|
+
`No folder named "${opts.folder}" exists in this workspace \u2014 list them with \`artifact-cafe folders --workspace <handle>\`, or create it with \`folders create\`.`
|
|
2843
|
+
);
|
|
2844
|
+
}
|
|
2845
|
+
if (matches.length > 1) {
|
|
2846
|
+
throw new Error(
|
|
2847
|
+
`More than one folder is named "${opts.folder}"; pass the folder id instead.`
|
|
2848
|
+
);
|
|
2849
|
+
}
|
|
2850
|
+
folderId = matches[0].id;
|
|
2851
|
+
folderName = matches[0].name;
|
|
2852
|
+
}
|
|
2853
|
+
if (artifact.folderId === folderId) {
|
|
2854
|
+
const where = folderName ? `folder "${folderName}"` : "no folder";
|
|
2855
|
+
if (opts.json) {
|
|
2856
|
+
console.log(
|
|
2857
|
+
JSON.stringify(
|
|
2858
|
+
{
|
|
2859
|
+
artifactId: artifact.id,
|
|
2860
|
+
folder: folderName ? { id: folderId, name: folderName } : null,
|
|
2861
|
+
moved: false,
|
|
2862
|
+
title: artifact.title,
|
|
2863
|
+
visibility: artifact.visibility
|
|
2864
|
+
},
|
|
2865
|
+
null,
|
|
2866
|
+
2
|
|
2867
|
+
)
|
|
2868
|
+
);
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
console.log(`"${artifact.title}" is already in ${where} \u2014 nothing to do.`);
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
const updated = await client.updateArtifact(artifact.id, { folderId });
|
|
2875
|
+
if (opts.json) {
|
|
2876
|
+
console.log(
|
|
2877
|
+
JSON.stringify(
|
|
2878
|
+
{
|
|
2879
|
+
artifactId: updated.id,
|
|
2880
|
+
folder: folderName ? { id: folderId, name: folderName } : null,
|
|
2881
|
+
moved: true,
|
|
2882
|
+
previousVisibility: artifact.visibility,
|
|
2883
|
+
title: updated.title,
|
|
2884
|
+
visibility: updated.visibility
|
|
2885
|
+
},
|
|
2886
|
+
null,
|
|
2887
|
+
2
|
|
2888
|
+
)
|
|
2889
|
+
);
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
const destination = folderName ? `"${folderName}"` : "Unfiled";
|
|
2893
|
+
console.log(`\u2713 Moved "${updated.title}" \u2192 ${destination}`);
|
|
2894
|
+
if (updated.visibility !== artifact.visibility) {
|
|
2895
|
+
console.log(
|
|
2896
|
+
` Visibility: ${artifact.visibility} \u2192 ${updated.visibility} (folder default)`
|
|
2897
|
+
);
|
|
2898
|
+
if (updated.visibility === "public") {
|
|
2899
|
+
console.log(" Now public \u2014 visible to anyone.");
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
// src/open.ts
|
|
2905
|
+
import path11 from "path";
|
|
2906
|
+
async function openArtifact(opts) {
|
|
2907
|
+
const dir = path11.resolve(opts.dir);
|
|
2908
|
+
const target = resolveArtifactTarget(dir, {
|
|
2909
|
+
apiKey: opts.apiKey,
|
|
2910
|
+
artifact: opts.artifact
|
|
2911
|
+
});
|
|
2580
2912
|
const url = target.url ?? reviewUrl(
|
|
2581
2913
|
target.apiUrl,
|
|
2582
2914
|
(await createApiClient(target.apiUrl, target.credential).getArtifact(
|
|
@@ -2590,7 +2922,7 @@ async function openArtifact(opts) {
|
|
|
2590
2922
|
}
|
|
2591
2923
|
|
|
2592
2924
|
// src/protect.ts
|
|
2593
|
-
import
|
|
2925
|
+
import path12 from "path";
|
|
2594
2926
|
async function protect(opts) {
|
|
2595
2927
|
if (opts.off && opts.password !== void 0) {
|
|
2596
2928
|
throw new Error("Pass either --password or --off, not both.");
|
|
@@ -2607,7 +2939,7 @@ async function protect(opts) {
|
|
|
2607
2939
|
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
2608
2940
|
}
|
|
2609
2941
|
}
|
|
2610
|
-
const dir =
|
|
2942
|
+
const dir = path12.resolve(opts.dir);
|
|
2611
2943
|
const target = resolveArtifactTarget(dir, {
|
|
2612
2944
|
apiKey: opts.apiKey,
|
|
2613
2945
|
artifact: opts.artifact
|
|
@@ -2632,7 +2964,7 @@ async function protect(opts) {
|
|
|
2632
2964
|
}
|
|
2633
2965
|
|
|
2634
2966
|
// src/reply.ts
|
|
2635
|
-
import
|
|
2967
|
+
import path13 from "path";
|
|
2636
2968
|
async function readStdin3() {
|
|
2637
2969
|
if (process.stdin.isTTY) {
|
|
2638
2970
|
return "";
|
|
@@ -2656,7 +2988,7 @@ async function replyToThread(opts) {
|
|
|
2656
2988
|
'Nothing to post. Pass the reply with --body "\u2026", or pipe it on stdin.'
|
|
2657
2989
|
);
|
|
2658
2990
|
}
|
|
2659
|
-
const dir =
|
|
2991
|
+
const dir = path13.resolve(opts.dir);
|
|
2660
2992
|
const target = resolveArtifactTarget(dir, {
|
|
2661
2993
|
apiKey: opts.apiKey,
|
|
2662
2994
|
artifact: opts.artifact
|
|
@@ -2685,7 +3017,7 @@ async function replyToThread(opts) {
|
|
|
2685
3017
|
}
|
|
2686
3018
|
|
|
2687
3019
|
// src/resolve.ts
|
|
2688
|
-
import
|
|
3020
|
+
import path14 from "path";
|
|
2689
3021
|
async function changeThreadStatus(opts) {
|
|
2690
3022
|
const threadId = opts.thread?.trim();
|
|
2691
3023
|
if (!threadId) {
|
|
@@ -2693,7 +3025,7 @@ async function changeThreadStatus(opts) {
|
|
|
2693
3025
|
`Pass the thread id to ${opts.action} \u2014 get it from \`artifact-cafe comments --json\`.`
|
|
2694
3026
|
);
|
|
2695
3027
|
}
|
|
2696
|
-
const dir =
|
|
3028
|
+
const dir = path14.resolve(opts.dir);
|
|
2697
3029
|
const target = resolveArtifactTarget(dir, {
|
|
2698
3030
|
apiKey: opts.apiKey,
|
|
2699
3031
|
artifact: opts.artifact
|
|
@@ -2729,11 +3061,47 @@ async function changeThreadStatus(opts) {
|
|
|
2729
3061
|
console.log(`\u2713 ${verb} ${thread.id} \u2014 now ${thread.status}.`);
|
|
2730
3062
|
}
|
|
2731
3063
|
|
|
3064
|
+
// src/review.ts
|
|
3065
|
+
import path15 from "path";
|
|
3066
|
+
var OUTCOME = {
|
|
3067
|
+
off: "Plain artifact \u2014 the review tools are hidden entirely",
|
|
3068
|
+
open: "Open for review \u2014 anyone who can open it can comment",
|
|
3069
|
+
readonly: "Read-only \u2014 earlier comments stay visible, no new ones"
|
|
3070
|
+
};
|
|
3071
|
+
async function review(opts) {
|
|
3072
|
+
const parsed = reviewModeSchema.safeParse(opts.mode);
|
|
3073
|
+
if (!parsed.success) {
|
|
3074
|
+
throw new Error(
|
|
3075
|
+
"Provide a mode: open, readonly, or off. `readonly` keeps existing comments readable and refuses new ones; `off` hides the review layer entirely."
|
|
3076
|
+
);
|
|
3077
|
+
}
|
|
3078
|
+
const mode = parsed.data;
|
|
3079
|
+
const dir = path15.resolve(opts.dir);
|
|
3080
|
+
const target = resolveArtifactTarget(dir, {
|
|
3081
|
+
apiKey: opts.apiKey,
|
|
3082
|
+
artifact: opts.artifact
|
|
3083
|
+
});
|
|
3084
|
+
const client = createApiClient(target.apiUrl, target.credential);
|
|
3085
|
+
const res = await client.setReviewMode(target.artifactId, mode);
|
|
3086
|
+
if (opts.json) {
|
|
3087
|
+
console.log(JSON.stringify({ reviewMode: res.mode }, null, 2));
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
const url = target.url ?? reviewUrl(
|
|
3091
|
+
target.apiUrl,
|
|
3092
|
+
(await client.getArtifact(target.artifactId)).slug
|
|
3093
|
+
);
|
|
3094
|
+
console.log(`
|
|
3095
|
+
\u2713 ${OUTCOME[res.mode]}
|
|
3096
|
+
${url}
|
|
3097
|
+
`);
|
|
3098
|
+
}
|
|
3099
|
+
|
|
2732
3100
|
// src/share.ts
|
|
2733
|
-
import
|
|
3101
|
+
import path16 from "path";
|
|
2734
3102
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2735
3103
|
async function share(opts) {
|
|
2736
|
-
const dir =
|
|
3104
|
+
const dir = path16.resolve(opts.dir);
|
|
2737
3105
|
const target = resolveArtifactTarget(dir, {
|
|
2738
3106
|
apiKey: opts.apiKey,
|
|
2739
3107
|
artifact: opts.artifact
|
|
@@ -2788,7 +3156,7 @@ General access: ${visibility}`);
|
|
|
2788
3156
|
console.log("");
|
|
2789
3157
|
}
|
|
2790
3158
|
async function unshare(opts) {
|
|
2791
|
-
const dir =
|
|
3159
|
+
const dir = path16.resolve(opts.dir);
|
|
2792
3160
|
const target = resolveArtifactTarget(dir, {
|
|
2793
3161
|
apiKey: opts.apiKey,
|
|
2794
3162
|
artifact: opts.artifact
|
|
@@ -2811,7 +3179,7 @@ async function unshare(opts) {
|
|
|
2811
3179
|
}
|
|
2812
3180
|
|
|
2813
3181
|
// src/status.ts
|
|
2814
|
-
import
|
|
3182
|
+
import path17 from "path";
|
|
2815
3183
|
async function status(opts) {
|
|
2816
3184
|
if (!AGENT_STATES.includes(opts.state)) {
|
|
2817
3185
|
throw new Error(
|
|
@@ -2819,7 +3187,7 @@ async function status(opts) {
|
|
|
2819
3187
|
);
|
|
2820
3188
|
}
|
|
2821
3189
|
const state = opts.state;
|
|
2822
|
-
const dir =
|
|
3190
|
+
const dir = path17.resolve(opts.dir);
|
|
2823
3191
|
const target = resolveArtifactTarget(dir, {
|
|
2824
3192
|
apiKey: opts.apiKey,
|
|
2825
3193
|
artifact: opts.artifact
|
|
@@ -2848,26 +3216,26 @@ async function status(opts) {
|
|
|
2848
3216
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
2849
3217
|
import { createRequire } from "module";
|
|
2850
3218
|
import { homedir as homedir2 } from "os";
|
|
2851
|
-
import
|
|
2852
|
-
import { z as
|
|
3219
|
+
import path18 from "path";
|
|
3220
|
+
import { z as z20 } from "zod";
|
|
2853
3221
|
var REGISTRY_LATEST_URL = "https://registry.npmjs.org/artifact-cafe/latest";
|
|
2854
3222
|
var SKILL_SOURCE_URL = "https://raw.githubusercontent.com/Artifact-Cafe/skill/main/artifact-cafe/SKILL.md";
|
|
2855
3223
|
var SKILL_UPDATE_COMMAND = "npx skills add artifact-cafe/skill --skill artifact-cafe -g";
|
|
2856
3224
|
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2857
3225
|
var FETCH_TIMEOUT_MS = 2e3;
|
|
2858
3226
|
var SKILL_VERSION_PATTERN = /\*\*Skill version:\s*(\d+\.\d+\.\d+)\*\*/;
|
|
2859
|
-
var cacheSchema =
|
|
2860
|
-
checkedAt:
|
|
2861
|
-
cliLatest:
|
|
2862
|
-
skillLatest:
|
|
2863
|
-
version:
|
|
3227
|
+
var cacheSchema = z20.object({
|
|
3228
|
+
checkedAt: z20.number(),
|
|
3229
|
+
cliLatest: z20.string().nullable(),
|
|
3230
|
+
skillLatest: z20.string().nullable(),
|
|
3231
|
+
version: z20.literal(1)
|
|
2864
3232
|
});
|
|
2865
3233
|
function cliVersion() {
|
|
2866
3234
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
2867
3235
|
return pkg.version;
|
|
2868
3236
|
}
|
|
2869
3237
|
function cachePath() {
|
|
2870
|
-
return
|
|
3238
|
+
return path18.join(homedir2(), ".artifactcafe", "update-check.json");
|
|
2871
3239
|
}
|
|
2872
3240
|
function readCache() {
|
|
2873
3241
|
try {
|
|
@@ -2878,7 +3246,7 @@ function readCache() {
|
|
|
2878
3246
|
}
|
|
2879
3247
|
function writeCache(cache) {
|
|
2880
3248
|
try {
|
|
2881
|
-
mkdirSync3(
|
|
3249
|
+
mkdirSync3(path18.dirname(cachePath()), { mode: 448, recursive: true });
|
|
2882
3250
|
writeFileSync3(cachePath(), `${JSON.stringify(cache, null, 2)}
|
|
2883
3251
|
`);
|
|
2884
3252
|
} catch {
|
|
@@ -2915,7 +3283,7 @@ async function fetchText(url) {
|
|
|
2915
3283
|
}
|
|
2916
3284
|
async function fetchLatestVersions(previous) {
|
|
2917
3285
|
const [cli, skill] = await Promise.all([
|
|
2918
|
-
fetchJson(REGISTRY_LATEST_URL).then((body) =>
|
|
3286
|
+
fetchJson(REGISTRY_LATEST_URL).then((body) => z20.object({ version: z20.string() }).parse(body).version).catch(() => null),
|
|
2919
3287
|
fetchText(SKILL_SOURCE_URL).then((body) => SKILL_VERSION_PATTERN.exec(body)?.[1] ?? null).catch(() => null)
|
|
2920
3288
|
]);
|
|
2921
3289
|
return {
|
|
@@ -2931,7 +3299,7 @@ function installedSkillVersions() {
|
|
|
2931
3299
|
const versions = [];
|
|
2932
3300
|
for (const root of roots) {
|
|
2933
3301
|
for (const dir of dirs) {
|
|
2934
|
-
const file =
|
|
3302
|
+
const file = path18.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
|
|
2935
3303
|
if (!existsSync3(file)) {
|
|
2936
3304
|
continue;
|
|
2937
3305
|
}
|
|
@@ -3072,6 +3440,10 @@ var publish2 = defineCommand({
|
|
|
3072
3440
|
description: "Password-protect the artifact (min 6 chars)",
|
|
3073
3441
|
type: "string"
|
|
3074
3442
|
},
|
|
3443
|
+
review: {
|
|
3444
|
+
description: "Review mode: open (default), readonly, or off (plain artifact)",
|
|
3445
|
+
type: "string"
|
|
3446
|
+
},
|
|
3075
3447
|
title: { description: "Artifact title", type: "string" },
|
|
3076
3448
|
workspace: {
|
|
3077
3449
|
description: "Publish into this workspace handle",
|
|
@@ -3091,6 +3463,7 @@ var publish2 = defineCommand({
|
|
|
3091
3463
|
notes: readNotesArg(process.argv.slice(2)),
|
|
3092
3464
|
open: !args["no-open"],
|
|
3093
3465
|
password: args.password,
|
|
3466
|
+
review: args.review,
|
|
3094
3467
|
title: args.title,
|
|
3095
3468
|
workspace: args.workspace
|
|
3096
3469
|
});
|
|
@@ -3161,7 +3534,11 @@ var list2 = defineCommand({
|
|
|
3161
3534
|
description: "List with this API key (default: stored login)",
|
|
3162
3535
|
type: "string"
|
|
3163
3536
|
},
|
|
3164
|
-
json: { description: "Machine-readable output", type: "boolean" }
|
|
3537
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
3538
|
+
workspace: {
|
|
3539
|
+
description: "List this workspace's artifacts, grouped by folder",
|
|
3540
|
+
type: "string"
|
|
3541
|
+
}
|
|
3165
3542
|
},
|
|
3166
3543
|
meta: {
|
|
3167
3544
|
description: "List the artifacts your account owns",
|
|
@@ -3169,7 +3546,11 @@ var list2 = defineCommand({
|
|
|
3169
3546
|
},
|
|
3170
3547
|
async run({ args }) {
|
|
3171
3548
|
try {
|
|
3172
|
-
await list({
|
|
3549
|
+
await list({
|
|
3550
|
+
apiKey: args["api-key"],
|
|
3551
|
+
json: Boolean(args.json),
|
|
3552
|
+
workspace: args.workspace
|
|
3553
|
+
});
|
|
3173
3554
|
} catch (err) {
|
|
3174
3555
|
printCliError(err, Boolean(args.json));
|
|
3175
3556
|
process.exit(1);
|
|
@@ -3178,26 +3559,43 @@ var list2 = defineCommand({
|
|
|
3178
3559
|
});
|
|
3179
3560
|
var folders2 = defineCommand({
|
|
3180
3561
|
args: {
|
|
3562
|
+
action: {
|
|
3563
|
+
description: 'Optional action: "create"',
|
|
3564
|
+
required: false,
|
|
3565
|
+
type: "positional"
|
|
3566
|
+
},
|
|
3181
3567
|
"api-key": {
|
|
3182
|
-
description: "
|
|
3568
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
3183
3569
|
type: "string"
|
|
3184
3570
|
},
|
|
3185
3571
|
json: { description: "Machine-readable output", type: "boolean" },
|
|
3572
|
+
name: {
|
|
3573
|
+
description: "Folder name (with create)",
|
|
3574
|
+
required: false,
|
|
3575
|
+
type: "positional"
|
|
3576
|
+
},
|
|
3577
|
+
visibility: {
|
|
3578
|
+
description: "With create: workspace (default) | public",
|
|
3579
|
+
type: "string"
|
|
3580
|
+
},
|
|
3186
3581
|
workspace: {
|
|
3187
|
-
description: "Workspace handle
|
|
3582
|
+
description: "Workspace handle to list or create in",
|
|
3188
3583
|
required: true,
|
|
3189
3584
|
type: "string"
|
|
3190
3585
|
}
|
|
3191
3586
|
},
|
|
3192
3587
|
meta: {
|
|
3193
|
-
description: "List folders
|
|
3588
|
+
description: "List a workspace's folders, or create one (folders create)",
|
|
3194
3589
|
name: "folders"
|
|
3195
3590
|
},
|
|
3196
3591
|
async run({ args }) {
|
|
3197
3592
|
try {
|
|
3198
3593
|
await folders({
|
|
3594
|
+
action: args.action,
|
|
3199
3595
|
apiKey: args["api-key"],
|
|
3200
3596
|
json: Boolean(args.json),
|
|
3597
|
+
name: args.name,
|
|
3598
|
+
visibility: args.visibility,
|
|
3201
3599
|
workspace: args.workspace
|
|
3202
3600
|
});
|
|
3203
3601
|
} catch (err) {
|
|
@@ -3206,6 +3604,51 @@ var folders2 = defineCommand({
|
|
|
3206
3604
|
}
|
|
3207
3605
|
}
|
|
3208
3606
|
});
|
|
3607
|
+
var move2 = defineCommand({
|
|
3608
|
+
args: {
|
|
3609
|
+
"api-key": {
|
|
3610
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
3611
|
+
type: "string"
|
|
3612
|
+
},
|
|
3613
|
+
artifact: {
|
|
3614
|
+
description: "Move this artifact ID instead of the linked folder",
|
|
3615
|
+
type: "string"
|
|
3616
|
+
},
|
|
3617
|
+
dir: {
|
|
3618
|
+
default: ".",
|
|
3619
|
+
description: "Linked directory to resolve the artifact from (default: .)",
|
|
3620
|
+
type: "string"
|
|
3621
|
+
},
|
|
3622
|
+
folder: {
|
|
3623
|
+
description: "Target folder name (or id) in the artifact's workspace",
|
|
3624
|
+
type: "string"
|
|
3625
|
+
},
|
|
3626
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
3627
|
+
unfiled: {
|
|
3628
|
+
description: "Move out of its folder (workspace root)",
|
|
3629
|
+
type: "boolean"
|
|
3630
|
+
}
|
|
3631
|
+
},
|
|
3632
|
+
meta: {
|
|
3633
|
+
description: "Move an artifact into a workspace folder (or --unfiled)",
|
|
3634
|
+
name: "move"
|
|
3635
|
+
},
|
|
3636
|
+
async run({ args }) {
|
|
3637
|
+
try {
|
|
3638
|
+
await move({
|
|
3639
|
+
apiKey: args["api-key"],
|
|
3640
|
+
artifact: args.artifact,
|
|
3641
|
+
dir: args.dir,
|
|
3642
|
+
folder: args.folder,
|
|
3643
|
+
json: Boolean(args.json),
|
|
3644
|
+
unfiled: Boolean(args.unfiled)
|
|
3645
|
+
});
|
|
3646
|
+
} catch (err) {
|
|
3647
|
+
printCliError(err, Boolean(args.json));
|
|
3648
|
+
process.exit(1);
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
});
|
|
3209
3652
|
var workspaces2 = defineCommand({
|
|
3210
3653
|
args: {
|
|
3211
3654
|
"api-key": {
|
|
@@ -3474,6 +3917,47 @@ var protect2 = defineCommand({
|
|
|
3474
3917
|
}
|
|
3475
3918
|
}
|
|
3476
3919
|
});
|
|
3920
|
+
var review2 = defineCommand({
|
|
3921
|
+
args: {
|
|
3922
|
+
"api-key": {
|
|
3923
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
3924
|
+
type: "string"
|
|
3925
|
+
},
|
|
3926
|
+
artifact: {
|
|
3927
|
+
description: "Act on an artifact ID instead of the linked folder",
|
|
3928
|
+
type: "string"
|
|
3929
|
+
},
|
|
3930
|
+
dir: {
|
|
3931
|
+
default: ".",
|
|
3932
|
+
description: "Published directory",
|
|
3933
|
+
type: "string"
|
|
3934
|
+
},
|
|
3935
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
3936
|
+
mode: {
|
|
3937
|
+
description: "open, readonly, or off (plain artifact)",
|
|
3938
|
+
required: true,
|
|
3939
|
+
type: "positional"
|
|
3940
|
+
}
|
|
3941
|
+
},
|
|
3942
|
+
meta: {
|
|
3943
|
+
description: "Open, close, or freeze review on the linked artifact",
|
|
3944
|
+
name: "review"
|
|
3945
|
+
},
|
|
3946
|
+
async run({ args }) {
|
|
3947
|
+
try {
|
|
3948
|
+
await review({
|
|
3949
|
+
apiKey: args["api-key"],
|
|
3950
|
+
artifact: args.artifact,
|
|
3951
|
+
dir: args.dir,
|
|
3952
|
+
json: Boolean(args.json),
|
|
3953
|
+
mode: args.mode
|
|
3954
|
+
});
|
|
3955
|
+
} catch (err) {
|
|
3956
|
+
printCliError(err, Boolean(args.json));
|
|
3957
|
+
process.exit(1);
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
});
|
|
3477
3961
|
var threadStatusCommand = (action) => defineCommand({
|
|
3478
3962
|
args: {
|
|
3479
3963
|
"api-key": {
|
|
@@ -3884,12 +4368,14 @@ var main = defineCommand({
|
|
|
3884
4368
|
listen,
|
|
3885
4369
|
login: login2,
|
|
3886
4370
|
logout: logout2,
|
|
4371
|
+
move: move2,
|
|
3887
4372
|
open,
|
|
3888
4373
|
protect: protect2,
|
|
3889
4374
|
publish: publish2,
|
|
3890
4375
|
reopen,
|
|
3891
4376
|
reply,
|
|
3892
4377
|
resolve,
|
|
4378
|
+
review: review2,
|
|
3893
4379
|
share: share2,
|
|
3894
4380
|
status: statusCmd,
|
|
3895
4381
|
unshare: unshare2,
|