artifact-cafe 0.0.8 → 0.0.9
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 +1049 -433
- 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(),
|
|
@@ -81,107 +84,260 @@ var artifactListResponseSchema = z.object({
|
|
|
81
84
|
});
|
|
82
85
|
|
|
83
86
|
// ../shared/src/comments.ts
|
|
87
|
+
import { z as z3 } from "zod";
|
|
88
|
+
|
|
89
|
+
// ../shared/src/publish.ts
|
|
84
90
|
import { z as z2 } from "zod";
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
function isSafeArtifactPath(p) {
|
|
92
|
+
if (p.length === 0 || p.length > 1024) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (p.includes("\\") || p.includes("\0")) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (p.startsWith("/") || p.startsWith("~")) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
|
|
102
|
+
}
|
|
103
|
+
var manifestFileSchema = z2.object({
|
|
104
|
+
hash: z2.string().regex(/^sha256_[a-f0-9]{64}$/),
|
|
105
|
+
mimeType: z2.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
|
|
106
|
+
path: z2.string().min(1).max(1024).refine(isSafeArtifactPath, {
|
|
107
|
+
message: "path must be a safe relative path"
|
|
108
|
+
}),
|
|
109
|
+
size: z2.number().int().positive()
|
|
110
|
+
});
|
|
111
|
+
var publishSourceSchema = z2.object({
|
|
112
|
+
agent: z2.string().optional(),
|
|
113
|
+
runId: z2.string().optional(),
|
|
114
|
+
type: z2.literal("cli")
|
|
115
|
+
});
|
|
116
|
+
var createPublishSessionSchema = z2.object({
|
|
117
|
+
artifactId: z2.string().nullable(),
|
|
118
|
+
entryPath: z2.string().optional(),
|
|
119
|
+
files: z2.array(manifestFileSchema).min(1).max(1e3),
|
|
120
|
+
// SPEC-2 §6.4 — workspace placement is available only to authenticated
|
|
121
|
+
// members; the API enforces membership and folder ownership. folderId
|
|
122
|
+
// without workspaceId resolves against the account's personal workspace.
|
|
123
|
+
folderId: z2.string().min(1).optional(),
|
|
124
|
+
source: publishSourceSchema,
|
|
125
|
+
title: z2.string().max(200).optional(),
|
|
126
|
+
workspaceId: z2.string().min(1).optional()
|
|
127
|
+
});
|
|
128
|
+
var publishSessionResponseSchema = z2.object({
|
|
129
|
+
artifactId: z2.string(),
|
|
130
|
+
expiresAt: z2.string(),
|
|
131
|
+
filesToUpload: z2.array(
|
|
132
|
+
z2.object({
|
|
133
|
+
method: z2.literal("PUT"),
|
|
134
|
+
path: z2.string(),
|
|
135
|
+
uploadUrl: z2.string().url()
|
|
136
|
+
})
|
|
137
|
+
),
|
|
138
|
+
publishSessionId: z2.string(),
|
|
139
|
+
skippedFiles: z2.array(z2.string()),
|
|
140
|
+
versionId: z2.string()
|
|
90
141
|
});
|
|
91
|
-
var
|
|
142
|
+
var finalizeResponseSchema = z2.object({
|
|
143
|
+
artifactId: z2.string(),
|
|
144
|
+
// API-key publishes create the artifact already claimed (SPEC §5.4) —
|
|
145
|
+
// the CLI uses this to tell "claim URL pending" from "already yours".
|
|
146
|
+
claimed: z2.boolean(),
|
|
147
|
+
// Returned only on first publish of a new artifact (one-time).
|
|
148
|
+
claimUrl: z2.string().url().optional(),
|
|
149
|
+
publishToken: z2.string().optional(),
|
|
150
|
+
url: z2.string().url(),
|
|
151
|
+
versionId: z2.string(),
|
|
152
|
+
versionNumber: z2.number().int().positive()
|
|
153
|
+
});
|
|
154
|
+
var artifactResponseSchema = z2.object({
|
|
155
|
+
claimed: z2.boolean(),
|
|
156
|
+
currentVersion: z2.object({
|
|
157
|
+
entryUrl: z2.string().url(),
|
|
158
|
+
id: z2.string(),
|
|
159
|
+
versionNumber: z2.number().int().positive()
|
|
160
|
+
}).nullable(),
|
|
161
|
+
// SPEC-2 §6.4 — placement + visibility surface on the artifact.
|
|
162
|
+
folderId: z2.string().nullable(),
|
|
163
|
+
id: z2.string(),
|
|
164
|
+
slug: z2.string(),
|
|
165
|
+
title: z2.string(),
|
|
166
|
+
visibility: z2.enum(["public", "unlisted", "workspace"]),
|
|
167
|
+
workspaceId: z2.string().nullable()
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// ../shared/src/comments.ts
|
|
171
|
+
var anchorPageSchema = z3.string().max(1024).refine(isSafeArtifactPath, {
|
|
172
|
+
message: "page must be a safe relative path"
|
|
173
|
+
}).optional();
|
|
174
|
+
var elementSnapshotSchema = z3.object({
|
|
175
|
+
classes: z3.string().max(256).optional(),
|
|
176
|
+
id: z3.string().max(256).optional(),
|
|
177
|
+
tag: z3.string().min(1).max(128),
|
|
178
|
+
text: z3.string().max(200).optional()
|
|
179
|
+
});
|
|
180
|
+
var pointAnchorSchema = z3.object({
|
|
92
181
|
element: elementSnapshotSchema.optional(),
|
|
93
182
|
// elementPath is the primary anchor; x/y are offsets within the
|
|
94
183
|
// 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
|
-
|
|
184
|
+
elementPath: z3.string().max(2048),
|
|
185
|
+
page: anchorPageSchema,
|
|
186
|
+
type: z3.literal("point"),
|
|
187
|
+
viewportHeight: z3.number().int().positive().max(1e5),
|
|
188
|
+
viewportWidth: z3.number().int().positive().max(1e5),
|
|
189
|
+
x: z3.number().min(0).max(1),
|
|
190
|
+
y: z3.number().min(0).max(1)
|
|
191
|
+
});
|
|
192
|
+
var textAnchorSchema = z3.object({
|
|
193
|
+
elementPath: z3.string().max(2048),
|
|
194
|
+
endOffset: z3.number().int().nonnegative().max(1e8),
|
|
195
|
+
page: anchorPageSchema,
|
|
196
|
+
prefix: z3.string().max(200).optional(),
|
|
197
|
+
quote: z3.string().min(1).max(1e3),
|
|
198
|
+
startOffset: z3.number().int().nonnegative().max(1e8),
|
|
199
|
+
suffix: z3.string().max(200).optional(),
|
|
200
|
+
type: z3.literal("text")
|
|
201
|
+
});
|
|
202
|
+
var pageAnchorSchema = z3.object({
|
|
203
|
+
page: anchorPageSchema,
|
|
204
|
+
type: z3.literal("page")
|
|
205
|
+
});
|
|
206
|
+
var anchorSchema = z3.discriminatedUnion("type", [
|
|
112
207
|
pointAnchorSchema,
|
|
113
|
-
textAnchorSchema
|
|
208
|
+
textAnchorSchema,
|
|
209
|
+
pageAnchorSchema
|
|
114
210
|
]);
|
|
115
|
-
var displayNameSchema =
|
|
116
|
-
var guestSchema =
|
|
211
|
+
var displayNameSchema = z3.string().trim().min(1).max(64);
|
|
212
|
+
var guestSchema = z3.object({
|
|
117
213
|
displayName: displayNameSchema,
|
|
118
|
-
guestId:
|
|
214
|
+
guestId: z3.string().min(16)
|
|
119
215
|
});
|
|
120
|
-
var
|
|
216
|
+
var viaLabelSchema = z3.string().trim().min(1).max(64);
|
|
217
|
+
var createCommentThreadSchema = z3.object({
|
|
121
218
|
anchor: anchorSchema,
|
|
122
|
-
body:
|
|
219
|
+
body: z3.string().min(1).max(1e4),
|
|
123
220
|
guest: guestSchema.optional(),
|
|
124
|
-
versionId:
|
|
221
|
+
versionId: z3.string(),
|
|
222
|
+
via: viaLabelSchema.optional()
|
|
125
223
|
});
|
|
126
|
-
var createCommentMessageSchema =
|
|
127
|
-
body:
|
|
224
|
+
var createCommentMessageSchema = z3.object({
|
|
225
|
+
body: z3.string().min(1).max(1e4),
|
|
226
|
+
guest: guestSchema.optional(),
|
|
227
|
+
via: viaLabelSchema.optional()
|
|
228
|
+
});
|
|
229
|
+
var editCommentMessageSchema = z3.object({
|
|
230
|
+
body: z3.string().min(1).max(1e4),
|
|
128
231
|
guest: guestSchema.optional()
|
|
129
232
|
});
|
|
130
|
-
var threadActionSchema =
|
|
233
|
+
var threadActionSchema = z3.object({
|
|
131
234
|
guest: guestSchema.optional()
|
|
132
235
|
});
|
|
133
|
-
var
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
236
|
+
var REACTION_EMOJI = [
|
|
237
|
+
"\u{1F44D}",
|
|
238
|
+
"\u{1F44E}",
|
|
239
|
+
"\u2764\uFE0F",
|
|
240
|
+
"\u{1F389}",
|
|
241
|
+
"\u{1F4A1}",
|
|
242
|
+
"\u{1F440}",
|
|
243
|
+
"\u{1F680}",
|
|
244
|
+
"\u2615"
|
|
245
|
+
];
|
|
246
|
+
var reactionEmojiSchema = z3.enum(REACTION_EMOJI);
|
|
247
|
+
var toggleReactionSchema = z3.object({
|
|
248
|
+
emoji: reactionEmojiSchema,
|
|
249
|
+
guest: guestSchema.optional()
|
|
139
250
|
});
|
|
140
|
-
var
|
|
251
|
+
var commentAuthorSchema = z3.object({
|
|
252
|
+
avatarUrl: z3.string().nullable(),
|
|
253
|
+
displayName: z3.string(),
|
|
254
|
+
guestId: z3.string().nullable(),
|
|
255
|
+
isViewer: z3.boolean(),
|
|
256
|
+
kind: z3.enum(["member", "guest"]),
|
|
257
|
+
// Optional "via" attribution (§6.4). Null for plain comments; a bounded
|
|
258
|
+
// label the shell appends after the display name.
|
|
259
|
+
via: z3.string().nullable()
|
|
260
|
+
});
|
|
261
|
+
var messageReactionSchema = z3.object({
|
|
262
|
+
emoji: z3.string(),
|
|
263
|
+
reactors: z3.array(commentAuthorSchema)
|
|
264
|
+
});
|
|
265
|
+
var commentMessageSchema = z3.object({
|
|
141
266
|
author: commentAuthorSchema,
|
|
142
|
-
body
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
267
|
+
// Empty string for a deleted message — the body is stripped server-side
|
|
268
|
+
// (§6.7.1 tombstone), never sent to the client and hidden there.
|
|
269
|
+
body: z3.string(),
|
|
270
|
+
createdAt: z3.string(),
|
|
271
|
+
// Soft-deleted (§6.7.1). A tombstone keeps thread structure and replies;
|
|
272
|
+
// the shell renders "Comment deleted" instead of the (absent) body.
|
|
273
|
+
deleted: z3.boolean(),
|
|
274
|
+
// Last edit time (§6.7.1), or null if never edited — drives the "edited"
|
|
275
|
+
// badge. No edit history is kept.
|
|
276
|
+
editedAt: z3.string().nullable(),
|
|
277
|
+
id: z3.string(),
|
|
278
|
+
// Defaulted, not required: a new CLI parsing an older server's
|
|
279
|
+
// response must not fail on the missing key.
|
|
280
|
+
reactions: z3.array(messageReactionSchema).default([])
|
|
281
|
+
});
|
|
282
|
+
var commentThreadSchema = z3.object({
|
|
147
283
|
anchor: anchorSchema,
|
|
148
|
-
artifactId:
|
|
149
|
-
createdAt:
|
|
284
|
+
artifactId: z3.string(),
|
|
285
|
+
createdAt: z3.string(),
|
|
150
286
|
createdBy: commentAuthorSchema,
|
|
151
|
-
id:
|
|
152
|
-
messages:
|
|
153
|
-
status:
|
|
154
|
-
versionId:
|
|
155
|
-
});
|
|
156
|
-
var commentThreadListSchema =
|
|
157
|
-
threads:
|
|
158
|
-
});
|
|
159
|
-
var commentExportQuerySchema =
|
|
160
|
-
status:
|
|
161
|
-
version:
|
|
162
|
-
});
|
|
163
|
-
var
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
287
|
+
id: z3.string(),
|
|
288
|
+
messages: z3.array(commentMessageSchema),
|
|
289
|
+
status: z3.enum(["open", "resolved"]),
|
|
290
|
+
versionId: z3.string()
|
|
291
|
+
});
|
|
292
|
+
var commentThreadListSchema = z3.object({
|
|
293
|
+
threads: z3.array(commentThreadSchema)
|
|
294
|
+
});
|
|
295
|
+
var commentExportQuerySchema = z3.object({
|
|
296
|
+
status: z3.enum(["open", "resolved", "all"]).default("open"),
|
|
297
|
+
version: z3.enum(["current", "all"]).default("current")
|
|
298
|
+
});
|
|
299
|
+
var exportReactionSchema = z3.object({
|
|
300
|
+
count: z3.number().int().positive(),
|
|
301
|
+
emoji: z3.string()
|
|
302
|
+
});
|
|
303
|
+
var commentExportReplySchema = z3.object({
|
|
304
|
+
author: z3.string(),
|
|
305
|
+
authorKind: z3.enum(["member", "guest"]),
|
|
306
|
+
body: z3.string(),
|
|
307
|
+
createdAt: z3.string(),
|
|
308
|
+
// Soft-delete tombstone (§6.7.1): body is "" when true.
|
|
309
|
+
deleted: z3.boolean(),
|
|
310
|
+
editedAt: z3.string().nullable(),
|
|
311
|
+
// The message id — what `edit`/`delete` target (§5.1).
|
|
312
|
+
id: z3.string(),
|
|
313
|
+
reactions: z3.array(exportReactionSchema).optional()
|
|
314
|
+
});
|
|
315
|
+
var commentExportThreadSchema = z3.object({
|
|
170
316
|
anchor: anchorSchema,
|
|
171
|
-
author:
|
|
172
|
-
authorKind:
|
|
173
|
-
body:
|
|
174
|
-
createdAt:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
317
|
+
author: z3.string(),
|
|
318
|
+
authorKind: z3.enum(["member", "guest"]),
|
|
319
|
+
body: z3.string(),
|
|
320
|
+
createdAt: z3.string(),
|
|
321
|
+
deleted: z3.boolean(),
|
|
322
|
+
editedAt: z3.string().nullable(),
|
|
323
|
+
id: z3.string(),
|
|
324
|
+
// The opening message's id (distinct from the thread `id`) — `edit`/
|
|
325
|
+
// `delete` operate on messages, so the CLI needs it to target the first
|
|
326
|
+
// comment, not just the replies.
|
|
327
|
+
messageId: z3.string(),
|
|
328
|
+
reactions: z3.array(exportReactionSchema).optional(),
|
|
329
|
+
replies: z3.array(commentExportReplySchema),
|
|
330
|
+
status: z3.enum(["open", "resolved"]),
|
|
331
|
+
versionNumber: z3.number().int().positive()
|
|
332
|
+
});
|
|
333
|
+
var commentExportSchema = z3.object({
|
|
334
|
+
artifactId: z3.string(),
|
|
335
|
+
comments: z3.array(commentExportThreadSchema),
|
|
183
336
|
// The artifact's current version (null before the first finalize).
|
|
184
|
-
versionNumber:
|
|
337
|
+
versionNumber: z3.number().int().positive().nullable()
|
|
338
|
+
});
|
|
339
|
+
var commentReadResponseSchema = z3.object({
|
|
340
|
+
lastSeenAt: z3.string()
|
|
185
341
|
});
|
|
186
342
|
function elementLabel(el) {
|
|
187
343
|
if (el.id) {
|
|
@@ -192,147 +348,163 @@ function elementLabel(el) {
|
|
|
192
348
|
}
|
|
193
349
|
|
|
194
350
|
// ../shared/src/errors.ts
|
|
195
|
-
import { z as
|
|
196
|
-
var apiErrorSchema =
|
|
197
|
-
code:
|
|
198
|
-
docsUrl:
|
|
199
|
-
message:
|
|
200
|
-
retryAfter:
|
|
351
|
+
import { z as z4 } from "zod";
|
|
352
|
+
var apiErrorSchema = z4.object({
|
|
353
|
+
code: z4.string(),
|
|
354
|
+
docsUrl: z4.string().url(),
|
|
355
|
+
message: z4.string(),
|
|
356
|
+
retryAfter: z4.number().int().positive().optional()
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// ../shared/src/feedback.ts
|
|
360
|
+
import { z as z5 } from "zod";
|
|
361
|
+
var feedbackRequestSchema = z5.object({
|
|
362
|
+
email: z5.string().trim().toLowerCase().email().optional().or(z5.literal("")),
|
|
363
|
+
message: z5.string().trim().min(1).max(4e3)
|
|
364
|
+
});
|
|
365
|
+
var feedbackResponseSchema = z5.object({
|
|
366
|
+
sent: z5.literal(true)
|
|
201
367
|
});
|
|
202
368
|
|
|
203
369
|
// ../shared/src/me.ts
|
|
204
|
-
import { z as
|
|
205
|
-
var ownArtifactSchema =
|
|
206
|
-
createdAt:
|
|
207
|
-
currentVersionNumber:
|
|
370
|
+
import { z as z6 } from "zod";
|
|
371
|
+
var ownArtifactSchema = z6.object({
|
|
372
|
+
createdAt: z6.string(),
|
|
373
|
+
currentVersionNumber: z6.number().nullable(),
|
|
208
374
|
// Owned artifacts normally never expire; non-null only while an
|
|
209
375
|
// expiry is still set (e.g. an unclaimed publish surfaced here later).
|
|
210
|
-
expiresAt:
|
|
376
|
+
expiresAt: z6.string().nullable(),
|
|
211
377
|
// SPEC-2 §8.4 — the containing folder for workspace-owned artifacts;
|
|
212
378
|
// both null for personal artifacts. `folderId` drives the dashboard's
|
|
213
379
|
// move control and folder filter; `folderName` is the display label.
|
|
214
|
-
folderId:
|
|
215
|
-
folderName:
|
|
380
|
+
folderId: z6.string().nullable(),
|
|
381
|
+
folderName: z6.string().nullable(),
|
|
216
382
|
// Whether a publish token is still active — revocable from the
|
|
217
383
|
// 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
|
-
|
|
384
|
+
hasPublishToken: z6.boolean(),
|
|
385
|
+
id: z6.string(),
|
|
386
|
+
openThreads: z6.number(),
|
|
387
|
+
protected: z6.boolean(),
|
|
388
|
+
slug: z6.string(),
|
|
389
|
+
title: z6.string(),
|
|
390
|
+
totalViews: z6.number(),
|
|
391
|
+
// SPEC §6.20 — messages by others newer than the viewer's last-seen
|
|
392
|
+
// watermark. 0 for signed-out/API-key-less contexts.
|
|
393
|
+
unreadComments: z6.number(),
|
|
394
|
+
updatedAt: z6.string(),
|
|
395
|
+
url: z6.string(),
|
|
396
|
+
versionCount: z6.number()
|
|
397
|
+
});
|
|
398
|
+
var ownArtifactsResponseSchema = z6.object({
|
|
399
|
+
artifacts: z6.array(ownArtifactSchema)
|
|
400
|
+
});
|
|
401
|
+
var revokePublishTokenResponseSchema = z6.object({
|
|
402
|
+
revoked: z6.boolean()
|
|
234
403
|
});
|
|
235
404
|
|
|
236
405
|
// ../shared/src/owner.ts
|
|
237
|
-
import { z as
|
|
406
|
+
import { z as z8 } from "zod";
|
|
238
407
|
|
|
239
408
|
// ../shared/src/workspaces.ts
|
|
240
|
-
import { z as
|
|
409
|
+
import { z as z7 } from "zod";
|
|
241
410
|
var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/;
|
|
242
|
-
var workspaceRoleSchema =
|
|
243
|
-
var workspaceInviteRoleSchema =
|
|
244
|
-
var workspaceHandleSchema =
|
|
411
|
+
var workspaceRoleSchema = z7.enum(["owner", "admin", "member"]);
|
|
412
|
+
var workspaceInviteRoleSchema = z7.enum(["admin", "member"]);
|
|
413
|
+
var workspaceHandleSchema = z7.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
|
|
245
414
|
message: "handle must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
246
415
|
});
|
|
247
|
-
var createWorkspaceSchema =
|
|
416
|
+
var createWorkspaceSchema = z7.object({
|
|
248
417
|
handle: workspaceHandleSchema.optional(),
|
|
249
|
-
name:
|
|
418
|
+
name: z7.string().trim().min(1).max(100)
|
|
250
419
|
});
|
|
251
|
-
var updateWorkspaceSchema =
|
|
420
|
+
var updateWorkspaceSchema = z7.object({
|
|
252
421
|
handle: workspaceHandleSchema.optional(),
|
|
253
|
-
name:
|
|
422
|
+
name: z7.string().trim().min(1).max(100).optional()
|
|
254
423
|
}).refine((body) => body.handle !== void 0 || body.name !== void 0, {
|
|
255
424
|
message: "provide at least one of name or handle"
|
|
256
425
|
});
|
|
257
|
-
var workspaceSchema =
|
|
258
|
-
createdAt:
|
|
259
|
-
handle:
|
|
260
|
-
id:
|
|
261
|
-
|
|
426
|
+
var workspaceSchema = z7.object({
|
|
427
|
+
createdAt: z7.string(),
|
|
428
|
+
handle: z7.string(),
|
|
429
|
+
id: z7.string(),
|
|
430
|
+
// Auto-provisioned solo workspace, one per user: non-deletable, takes
|
|
431
|
+
// no members, renameable like any workspace.
|
|
432
|
+
isPersonal: z7.boolean(),
|
|
433
|
+
name: z7.string(),
|
|
262
434
|
role: workspaceRoleSchema.nullable(),
|
|
263
|
-
updatedAt:
|
|
435
|
+
updatedAt: z7.string()
|
|
264
436
|
});
|
|
265
|
-
var workspaceListResponseSchema =
|
|
266
|
-
workspaces:
|
|
437
|
+
var workspaceListResponseSchema = z7.object({
|
|
438
|
+
workspaces: z7.array(workspaceSchema)
|
|
267
439
|
});
|
|
268
|
-
var deleteWorkspaceResponseSchema =
|
|
269
|
-
deleted:
|
|
440
|
+
var deleteWorkspaceResponseSchema = z7.object({
|
|
441
|
+
deleted: z7.literal(true)
|
|
270
442
|
});
|
|
271
|
-
var workspaceMemberSchema =
|
|
272
|
-
createdAt:
|
|
273
|
-
email:
|
|
274
|
-
image:
|
|
275
|
-
name:
|
|
443
|
+
var workspaceMemberSchema = z7.object({
|
|
444
|
+
createdAt: z7.string(),
|
|
445
|
+
email: z7.string().email(),
|
|
446
|
+
image: z7.string().nullable(),
|
|
447
|
+
name: z7.string(),
|
|
276
448
|
role: workspaceRoleSchema,
|
|
277
|
-
userId:
|
|
449
|
+
userId: z7.string()
|
|
278
450
|
});
|
|
279
|
-
var workspaceMemberListResponseSchema =
|
|
280
|
-
members:
|
|
451
|
+
var workspaceMemberListResponseSchema = z7.object({
|
|
452
|
+
members: z7.array(workspaceMemberSchema)
|
|
281
453
|
});
|
|
282
|
-
var updateWorkspaceMemberSchema =
|
|
454
|
+
var updateWorkspaceMemberSchema = z7.object({
|
|
283
455
|
role: workspaceRoleSchema
|
|
284
456
|
});
|
|
285
|
-
var removeWorkspaceMemberResponseSchema =
|
|
286
|
-
removed:
|
|
457
|
+
var removeWorkspaceMemberResponseSchema = z7.object({
|
|
458
|
+
removed: z7.literal(true)
|
|
287
459
|
});
|
|
288
|
-
var createWorkspaceInviteSchema =
|
|
289
|
-
email:
|
|
290
|
-
expiresAt:
|
|
291
|
-
maxUses:
|
|
460
|
+
var createWorkspaceInviteSchema = z7.object({
|
|
461
|
+
email: z7.string().trim().toLowerCase().email().optional(),
|
|
462
|
+
expiresAt: z7.string().datetime(),
|
|
463
|
+
maxUses: z7.number().int().positive().optional(),
|
|
292
464
|
role: workspaceInviteRoleSchema
|
|
293
465
|
});
|
|
294
|
-
var workspaceInviteSchema =
|
|
295
|
-
createdAt:
|
|
296
|
-
email:
|
|
297
|
-
expiresAt:
|
|
298
|
-
id:
|
|
299
|
-
maxUses:
|
|
466
|
+
var workspaceInviteSchema = z7.object({
|
|
467
|
+
createdAt: z7.string(),
|
|
468
|
+
email: z7.string().email().nullable(),
|
|
469
|
+
expiresAt: z7.string(),
|
|
470
|
+
id: z7.string(),
|
|
471
|
+
maxUses: z7.number().int().positive().nullable(),
|
|
300
472
|
role: workspaceInviteRoleSchema,
|
|
301
|
-
tokenPrefix:
|
|
302
|
-
useCount:
|
|
473
|
+
tokenPrefix: z7.string(),
|
|
474
|
+
useCount: z7.number().int().nonnegative()
|
|
303
475
|
});
|
|
304
476
|
var createdWorkspaceInviteSchema = workspaceInviteSchema.extend({
|
|
305
|
-
url:
|
|
477
|
+
url: z7.string().url()
|
|
306
478
|
});
|
|
307
|
-
var workspaceInviteListResponseSchema =
|
|
308
|
-
invites:
|
|
479
|
+
var workspaceInviteListResponseSchema = z7.object({
|
|
480
|
+
invites: z7.array(workspaceInviteSchema)
|
|
309
481
|
});
|
|
310
|
-
var revokeWorkspaceInviteResponseSchema =
|
|
311
|
-
revoked:
|
|
482
|
+
var revokeWorkspaceInviteResponseSchema = z7.object({
|
|
483
|
+
revoked: z7.literal(true)
|
|
312
484
|
});
|
|
313
|
-
var acceptWorkspaceInviteSchema =
|
|
314
|
-
token:
|
|
485
|
+
var acceptWorkspaceInviteSchema = z7.object({
|
|
486
|
+
token: z7.string().min(1)
|
|
315
487
|
});
|
|
316
|
-
var acceptWorkspaceInviteResponseSchema =
|
|
488
|
+
var acceptWorkspaceInviteResponseSchema = z7.object({
|
|
317
489
|
role: workspaceRoleSchema,
|
|
318
490
|
workspace: workspaceSchema
|
|
319
491
|
});
|
|
320
|
-
var folderVisibilitySchema =
|
|
321
|
-
var artifactVisibilitySchema =
|
|
492
|
+
var folderVisibilitySchema = z7.enum(["public", "workspace"]);
|
|
493
|
+
var artifactVisibilitySchema = z7.enum([
|
|
322
494
|
"public",
|
|
323
495
|
"unlisted",
|
|
324
496
|
"workspace"
|
|
325
497
|
]);
|
|
326
|
-
var createFolderSchema =
|
|
327
|
-
name:
|
|
498
|
+
var createFolderSchema = z7.object({
|
|
499
|
+
name: z7.string().trim().min(1).max(100),
|
|
328
500
|
visibility: folderVisibilitySchema
|
|
329
501
|
});
|
|
330
|
-
var updateFolderSchema =
|
|
502
|
+
var updateFolderSchema = z7.object({
|
|
331
503
|
// With `visibility`: bulk-update the folder's artifacts to match
|
|
332
504
|
// (SPEC-2 §3.4 "Changing a folder's visibility offers a bulk update").
|
|
333
|
-
cascade:
|
|
334
|
-
name:
|
|
335
|
-
sortOrder:
|
|
505
|
+
cascade: z7.boolean().optional(),
|
|
506
|
+
name: z7.string().trim().min(1).max(100).optional(),
|
|
507
|
+
sortOrder: z7.number().int().min(0).optional(),
|
|
336
508
|
visibility: folderVisibilitySchema.optional()
|
|
337
509
|
}).refine(
|
|
338
510
|
(body) => body.name !== void 0 || body.visibility !== void 0 || body.sortOrder !== void 0,
|
|
@@ -340,248 +512,168 @@ var updateFolderSchema = z5.object({
|
|
|
340
512
|
).refine((body) => !body.cascade || body.visibility !== void 0, {
|
|
341
513
|
message: "cascade requires visibility"
|
|
342
514
|
});
|
|
343
|
-
var folderSchema =
|
|
344
|
-
createdAt:
|
|
345
|
-
id:
|
|
346
|
-
name:
|
|
515
|
+
var folderSchema = z7.object({
|
|
516
|
+
createdAt: z7.string(),
|
|
517
|
+
id: z7.string(),
|
|
518
|
+
name: z7.string(),
|
|
347
519
|
// Permalink segment for the public folder page (/w/{handle}/f/{slug}).
|
|
348
520
|
// Server-derived from the name; renames regenerate it.
|
|
349
|
-
slug:
|
|
350
|
-
sortOrder:
|
|
351
|
-
updatedAt:
|
|
521
|
+
slug: z7.string(),
|
|
522
|
+
sortOrder: z7.number().int(),
|
|
523
|
+
updatedAt: z7.string(),
|
|
352
524
|
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:
|
|
525
|
+
workspaceId: z7.string()
|
|
526
|
+
});
|
|
527
|
+
var folderListResponseSchema = z7.object({
|
|
528
|
+
folders: z7.array(folderSchema)
|
|
529
|
+
});
|
|
530
|
+
var deleteFolderResponseSchema = z7.object({
|
|
531
|
+
deleted: z7.literal(true)
|
|
532
|
+
});
|
|
533
|
+
var workspaceArtifactSchema = z7.object({
|
|
534
|
+
createdAt: z7.string(),
|
|
535
|
+
currentVersionNumber: z7.number().int().nullable(),
|
|
536
|
+
folderId: z7.string().nullable(),
|
|
537
|
+
id: z7.string(),
|
|
538
|
+
openCommentCount: z7.number().int().nonnegative(),
|
|
539
|
+
slug: z7.string(),
|
|
540
|
+
title: z7.string(),
|
|
541
|
+
updatedAt: z7.string(),
|
|
542
|
+
url: z7.string().url(),
|
|
543
|
+
versionCount: z7.number().int().nonnegative(),
|
|
372
544
|
visibility: artifactVisibilitySchema
|
|
373
545
|
});
|
|
374
|
-
var workspaceArtifactListResponseSchema =
|
|
375
|
-
artifacts:
|
|
546
|
+
var workspaceArtifactListResponseSchema = z7.object({
|
|
547
|
+
artifacts: z7.array(workspaceArtifactSchema)
|
|
376
548
|
});
|
|
377
549
|
|
|
378
550
|
// ../shared/src/owner.ts
|
|
379
551
|
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:
|
|
552
|
+
var claimResponseSchema = z8.object({
|
|
553
|
+
artifactId: z8.string(),
|
|
554
|
+
slug: z8.string(),
|
|
555
|
+
title: z8.string(),
|
|
556
|
+
url: z8.string().url()
|
|
385
557
|
});
|
|
386
|
-
var updateArtifactSchema =
|
|
558
|
+
var updateArtifactSchema = z8.object({
|
|
387
559
|
// SPEC-2 §6.4 — placement + visibility. `folderId: null` clears the
|
|
388
560
|
// folder; `workspaceId: null` moves the artifact out of its workspace
|
|
389
561
|
// (§5.5 ownership transfer).
|
|
390
|
-
folderId:
|
|
391
|
-
slug:
|
|
562
|
+
folderId: z8.string().min(1).nullable().optional(),
|
|
563
|
+
slug: z8.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
392
564
|
message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
393
565
|
}).optional(),
|
|
394
|
-
title:
|
|
566
|
+
title: z8.string().min(1).max(200).optional(),
|
|
395
567
|
visibility: artifactVisibilitySchema.optional(),
|
|
396
|
-
workspaceId:
|
|
568
|
+
workspaceId: z8.string().min(1).nullable().optional()
|
|
397
569
|
}).refine(
|
|
398
570
|
(body) => body.title !== void 0 || body.slug !== void 0 || body.visibility !== void 0 || body.folderId !== void 0 || body.workspaceId !== void 0,
|
|
399
571
|
{
|
|
400
572
|
message: "provide at least one of title, slug, visibility, folderId, workspaceId"
|
|
401
573
|
}
|
|
402
574
|
);
|
|
403
|
-
var deleteArtifactResponseSchema =
|
|
404
|
-
deleted:
|
|
575
|
+
var deleteArtifactResponseSchema = z8.object({
|
|
576
|
+
deleted: z8.literal(true)
|
|
405
577
|
});
|
|
406
578
|
|
|
407
579
|
// ../shared/src/presence.ts
|
|
408
|
-
import { z as
|
|
580
|
+
import { z as z9 } from "zod";
|
|
409
581
|
var AGENT_PRESENCE = {
|
|
410
582
|
heartbeatSeconds: 15,
|
|
411
583
|
shellPollSeconds: 15,
|
|
412
584
|
ttlSeconds: 45
|
|
413
585
|
};
|
|
414
|
-
var agentHeartbeatResponseSchema =
|
|
415
|
-
listening:
|
|
416
|
-
ttlSeconds:
|
|
586
|
+
var agentHeartbeatResponseSchema = z9.object({
|
|
587
|
+
listening: z9.literal(true),
|
|
588
|
+
ttlSeconds: z9.number().int().positive()
|
|
417
589
|
});
|
|
418
|
-
var agentPresenceSchema =
|
|
419
|
-
listening:
|
|
420
|
-
versionNumber:
|
|
590
|
+
var agentPresenceSchema = z9.object({
|
|
591
|
+
listening: z9.boolean(),
|
|
592
|
+
versionNumber: z9.number().int().positive().nullable()
|
|
421
593
|
});
|
|
422
594
|
|
|
423
595
|
// ../shared/src/protection.ts
|
|
424
|
-
import { z as
|
|
596
|
+
import { z as z10 } from "zod";
|
|
425
597
|
var PASSWORD_MIN = 6;
|
|
426
598
|
var PASSWORD_MAX = 128;
|
|
427
|
-
var gatePasswordSchema =
|
|
428
|
-
var setProtectionSchema =
|
|
599
|
+
var gatePasswordSchema = z10.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
|
|
600
|
+
var setProtectionSchema = z10.object({
|
|
429
601
|
password: gatePasswordSchema.nullable()
|
|
430
602
|
});
|
|
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)
|
|
603
|
+
var protectionResponseSchema = z10.object({
|
|
604
|
+
protected: z10.boolean()
|
|
439
605
|
});
|
|
440
|
-
|
|
441
|
-
|
|
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")
|
|
606
|
+
var unlockSchema = z10.object({
|
|
607
|
+
password: z10.string().min(1).max(1024)
|
|
467
608
|
});
|
|
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()
|
|
478
|
-
});
|
|
479
|
-
var publishSessionResponseSchema = z9.object({
|
|
480
|
-
artifactId: z9.string(),
|
|
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()
|
|
609
|
+
var unlockResponseSchema = z10.object({
|
|
610
|
+
unlocked: z10.literal(true)
|
|
519
611
|
});
|
|
520
612
|
|
|
521
613
|
// ../shared/src/report.ts
|
|
522
|
-
import { z as
|
|
523
|
-
var reportRequestSchema =
|
|
524
|
-
reason:
|
|
614
|
+
import { z as z11 } from "zod";
|
|
615
|
+
var reportRequestSchema = z11.object({
|
|
616
|
+
reason: z11.string().max(1e3).optional()
|
|
525
617
|
});
|
|
526
|
-
var reportResponseSchema =
|
|
527
|
-
reported:
|
|
618
|
+
var reportResponseSchema = z11.object({
|
|
619
|
+
reported: z11.literal(true)
|
|
528
620
|
});
|
|
529
621
|
|
|
530
622
|
// ../shared/src/stats.ts
|
|
531
|
-
import { z as
|
|
532
|
-
var avgViewSecondsSchema =
|
|
533
|
-
var versionViewStatsSchema =
|
|
623
|
+
import { z as z12 } from "zod";
|
|
624
|
+
var avgViewSecondsSchema = z12.number().int().nonnegative().nullable();
|
|
625
|
+
var versionViewStatsSchema = z12.object({
|
|
534
626
|
avgViewSeconds: avgViewSecondsSchema,
|
|
535
627
|
// Views from inside the review shell's iframe vs. direct visits to
|
|
536
628
|
// the render URL. views = embeddedViews + directViews.
|
|
537
|
-
directViews:
|
|
538
|
-
embeddedViews:
|
|
629
|
+
directViews: z12.number().int().nonnegative(),
|
|
630
|
+
embeddedViews: z12.number().int().nonnegative(),
|
|
539
631
|
// Distinct hashed viewer IPs across the version's lifetime.
|
|
540
|
-
uniqueViewers:
|
|
541
|
-
versionNumber:
|
|
542
|
-
views:
|
|
632
|
+
uniqueViewers: z12.number().int().nonnegative(),
|
|
633
|
+
versionNumber: z12.number().int().positive(),
|
|
634
|
+
views: z12.number().int().nonnegative()
|
|
543
635
|
});
|
|
544
|
-
var dailyViewsSchema =
|
|
636
|
+
var dailyViewsSchema = z12.object({
|
|
545
637
|
avgViewSeconds: avgViewSecondsSchema,
|
|
546
638
|
// UTC day, YYYY-MM-DD.
|
|
547
|
-
day:
|
|
548
|
-
uniqueViewers:
|
|
549
|
-
views:
|
|
639
|
+
day: z12.string(),
|
|
640
|
+
uniqueViewers: z12.number().int().nonnegative(),
|
|
641
|
+
views: z12.number().int().nonnegative()
|
|
550
642
|
});
|
|
551
|
-
var artifactStatsResponseSchema =
|
|
643
|
+
var artifactStatsResponseSchema = z12.object({
|
|
552
644
|
avgViewSeconds: avgViewSecondsSchema,
|
|
553
|
-
daily:
|
|
645
|
+
daily: z12.array(dailyViewsSchema),
|
|
554
646
|
// Distinct hashed viewer IPs across all versions and days.
|
|
555
|
-
totalUniqueViewers:
|
|
556
|
-
totalViews:
|
|
557
|
-
versions:
|
|
647
|
+
totalUniqueViewers: z12.number().int().nonnegative(),
|
|
648
|
+
totalViews: z12.number().int().nonnegative(),
|
|
649
|
+
versions: z12.array(versionViewStatsSchema)
|
|
558
650
|
});
|
|
559
651
|
|
|
560
652
|
// ../shared/src/versions.ts
|
|
561
|
-
import { z as
|
|
562
|
-
var versionSourceSchema =
|
|
563
|
-
agentName:
|
|
564
|
-
type:
|
|
653
|
+
import { z as z13 } from "zod";
|
|
654
|
+
var versionSourceSchema = z13.object({
|
|
655
|
+
agentName: z13.string().nullable(),
|
|
656
|
+
type: z13.string()
|
|
565
657
|
});
|
|
566
|
-
var versionChangesSchema =
|
|
567
|
-
added:
|
|
568
|
-
changed:
|
|
569
|
-
removed:
|
|
658
|
+
var versionChangesSchema = z13.object({
|
|
659
|
+
added: z13.array(z13.string()),
|
|
660
|
+
changed: z13.array(z13.string()),
|
|
661
|
+
removed: z13.array(z13.string())
|
|
570
662
|
});
|
|
571
|
-
var versionSummarySchema =
|
|
663
|
+
var versionSummarySchema = z13.object({
|
|
572
664
|
// Absent for the first version — there is no previous version to diff against.
|
|
573
665
|
changes: versionChangesSchema.nullable(),
|
|
574
|
-
createdAt:
|
|
575
|
-
entryUrl:
|
|
576
|
-
fileCount:
|
|
577
|
-
id:
|
|
578
|
-
isCurrent:
|
|
666
|
+
createdAt: z13.string(),
|
|
667
|
+
entryUrl: z13.string().url(),
|
|
668
|
+
fileCount: z13.number().int().nonnegative(),
|
|
669
|
+
id: z13.string(),
|
|
670
|
+
isCurrent: z13.boolean(),
|
|
579
671
|
source: versionSourceSchema,
|
|
580
|
-
totalSizeBytes:
|
|
581
|
-
versionNumber:
|
|
672
|
+
totalSizeBytes: z13.number().int().nonnegative(),
|
|
673
|
+
versionNumber: z13.number().int().positive()
|
|
582
674
|
});
|
|
583
|
-
var versionListResponseSchema =
|
|
584
|
-
versions:
|
|
675
|
+
var versionListResponseSchema = z13.object({
|
|
676
|
+
versions: z13.array(versionSummarySchema)
|
|
585
677
|
});
|
|
586
678
|
|
|
587
679
|
// src/api.ts
|
|
@@ -651,6 +743,36 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
651
743
|
(d) => publishSessionResponseSchema.parse(d)
|
|
652
744
|
);
|
|
653
745
|
},
|
|
746
|
+
// SPEC §6.4 — open a new comment thread as the account this client's
|
|
747
|
+
// key belongs to (agent-originated review, §5.1 `comment`). The create
|
|
748
|
+
// route rejects a bare publish token (agents respond, accounts
|
|
749
|
+
// originate), so this must carry an account credential.
|
|
750
|
+
async createThread(artifactId, body) {
|
|
751
|
+
return request(
|
|
752
|
+
`${base}/api/v1/artifacts/${artifactId}/comments`,
|
|
753
|
+
{ body: JSON.stringify(body), headers, method: "POST" },
|
|
754
|
+
(d) => commentThreadSchema.parse(d)
|
|
755
|
+
);
|
|
756
|
+
},
|
|
757
|
+
// SPEC §6.7.1 — soft-delete a message you authored. Authorized by this
|
|
758
|
+
// client's credential (account key, or the publish token for an agent
|
|
759
|
+
// message); a guest deletes from the browser, not the CLI.
|
|
760
|
+
async deleteMessage(messageId) {
|
|
761
|
+
await request(
|
|
762
|
+
`${base}/api/v1/comment-messages/${messageId}`,
|
|
763
|
+
{ body: "{}", headers, method: "DELETE" },
|
|
764
|
+
() => void 0
|
|
765
|
+
);
|
|
766
|
+
},
|
|
767
|
+
// SPEC §6.7.1 — edit a message you authored (new body + "edited" badge).
|
|
768
|
+
// Same credential rule as delete; returns the updated thread.
|
|
769
|
+
async editMessage(messageId, body) {
|
|
770
|
+
return request(
|
|
771
|
+
`${base}/api/v1/comment-messages/${messageId}`,
|
|
772
|
+
{ body: JSON.stringify({ body }), headers, method: "PATCH" },
|
|
773
|
+
(d) => commentThreadSchema.parse(d)
|
|
774
|
+
);
|
|
775
|
+
},
|
|
654
776
|
async exportComments(artifactId, query) {
|
|
655
777
|
const params = new URLSearchParams({
|
|
656
778
|
status: query.status,
|
|
@@ -772,17 +894,17 @@ import {
|
|
|
772
894
|
writeFileSync as writeFileSync2
|
|
773
895
|
} from "fs";
|
|
774
896
|
import path2 from "path";
|
|
775
|
-
import { z as
|
|
897
|
+
import { z as z15 } from "zod";
|
|
776
898
|
|
|
777
899
|
// src/credentials.ts
|
|
778
900
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
779
901
|
import { homedir } from "os";
|
|
780
902
|
import path from "path";
|
|
781
|
-
import { z as
|
|
903
|
+
import { z as z14 } from "zod";
|
|
782
904
|
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
783
|
-
var credentialsSchema =
|
|
784
|
-
keys:
|
|
785
|
-
version:
|
|
905
|
+
var credentialsSchema = z14.object({
|
|
906
|
+
keys: z14.record(z14.string(), z14.string()),
|
|
907
|
+
version: z14.literal(1)
|
|
786
908
|
});
|
|
787
909
|
function credentialsPath() {
|
|
788
910
|
return path.join(homedir(), ".artifactcafe", "credentials");
|
|
@@ -832,22 +954,28 @@ function resolveApiKey(apiUrl2, flag) {
|
|
|
832
954
|
}
|
|
833
955
|
|
|
834
956
|
// src/config.ts
|
|
835
|
-
var configSchema =
|
|
836
|
-
apiUrl:
|
|
837
|
-
artifactId:
|
|
838
|
-
folder:
|
|
839
|
-
folderId:
|
|
957
|
+
var configSchema = z15.object({
|
|
958
|
+
apiUrl: z15.string(),
|
|
959
|
+
artifactId: z15.string(),
|
|
960
|
+
folder: z15.string().optional(),
|
|
961
|
+
folderId: z15.string().optional(),
|
|
840
962
|
// Optional: a folder linked by `link` to an artifact you own carries no
|
|
841
963
|
// token — owner actions authorize via your stored login instead. A first
|
|
842
964
|
// publish still writes the anonymous-mode token here.
|
|
843
|
-
publishToken:
|
|
844
|
-
slug:
|
|
845
|
-
url:
|
|
846
|
-
workspace:
|
|
847
|
-
workspaceId:
|
|
848
|
-
|
|
965
|
+
publishToken: z15.string().optional(),
|
|
966
|
+
slug: z15.string(),
|
|
967
|
+
url: z15.string(),
|
|
968
|
+
workspace: z15.string().optional(),
|
|
969
|
+
workspaceId: z15.string().optional(),
|
|
970
|
+
workspaceKind: z15.enum(["personal", "workspace"]).optional()
|
|
971
|
+
});
|
|
972
|
+
var projectConfigSchema = z15.object({
|
|
973
|
+
folder: z15.string().trim().min(1).optional(),
|
|
974
|
+
workspace: z15.string().trim().min(1)
|
|
975
|
+
}).strict();
|
|
849
976
|
var CONFIG_DIR = ".artifactcafe";
|
|
850
977
|
var CONFIG_FILE = "config.json";
|
|
978
|
+
var PROJECT_CONFIG_FILE = "artifact-cafe.json";
|
|
851
979
|
function readConfig(dir) {
|
|
852
980
|
const file = path2.join(dir, CONFIG_DIR, CONFIG_FILE);
|
|
853
981
|
if (!existsSync2(file)) {
|
|
@@ -869,6 +997,48 @@ function writeConfig(dir, config) {
|
|
|
869
997
|
);
|
|
870
998
|
ensureGitignored(dir);
|
|
871
999
|
}
|
|
1000
|
+
function readProjectConfig(dir) {
|
|
1001
|
+
let current = path2.resolve(dir);
|
|
1002
|
+
while (true) {
|
|
1003
|
+
const file = path2.join(current, PROJECT_CONFIG_FILE);
|
|
1004
|
+
if (existsSync2(file)) {
|
|
1005
|
+
try {
|
|
1006
|
+
return projectConfigSchema.parse(
|
|
1007
|
+
JSON.parse(readFileSync2(file, "utf8"))
|
|
1008
|
+
);
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
const message = error instanceof z15.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
|
|
1011
|
+
throw new Error(`Invalid ${file}: ${message}.`);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (existsSync2(path2.join(current, ".git"))) {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
const parent = path2.dirname(current);
|
|
1018
|
+
if (parent === current) {
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
current = parent;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function writeProjectConfig(dir, config) {
|
|
1025
|
+
let root = path2.resolve(dir);
|
|
1026
|
+
while (!existsSync2(path2.join(root, ".git"))) {
|
|
1027
|
+
const parent = path2.dirname(root);
|
|
1028
|
+
if (parent === root) {
|
|
1029
|
+
root = path2.resolve(dir);
|
|
1030
|
+
break;
|
|
1031
|
+
}
|
|
1032
|
+
root = parent;
|
|
1033
|
+
}
|
|
1034
|
+
const file = path2.join(root, PROJECT_CONFIG_FILE);
|
|
1035
|
+
writeFileSync2(
|
|
1036
|
+
file,
|
|
1037
|
+
`${JSON.stringify(projectConfigSchema.parse(config), null, 2)}
|
|
1038
|
+
`
|
|
1039
|
+
);
|
|
1040
|
+
return file;
|
|
1041
|
+
}
|
|
872
1042
|
function ensureGitignored(dir) {
|
|
873
1043
|
const gitignore = path2.join(dir, ".gitignore");
|
|
874
1044
|
if (!existsSync2(gitignore)) {
|
|
@@ -907,7 +1077,107 @@ function resolveArtifactTarget(dir, opts = {}) {
|
|
|
907
1077
|
return { apiUrl: apiUrl2, artifactId, credential, url };
|
|
908
1078
|
}
|
|
909
1079
|
|
|
1080
|
+
// src/comment.ts
|
|
1081
|
+
async function readStdin() {
|
|
1082
|
+
if (process.stdin.isTTY) {
|
|
1083
|
+
return "";
|
|
1084
|
+
}
|
|
1085
|
+
const chunks = [];
|
|
1086
|
+
for await (const chunk of process.stdin) {
|
|
1087
|
+
chunks.push(chunk);
|
|
1088
|
+
}
|
|
1089
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
1090
|
+
}
|
|
1091
|
+
function buildAnchor(opts) {
|
|
1092
|
+
if (opts.quote !== void 0) {
|
|
1093
|
+
const quote = opts.quote.trim();
|
|
1094
|
+
if (!quote) {
|
|
1095
|
+
throw new Error(
|
|
1096
|
+
"--quote can't be empty. Omit it entirely for a page-level comment."
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
const anchor2 = {
|
|
1100
|
+
elementPath: "body",
|
|
1101
|
+
endOffset: 0,
|
|
1102
|
+
prefix: opts.prefix ?? "",
|
|
1103
|
+
quote,
|
|
1104
|
+
startOffset: 0,
|
|
1105
|
+
suffix: opts.suffix ?? "",
|
|
1106
|
+
type: "text"
|
|
1107
|
+
};
|
|
1108
|
+
if (opts.page) {
|
|
1109
|
+
anchor2.page = opts.page;
|
|
1110
|
+
}
|
|
1111
|
+
return anchor2;
|
|
1112
|
+
}
|
|
1113
|
+
if (opts.prefix || opts.suffix) {
|
|
1114
|
+
throw new Error("--prefix and --suffix only apply together with --quote.");
|
|
1115
|
+
}
|
|
1116
|
+
const anchor = { type: "page" };
|
|
1117
|
+
if (opts.page) {
|
|
1118
|
+
anchor.page = opts.page;
|
|
1119
|
+
}
|
|
1120
|
+
return anchor;
|
|
1121
|
+
}
|
|
1122
|
+
async function comment(opts) {
|
|
1123
|
+
const body = (opts.body ?? await readStdin()).trim();
|
|
1124
|
+
if (!body) {
|
|
1125
|
+
throw new Error(
|
|
1126
|
+
'Nothing to post. Pass the comment with --body "\u2026", or pipe it on stdin.'
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
const anchor = buildAnchor(opts);
|
|
1130
|
+
const dir = path3.resolve(opts.dir);
|
|
1131
|
+
const target = resolveArtifactTarget(dir, {
|
|
1132
|
+
apiKey: opts.apiKey,
|
|
1133
|
+
artifact: opts.artifact
|
|
1134
|
+
});
|
|
1135
|
+
const credential = resolveApiKey(target.apiUrl, opts.apiKey) ?? target.credential;
|
|
1136
|
+
const client = createApiClient(target.apiUrl, credential);
|
|
1137
|
+
const artifact = await client.getArtifact(target.artifactId);
|
|
1138
|
+
if (!artifact.currentVersion) {
|
|
1139
|
+
throw new Error(
|
|
1140
|
+
"This artifact has no published version yet \u2014 publish before commenting."
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
let thread;
|
|
1144
|
+
try {
|
|
1145
|
+
thread = await client.createThread(target.artifactId, {
|
|
1146
|
+
anchor,
|
|
1147
|
+
body,
|
|
1148
|
+
versionId: artifact.currentVersion.id,
|
|
1149
|
+
via: opts.via
|
|
1150
|
+
});
|
|
1151
|
+
} catch (err) {
|
|
1152
|
+
if (err instanceof ApiRequestError && err.status === 401) {
|
|
1153
|
+
throw new Error(
|
|
1154
|
+
"Publish tokens can't open threads. Run `artifact-cafe login` to comment as your account."
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
throw err;
|
|
1158
|
+
}
|
|
1159
|
+
if (opts.json) {
|
|
1160
|
+
console.log(
|
|
1161
|
+
JSON.stringify(
|
|
1162
|
+
{
|
|
1163
|
+
anchorType: thread.anchor.type,
|
|
1164
|
+
id: thread.id,
|
|
1165
|
+
messageId: thread.messages[0]?.id,
|
|
1166
|
+
versionId: thread.versionId
|
|
1167
|
+
},
|
|
1168
|
+
null,
|
|
1169
|
+
2
|
|
1170
|
+
)
|
|
1171
|
+
);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
const url = target.url ?? reviewUrl(target.apiUrl, artifact.slug);
|
|
1175
|
+
const where = anchor.type === "text" ? ` on \u201C${anchor.quote}\u201D` : " on the page";
|
|
1176
|
+
console.log(`\u2713 Opened thread ${thread.id}${where} \u2014 ${url}`);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
910
1179
|
// src/comments.ts
|
|
1180
|
+
import path4 from "path";
|
|
911
1181
|
var STATUSES = ["open", "resolved", "all"];
|
|
912
1182
|
var VERSIONS = ["current", "all"];
|
|
913
1183
|
var pollSeconds = () => Number(process.env.AC_WAIT_POLL_SECONDS ?? 5);
|
|
@@ -926,7 +1196,7 @@ async function comments(opts) {
|
|
|
926
1196
|
}
|
|
927
1197
|
const status = opts.status;
|
|
928
1198
|
const version = opts.version;
|
|
929
|
-
const dir =
|
|
1199
|
+
const dir = path4.resolve(opts.dir);
|
|
930
1200
|
const target = resolveArtifactTarget(dir, {
|
|
931
1201
|
apiKey: opts.apiKey,
|
|
932
1202
|
artifact: opts.artifact
|
|
@@ -1085,21 +1355,30 @@ function printHuman(data, url, status, version) {
|
|
|
1085
1355
|
function printThread(t) {
|
|
1086
1356
|
const marker = t.status === "resolved" ? "\u2713" : "\u25CF";
|
|
1087
1357
|
const date = t.createdAt.slice(0, 10);
|
|
1358
|
+
const edited = t.editedAt && !t.deleted ? " \xB7 edited" : "";
|
|
1088
1359
|
console.log(
|
|
1089
|
-
`${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}`
|
|
1360
|
+
`${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}${edited}`
|
|
1090
1361
|
);
|
|
1091
1362
|
if (t.anchor.type === "text") {
|
|
1092
1363
|
console.log(` \u21B3 "${t.anchor.quote}"`);
|
|
1093
|
-
} else if (t.anchor.
|
|
1364
|
+
} else if (t.anchor.type === "point") {
|
|
1094
1365
|
const el = t.anchor.element;
|
|
1095
|
-
|
|
1096
|
-
|
|
1366
|
+
if (el) {
|
|
1367
|
+
const text = el.text ? ` "${el.text}"` : "";
|
|
1368
|
+
console.log(
|
|
1369
|
+
` \u21B3 at ${elementLabel(el)}${text} \u2014 ${t.anchor.elementPath}`
|
|
1370
|
+
);
|
|
1371
|
+
} else {
|
|
1372
|
+
console.log(` \u21B3 at ${t.anchor.elementPath}`);
|
|
1373
|
+
}
|
|
1097
1374
|
} else {
|
|
1098
|
-
console.log(` \u21B3
|
|
1375
|
+
console.log(t.anchor.page ? ` \u21B3 on ${t.anchor.page}` : " \u21B3 page comment");
|
|
1099
1376
|
}
|
|
1100
|
-
console.log(indent(t.body, " "));
|
|
1377
|
+
console.log(indent(t.deleted ? "[deleted]" : t.body, " "));
|
|
1101
1378
|
for (const reply2 of t.replies) {
|
|
1102
|
-
|
|
1379
|
+
const replyEdited = reply2.editedAt && !reply2.deleted ? " (edited)" : "";
|
|
1380
|
+
const replyBody = reply2.deleted ? "[deleted]" : reply2.body;
|
|
1381
|
+
console.log(indent(`${reply2.author}: ${replyBody}${replyEdited}`, " "));
|
|
1103
1382
|
}
|
|
1104
1383
|
console.log("");
|
|
1105
1384
|
}
|
|
@@ -1108,19 +1387,20 @@ function indent(text, pad) {
|
|
|
1108
1387
|
}
|
|
1109
1388
|
|
|
1110
1389
|
// src/dashboard.ts
|
|
1111
|
-
import
|
|
1390
|
+
import path7 from "path";
|
|
1112
1391
|
|
|
1113
1392
|
// src/publish.ts
|
|
1114
1393
|
import { exec } from "child_process";
|
|
1115
1394
|
import { readFileSync as readFileSync4 } from "fs";
|
|
1116
|
-
import
|
|
1395
|
+
import path6 from "path";
|
|
1396
|
+
import { createInterface } from "readline";
|
|
1117
1397
|
|
|
1118
1398
|
// src/manifest.ts
|
|
1119
1399
|
import { createHash } from "crypto";
|
|
1120
1400
|
import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
|
|
1121
|
-
import
|
|
1401
|
+
import path5 from "path";
|
|
1122
1402
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".artifactcafe"]);
|
|
1123
|
-
var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
|
|
1403
|
+
var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store", "artifact-cafe.json"]);
|
|
1124
1404
|
function isIgnoredFile(name) {
|
|
1125
1405
|
if (IGNORED_FILES.has(name)) {
|
|
1126
1406
|
return true;
|
|
@@ -1167,7 +1447,7 @@ var MIME_TYPES = {
|
|
|
1167
1447
|
".xml": "application/xml"
|
|
1168
1448
|
};
|
|
1169
1449
|
function mimeFor(filePath) {
|
|
1170
|
-
return MIME_TYPES[
|
|
1450
|
+
return MIME_TYPES[path5.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
1171
1451
|
}
|
|
1172
1452
|
function scanDirectory(dir) {
|
|
1173
1453
|
const files = [];
|
|
@@ -1175,7 +1455,7 @@ function scanDirectory(dir) {
|
|
|
1175
1455
|
function walk(current, relPrefix) {
|
|
1176
1456
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
1177
1457
|
const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
|
|
1178
|
-
const abs =
|
|
1458
|
+
const abs = path5.join(current, entry.name);
|
|
1179
1459
|
if (entry.isSymbolicLink()) {
|
|
1180
1460
|
skipped.push(rel);
|
|
1181
1461
|
continue;
|
|
@@ -1241,7 +1521,7 @@ function resolveEntry(files, explicit) {
|
|
|
1241
1521
|
// src/publish.ts
|
|
1242
1522
|
var UPLOAD_CONCURRENCY = 6;
|
|
1243
1523
|
async function publish(opts) {
|
|
1244
|
-
const dir =
|
|
1524
|
+
const dir = path6.resolve(opts.dir);
|
|
1245
1525
|
const config = readConfig(dir);
|
|
1246
1526
|
if (opts.password !== void 0) {
|
|
1247
1527
|
const parsed = gatePasswordSchema.safeParse(opts.password);
|
|
@@ -1253,7 +1533,13 @@ async function publish(opts) {
|
|
|
1253
1533
|
const token = (artifactId !== null && artifactId === config?.artifactId ? config.publishToken : void 0) ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
1254
1534
|
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
|
|
1255
1535
|
const apiKey = resolveApiKey(apiUrl2, opts.apiKey);
|
|
1256
|
-
const placement = await resolvePlacement({
|
|
1536
|
+
const placement = await resolvePlacement({
|
|
1537
|
+
apiKey,
|
|
1538
|
+
apiUrl: apiUrl2,
|
|
1539
|
+
config,
|
|
1540
|
+
isNewArtifact: artifactId === null,
|
|
1541
|
+
opts
|
|
1542
|
+
});
|
|
1257
1543
|
const { files, skipped } = scanDirectory(dir);
|
|
1258
1544
|
if (files.length === 0) {
|
|
1259
1545
|
throw new Error(`Nothing to publish in ${dir} (after ignore rules).`);
|
|
@@ -1278,7 +1564,7 @@ async function publish(opts) {
|
|
|
1278
1564
|
type: "cli"
|
|
1279
1565
|
},
|
|
1280
1566
|
title: opts.title,
|
|
1281
|
-
...placement.
|
|
1567
|
+
...placement.publishWorkspaceId ? { workspaceId: placement.publishWorkspaceId } : {}
|
|
1282
1568
|
};
|
|
1283
1569
|
let credential = apiKey ?? token;
|
|
1284
1570
|
let client = createApiClient(apiUrl2, credential);
|
|
@@ -1310,8 +1596,9 @@ async function publish(opts) {
|
|
|
1310
1596
|
);
|
|
1311
1597
|
});
|
|
1312
1598
|
const result = await client.finalize(session.publishSessionId);
|
|
1599
|
+
const rememberedProjectFile = placement.rememberProjectDefault ? writeProjectConfig(dir, placement.rememberProjectDefault) : null;
|
|
1313
1600
|
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;
|
|
1601
|
+
const shouldWriteConfig = result.publishToken !== void 0 || config?.artifactId === result.artifactId || opts.workspace !== void 0 || opts.folder !== void 0 || placement.workspaceId !== void 0;
|
|
1315
1602
|
if (shouldWriteConfig) {
|
|
1316
1603
|
writeConfig(dir, {
|
|
1317
1604
|
apiUrl: apiUrl2,
|
|
@@ -1322,7 +1609,8 @@ async function publish(opts) {
|
|
|
1322
1609
|
slug: slugFromUrl(result.url),
|
|
1323
1610
|
url: result.url,
|
|
1324
1611
|
...placement.workspace ? { workspace: placement.workspace } : {},
|
|
1325
|
-
...placement.workspaceId ? { workspaceId: placement.workspaceId } : {}
|
|
1612
|
+
...placement.workspaceId ? { workspaceId: placement.workspaceId } : {},
|
|
1613
|
+
...placement.destination === "personal" || placement.destination === "workspace" ? { workspaceKind: placement.destination } : {}
|
|
1326
1614
|
});
|
|
1327
1615
|
}
|
|
1328
1616
|
if (opts.password !== void 0) {
|
|
@@ -1338,6 +1626,8 @@ async function publish(opts) {
|
|
|
1338
1626
|
{
|
|
1339
1627
|
artifactId: result.artifactId,
|
|
1340
1628
|
claimed: result.claimed,
|
|
1629
|
+
destination: placement.destination,
|
|
1630
|
+
destinationSource: placement.destinationSource,
|
|
1341
1631
|
folder: placement.folder ?? null,
|
|
1342
1632
|
folderId: placement.folderId ?? null,
|
|
1343
1633
|
url: result.url,
|
|
@@ -1356,6 +1646,12 @@ async function publish(opts) {
|
|
|
1356
1646
|
);
|
|
1357
1647
|
} else if (result.versionNumber === 1) {
|
|
1358
1648
|
console.log("\n\u2713 Published to artifact.cafe\n");
|
|
1649
|
+
console.log(`Destination: ${placement.destinationLabel}
|
|
1650
|
+
`);
|
|
1651
|
+
if (rememberedProjectFile) {
|
|
1652
|
+
console.log(`Project default: ${rememberedProjectFile}
|
|
1653
|
+
`);
|
|
1654
|
+
}
|
|
1359
1655
|
console.log(`Review URL:
|
|
1360
1656
|
${result.url}
|
|
1361
1657
|
`);
|
|
@@ -1375,6 +1671,12 @@ ${result.claimUrl}
|
|
|
1375
1671
|
console.log(`
|
|
1376
1672
|
\u2713 Published v${result.versionNumber}
|
|
1377
1673
|
`);
|
|
1674
|
+
console.log(`Destination: ${placement.destinationLabel}
|
|
1675
|
+
`);
|
|
1676
|
+
if (rememberedProjectFile) {
|
|
1677
|
+
console.log(`Project default: ${rememberedProjectFile}
|
|
1678
|
+
`);
|
|
1679
|
+
}
|
|
1378
1680
|
console.log(`${result.url}
|
|
1379
1681
|
`);
|
|
1380
1682
|
if (opts.password !== void 0) {
|
|
@@ -1386,75 +1688,181 @@ ${result.claimUrl}
|
|
|
1386
1688
|
}
|
|
1387
1689
|
}
|
|
1388
1690
|
async function resolvePlacement(params) {
|
|
1389
|
-
const { apiKey, apiUrl: apiUrl2, config, opts } = params;
|
|
1691
|
+
const { apiKey, apiUrl: apiUrl2, config, isNewArtifact, opts } = params;
|
|
1390
1692
|
const hasPlacementFlag = opts.workspace !== void 0 || opts.folder !== void 0;
|
|
1391
1693
|
if (!hasPlacementFlag && config?.workspaceId) {
|
|
1392
|
-
|
|
1694
|
+
const isPersonal = config.workspaceKind === "personal";
|
|
1695
|
+
if (!(apiKey || isPersonal)) {
|
|
1393
1696
|
throw new Error(
|
|
1394
1697
|
`Publishing into workspace ${config.workspace ?? config.workspaceId} needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
1395
1698
|
);
|
|
1396
1699
|
}
|
|
1397
1700
|
return {
|
|
1701
|
+
destination: isPersonal ? "personal" : "workspace",
|
|
1702
|
+
destinationLabel: isPersonal ? "Personal" : config.workspace ?? config.workspaceId,
|
|
1703
|
+
destinationSource: "artifact-config",
|
|
1398
1704
|
...config.folder ? { folder: config.folder } : {},
|
|
1399
1705
|
...config.folderId ? { folderId: config.folderId } : {},
|
|
1400
1706
|
...config.workspace ? { workspace: config.workspace } : {},
|
|
1707
|
+
...apiKey ? { publishWorkspaceId: config.workspaceId } : {},
|
|
1401
1708
|
workspaceId: config.workspaceId
|
|
1402
1709
|
};
|
|
1403
1710
|
}
|
|
1404
|
-
if (!hasPlacementFlag) {
|
|
1405
|
-
return {
|
|
1711
|
+
if (!(hasPlacementFlag || isNewArtifact)) {
|
|
1712
|
+
return {
|
|
1713
|
+
destination: "existing-artifact",
|
|
1714
|
+
destinationLabel: "Existing artifact",
|
|
1715
|
+
destinationSource: "artifact-config"
|
|
1716
|
+
};
|
|
1406
1717
|
}
|
|
1407
|
-
|
|
1718
|
+
const projectConfig = isNewArtifact ? readProjectConfig(opts.dir) : null;
|
|
1719
|
+
const wantsWorkspace = opts.workspace !== void 0 || opts.folder !== void 0 || projectConfig !== null;
|
|
1720
|
+
if (wantsWorkspace && !apiKey) {
|
|
1408
1721
|
throw new Error(
|
|
1409
1722
|
"Publishing into a workspace needs an account \u2014 run `artifact-cafe login` first."
|
|
1410
1723
|
);
|
|
1411
1724
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1725
|
+
if (!(hasPlacementFlag || projectConfig)) {
|
|
1726
|
+
if (!apiKey) {
|
|
1727
|
+
return {
|
|
1728
|
+
destination: "anonymous",
|
|
1729
|
+
destinationLabel: "Anonymous",
|
|
1730
|
+
destinationSource: "anonymous-default"
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
if (shouldPromptForWorkspace(opts)) {
|
|
1734
|
+
const data2 = await createApiClient(apiUrl2, apiKey).listWorkspaces();
|
|
1735
|
+
if (data2.workspaces.length > 1) {
|
|
1736
|
+
const choice = await pickWorkspace(data2.workspaces);
|
|
1737
|
+
return {
|
|
1738
|
+
destination: choice.workspace.isPersonal ? "personal" : "workspace",
|
|
1739
|
+
destinationLabel: choice.workspace.isPersonal ? "Personal" : choice.workspace.name,
|
|
1740
|
+
destinationSource: "interactive",
|
|
1741
|
+
publishWorkspaceId: choice.workspace.id,
|
|
1742
|
+
...choice.remember ? {
|
|
1743
|
+
rememberProjectDefault: {
|
|
1744
|
+
workspace: choice.workspace.handle
|
|
1745
|
+
}
|
|
1746
|
+
} : {},
|
|
1747
|
+
workspace: choice.workspace.handle,
|
|
1748
|
+
workspaceId: choice.workspace.id
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return {
|
|
1753
|
+
destination: "personal",
|
|
1754
|
+
destinationLabel: "Personal",
|
|
1755
|
+
destinationSource: "personal-default"
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
if (!isNewArtifact && opts.folder !== void 0 && opts.workspace === void 0 && !(config?.workspace || config?.workspaceId)) {
|
|
1414
1759
|
throw new Error("--folder requires --workspace or a stored workspace.");
|
|
1415
1760
|
}
|
|
1416
1761
|
const client = createApiClient(apiUrl2, apiKey);
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
const workspace = data2.workspaces.find(
|
|
1762
|
+
const data = await client.listWorkspaces();
|
|
1763
|
+
const workspaceHandle = opts.workspace ?? config?.workspace ?? projectConfig?.workspace;
|
|
1764
|
+
let workspace;
|
|
1765
|
+
if (workspaceHandle) {
|
|
1766
|
+
workspace = data.workspaces.find(
|
|
1423
1767
|
(candidate) => candidate.handle === workspaceHandle
|
|
1424
1768
|
);
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1769
|
+
} else if (config?.workspaceId) {
|
|
1770
|
+
workspace = data.workspaces.find(
|
|
1771
|
+
(candidate) => candidate.id === config.workspaceId
|
|
1772
|
+
);
|
|
1773
|
+
} else {
|
|
1774
|
+
workspace = data.workspaces.find((candidate) => candidate.isPersonal);
|
|
1775
|
+
}
|
|
1776
|
+
if (!workspace) {
|
|
1777
|
+
const target = workspaceHandle ? `workspace with handle "${workspaceHandle}"` : "personal workspace";
|
|
1778
|
+
throw new Error(`No ${target} belongs to this account.`);
|
|
1431
1779
|
}
|
|
1432
|
-
const folderName = opts.workspace !== void 0 && opts.folder === void 0 ? void 0 : opts.folder ??
|
|
1780
|
+
const folderName = opts.workspace !== void 0 && opts.folder === void 0 ? void 0 : opts.folder ?? projectConfig?.folder;
|
|
1781
|
+
const destinationSource = hasPlacementFlag ? "explicit" : "project-default";
|
|
1433
1782
|
if (!folderName) {
|
|
1434
|
-
return {
|
|
1783
|
+
return {
|
|
1784
|
+
destination: workspace.isPersonal ? "personal" : "workspace",
|
|
1785
|
+
destinationLabel: workspace.isPersonal ? "Personal" : workspace.name,
|
|
1786
|
+
destinationSource,
|
|
1787
|
+
publishWorkspaceId: workspace.id,
|
|
1788
|
+
workspace: workspace.handle,
|
|
1789
|
+
workspaceId: workspace.id
|
|
1790
|
+
};
|
|
1435
1791
|
}
|
|
1436
|
-
const
|
|
1437
|
-
const matches =
|
|
1792
|
+
const folderData = await client.listFolders(workspace.id);
|
|
1793
|
+
const matches = folderData.folders.filter(
|
|
1438
1794
|
(candidate) => candidate.name === folderName || candidate.id === folderName
|
|
1439
1795
|
);
|
|
1440
1796
|
if (matches.length === 0) {
|
|
1441
1797
|
throw new Error(
|
|
1442
|
-
`No folder named "${folderName}" exists in workspace ${
|
|
1798
|
+
`No folder named "${folderName}" exists in workspace ${workspace.handle}.`
|
|
1443
1799
|
);
|
|
1444
1800
|
}
|
|
1445
1801
|
if (matches.length > 1) {
|
|
1446
1802
|
throw new Error(
|
|
1447
|
-
`More than one folder is named "${folderName}" in workspace ${
|
|
1803
|
+
`More than one folder is named "${folderName}" in workspace ${workspace.handle}; pass the folder id instead.`
|
|
1448
1804
|
);
|
|
1449
1805
|
}
|
|
1450
1806
|
const folder = matches[0];
|
|
1451
1807
|
return {
|
|
1808
|
+
destination: workspace.isPersonal ? "personal" : "workspace",
|
|
1809
|
+
destinationLabel: workspace.isPersonal ? "Personal" : workspace.name,
|
|
1810
|
+
destinationSource,
|
|
1452
1811
|
folder: folder.name,
|
|
1453
1812
|
folderId: folder.id,
|
|
1454
|
-
|
|
1455
|
-
|
|
1813
|
+
publishWorkspaceId: workspace.id,
|
|
1814
|
+
workspace: workspace.handle,
|
|
1815
|
+
workspaceId: workspace.id
|
|
1456
1816
|
};
|
|
1457
1817
|
}
|
|
1818
|
+
function shouldPromptForWorkspace(opts) {
|
|
1819
|
+
if (opts.json || process.env.ARTIFACT_CAFE_AGENT) {
|
|
1820
|
+
return false;
|
|
1821
|
+
}
|
|
1822
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1823
|
+
}
|
|
1824
|
+
function pickWorkspace(workspaces3) {
|
|
1825
|
+
const choices = [...workspaces3].sort((a, b) => {
|
|
1826
|
+
if (a.isPersonal !== b.isPersonal) {
|
|
1827
|
+
return a.isPersonal ? -1 : 1;
|
|
1828
|
+
}
|
|
1829
|
+
return a.name.localeCompare(b.name);
|
|
1830
|
+
});
|
|
1831
|
+
console.log("Where should this artifact live?\n");
|
|
1832
|
+
choices.forEach((workspace, index) => {
|
|
1833
|
+
const label = workspace.isPersonal ? "Personal" : workspace.name;
|
|
1834
|
+
const handle = workspace.isPersonal ? "" : ` \xB7 ${workspace.handle}`;
|
|
1835
|
+
console.log(` ${String(index + 1).padStart(2)}. ${label}${handle}`);
|
|
1836
|
+
});
|
|
1837
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1838
|
+
return new Promise((resolve2, reject) => {
|
|
1839
|
+
rl.question(`
|
|
1840
|
+
Publish where? [1-${choices.length}]: `, (answer) => {
|
|
1841
|
+
const index = Number(answer.trim()) - 1;
|
|
1842
|
+
const workspace = choices[index];
|
|
1843
|
+
if (!(Number.isInteger(index) && workspace)) {
|
|
1844
|
+
rl.close();
|
|
1845
|
+
reject(new Error(`Enter a number between 1 and ${choices.length}.`));
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
if (workspace.isPersonal) {
|
|
1849
|
+
rl.close();
|
|
1850
|
+
resolve2({ remember: false, workspace });
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
rl.question(
|
|
1854
|
+
`Remember ${workspace.name} for new artifacts in this project? [y/N]: `,
|
|
1855
|
+
(rememberAnswer) => {
|
|
1856
|
+
rl.close();
|
|
1857
|
+
resolve2({
|
|
1858
|
+
remember: /^y(?:es)?$/i.test(rememberAnswer.trim()),
|
|
1859
|
+
workspace
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
);
|
|
1863
|
+
});
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1458
1866
|
async function inBatches(items, concurrency, fn) {
|
|
1459
1867
|
let next = 0;
|
|
1460
1868
|
const workers = Array.from(
|
|
@@ -1491,7 +1899,7 @@ function openInBrowser(url) {
|
|
|
1491
1899
|
|
|
1492
1900
|
// src/dashboard.ts
|
|
1493
1901
|
function openDashboard(opts) {
|
|
1494
|
-
const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(
|
|
1902
|
+
const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path7.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
|
|
1495
1903
|
const url = `${base.replace(/\/$/, "")}/dashboard`;
|
|
1496
1904
|
console.log(url);
|
|
1497
1905
|
if (process.stdout.isTTY) {
|
|
@@ -1580,8 +1988,8 @@ function printFolder(folder) {
|
|
|
1580
1988
|
}
|
|
1581
1989
|
|
|
1582
1990
|
// src/link.ts
|
|
1583
|
-
import
|
|
1584
|
-
import { createInterface } from "readline";
|
|
1991
|
+
import path8 from "path";
|
|
1992
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1585
1993
|
async function link(opts) {
|
|
1586
1994
|
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
1587
1995
|
/\/$/,
|
|
@@ -1623,7 +2031,7 @@ async function link(opts) {
|
|
|
1623
2031
|
}
|
|
1624
2032
|
chosen = await pickArtifact(artifacts);
|
|
1625
2033
|
}
|
|
1626
|
-
const dir =
|
|
2034
|
+
const dir = path8.resolve(opts.dir);
|
|
1627
2035
|
writeConfig(dir, {
|
|
1628
2036
|
apiUrl: apiUrl2,
|
|
1629
2037
|
artifactId: chosen.id,
|
|
@@ -1660,7 +2068,7 @@ function pickArtifact(artifacts) {
|
|
|
1660
2068
|
` ${String(i + 1).padStart(2)}. ${a.title} \xB7 ${v} \xB7 ${a.url}`
|
|
1661
2069
|
);
|
|
1662
2070
|
});
|
|
1663
|
-
const rl =
|
|
2071
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
1664
2072
|
return new Promise((resolve2, reject) => {
|
|
1665
2073
|
rl.question(`
|
|
1666
2074
|
Link which? [1-${artifacts.length}]: `, (answer) => {
|
|
@@ -1732,13 +2140,13 @@ function relativeTime(iso) {
|
|
|
1732
2140
|
}
|
|
1733
2141
|
|
|
1734
2142
|
// src/login.ts
|
|
1735
|
-
import { createInterface as
|
|
2143
|
+
import { createInterface as createInterface3 } from "readline";
|
|
1736
2144
|
function apiUrl() {
|
|
1737
2145
|
return (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
1738
2146
|
}
|
|
1739
2147
|
function promptSecret(question) {
|
|
1740
2148
|
return new Promise((resolve2) => {
|
|
1741
|
-
const rl =
|
|
2149
|
+
const rl = createInterface3({
|
|
1742
2150
|
input: process.stdin,
|
|
1743
2151
|
output: process.stdout
|
|
1744
2152
|
});
|
|
@@ -1845,10 +2253,69 @@ function logout(opts) {
|
|
|
1845
2253
|
);
|
|
1846
2254
|
}
|
|
1847
2255
|
|
|
2256
|
+
// src/message.ts
|
|
2257
|
+
import path9 from "path";
|
|
2258
|
+
async function readStdin2() {
|
|
2259
|
+
if (process.stdin.isTTY) {
|
|
2260
|
+
return "";
|
|
2261
|
+
}
|
|
2262
|
+
const chunks = [];
|
|
2263
|
+
for await (const chunk of process.stdin) {
|
|
2264
|
+
chunks.push(chunk);
|
|
2265
|
+
}
|
|
2266
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
2267
|
+
}
|
|
2268
|
+
async function editMessage(opts) {
|
|
2269
|
+
const messageId = opts.message?.trim();
|
|
2270
|
+
if (!messageId) {
|
|
2271
|
+
throw new Error(
|
|
2272
|
+
"Pass the message id to edit \u2014 get it from `artifact-cafe comments --json`."
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
const body = (opts.body ?? await readStdin2()).trim();
|
|
2276
|
+
if (!body) {
|
|
2277
|
+
throw new Error(
|
|
2278
|
+
'Nothing to save. Pass the new text with --body "\u2026", or pipe it on stdin.'
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
const dir = path9.resolve(opts.dir);
|
|
2282
|
+
const target = resolveArtifactTarget(dir, {
|
|
2283
|
+
apiKey: opts.apiKey,
|
|
2284
|
+
artifact: opts.artifact
|
|
2285
|
+
});
|
|
2286
|
+
const client = createApiClient(target.apiUrl, target.credential);
|
|
2287
|
+
const thread = await client.editMessage(messageId, body);
|
|
2288
|
+
if (opts.json) {
|
|
2289
|
+
console.log(JSON.stringify({ id: thread.id, messageId }, null, 2));
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
console.log(`\u2713 Edited ${messageId}.`);
|
|
2293
|
+
}
|
|
2294
|
+
async function deleteMessage(opts) {
|
|
2295
|
+
const messageId = opts.message?.trim();
|
|
2296
|
+
if (!messageId) {
|
|
2297
|
+
throw new Error(
|
|
2298
|
+
"Pass the message id to delete \u2014 get it from `artifact-cafe comments --json`."
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
const dir = path9.resolve(opts.dir);
|
|
2302
|
+
const target = resolveArtifactTarget(dir, {
|
|
2303
|
+
apiKey: opts.apiKey,
|
|
2304
|
+
artifact: opts.artifact
|
|
2305
|
+
});
|
|
2306
|
+
const client = createApiClient(target.apiUrl, target.credential);
|
|
2307
|
+
await client.deleteMessage(messageId);
|
|
2308
|
+
if (opts.json) {
|
|
2309
|
+
console.log(JSON.stringify({ deleted: true, messageId }, null, 2));
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
console.log(`\u2713 Deleted ${messageId}.`);
|
|
2313
|
+
}
|
|
2314
|
+
|
|
1848
2315
|
// src/open.ts
|
|
1849
|
-
import
|
|
2316
|
+
import path10 from "path";
|
|
1850
2317
|
async function openArtifact(opts) {
|
|
1851
|
-
const dir =
|
|
2318
|
+
const dir = path10.resolve(opts.dir);
|
|
1852
2319
|
const target = resolveArtifactTarget(dir, {
|
|
1853
2320
|
apiKey: opts.apiKey,
|
|
1854
2321
|
artifact: opts.artifact
|
|
@@ -1866,7 +2333,7 @@ async function openArtifact(opts) {
|
|
|
1866
2333
|
}
|
|
1867
2334
|
|
|
1868
2335
|
// src/protect.ts
|
|
1869
|
-
import
|
|
2336
|
+
import path11 from "path";
|
|
1870
2337
|
async function protect(opts) {
|
|
1871
2338
|
if (opts.off && opts.password !== void 0) {
|
|
1872
2339
|
throw new Error("Pass either --password or --off, not both.");
|
|
@@ -1883,7 +2350,7 @@ async function protect(opts) {
|
|
|
1883
2350
|
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
1884
2351
|
}
|
|
1885
2352
|
}
|
|
1886
|
-
const dir =
|
|
2353
|
+
const dir = path11.resolve(opts.dir);
|
|
1887
2354
|
const target = resolveArtifactTarget(dir, {
|
|
1888
2355
|
apiKey: opts.apiKey,
|
|
1889
2356
|
artifact: opts.artifact
|
|
@@ -1908,8 +2375,8 @@ async function protect(opts) {
|
|
|
1908
2375
|
}
|
|
1909
2376
|
|
|
1910
2377
|
// src/reply.ts
|
|
1911
|
-
import
|
|
1912
|
-
async function
|
|
2378
|
+
import path12 from "path";
|
|
2379
|
+
async function readStdin3() {
|
|
1913
2380
|
if (process.stdin.isTTY) {
|
|
1914
2381
|
return "";
|
|
1915
2382
|
}
|
|
@@ -1926,13 +2393,13 @@ async function replyToThread(opts) {
|
|
|
1926
2393
|
"Pass the thread id to reply to \u2014 get it from `artifact-cafe comments --json`."
|
|
1927
2394
|
);
|
|
1928
2395
|
}
|
|
1929
|
-
const body = (opts.body ?? await
|
|
2396
|
+
const body = (opts.body ?? await readStdin3()).trim();
|
|
1930
2397
|
if (!body) {
|
|
1931
2398
|
throw new Error(
|
|
1932
2399
|
'Nothing to post. Pass the reply with --body "\u2026", or pipe it on stdin.'
|
|
1933
2400
|
);
|
|
1934
2401
|
}
|
|
1935
|
-
const dir =
|
|
2402
|
+
const dir = path12.resolve(opts.dir);
|
|
1936
2403
|
const target = resolveArtifactTarget(dir, {
|
|
1937
2404
|
apiKey: opts.apiKey,
|
|
1938
2405
|
artifact: opts.artifact
|
|
@@ -1961,7 +2428,7 @@ async function replyToThread(opts) {
|
|
|
1961
2428
|
}
|
|
1962
2429
|
|
|
1963
2430
|
// src/resolve.ts
|
|
1964
|
-
import
|
|
2431
|
+
import path13 from "path";
|
|
1965
2432
|
async function changeThreadStatus(opts) {
|
|
1966
2433
|
const threadId = opts.thread?.trim();
|
|
1967
2434
|
if (!threadId) {
|
|
@@ -1969,7 +2436,7 @@ async function changeThreadStatus(opts) {
|
|
|
1969
2436
|
`Pass the thread id to ${opts.action} \u2014 get it from \`artifact-cafe comments --json\`.`
|
|
1970
2437
|
);
|
|
1971
2438
|
}
|
|
1972
|
-
const dir =
|
|
2439
|
+
const dir = path13.resolve(opts.dir);
|
|
1973
2440
|
const target = resolveArtifactTarget(dir, {
|
|
1974
2441
|
apiKey: opts.apiKey,
|
|
1975
2442
|
artifact: opts.artifact
|
|
@@ -1990,26 +2457,26 @@ async function changeThreadStatus(opts) {
|
|
|
1990
2457
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
1991
2458
|
import { createRequire } from "module";
|
|
1992
2459
|
import { homedir as homedir2 } from "os";
|
|
1993
|
-
import
|
|
1994
|
-
import { z as
|
|
2460
|
+
import path14 from "path";
|
|
2461
|
+
import { z as z16 } from "zod";
|
|
1995
2462
|
var REGISTRY_LATEST_URL = "https://registry.npmjs.org/artifact-cafe/latest";
|
|
1996
2463
|
var SKILL_SOURCE_URL = "https://raw.githubusercontent.com/Artifact-Cafe/skill/main/artifact-cafe/SKILL.md";
|
|
1997
2464
|
var SKILL_UPDATE_COMMAND = "npx skills add artifact-cafe/skill --skill artifact-cafe -g";
|
|
1998
2465
|
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1999
2466
|
var FETCH_TIMEOUT_MS = 2e3;
|
|
2000
2467
|
var SKILL_VERSION_PATTERN = /\*\*Skill version:\s*(\d+\.\d+\.\d+)\*\*/;
|
|
2001
|
-
var cacheSchema =
|
|
2002
|
-
checkedAt:
|
|
2003
|
-
cliLatest:
|
|
2004
|
-
skillLatest:
|
|
2005
|
-
version:
|
|
2468
|
+
var cacheSchema = z16.object({
|
|
2469
|
+
checkedAt: z16.number(),
|
|
2470
|
+
cliLatest: z16.string().nullable(),
|
|
2471
|
+
skillLatest: z16.string().nullable(),
|
|
2472
|
+
version: z16.literal(1)
|
|
2006
2473
|
});
|
|
2007
2474
|
function cliVersion() {
|
|
2008
2475
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
2009
2476
|
return pkg.version;
|
|
2010
2477
|
}
|
|
2011
2478
|
function cachePath() {
|
|
2012
|
-
return
|
|
2479
|
+
return path14.join(homedir2(), ".artifactcafe", "update-check.json");
|
|
2013
2480
|
}
|
|
2014
2481
|
function readCache() {
|
|
2015
2482
|
try {
|
|
@@ -2020,7 +2487,7 @@ function readCache() {
|
|
|
2020
2487
|
}
|
|
2021
2488
|
function writeCache(cache) {
|
|
2022
2489
|
try {
|
|
2023
|
-
mkdirSync3(
|
|
2490
|
+
mkdirSync3(path14.dirname(cachePath()), { mode: 448, recursive: true });
|
|
2024
2491
|
writeFileSync3(cachePath(), `${JSON.stringify(cache, null, 2)}
|
|
2025
2492
|
`);
|
|
2026
2493
|
} catch {
|
|
@@ -2057,7 +2524,7 @@ async function fetchText(url) {
|
|
|
2057
2524
|
}
|
|
2058
2525
|
async function fetchLatestVersions(previous) {
|
|
2059
2526
|
const [cli, skill] = await Promise.all([
|
|
2060
|
-
fetchJson(REGISTRY_LATEST_URL).then((body) =>
|
|
2527
|
+
fetchJson(REGISTRY_LATEST_URL).then((body) => z16.object({ version: z16.string() }).parse(body).version).catch(() => null),
|
|
2061
2528
|
fetchText(SKILL_SOURCE_URL).then((body) => SKILL_VERSION_PATTERN.exec(body)?.[1] ?? null).catch(() => null)
|
|
2062
2529
|
]);
|
|
2063
2530
|
return {
|
|
@@ -2073,7 +2540,7 @@ function installedSkillVersions() {
|
|
|
2073
2540
|
const versions = [];
|
|
2074
2541
|
for (const root of roots) {
|
|
2075
2542
|
for (const dir of dirs) {
|
|
2076
|
-
const file =
|
|
2543
|
+
const file = path14.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
|
|
2077
2544
|
if (!existsSync3(file)) {
|
|
2078
2545
|
continue;
|
|
2079
2546
|
}
|
|
@@ -2651,6 +3118,152 @@ var reply = defineCommand({
|
|
|
2651
3118
|
}
|
|
2652
3119
|
}
|
|
2653
3120
|
});
|
|
3121
|
+
var comment2 = defineCommand({
|
|
3122
|
+
args: {
|
|
3123
|
+
"api-key": {
|
|
3124
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
3125
|
+
type: "string"
|
|
3126
|
+
},
|
|
3127
|
+
artifact: {
|
|
3128
|
+
description: "Act on an artifact ID instead of the linked folder",
|
|
3129
|
+
type: "string"
|
|
3130
|
+
},
|
|
3131
|
+
body: {
|
|
3132
|
+
description: "Comment text (omit to read from stdin)",
|
|
3133
|
+
type: "string"
|
|
3134
|
+
},
|
|
3135
|
+
dir: {
|
|
3136
|
+
default: ".",
|
|
3137
|
+
description: "Published/linked directory",
|
|
3138
|
+
type: "positional"
|
|
3139
|
+
},
|
|
3140
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
3141
|
+
page: {
|
|
3142
|
+
description: "Scope to one document (e.g. pages/notes.html)",
|
|
3143
|
+
type: "string"
|
|
3144
|
+
},
|
|
3145
|
+
prefix: {
|
|
3146
|
+
description: "Text just before --quote (disambiguates repeats)",
|
|
3147
|
+
type: "string"
|
|
3148
|
+
},
|
|
3149
|
+
quote: {
|
|
3150
|
+
description: "Anchor to this text (omit for a page-level comment)",
|
|
3151
|
+
type: "string"
|
|
3152
|
+
},
|
|
3153
|
+
suffix: {
|
|
3154
|
+
description: "Text just after --quote (disambiguates repeats)",
|
|
3155
|
+
type: "string"
|
|
3156
|
+
},
|
|
3157
|
+
via: {
|
|
3158
|
+
description: 'Attribution label, e.g. "Claude Code"',
|
|
3159
|
+
type: "string"
|
|
3160
|
+
}
|
|
3161
|
+
},
|
|
3162
|
+
meta: {
|
|
3163
|
+
description: "Open a comment thread (as your account)",
|
|
3164
|
+
name: "comment"
|
|
3165
|
+
},
|
|
3166
|
+
async run({ args }) {
|
|
3167
|
+
try {
|
|
3168
|
+
await comment({
|
|
3169
|
+
apiKey: args["api-key"],
|
|
3170
|
+
artifact: args.artifact,
|
|
3171
|
+
body: args.body,
|
|
3172
|
+
dir: args.dir,
|
|
3173
|
+
json: Boolean(args.json),
|
|
3174
|
+
page: args.page,
|
|
3175
|
+
prefix: args.prefix,
|
|
3176
|
+
quote: args.quote,
|
|
3177
|
+
suffix: args.suffix,
|
|
3178
|
+
via: args.via
|
|
3179
|
+
});
|
|
3180
|
+
} catch (err) {
|
|
3181
|
+
printCliError(err, Boolean(args.json));
|
|
3182
|
+
process.exit(1);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
});
|
|
3186
|
+
var edit = defineCommand({
|
|
3187
|
+
args: {
|
|
3188
|
+
"api-key": {
|
|
3189
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
3190
|
+
type: "string"
|
|
3191
|
+
},
|
|
3192
|
+
artifact: {
|
|
3193
|
+
description: "Act on an artifact ID instead of the linked folder",
|
|
3194
|
+
type: "string"
|
|
3195
|
+
},
|
|
3196
|
+
body: {
|
|
3197
|
+
description: "New comment text (omit to read from stdin)",
|
|
3198
|
+
type: "string"
|
|
3199
|
+
},
|
|
3200
|
+
dir: {
|
|
3201
|
+
default: ".",
|
|
3202
|
+
description: "Linked directory to authorize from (default: .)",
|
|
3203
|
+
type: "string"
|
|
3204
|
+
},
|
|
3205
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
3206
|
+
message: {
|
|
3207
|
+
description: "Message id (from `comments --json`)",
|
|
3208
|
+
required: true,
|
|
3209
|
+
type: "positional"
|
|
3210
|
+
}
|
|
3211
|
+
},
|
|
3212
|
+
meta: { description: "Edit a comment you authored", name: "edit" },
|
|
3213
|
+
async run({ args }) {
|
|
3214
|
+
try {
|
|
3215
|
+
await editMessage({
|
|
3216
|
+
apiKey: args["api-key"],
|
|
3217
|
+
artifact: args.artifact,
|
|
3218
|
+
body: args.body,
|
|
3219
|
+
dir: args.dir,
|
|
3220
|
+
json: Boolean(args.json),
|
|
3221
|
+
message: args.message
|
|
3222
|
+
});
|
|
3223
|
+
} catch (err) {
|
|
3224
|
+
printCliError(err, Boolean(args.json));
|
|
3225
|
+
process.exit(1);
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
});
|
|
3229
|
+
var del = defineCommand({
|
|
3230
|
+
args: {
|
|
3231
|
+
"api-key": {
|
|
3232
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
3233
|
+
type: "string"
|
|
3234
|
+
},
|
|
3235
|
+
artifact: {
|
|
3236
|
+
description: "Act on an artifact ID instead of the linked folder",
|
|
3237
|
+
type: "string"
|
|
3238
|
+
},
|
|
3239
|
+
dir: {
|
|
3240
|
+
default: ".",
|
|
3241
|
+
description: "Linked directory to authorize from (default: .)",
|
|
3242
|
+
type: "string"
|
|
3243
|
+
},
|
|
3244
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
3245
|
+
message: {
|
|
3246
|
+
description: "Message id (from `comments --json`)",
|
|
3247
|
+
required: true,
|
|
3248
|
+
type: "positional"
|
|
3249
|
+
}
|
|
3250
|
+
},
|
|
3251
|
+
meta: { description: "Delete a comment you authored", name: "delete" },
|
|
3252
|
+
async run({ args }) {
|
|
3253
|
+
try {
|
|
3254
|
+
await deleteMessage({
|
|
3255
|
+
apiKey: args["api-key"],
|
|
3256
|
+
artifact: args.artifact,
|
|
3257
|
+
dir: args.dir,
|
|
3258
|
+
json: Boolean(args.json),
|
|
3259
|
+
message: args.message
|
|
3260
|
+
});
|
|
3261
|
+
} catch (err) {
|
|
3262
|
+
printCliError(err, Boolean(args.json));
|
|
3263
|
+
process.exit(1);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
});
|
|
2654
3267
|
var open = defineCommand({
|
|
2655
3268
|
args: {
|
|
2656
3269
|
"api-key": {
|
|
@@ -2720,8 +3333,11 @@ var main = defineCommand({
|
|
|
2720
3333
|
await showUsage(cmd);
|
|
2721
3334
|
},
|
|
2722
3335
|
subCommands: {
|
|
3336
|
+
comment: comment2,
|
|
2723
3337
|
comments: comments2,
|
|
2724
3338
|
dashboard,
|
|
3339
|
+
delete: del,
|
|
3340
|
+
edit,
|
|
2725
3341
|
folders: folders2,
|
|
2726
3342
|
link: link2,
|
|
2727
3343
|
list: list2,
|