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