artifact-cafe 0.0.4 → 0.0.7
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 +962 -243
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { defineCommand, runMain } from "citty";
|
|
4
|
+
import { defineCommand, runMain, showUsage } from "citty";
|
|
5
|
+
|
|
6
|
+
// src/banner.ts
|
|
7
|
+
var AMBER = "\x1B[38;2;214;138;57m";
|
|
8
|
+
var BOLD = "\x1B[1m";
|
|
9
|
+
var RESET = "\x1B[0m";
|
|
10
|
+
var MUG = [
|
|
11
|
+
" \u2596",
|
|
12
|
+
" \u2598 \u259D",
|
|
13
|
+
" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
|
|
14
|
+
" \u2502 \u251C\u2500\u256E",
|
|
15
|
+
" \u2502 \u2502 \u2502",
|
|
16
|
+
" \u2502 \u251C\u2500\u256F",
|
|
17
|
+
" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
|
|
18
|
+
" \u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E"
|
|
19
|
+
];
|
|
20
|
+
var WORDMARK = " artifact.cafe";
|
|
21
|
+
function printBanner() {
|
|
22
|
+
const color = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
23
|
+
const paint = (line, codes) => color ? `${codes}${line}${RESET}` : line;
|
|
24
|
+
console.log();
|
|
25
|
+
for (const line of MUG) {
|
|
26
|
+
console.log(paint(line, AMBER));
|
|
27
|
+
}
|
|
28
|
+
console.log();
|
|
29
|
+
console.log(paint(WORDMARK, AMBER + BOLD));
|
|
30
|
+
console.log();
|
|
31
|
+
}
|
|
5
32
|
|
|
6
33
|
// src/comments.ts
|
|
7
|
-
import
|
|
34
|
+
import path3 from "path";
|
|
8
35
|
|
|
9
36
|
// ../shared/src/account.ts
|
|
10
37
|
import { z } from "zod";
|
|
@@ -103,8 +130,11 @@ var threadActionSchema = z2.object({
|
|
|
103
130
|
guest: guestSchema.optional()
|
|
104
131
|
});
|
|
105
132
|
var commentAuthorSchema = z2.object({
|
|
133
|
+
avatarUrl: z2.string().nullable(),
|
|
106
134
|
displayName: z2.string(),
|
|
107
|
-
guestId: z2.string().nullable()
|
|
135
|
+
guestId: z2.string().nullable(),
|
|
136
|
+
isViewer: z2.boolean(),
|
|
137
|
+
kind: z2.enum(["member", "guest"])
|
|
108
138
|
});
|
|
109
139
|
var commentMessageSchema = z2.object({
|
|
110
140
|
author: commentAuthorSchema,
|
|
@@ -131,12 +161,14 @@ var commentExportQuerySchema = z2.object({
|
|
|
131
161
|
});
|
|
132
162
|
var commentExportReplySchema = z2.object({
|
|
133
163
|
author: z2.string(),
|
|
164
|
+
authorKind: z2.enum(["member", "guest"]),
|
|
134
165
|
body: z2.string(),
|
|
135
166
|
createdAt: z2.string()
|
|
136
167
|
});
|
|
137
168
|
var commentExportThreadSchema = z2.object({
|
|
138
169
|
anchor: anchorSchema,
|
|
139
170
|
author: z2.string(),
|
|
171
|
+
authorKind: z2.enum(["member", "guest"]),
|
|
140
172
|
body: z2.string(),
|
|
141
173
|
createdAt: z2.string(),
|
|
142
174
|
id: z2.string(),
|
|
@@ -172,6 +204,15 @@ import { z as z4 } from "zod";
|
|
|
172
204
|
var ownArtifactSchema = z4.object({
|
|
173
205
|
createdAt: z4.string(),
|
|
174
206
|
currentVersionNumber: z4.number().nullable(),
|
|
207
|
+
// Owned artifacts normally never expire; non-null only while an
|
|
208
|
+
// expiry is still set (e.g. an unclaimed publish surfaced here later).
|
|
209
|
+
expiresAt: z4.string().nullable(),
|
|
210
|
+
// SPEC-2 §8.4 — the containing folder's name for workspace-owned
|
|
211
|
+
// artifacts; always null for personal artifacts.
|
|
212
|
+
folderName: z4.string().nullable(),
|
|
213
|
+
// Whether a publish token is still active — revocable from the
|
|
214
|
+
// dashboard (§6.18); the token itself is never returned.
|
|
215
|
+
hasPublishToken: z4.boolean(),
|
|
175
216
|
id: z4.string(),
|
|
176
217
|
openThreads: z4.number(),
|
|
177
218
|
protected: z4.boolean(),
|
|
@@ -185,64 +226,217 @@ var ownArtifactSchema = z4.object({
|
|
|
185
226
|
var ownArtifactsResponseSchema = z4.object({
|
|
186
227
|
artifacts: z4.array(ownArtifactSchema)
|
|
187
228
|
});
|
|
229
|
+
var revokePublishTokenResponseSchema = z4.object({
|
|
230
|
+
revoked: z4.boolean()
|
|
231
|
+
});
|
|
188
232
|
|
|
189
233
|
// ../shared/src/owner.ts
|
|
234
|
+
import { z as z6 } from "zod";
|
|
235
|
+
|
|
236
|
+
// ../shared/src/workspaces.ts
|
|
190
237
|
import { z as z5 } from "zod";
|
|
191
|
-
var
|
|
192
|
-
var
|
|
193
|
-
|
|
238
|
+
var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/;
|
|
239
|
+
var workspaceRoleSchema = z5.enum(["owner", "admin", "member"]);
|
|
240
|
+
var workspaceInviteRoleSchema = z5.enum(["admin", "member"]);
|
|
241
|
+
var workspaceHandleSchema = z5.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
|
|
242
|
+
message: "handle must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
243
|
+
});
|
|
244
|
+
var createWorkspaceSchema = z5.object({
|
|
245
|
+
handle: workspaceHandleSchema.optional(),
|
|
246
|
+
name: z5.string().trim().min(1).max(100)
|
|
247
|
+
});
|
|
248
|
+
var updateWorkspaceSchema = z5.object({
|
|
249
|
+
handle: workspaceHandleSchema.optional(),
|
|
250
|
+
name: z5.string().trim().min(1).max(100).optional()
|
|
251
|
+
}).refine((body) => body.handle !== void 0 || body.name !== void 0, {
|
|
252
|
+
message: "provide at least one of name or handle"
|
|
253
|
+
});
|
|
254
|
+
var workspaceSchema = z5.object({
|
|
255
|
+
createdAt: z5.string(),
|
|
256
|
+
handle: z5.string(),
|
|
257
|
+
id: z5.string(),
|
|
258
|
+
name: z5.string(),
|
|
259
|
+
role: workspaceRoleSchema.nullable(),
|
|
260
|
+
updatedAt: z5.string()
|
|
261
|
+
});
|
|
262
|
+
var workspaceListResponseSchema = z5.object({
|
|
263
|
+
workspaces: z5.array(workspaceSchema)
|
|
264
|
+
});
|
|
265
|
+
var deleteWorkspaceResponseSchema = z5.object({
|
|
266
|
+
deleted: z5.literal(true)
|
|
267
|
+
});
|
|
268
|
+
var workspaceMemberSchema = z5.object({
|
|
269
|
+
createdAt: z5.string(),
|
|
270
|
+
email: z5.string().email(),
|
|
271
|
+
image: z5.string().nullable(),
|
|
272
|
+
name: z5.string(),
|
|
273
|
+
role: workspaceRoleSchema,
|
|
274
|
+
userId: z5.string()
|
|
275
|
+
});
|
|
276
|
+
var workspaceMemberListResponseSchema = z5.object({
|
|
277
|
+
members: z5.array(workspaceMemberSchema)
|
|
278
|
+
});
|
|
279
|
+
var updateWorkspaceMemberSchema = z5.object({
|
|
280
|
+
role: workspaceRoleSchema
|
|
281
|
+
});
|
|
282
|
+
var removeWorkspaceMemberResponseSchema = z5.object({
|
|
283
|
+
removed: z5.literal(true)
|
|
284
|
+
});
|
|
285
|
+
var createWorkspaceInviteSchema = z5.object({
|
|
286
|
+
email: z5.string().trim().toLowerCase().email().optional(),
|
|
287
|
+
expiresAt: z5.string().datetime(),
|
|
288
|
+
maxUses: z5.number().int().positive().optional(),
|
|
289
|
+
role: workspaceInviteRoleSchema
|
|
290
|
+
});
|
|
291
|
+
var workspaceInviteSchema = z5.object({
|
|
292
|
+
createdAt: z5.string(),
|
|
293
|
+
email: z5.string().email().nullable(),
|
|
294
|
+
expiresAt: z5.string(),
|
|
295
|
+
id: z5.string(),
|
|
296
|
+
maxUses: z5.number().int().positive().nullable(),
|
|
297
|
+
role: workspaceInviteRoleSchema,
|
|
298
|
+
tokenPrefix: z5.string(),
|
|
299
|
+
useCount: z5.number().int().nonnegative()
|
|
300
|
+
});
|
|
301
|
+
var createdWorkspaceInviteSchema = workspaceInviteSchema.extend({
|
|
302
|
+
url: z5.string().url()
|
|
303
|
+
});
|
|
304
|
+
var workspaceInviteListResponseSchema = z5.object({
|
|
305
|
+
invites: z5.array(workspaceInviteSchema)
|
|
306
|
+
});
|
|
307
|
+
var revokeWorkspaceInviteResponseSchema = z5.object({
|
|
308
|
+
revoked: z5.literal(true)
|
|
309
|
+
});
|
|
310
|
+
var acceptWorkspaceInviteSchema = z5.object({
|
|
311
|
+
token: z5.string().min(1)
|
|
312
|
+
});
|
|
313
|
+
var acceptWorkspaceInviteResponseSchema = z5.object({
|
|
314
|
+
role: workspaceRoleSchema,
|
|
315
|
+
workspace: workspaceSchema
|
|
316
|
+
});
|
|
317
|
+
var folderVisibilitySchema = z5.enum(["public", "workspace"]);
|
|
318
|
+
var artifactVisibilitySchema = z5.enum([
|
|
319
|
+
"public",
|
|
320
|
+
"unlisted",
|
|
321
|
+
"workspace"
|
|
322
|
+
]);
|
|
323
|
+
var createFolderSchema = z5.object({
|
|
324
|
+
name: z5.string().trim().min(1).max(100),
|
|
325
|
+
visibility: folderVisibilitySchema
|
|
326
|
+
});
|
|
327
|
+
var updateFolderSchema = z5.object({
|
|
328
|
+
// With `visibility`: bulk-update the folder's artifacts to match
|
|
329
|
+
// (SPEC-2 §3.4 "Changing a folder's visibility offers a bulk update").
|
|
330
|
+
cascade: z5.boolean().optional(),
|
|
331
|
+
name: z5.string().trim().min(1).max(100).optional(),
|
|
332
|
+
sortOrder: z5.number().int().min(0).optional(),
|
|
333
|
+
visibility: folderVisibilitySchema.optional()
|
|
334
|
+
}).refine(
|
|
335
|
+
(body) => body.name !== void 0 || body.visibility !== void 0 || body.sortOrder !== void 0,
|
|
336
|
+
{ message: "provide at least one of name, visibility, sortOrder" }
|
|
337
|
+
).refine((body) => !body.cascade || body.visibility !== void 0, {
|
|
338
|
+
message: "cascade requires visibility"
|
|
339
|
+
});
|
|
340
|
+
var folderSchema = z5.object({
|
|
341
|
+
createdAt: z5.string(),
|
|
342
|
+
id: z5.string(),
|
|
343
|
+
name: z5.string(),
|
|
344
|
+
// Permalink segment for the public folder page (/w/{handle}/f/{slug}).
|
|
345
|
+
// Server-derived from the name; renames regenerate it.
|
|
346
|
+
slug: z5.string(),
|
|
347
|
+
sortOrder: z5.number().int(),
|
|
348
|
+
updatedAt: z5.string(),
|
|
349
|
+
visibility: folderVisibilitySchema,
|
|
350
|
+
workspaceId: z5.string()
|
|
351
|
+
});
|
|
352
|
+
var folderListResponseSchema = z5.object({
|
|
353
|
+
folders: z5.array(folderSchema)
|
|
354
|
+
});
|
|
355
|
+
var deleteFolderResponseSchema = z5.object({
|
|
356
|
+
deleted: z5.literal(true)
|
|
357
|
+
});
|
|
358
|
+
var workspaceArtifactSchema = z5.object({
|
|
359
|
+
createdAt: z5.string(),
|
|
360
|
+
currentVersionNumber: z5.number().int().nullable(),
|
|
361
|
+
folderId: z5.string().nullable(),
|
|
362
|
+
id: z5.string(),
|
|
363
|
+
openCommentCount: z5.number().int().nonnegative(),
|
|
194
364
|
slug: z5.string(),
|
|
195
365
|
title: z5.string(),
|
|
196
|
-
|
|
366
|
+
updatedAt: z5.string(),
|
|
367
|
+
url: z5.string().url(),
|
|
368
|
+
versionCount: z5.number().int().nonnegative(),
|
|
369
|
+
visibility: artifactVisibilitySchema
|
|
370
|
+
});
|
|
371
|
+
var workspaceArtifactListResponseSchema = z5.object({
|
|
372
|
+
artifacts: z5.array(workspaceArtifactSchema)
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// ../shared/src/owner.ts
|
|
376
|
+
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
|
|
377
|
+
var claimResponseSchema = z6.object({
|
|
378
|
+
artifactId: z6.string(),
|
|
379
|
+
slug: z6.string(),
|
|
380
|
+
title: z6.string(),
|
|
381
|
+
url: z6.string().url()
|
|
197
382
|
});
|
|
198
|
-
var updateArtifactSchema =
|
|
199
|
-
|
|
383
|
+
var updateArtifactSchema = z6.object({
|
|
384
|
+
// SPEC-2 §6.4 — placement + visibility. `folderId: null` clears the
|
|
385
|
+
// folder; `workspaceId: null` moves the artifact out of its workspace
|
|
386
|
+
// (§5.5 ownership transfer).
|
|
387
|
+
folderId: z6.string().min(1).nullable().optional(),
|
|
388
|
+
slug: z6.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
200
389
|
message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
201
390
|
}).optional(),
|
|
202
|
-
title:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
|
|
391
|
+
title: z6.string().min(1).max(200).optional(),
|
|
392
|
+
visibility: artifactVisibilitySchema.optional(),
|
|
393
|
+
workspaceId: z6.string().min(1).nullable().optional()
|
|
394
|
+
}).refine(
|
|
395
|
+
(body) => body.title !== void 0 || body.slug !== void 0 || body.visibility !== void 0 || body.folderId !== void 0 || body.workspaceId !== void 0,
|
|
396
|
+
{
|
|
397
|
+
message: "provide at least one of title, slug, visibility, folderId, workspaceId"
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
var deleteArtifactResponseSchema = z6.object({
|
|
401
|
+
deleted: z6.literal(true)
|
|
208
402
|
});
|
|
209
403
|
|
|
210
404
|
// ../shared/src/presence.ts
|
|
211
|
-
import { z as
|
|
405
|
+
import { z as z7 } from "zod";
|
|
212
406
|
var AGENT_PRESENCE = {
|
|
213
407
|
heartbeatSeconds: 15,
|
|
214
408
|
shellPollSeconds: 15,
|
|
215
409
|
ttlSeconds: 45
|
|
216
410
|
};
|
|
217
|
-
var agentHeartbeatResponseSchema =
|
|
218
|
-
listening:
|
|
219
|
-
ttlSeconds:
|
|
411
|
+
var agentHeartbeatResponseSchema = z7.object({
|
|
412
|
+
listening: z7.literal(true),
|
|
413
|
+
ttlSeconds: z7.number().int().positive()
|
|
220
414
|
});
|
|
221
|
-
var agentPresenceSchema =
|
|
222
|
-
listening:
|
|
223
|
-
versionNumber:
|
|
415
|
+
var agentPresenceSchema = z7.object({
|
|
416
|
+
listening: z7.boolean(),
|
|
417
|
+
versionNumber: z7.number().int().positive().nullable()
|
|
224
418
|
});
|
|
225
419
|
|
|
226
420
|
// ../shared/src/protection.ts
|
|
227
|
-
import { z as
|
|
421
|
+
import { z as z8 } from "zod";
|
|
228
422
|
var PASSWORD_MIN = 6;
|
|
229
423
|
var PASSWORD_MAX = 128;
|
|
230
|
-
var gatePasswordSchema =
|
|
231
|
-
var setProtectionSchema =
|
|
424
|
+
var gatePasswordSchema = z8.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
|
|
425
|
+
var setProtectionSchema = z8.object({
|
|
232
426
|
password: gatePasswordSchema.nullable()
|
|
233
427
|
});
|
|
234
|
-
var protectionResponseSchema =
|
|
235
|
-
protected:
|
|
428
|
+
var protectionResponseSchema = z8.object({
|
|
429
|
+
protected: z8.boolean()
|
|
236
430
|
});
|
|
237
|
-
var unlockSchema =
|
|
238
|
-
password:
|
|
431
|
+
var unlockSchema = z8.object({
|
|
432
|
+
password: z8.string().min(1).max(1024)
|
|
239
433
|
});
|
|
240
|
-
var unlockResponseSchema =
|
|
241
|
-
unlocked:
|
|
434
|
+
var unlockResponseSchema = z8.object({
|
|
435
|
+
unlocked: z8.literal(true)
|
|
242
436
|
});
|
|
243
437
|
|
|
244
438
|
// ../shared/src/publish.ts
|
|
245
|
-
import { z as
|
|
439
|
+
import { z as z9 } from "zod";
|
|
246
440
|
function isSafeArtifactPath(p) {
|
|
247
441
|
if (p.length === 0 || p.length > 1024) {
|
|
248
442
|
return false;
|
|
@@ -255,128 +449,136 @@ function isSafeArtifactPath(p) {
|
|
|
255
449
|
}
|
|
256
450
|
return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
|
|
257
451
|
}
|
|
258
|
-
var manifestFileSchema =
|
|
259
|
-
hash:
|
|
260
|
-
mimeType:
|
|
261
|
-
path:
|
|
452
|
+
var manifestFileSchema = z9.object({
|
|
453
|
+
hash: z9.string().regex(/^sha256_[a-f0-9]{64}$/),
|
|
454
|
+
mimeType: z9.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
|
|
455
|
+
path: z9.string().min(1).max(1024).refine(isSafeArtifactPath, {
|
|
262
456
|
message: "path must be a safe relative path"
|
|
263
457
|
}),
|
|
264
|
-
size:
|
|
458
|
+
size: z9.number().int().positive()
|
|
265
459
|
});
|
|
266
|
-
var publishSourceSchema =
|
|
267
|
-
agent:
|
|
268
|
-
runId:
|
|
269
|
-
type:
|
|
460
|
+
var publishSourceSchema = z9.object({
|
|
461
|
+
agent: z9.string().optional(),
|
|
462
|
+
runId: z9.string().optional(),
|
|
463
|
+
type: z9.literal("cli")
|
|
270
464
|
});
|
|
271
|
-
var createPublishSessionSchema =
|
|
272
|
-
artifactId:
|
|
273
|
-
entryPath:
|
|
274
|
-
files:
|
|
465
|
+
var createPublishSessionSchema = z9.object({
|
|
466
|
+
artifactId: z9.string().nullable(),
|
|
467
|
+
entryPath: z9.string().optional(),
|
|
468
|
+
files: z9.array(manifestFileSchema).min(1).max(1e3),
|
|
469
|
+
// SPEC-2 §6.4 — workspace placement is available only to authenticated
|
|
470
|
+
// members; the API enforces membership and folder ownership.
|
|
471
|
+
folderId: z9.string().min(1).optional(),
|
|
275
472
|
source: publishSourceSchema,
|
|
276
|
-
title:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
473
|
+
title: z9.string().max(200).optional(),
|
|
474
|
+
workspaceId: z9.string().min(1).optional()
|
|
475
|
+
});
|
|
476
|
+
var publishSessionResponseSchema = z9.object({
|
|
477
|
+
artifactId: z9.string(),
|
|
478
|
+
expiresAt: z9.string(),
|
|
479
|
+
filesToUpload: z9.array(
|
|
480
|
+
z9.object({
|
|
481
|
+
method: z9.literal("PUT"),
|
|
482
|
+
path: z9.string(),
|
|
483
|
+
uploadUrl: z9.string().url()
|
|
286
484
|
})
|
|
287
485
|
),
|
|
288
|
-
publishSessionId:
|
|
289
|
-
skippedFiles:
|
|
290
|
-
versionId:
|
|
486
|
+
publishSessionId: z9.string(),
|
|
487
|
+
skippedFiles: z9.array(z9.string()),
|
|
488
|
+
versionId: z9.string()
|
|
291
489
|
});
|
|
292
|
-
var finalizeResponseSchema =
|
|
293
|
-
artifactId:
|
|
490
|
+
var finalizeResponseSchema = z9.object({
|
|
491
|
+
artifactId: z9.string(),
|
|
294
492
|
// API-key publishes create the artifact already claimed (SPEC §5.4) —
|
|
295
493
|
// the CLI uses this to tell "claim URL pending" from "already yours".
|
|
296
|
-
claimed:
|
|
494
|
+
claimed: z9.boolean(),
|
|
297
495
|
// Returned only on first publish of a new artifact (one-time).
|
|
298
|
-
claimUrl:
|
|
299
|
-
publishToken:
|
|
300
|
-
url:
|
|
301
|
-
versionId:
|
|
302
|
-
versionNumber:
|
|
303
|
-
});
|
|
304
|
-
var artifactResponseSchema =
|
|
305
|
-
claimed:
|
|
306
|
-
currentVersion:
|
|
307
|
-
entryUrl:
|
|
308
|
-
id:
|
|
309
|
-
versionNumber:
|
|
496
|
+
claimUrl: z9.string().url().optional(),
|
|
497
|
+
publishToken: z9.string().optional(),
|
|
498
|
+
url: z9.string().url(),
|
|
499
|
+
versionId: z9.string(),
|
|
500
|
+
versionNumber: z9.number().int().positive()
|
|
501
|
+
});
|
|
502
|
+
var artifactResponseSchema = z9.object({
|
|
503
|
+
claimed: z9.boolean(),
|
|
504
|
+
currentVersion: z9.object({
|
|
505
|
+
entryUrl: z9.string().url(),
|
|
506
|
+
id: z9.string(),
|
|
507
|
+
versionNumber: z9.number().int().positive()
|
|
310
508
|
}).nullable(),
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
509
|
+
// SPEC-2 §6.4 — placement + visibility surface on the artifact.
|
|
510
|
+
folderId: z9.string().nullable(),
|
|
511
|
+
id: z9.string(),
|
|
512
|
+
slug: z9.string(),
|
|
513
|
+
title: z9.string(),
|
|
514
|
+
visibility: z9.enum(["public", "unlisted", "workspace"]),
|
|
515
|
+
workspaceId: z9.string().nullable()
|
|
314
516
|
});
|
|
315
517
|
|
|
316
518
|
// ../shared/src/report.ts
|
|
317
|
-
import { z as
|
|
318
|
-
var reportRequestSchema =
|
|
319
|
-
reason:
|
|
519
|
+
import { z as z10 } from "zod";
|
|
520
|
+
var reportRequestSchema = z10.object({
|
|
521
|
+
reason: z10.string().max(1e3).optional()
|
|
320
522
|
});
|
|
321
|
-
var reportResponseSchema =
|
|
322
|
-
reported:
|
|
523
|
+
var reportResponseSchema = z10.object({
|
|
524
|
+
reported: z10.literal(true)
|
|
323
525
|
});
|
|
324
526
|
|
|
325
527
|
// ../shared/src/stats.ts
|
|
326
|
-
import { z as
|
|
327
|
-
var avgViewSecondsSchema =
|
|
328
|
-
var versionViewStatsSchema =
|
|
528
|
+
import { z as z11 } from "zod";
|
|
529
|
+
var avgViewSecondsSchema = z11.number().int().nonnegative().nullable();
|
|
530
|
+
var versionViewStatsSchema = z11.object({
|
|
329
531
|
avgViewSeconds: avgViewSecondsSchema,
|
|
330
532
|
// Views from inside the review shell's iframe vs. direct visits to
|
|
331
533
|
// the render URL. views = embeddedViews + directViews.
|
|
332
|
-
directViews:
|
|
333
|
-
embeddedViews:
|
|
534
|
+
directViews: z11.number().int().nonnegative(),
|
|
535
|
+
embeddedViews: z11.number().int().nonnegative(),
|
|
334
536
|
// Distinct hashed viewer IPs across the version's lifetime.
|
|
335
|
-
uniqueViewers:
|
|
336
|
-
versionNumber:
|
|
337
|
-
views:
|
|
537
|
+
uniqueViewers: z11.number().int().nonnegative(),
|
|
538
|
+
versionNumber: z11.number().int().positive(),
|
|
539
|
+
views: z11.number().int().nonnegative()
|
|
338
540
|
});
|
|
339
|
-
var dailyViewsSchema =
|
|
541
|
+
var dailyViewsSchema = z11.object({
|
|
340
542
|
avgViewSeconds: avgViewSecondsSchema,
|
|
341
543
|
// UTC day, YYYY-MM-DD.
|
|
342
|
-
day:
|
|
343
|
-
uniqueViewers:
|
|
344
|
-
views:
|
|
544
|
+
day: z11.string(),
|
|
545
|
+
uniqueViewers: z11.number().int().nonnegative(),
|
|
546
|
+
views: z11.number().int().nonnegative()
|
|
345
547
|
});
|
|
346
|
-
var artifactStatsResponseSchema =
|
|
548
|
+
var artifactStatsResponseSchema = z11.object({
|
|
347
549
|
avgViewSeconds: avgViewSecondsSchema,
|
|
348
|
-
daily:
|
|
550
|
+
daily: z11.array(dailyViewsSchema),
|
|
349
551
|
// Distinct hashed viewer IPs across all versions and days.
|
|
350
|
-
totalUniqueViewers:
|
|
351
|
-
totalViews:
|
|
352
|
-
versions:
|
|
552
|
+
totalUniqueViewers: z11.number().int().nonnegative(),
|
|
553
|
+
totalViews: z11.number().int().nonnegative(),
|
|
554
|
+
versions: z11.array(versionViewStatsSchema)
|
|
353
555
|
});
|
|
354
556
|
|
|
355
557
|
// ../shared/src/versions.ts
|
|
356
|
-
import { z as
|
|
357
|
-
var versionSourceSchema =
|
|
358
|
-
agentName:
|
|
359
|
-
type:
|
|
558
|
+
import { z as z12 } from "zod";
|
|
559
|
+
var versionSourceSchema = z12.object({
|
|
560
|
+
agentName: z12.string().nullable(),
|
|
561
|
+
type: z12.string()
|
|
360
562
|
});
|
|
361
|
-
var versionChangesSchema =
|
|
362
|
-
added:
|
|
363
|
-
changed:
|
|
364
|
-
removed:
|
|
563
|
+
var versionChangesSchema = z12.object({
|
|
564
|
+
added: z12.array(z12.string()),
|
|
565
|
+
changed: z12.array(z12.string()),
|
|
566
|
+
removed: z12.array(z12.string())
|
|
365
567
|
});
|
|
366
|
-
var versionSummarySchema =
|
|
568
|
+
var versionSummarySchema = z12.object({
|
|
367
569
|
// Absent for the first version — there is no previous version to diff against.
|
|
368
570
|
changes: versionChangesSchema.nullable(),
|
|
369
|
-
createdAt:
|
|
370
|
-
entryUrl:
|
|
371
|
-
fileCount:
|
|
372
|
-
id:
|
|
373
|
-
isCurrent:
|
|
571
|
+
createdAt: z12.string(),
|
|
572
|
+
entryUrl: z12.string().url(),
|
|
573
|
+
fileCount: z12.number().int().nonnegative(),
|
|
574
|
+
id: z12.string(),
|
|
575
|
+
isCurrent: z12.boolean(),
|
|
374
576
|
source: versionSourceSchema,
|
|
375
|
-
totalSizeBytes:
|
|
376
|
-
versionNumber:
|
|
577
|
+
totalSizeBytes: z12.number().int().nonnegative(),
|
|
578
|
+
versionNumber: z12.number().int().positive()
|
|
377
579
|
});
|
|
378
|
-
var versionListResponseSchema =
|
|
379
|
-
versions:
|
|
580
|
+
var versionListResponseSchema = z12.object({
|
|
581
|
+
versions: z12.array(versionSummarySchema)
|
|
380
582
|
});
|
|
381
583
|
|
|
382
584
|
// src/api.ts
|
|
@@ -480,6 +682,20 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
480
682
|
(d) => artifactListResponseSchema.parse(d)
|
|
481
683
|
);
|
|
482
684
|
},
|
|
685
|
+
async listFolders(workspaceId) {
|
|
686
|
+
return request(
|
|
687
|
+
`${base}/api/v1/workspaces/${workspaceId}/folders`,
|
|
688
|
+
{ headers, method: "GET" },
|
|
689
|
+
(d) => folderListResponseSchema.parse(d)
|
|
690
|
+
);
|
|
691
|
+
},
|
|
692
|
+
async listWorkspaces() {
|
|
693
|
+
return request(
|
|
694
|
+
`${base}/api/v1/workspaces`,
|
|
695
|
+
{ headers, method: "GET" },
|
|
696
|
+
(d) => workspaceListResponseSchema.parse(d)
|
|
697
|
+
);
|
|
698
|
+
},
|
|
483
699
|
// SPEC §6.14 — who the presented credential belongs to. Used by
|
|
484
700
|
// `login` to verify a pasted key and by `whoami`.
|
|
485
701
|
async me() {
|
|
@@ -519,37 +735,103 @@ async function uploadFile(uploadUrl, content, mimeType) {
|
|
|
519
735
|
// src/config.ts
|
|
520
736
|
import {
|
|
521
737
|
appendFileSync,
|
|
522
|
-
existsSync,
|
|
523
|
-
mkdirSync,
|
|
524
|
-
readFileSync,
|
|
525
|
-
writeFileSync
|
|
738
|
+
existsSync as existsSync2,
|
|
739
|
+
mkdirSync as mkdirSync2,
|
|
740
|
+
readFileSync as readFileSync2,
|
|
741
|
+
writeFileSync as writeFileSync2
|
|
526
742
|
} from "fs";
|
|
743
|
+
import path2 from "path";
|
|
744
|
+
import { z as z14 } from "zod";
|
|
745
|
+
|
|
746
|
+
// src/credentials.ts
|
|
747
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
748
|
+
import { homedir } from "os";
|
|
527
749
|
import path from "path";
|
|
528
|
-
import { z as
|
|
529
|
-
var
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
750
|
+
import { z as z13 } from "zod";
|
|
751
|
+
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
752
|
+
var credentialsSchema = z13.object({
|
|
753
|
+
keys: z13.record(z13.string(), z13.string()),
|
|
754
|
+
version: z13.literal(1)
|
|
755
|
+
});
|
|
756
|
+
function credentialsPath() {
|
|
757
|
+
return path.join(homedir(), ".artifactcafe", "credentials");
|
|
758
|
+
}
|
|
759
|
+
function apiOrigin(apiUrl2) {
|
|
760
|
+
return new URL(apiUrl2).origin;
|
|
761
|
+
}
|
|
762
|
+
function readCredentials() {
|
|
763
|
+
const file = credentialsPath();
|
|
764
|
+
if (!existsSync(file)) {
|
|
765
|
+
return { keys: {}, version: 1 };
|
|
766
|
+
}
|
|
767
|
+
try {
|
|
768
|
+
return credentialsSchema.parse(JSON.parse(readFileSync(file, "utf8")));
|
|
769
|
+
} catch {
|
|
770
|
+
return { keys: {}, version: 1 };
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function writeCredentials(credentials) {
|
|
774
|
+
const file = credentialsPath();
|
|
775
|
+
mkdirSync(path.dirname(file), { mode: 448, recursive: true });
|
|
776
|
+
writeFileSync(file, `${JSON.stringify(credentials, null, 2)}
|
|
777
|
+
`, {
|
|
778
|
+
mode: 384
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
function storedApiKey(apiUrl2) {
|
|
782
|
+
return readCredentials().keys[apiOrigin(apiUrl2)] ?? null;
|
|
783
|
+
}
|
|
784
|
+
function storeApiKey(apiUrl2, key) {
|
|
785
|
+
const credentials = readCredentials();
|
|
786
|
+
credentials.keys[apiOrigin(apiUrl2)] = key;
|
|
787
|
+
writeCredentials(credentials);
|
|
788
|
+
}
|
|
789
|
+
function removeApiKey(apiUrl2) {
|
|
790
|
+
const credentials = readCredentials();
|
|
791
|
+
const origin = apiOrigin(apiUrl2);
|
|
792
|
+
if (!(origin in credentials.keys)) {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
delete credentials.keys[origin];
|
|
796
|
+
writeCredentials(credentials);
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
function resolveApiKey(apiUrl2, flag) {
|
|
800
|
+
return flag ?? process.env.ARTIFACT_CAFE_API_KEY ?? storedApiKey(apiUrl2) ?? void 0;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/config.ts
|
|
804
|
+
var configSchema = z14.object({
|
|
805
|
+
apiUrl: z14.string(),
|
|
806
|
+
artifactId: z14.string(),
|
|
807
|
+
folder: z14.string().optional(),
|
|
808
|
+
folderId: z14.string().optional(),
|
|
809
|
+
// Optional: a folder linked by `link` to an artifact you own carries no
|
|
810
|
+
// token — owner actions authorize via your stored login instead. A first
|
|
811
|
+
// publish still writes the anonymous-mode token here.
|
|
812
|
+
publishToken: z14.string().optional(),
|
|
813
|
+
slug: z14.string(),
|
|
814
|
+
url: z14.string(),
|
|
815
|
+
workspace: z14.string().optional(),
|
|
816
|
+
workspaceId: z14.string().optional()
|
|
535
817
|
});
|
|
536
818
|
var CONFIG_DIR = ".artifactcafe";
|
|
537
819
|
var CONFIG_FILE = "config.json";
|
|
538
820
|
function readConfig(dir) {
|
|
539
|
-
const file =
|
|
540
|
-
if (!
|
|
821
|
+
const file = path2.join(dir, CONFIG_DIR, CONFIG_FILE);
|
|
822
|
+
if (!existsSync2(file)) {
|
|
541
823
|
return null;
|
|
542
824
|
}
|
|
543
825
|
try {
|
|
544
|
-
return configSchema.parse(JSON.parse(
|
|
826
|
+
return configSchema.parse(JSON.parse(readFileSync2(file, "utf8")));
|
|
545
827
|
} catch {
|
|
546
828
|
return null;
|
|
547
829
|
}
|
|
548
830
|
}
|
|
549
831
|
function writeConfig(dir, config) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
832
|
+
mkdirSync2(path2.join(dir, CONFIG_DIR), { recursive: true });
|
|
833
|
+
writeFileSync2(
|
|
834
|
+
path2.join(dir, CONFIG_DIR, CONFIG_FILE),
|
|
553
835
|
`${JSON.stringify(config, null, 2)}
|
|
554
836
|
`,
|
|
555
837
|
{ mode: 384 }
|
|
@@ -557,11 +839,11 @@ function writeConfig(dir, config) {
|
|
|
557
839
|
ensureGitignored(dir);
|
|
558
840
|
}
|
|
559
841
|
function ensureGitignored(dir) {
|
|
560
|
-
const gitignore =
|
|
561
|
-
if (!
|
|
842
|
+
const gitignore = path2.join(dir, ".gitignore");
|
|
843
|
+
if (!existsSync2(gitignore)) {
|
|
562
844
|
return;
|
|
563
845
|
}
|
|
564
|
-
const lines =
|
|
846
|
+
const lines = readFileSync2(gitignore, "utf8").split(/\r?\n/);
|
|
565
847
|
const present = lines.some((l) => {
|
|
566
848
|
const t = l.trim();
|
|
567
849
|
return t === CONFIG_DIR || t === `${CONFIG_DIR}/` || t === `/${CONFIG_DIR}` || t === `/${CONFIG_DIR}/`;
|
|
@@ -576,6 +858,23 @@ ${CONFIG_DIR}/
|
|
|
576
858
|
);
|
|
577
859
|
}
|
|
578
860
|
}
|
|
861
|
+
function reviewUrl(apiUrl2, slug) {
|
|
862
|
+
return `${apiUrl2.replace(/\/$/, "")}/a/${slug}`;
|
|
863
|
+
}
|
|
864
|
+
function resolveArtifactTarget(dir, opts = {}) {
|
|
865
|
+
const config = readConfig(dir);
|
|
866
|
+
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
|
|
867
|
+
const artifactId = opts.artifact ?? process.env.ARTIFACT_CAFE_ARTIFACT_ID ?? config?.artifactId ?? null;
|
|
868
|
+
if (!artifactId) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
`No artifact linked in ${dir}. Run \`artifact-cafe publish\` here, \`artifact-cafe link\` to bind one you own, or pass --artifact <id> (or set ARTIFACT_CAFE_ARTIFACT_ID) to act on an artifact linked elsewhere.`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
const folderToken = artifactId === config?.artifactId ? config.publishToken : void 0;
|
|
874
|
+
const credential = folderToken ?? process.env.ARTIFACT_CAFE_TOKEN ?? resolveApiKey(apiUrl2, opts.apiKey);
|
|
875
|
+
const url = artifactId === config?.artifactId ? config.url : void 0;
|
|
876
|
+
return { apiUrl: apiUrl2, artifactId, credential, url };
|
|
877
|
+
}
|
|
579
878
|
|
|
580
879
|
// src/comments.ts
|
|
581
880
|
var STATUSES = ["open", "resolved", "all"];
|
|
@@ -596,17 +895,16 @@ async function comments(opts) {
|
|
|
596
895
|
}
|
|
597
896
|
const status = opts.status;
|
|
598
897
|
const version = opts.version;
|
|
599
|
-
const dir =
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const
|
|
607
|
-
const client = createApiClient(apiUrl2, config.publishToken);
|
|
898
|
+
const dir = path3.resolve(opts.dir);
|
|
899
|
+
const target = resolveArtifactTarget(dir, {
|
|
900
|
+
apiKey: opts.apiKey,
|
|
901
|
+
artifact: opts.artifact
|
|
902
|
+
});
|
|
903
|
+
const client = createApiClient(target.apiUrl, target.credential);
|
|
904
|
+
const url = target.url ?? (opts.json ? "" : await deriveReviewUrl(client, target));
|
|
905
|
+
const ref = { artifactId: target.artifactId, url };
|
|
608
906
|
if (opts.wait) {
|
|
609
|
-
await waitForFeedback(client,
|
|
907
|
+
await waitForFeedback(client, ref, {
|
|
610
908
|
json: opts.json,
|
|
611
909
|
status,
|
|
612
910
|
timeoutSeconds: opts.timeout,
|
|
@@ -614,7 +912,7 @@ async function comments(opts) {
|
|
|
614
912
|
});
|
|
615
913
|
return;
|
|
616
914
|
}
|
|
617
|
-
const data = await client.exportComments(
|
|
915
|
+
const data = await client.exportComments(target.artifactId, {
|
|
618
916
|
status,
|
|
619
917
|
version
|
|
620
918
|
});
|
|
@@ -622,7 +920,11 @@ async function comments(opts) {
|
|
|
622
920
|
console.log(JSON.stringify(data, null, 2));
|
|
623
921
|
return;
|
|
624
922
|
}
|
|
625
|
-
printHuman(data,
|
|
923
|
+
printHuman(data, url, status, version);
|
|
924
|
+
}
|
|
925
|
+
async function deriveReviewUrl(client, target) {
|
|
926
|
+
const artifact = await client.getArtifact(target.artifactId);
|
|
927
|
+
return reviewUrl(target.apiUrl, artifact.slug);
|
|
626
928
|
}
|
|
627
929
|
var sleep = (s) => new Promise((r) => setTimeout(r, s * 1e3));
|
|
628
930
|
function messageCounts(data) {
|
|
@@ -777,63 +1079,6 @@ function indent(text, pad) {
|
|
|
777
1079
|
// src/dashboard.ts
|
|
778
1080
|
import path6 from "path";
|
|
779
1081
|
|
|
780
|
-
// src/credentials.ts
|
|
781
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
782
|
-
import { homedir } from "os";
|
|
783
|
-
import path3 from "path";
|
|
784
|
-
import { z as z13 } from "zod";
|
|
785
|
-
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
786
|
-
var credentialsSchema = z13.object({
|
|
787
|
-
keys: z13.record(z13.string(), z13.string()),
|
|
788
|
-
version: z13.literal(1)
|
|
789
|
-
});
|
|
790
|
-
function credentialsPath() {
|
|
791
|
-
return path3.join(homedir(), ".artifactcafe", "credentials");
|
|
792
|
-
}
|
|
793
|
-
function apiOrigin(apiUrl2) {
|
|
794
|
-
return new URL(apiUrl2).origin;
|
|
795
|
-
}
|
|
796
|
-
function readCredentials() {
|
|
797
|
-
const file = credentialsPath();
|
|
798
|
-
if (!existsSync2(file)) {
|
|
799
|
-
return { keys: {}, version: 1 };
|
|
800
|
-
}
|
|
801
|
-
try {
|
|
802
|
-
return credentialsSchema.parse(JSON.parse(readFileSync2(file, "utf8")));
|
|
803
|
-
} catch {
|
|
804
|
-
return { keys: {}, version: 1 };
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
function writeCredentials(credentials) {
|
|
808
|
-
const file = credentialsPath();
|
|
809
|
-
mkdirSync2(path3.dirname(file), { mode: 448, recursive: true });
|
|
810
|
-
writeFileSync2(file, `${JSON.stringify(credentials, null, 2)}
|
|
811
|
-
`, {
|
|
812
|
-
mode: 384
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
function storedApiKey(apiUrl2) {
|
|
816
|
-
return readCredentials().keys[apiOrigin(apiUrl2)] ?? null;
|
|
817
|
-
}
|
|
818
|
-
function storeApiKey(apiUrl2, key) {
|
|
819
|
-
const credentials = readCredentials();
|
|
820
|
-
credentials.keys[apiOrigin(apiUrl2)] = key;
|
|
821
|
-
writeCredentials(credentials);
|
|
822
|
-
}
|
|
823
|
-
function removeApiKey(apiUrl2) {
|
|
824
|
-
const credentials = readCredentials();
|
|
825
|
-
const origin = apiOrigin(apiUrl2);
|
|
826
|
-
if (!(origin in credentials.keys)) {
|
|
827
|
-
return false;
|
|
828
|
-
}
|
|
829
|
-
delete credentials.keys[origin];
|
|
830
|
-
writeCredentials(credentials);
|
|
831
|
-
return true;
|
|
832
|
-
}
|
|
833
|
-
function resolveApiKey(apiUrl2, flag) {
|
|
834
|
-
return flag ?? process.env.ARTIFACT_CAFE_API_KEY ?? storedApiKey(apiUrl2) ?? void 0;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
1082
|
// src/publish.ts
|
|
838
1083
|
import { exec } from "child_process";
|
|
839
1084
|
import { readFileSync as readFileSync4 } from "fs";
|
|
@@ -977,6 +1222,7 @@ async function publish(opts) {
|
|
|
977
1222
|
const token = (artifactId !== null && artifactId === config?.artifactId ? config.publishToken : void 0) ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
978
1223
|
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
|
|
979
1224
|
const apiKey = resolveApiKey(apiUrl2, opts.apiKey);
|
|
1225
|
+
const placement = await resolvePlacement({ apiKey, apiUrl: apiUrl2, config, opts });
|
|
980
1226
|
const { files, skipped } = scanDirectory(dir);
|
|
981
1227
|
if (files.length === 0) {
|
|
982
1228
|
throw new Error(`Nothing to publish in ${dir} (after ignore rules).`);
|
|
@@ -994,12 +1240,14 @@ async function publish(opts) {
|
|
|
994
1240
|
artifactId,
|
|
995
1241
|
entryPath,
|
|
996
1242
|
files: files.map(({ absPath: _absPath, ...manifest }) => manifest),
|
|
1243
|
+
...placement.folderId ? { folderId: placement.folderId } : {},
|
|
997
1244
|
source: {
|
|
998
1245
|
agent: process.env.ARTIFACT_CAFE_AGENT,
|
|
999
1246
|
runId: process.env.ARTIFACT_CAFE_RUN_ID,
|
|
1000
1247
|
type: "cli"
|
|
1001
1248
|
},
|
|
1002
|
-
title: opts.title
|
|
1249
|
+
title: opts.title,
|
|
1250
|
+
...placement.workspaceId ? { workspaceId: placement.workspaceId } : {}
|
|
1003
1251
|
};
|
|
1004
1252
|
let credential = apiKey ?? token;
|
|
1005
1253
|
let client = createApiClient(apiUrl2, credential);
|
|
@@ -1031,13 +1279,19 @@ async function publish(opts) {
|
|
|
1031
1279
|
);
|
|
1032
1280
|
});
|
|
1033
1281
|
const result = await client.finalize(session.publishSessionId);
|
|
1034
|
-
|
|
1282
|
+
const existingToken = config?.artifactId === result.artifactId ? config.publishToken : void 0;
|
|
1283
|
+
const shouldWriteConfig = result.publishToken !== void 0 || config?.artifactId === result.artifactId || opts.workspace !== void 0 || opts.folder !== void 0;
|
|
1284
|
+
if (shouldWriteConfig) {
|
|
1035
1285
|
writeConfig(dir, {
|
|
1036
1286
|
apiUrl: apiUrl2,
|
|
1037
1287
|
artifactId: result.artifactId,
|
|
1038
|
-
|
|
1288
|
+
...placement.folder ? { folder: placement.folder } : {},
|
|
1289
|
+
...placement.folderId ? { folderId: placement.folderId } : {},
|
|
1290
|
+
...result.publishToken ?? existingToken ? { publishToken: result.publishToken ?? existingToken } : {},
|
|
1039
1291
|
slug: slugFromUrl(result.url),
|
|
1040
|
-
url: result.url
|
|
1292
|
+
url: result.url,
|
|
1293
|
+
...placement.workspace ? { workspace: placement.workspace } : {},
|
|
1294
|
+
...placement.workspaceId ? { workspaceId: placement.workspaceId } : {}
|
|
1041
1295
|
});
|
|
1042
1296
|
}
|
|
1043
1297
|
if (opts.password !== void 0) {
|
|
@@ -1053,8 +1307,12 @@ async function publish(opts) {
|
|
|
1053
1307
|
{
|
|
1054
1308
|
artifactId: result.artifactId,
|
|
1055
1309
|
claimed: result.claimed,
|
|
1310
|
+
folder: placement.folder ?? null,
|
|
1311
|
+
folderId: placement.folderId ?? null,
|
|
1056
1312
|
url: result.url,
|
|
1057
1313
|
versionNumber: result.versionNumber,
|
|
1314
|
+
workspace: placement.workspace ?? null,
|
|
1315
|
+
workspaceId: placement.workspaceId ?? null,
|
|
1058
1316
|
...result.claimUrl ? { claimUrl: result.claimUrl } : {},
|
|
1059
1317
|
...opts.password === void 0 ? {} : { protected: true },
|
|
1060
1318
|
filesUploaded: session.filesToUpload.map((f) => f.path),
|
|
@@ -1096,6 +1354,76 @@ ${result.claimUrl}
|
|
|
1096
1354
|
openInBrowser(result.url);
|
|
1097
1355
|
}
|
|
1098
1356
|
}
|
|
1357
|
+
async function resolvePlacement(params) {
|
|
1358
|
+
const { apiKey, apiUrl: apiUrl2, config, opts } = params;
|
|
1359
|
+
const hasPlacementFlag = opts.workspace !== void 0 || opts.folder !== void 0;
|
|
1360
|
+
if (!hasPlacementFlag && config?.workspaceId) {
|
|
1361
|
+
if (!apiKey) {
|
|
1362
|
+
throw new Error(
|
|
1363
|
+
`Publishing into workspace ${config.workspace ?? config.workspaceId} needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
return {
|
|
1367
|
+
...config.folder ? { folder: config.folder } : {},
|
|
1368
|
+
...config.folderId ? { folderId: config.folderId } : {},
|
|
1369
|
+
...config.workspace ? { workspace: config.workspace } : {},
|
|
1370
|
+
workspaceId: config.workspaceId
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
if (!hasPlacementFlag) {
|
|
1374
|
+
return {};
|
|
1375
|
+
}
|
|
1376
|
+
if (!apiKey) {
|
|
1377
|
+
throw new Error(
|
|
1378
|
+
"Publishing into a workspace needs an account \u2014 run `artifact-cafe login` first."
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
const workspaceHandle = opts.workspace ?? config?.workspace;
|
|
1382
|
+
if (!workspaceHandle) {
|
|
1383
|
+
throw new Error("--folder requires --workspace or a stored workspace.");
|
|
1384
|
+
}
|
|
1385
|
+
const client = createApiClient(apiUrl2, apiKey);
|
|
1386
|
+
let workspaceId;
|
|
1387
|
+
if (opts.workspace === void 0 && config?.workspaceId) {
|
|
1388
|
+
({ workspaceId } = config);
|
|
1389
|
+
} else {
|
|
1390
|
+
const data2 = await client.listWorkspaces();
|
|
1391
|
+
const workspace = data2.workspaces.find(
|
|
1392
|
+
(candidate) => candidate.handle === workspaceHandle
|
|
1393
|
+
);
|
|
1394
|
+
if (!workspace) {
|
|
1395
|
+
throw new Error(
|
|
1396
|
+
`No workspace with handle "${workspaceHandle}" belongs to this account.`
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
workspaceId = workspace.id;
|
|
1400
|
+
}
|
|
1401
|
+
const folderName = opts.workspace !== void 0 && opts.folder === void 0 ? void 0 : opts.folder ?? config?.folder;
|
|
1402
|
+
if (!folderName) {
|
|
1403
|
+
return { workspace: workspaceHandle, workspaceId };
|
|
1404
|
+
}
|
|
1405
|
+
const data = await client.listFolders(workspaceId);
|
|
1406
|
+
const matches = data.folders.filter(
|
|
1407
|
+
(candidate) => candidate.name === folderName || candidate.id === folderName
|
|
1408
|
+
);
|
|
1409
|
+
if (matches.length === 0) {
|
|
1410
|
+
throw new Error(
|
|
1411
|
+
`No folder named "${folderName}" exists in workspace ${workspaceHandle}.`
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
if (matches.length > 1) {
|
|
1415
|
+
throw new Error(
|
|
1416
|
+
`More than one folder is named "${folderName}" in workspace ${workspaceHandle}; pass the folder id instead.`
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
const folder = matches[0];
|
|
1420
|
+
return {
|
|
1421
|
+
folder: folder.name,
|
|
1422
|
+
folderId: folder.id,
|
|
1423
|
+
workspace: workspaceHandle,
|
|
1424
|
+
workspaceId
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1099
1427
|
async function inBatches(items, concurrency, fn) {
|
|
1100
1428
|
let next = 0;
|
|
1101
1429
|
const workers = Array.from(
|
|
@@ -1169,6 +1497,148 @@ function printCliError(err, json) {
|
|
|
1169
1497
|
}
|
|
1170
1498
|
}
|
|
1171
1499
|
|
|
1500
|
+
// src/folders.ts
|
|
1501
|
+
async function folders(opts) {
|
|
1502
|
+
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
1503
|
+
/\/$/,
|
|
1504
|
+
""
|
|
1505
|
+
);
|
|
1506
|
+
const key = resolveApiKey(apiUrl2, opts.apiKey);
|
|
1507
|
+
if (!key) {
|
|
1508
|
+
throw new Error(
|
|
1509
|
+
`Listing folders needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
const client = createApiClient(apiUrl2, key);
|
|
1513
|
+
const workspaceData = await client.listWorkspaces();
|
|
1514
|
+
const workspace = workspaceData.workspaces.find(
|
|
1515
|
+
(candidate) => candidate.handle === opts.workspace
|
|
1516
|
+
);
|
|
1517
|
+
if (!workspace) {
|
|
1518
|
+
throw new Error(
|
|
1519
|
+
`No workspace with handle "${opts.workspace}" belongs to this account.`
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
const data = await client.listFolders(workspace.id);
|
|
1523
|
+
if (opts.json) {
|
|
1524
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
if (data.folders.length === 0) {
|
|
1528
|
+
console.log(`No folders in workspace ${workspace.handle}.`);
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
const plural = data.folders.length === 1 ? "" : "s";
|
|
1532
|
+
console.log(
|
|
1533
|
+
`${data.folders.length} folder${plural} in ${workspace.handle}
|
|
1534
|
+
`
|
|
1535
|
+
);
|
|
1536
|
+
for (const folder of data.folders) {
|
|
1537
|
+
printFolder(folder);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function printFolder(folder) {
|
|
1541
|
+
console.log(`${folder.name} (${folder.visibility})`);
|
|
1542
|
+
console.log(` ${folder.id}
|
|
1543
|
+
`);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// src/link.ts
|
|
1547
|
+
import path7 from "path";
|
|
1548
|
+
import { createInterface } from "readline";
|
|
1549
|
+
async function link(opts) {
|
|
1550
|
+
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
1551
|
+
/\/$/,
|
|
1552
|
+
""
|
|
1553
|
+
);
|
|
1554
|
+
const key = resolveApiKey(apiUrl2, opts.apiKey);
|
|
1555
|
+
if (!key) {
|
|
1556
|
+
throw new Error(
|
|
1557
|
+
`Linking needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
const { artifacts } = await createApiClient(apiUrl2, key).listArtifacts();
|
|
1561
|
+
if (artifacts.length === 0) {
|
|
1562
|
+
throw new Error(
|
|
1563
|
+
"No artifacts in your account yet \u2014 publish one first, or claim one via its claim link."
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
1566
|
+
const selector = opts.artifact ?? opts.slug;
|
|
1567
|
+
let chosen;
|
|
1568
|
+
if (opts.artifact) {
|
|
1569
|
+
chosen = artifacts.find((a) => a.id === opts.artifact);
|
|
1570
|
+
} else if (opts.slug) {
|
|
1571
|
+
chosen = artifacts.find((a) => a.slug === opts.slug);
|
|
1572
|
+
}
|
|
1573
|
+
if (selector && !chosen) {
|
|
1574
|
+
throw new Error(
|
|
1575
|
+
`No artifact "${selector}" in your account \u2014 run \`artifact-cafe list\` to see them.`
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
if (opts.json && !chosen) {
|
|
1579
|
+
console.log(JSON.stringify({ artifacts }, null, 2));
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
if (!chosen) {
|
|
1583
|
+
if (!process.stdin.isTTY) {
|
|
1584
|
+
throw new Error(
|
|
1585
|
+
"Pick an artifact with --artifact <id> or --slug <slug> (or --json to list them). Interactive selection needs a terminal."
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
chosen = await pickArtifact(artifacts);
|
|
1589
|
+
}
|
|
1590
|
+
const dir = path7.resolve(opts.dir);
|
|
1591
|
+
writeConfig(dir, {
|
|
1592
|
+
apiUrl: apiUrl2,
|
|
1593
|
+
artifactId: chosen.id,
|
|
1594
|
+
slug: chosen.slug,
|
|
1595
|
+
url: chosen.url
|
|
1596
|
+
// No publishToken — this folder authorizes via your login, not a secret.
|
|
1597
|
+
});
|
|
1598
|
+
if (opts.json) {
|
|
1599
|
+
console.log(
|
|
1600
|
+
JSON.stringify(
|
|
1601
|
+
{
|
|
1602
|
+
artifactId: chosen.id,
|
|
1603
|
+
linked: true,
|
|
1604
|
+
slug: chosen.slug,
|
|
1605
|
+
url: chosen.url
|
|
1606
|
+
},
|
|
1607
|
+
null,
|
|
1608
|
+
2
|
|
1609
|
+
)
|
|
1610
|
+
);
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
console.log(`
|
|
1614
|
+
\u2713 Linked ${dir} \u2192 "${chosen.title}" \u2014 ${chosen.url}`);
|
|
1615
|
+
console.log(
|
|
1616
|
+
" Owner actions (publish, comments --wait, protect) use your login.\n"
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
function pickArtifact(artifacts) {
|
|
1620
|
+
console.log("Your artifacts:\n");
|
|
1621
|
+
artifacts.forEach((a, i) => {
|
|
1622
|
+
const v = a.currentVersionNumber ? `v${a.currentVersionNumber}` : "\u2014";
|
|
1623
|
+
console.log(
|
|
1624
|
+
` ${String(i + 1).padStart(2)}. ${a.title} \xB7 ${v} \xB7 ${a.url}`
|
|
1625
|
+
);
|
|
1626
|
+
});
|
|
1627
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1628
|
+
return new Promise((resolve, reject) => {
|
|
1629
|
+
rl.question(`
|
|
1630
|
+
Link which? [1-${artifacts.length}]: `, (answer) => {
|
|
1631
|
+
rl.close();
|
|
1632
|
+
const n = Number(answer.trim());
|
|
1633
|
+
if (!(Number.isInteger(n) && n >= 1 && n <= artifacts.length)) {
|
|
1634
|
+
reject(new Error(`Enter a number between 1 and ${artifacts.length}.`));
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
resolve(artifacts[n - 1]);
|
|
1638
|
+
});
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1172
1642
|
// src/list.ts
|
|
1173
1643
|
async function list(opts) {
|
|
1174
1644
|
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
@@ -1226,13 +1696,13 @@ function relativeTime(iso) {
|
|
|
1226
1696
|
}
|
|
1227
1697
|
|
|
1228
1698
|
// src/login.ts
|
|
1229
|
-
import { createInterface } from "readline";
|
|
1699
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1230
1700
|
function apiUrl() {
|
|
1231
1701
|
return (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
1232
1702
|
}
|
|
1233
1703
|
function promptSecret(question) {
|
|
1234
1704
|
return new Promise((resolve) => {
|
|
1235
|
-
const rl =
|
|
1705
|
+
const rl = createInterface2({
|
|
1236
1706
|
input: process.stdin,
|
|
1237
1707
|
output: process.stdout
|
|
1238
1708
|
});
|
|
@@ -1253,7 +1723,7 @@ function promptSecret(question) {
|
|
|
1253
1723
|
}
|
|
1254
1724
|
async function login(opts) {
|
|
1255
1725
|
const base = apiUrl();
|
|
1256
|
-
const keysUrl = `${base}/
|
|
1726
|
+
const keysUrl = `${base}/dashboard?view=api-keys`;
|
|
1257
1727
|
let key = opts.apiKey?.trim();
|
|
1258
1728
|
if (!key) {
|
|
1259
1729
|
if (!opts.json) {
|
|
@@ -1335,28 +1805,32 @@ function logout(opts) {
|
|
|
1335
1805
|
}
|
|
1336
1806
|
console.log(`\u2713 Logged out of ${apiOrigin(base)} on this machine.`);
|
|
1337
1807
|
console.log(
|
|
1338
|
-
`The key itself is still active \u2014 revoke it at ${base}/
|
|
1808
|
+
`The key itself is still active \u2014 revoke it at ${base}/dashboard?view=api-keys if this machine is shared.`
|
|
1339
1809
|
);
|
|
1340
1810
|
}
|
|
1341
1811
|
|
|
1342
1812
|
// src/open.ts
|
|
1343
|
-
import
|
|
1344
|
-
function openArtifact(opts) {
|
|
1345
|
-
const dir =
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1813
|
+
import path8 from "path";
|
|
1814
|
+
async function openArtifact(opts) {
|
|
1815
|
+
const dir = path8.resolve(opts.dir);
|
|
1816
|
+
const target = resolveArtifactTarget(dir, {
|
|
1817
|
+
apiKey: opts.apiKey,
|
|
1818
|
+
artifact: opts.artifact
|
|
1819
|
+
});
|
|
1820
|
+
const url = target.url ?? reviewUrl(
|
|
1821
|
+
target.apiUrl,
|
|
1822
|
+
(await createApiClient(target.apiUrl, target.credential).getArtifact(
|
|
1823
|
+
target.artifactId
|
|
1824
|
+
)).slug
|
|
1825
|
+
);
|
|
1826
|
+
console.log(url);
|
|
1353
1827
|
if (process.stdout.isTTY) {
|
|
1354
|
-
openInBrowser(
|
|
1828
|
+
openInBrowser(url);
|
|
1355
1829
|
}
|
|
1356
1830
|
}
|
|
1357
1831
|
|
|
1358
1832
|
// src/protect.ts
|
|
1359
|
-
import
|
|
1833
|
+
import path9 from "path";
|
|
1360
1834
|
async function protect(opts) {
|
|
1361
1835
|
if (opts.off && opts.password !== void 0) {
|
|
1362
1836
|
throw new Error("Pass either --password or --off, not both.");
|
|
@@ -1373,30 +1847,64 @@ async function protect(opts) {
|
|
|
1373
1847
|
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
1374
1848
|
}
|
|
1375
1849
|
}
|
|
1376
|
-
const dir =
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
|
|
1384
|
-
const token = config.publishToken ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
1385
|
-
const client = createApiClient(apiUrl2, token);
|
|
1386
|
-
const res = await client.setProtection(config.artifactId, password);
|
|
1850
|
+
const dir = path9.resolve(opts.dir);
|
|
1851
|
+
const target = resolveArtifactTarget(dir, {
|
|
1852
|
+
apiKey: opts.apiKey,
|
|
1853
|
+
artifact: opts.artifact
|
|
1854
|
+
});
|
|
1855
|
+
const client = createApiClient(target.apiUrl, target.credential);
|
|
1856
|
+
const res = await client.setProtection(target.artifactId, password);
|
|
1387
1857
|
if (opts.json) {
|
|
1388
1858
|
console.log(JSON.stringify({ protected: res.protected }, null, 2));
|
|
1389
1859
|
return;
|
|
1390
1860
|
}
|
|
1861
|
+
const url = target.url ?? reviewUrl(
|
|
1862
|
+
target.apiUrl,
|
|
1863
|
+
(await client.getArtifact(target.artifactId)).slug
|
|
1864
|
+
);
|
|
1391
1865
|
console.log(
|
|
1392
1866
|
res.protected ? `
|
|
1393
|
-
\u2713 Password protection on \u2014 ${
|
|
1867
|
+
\u2713 Password protection on \u2014 ${url}
|
|
1394
1868
|
` : `
|
|
1395
|
-
\u2713 Password protection removed \u2014 ${
|
|
1869
|
+
\u2713 Password protection removed \u2014 ${url}
|
|
1396
1870
|
`
|
|
1397
1871
|
);
|
|
1398
1872
|
}
|
|
1399
1873
|
|
|
1874
|
+
// src/workspaces.ts
|
|
1875
|
+
async function workspaces(opts) {
|
|
1876
|
+
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
1877
|
+
/\/$/,
|
|
1878
|
+
""
|
|
1879
|
+
);
|
|
1880
|
+
const key = resolveApiKey(apiUrl2, opts.apiKey);
|
|
1881
|
+
if (!key) {
|
|
1882
|
+
throw new Error(
|
|
1883
|
+
`Listing workspaces needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
const data = await createApiClient(apiUrl2, key).listWorkspaces();
|
|
1887
|
+
if (opts.json) {
|
|
1888
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (data.workspaces.length === 0) {
|
|
1892
|
+
console.log("No workspaces yet \u2014 create one from the dashboard.");
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
const plural = data.workspaces.length === 1 ? "" : "s";
|
|
1896
|
+
console.log(`${data.workspaces.length} workspace${plural}
|
|
1897
|
+
`);
|
|
1898
|
+
for (const workspace of data.workspaces) {
|
|
1899
|
+
printWorkspace(workspace);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
function printWorkspace(workspace) {
|
|
1903
|
+
console.log(`${workspace.name} (${workspace.role ?? "non-member"})`);
|
|
1904
|
+
console.log(` ${workspace.handle} \xB7 ${workspace.id}
|
|
1905
|
+
`);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1400
1908
|
// src/index.ts
|
|
1401
1909
|
var publish2 = defineCommand({
|
|
1402
1910
|
args: {
|
|
@@ -1414,13 +1922,21 @@ var publish2 = defineCommand({
|
|
|
1414
1922
|
type: "positional"
|
|
1415
1923
|
},
|
|
1416
1924
|
entry: { description: "Entry file (default: auto-detect)", type: "string" },
|
|
1925
|
+
folder: {
|
|
1926
|
+
description: "Publish into this workspace folder name (or id)",
|
|
1927
|
+
type: "string"
|
|
1928
|
+
},
|
|
1417
1929
|
json: { description: "Machine-readable output", type: "boolean" },
|
|
1418
1930
|
"no-open": { description: "Don't open the review URL", type: "boolean" },
|
|
1419
1931
|
password: {
|
|
1420
1932
|
description: "Password-protect the artifact (min 6 chars)",
|
|
1421
1933
|
type: "string"
|
|
1422
1934
|
},
|
|
1423
|
-
title: { description: "Artifact title", type: "string" }
|
|
1935
|
+
title: { description: "Artifact title", type: "string" },
|
|
1936
|
+
workspace: {
|
|
1937
|
+
description: "Publish into this workspace handle",
|
|
1938
|
+
type: "string"
|
|
1939
|
+
}
|
|
1424
1940
|
},
|
|
1425
1941
|
meta: { description: "Publish a folder as an artifact", name: "publish" },
|
|
1426
1942
|
async run({ args }) {
|
|
@@ -1430,10 +1946,12 @@ var publish2 = defineCommand({
|
|
|
1430
1946
|
artifact: args.artifact,
|
|
1431
1947
|
dir: args.dir,
|
|
1432
1948
|
entry: args.entry,
|
|
1949
|
+
folder: args.folder,
|
|
1433
1950
|
json: Boolean(args.json),
|
|
1434
1951
|
open: !args["no-open"],
|
|
1435
1952
|
password: args.password,
|
|
1436
|
-
title: args.title
|
|
1953
|
+
title: args.title,
|
|
1954
|
+
workspace: args.workspace
|
|
1437
1955
|
});
|
|
1438
1956
|
} catch (err) {
|
|
1439
1957
|
printCliError(err, Boolean(args.json));
|
|
@@ -1517,8 +2035,110 @@ var list2 = defineCommand({
|
|
|
1517
2035
|
}
|
|
1518
2036
|
}
|
|
1519
2037
|
});
|
|
2038
|
+
var folders2 = defineCommand({
|
|
2039
|
+
args: {
|
|
2040
|
+
"api-key": {
|
|
2041
|
+
description: "List with this API key (default: stored login)",
|
|
2042
|
+
type: "string"
|
|
2043
|
+
},
|
|
2044
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
2045
|
+
workspace: {
|
|
2046
|
+
description: "Workspace handle whose folders to list",
|
|
2047
|
+
required: true,
|
|
2048
|
+
type: "string"
|
|
2049
|
+
}
|
|
2050
|
+
},
|
|
2051
|
+
meta: {
|
|
2052
|
+
description: "List folders in a workspace",
|
|
2053
|
+
name: "folders"
|
|
2054
|
+
},
|
|
2055
|
+
async run({ args }) {
|
|
2056
|
+
try {
|
|
2057
|
+
await folders({
|
|
2058
|
+
apiKey: args["api-key"],
|
|
2059
|
+
json: Boolean(args.json),
|
|
2060
|
+
workspace: args.workspace
|
|
2061
|
+
});
|
|
2062
|
+
} catch (err) {
|
|
2063
|
+
printCliError(err, Boolean(args.json));
|
|
2064
|
+
process.exit(1);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
var workspaces2 = defineCommand({
|
|
2069
|
+
args: {
|
|
2070
|
+
"api-key": {
|
|
2071
|
+
description: "List with this API key (default: stored login)",
|
|
2072
|
+
type: "string"
|
|
2073
|
+
},
|
|
2074
|
+
json: { description: "Machine-readable output", type: "boolean" }
|
|
2075
|
+
},
|
|
2076
|
+
meta: {
|
|
2077
|
+
description: "List the workspaces your account belongs to",
|
|
2078
|
+
name: "workspaces"
|
|
2079
|
+
},
|
|
2080
|
+
async run({ args }) {
|
|
2081
|
+
try {
|
|
2082
|
+
await workspaces({
|
|
2083
|
+
apiKey: args["api-key"],
|
|
2084
|
+
json: Boolean(args.json)
|
|
2085
|
+
});
|
|
2086
|
+
} catch (err) {
|
|
2087
|
+
printCliError(err, Boolean(args.json));
|
|
2088
|
+
process.exit(1);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
});
|
|
2092
|
+
var link2 = defineCommand({
|
|
2093
|
+
args: {
|
|
2094
|
+
"api-key": {
|
|
2095
|
+
description: "Link with this API key (default: stored login)",
|
|
2096
|
+
type: "string"
|
|
2097
|
+
},
|
|
2098
|
+
artifact: {
|
|
2099
|
+
description: "Link this artifact id (skip the picker)",
|
|
2100
|
+
type: "string"
|
|
2101
|
+
},
|
|
2102
|
+
dir: {
|
|
2103
|
+
default: ".",
|
|
2104
|
+
description: "Directory to link",
|
|
2105
|
+
type: "positional"
|
|
2106
|
+
},
|
|
2107
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
2108
|
+
slug: {
|
|
2109
|
+
description: "Link this artifact slug (skip the picker)",
|
|
2110
|
+
type: "string"
|
|
2111
|
+
}
|
|
2112
|
+
},
|
|
2113
|
+
meta: {
|
|
2114
|
+
description: "Link a folder to one of your artifacts (pick from your account)",
|
|
2115
|
+
name: "link"
|
|
2116
|
+
},
|
|
2117
|
+
async run({ args }) {
|
|
2118
|
+
try {
|
|
2119
|
+
await link({
|
|
2120
|
+
apiKey: args["api-key"],
|
|
2121
|
+
artifact: args.artifact,
|
|
2122
|
+
dir: args.dir,
|
|
2123
|
+
json: Boolean(args.json),
|
|
2124
|
+
slug: args.slug
|
|
2125
|
+
});
|
|
2126
|
+
} catch (err) {
|
|
2127
|
+
printCliError(err, Boolean(args.json));
|
|
2128
|
+
process.exit(1);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
});
|
|
1520
2132
|
var comments2 = defineCommand({
|
|
1521
2133
|
args: {
|
|
2134
|
+
"api-key": {
|
|
2135
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
2136
|
+
type: "string"
|
|
2137
|
+
},
|
|
2138
|
+
artifact: {
|
|
2139
|
+
description: "Act on an artifact ID instead of the linked folder",
|
|
2140
|
+
type: "string"
|
|
2141
|
+
},
|
|
1522
2142
|
dir: {
|
|
1523
2143
|
default: ".",
|
|
1524
2144
|
description: "Published directory",
|
|
@@ -1558,6 +2178,8 @@ var comments2 = defineCommand({
|
|
|
1558
2178
|
);
|
|
1559
2179
|
}
|
|
1560
2180
|
await comments({
|
|
2181
|
+
apiKey: args["api-key"],
|
|
2182
|
+
artifact: args.artifact,
|
|
1561
2183
|
dir: args.dir,
|
|
1562
2184
|
json: Boolean(args.json),
|
|
1563
2185
|
status: args.status,
|
|
@@ -1571,8 +2193,76 @@ var comments2 = defineCommand({
|
|
|
1571
2193
|
}
|
|
1572
2194
|
}
|
|
1573
2195
|
});
|
|
2196
|
+
var listen = defineCommand({
|
|
2197
|
+
args: {
|
|
2198
|
+
"api-key": {
|
|
2199
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
2200
|
+
type: "string"
|
|
2201
|
+
},
|
|
2202
|
+
artifact: {
|
|
2203
|
+
description: "Act on an artifact ID instead of the linked folder",
|
|
2204
|
+
type: "string"
|
|
2205
|
+
},
|
|
2206
|
+
dir: {
|
|
2207
|
+
default: ".",
|
|
2208
|
+
description: "Published directory",
|
|
2209
|
+
type: "positional"
|
|
2210
|
+
},
|
|
2211
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
2212
|
+
status: {
|
|
2213
|
+
default: "open",
|
|
2214
|
+
description: "Filter: open | resolved | all",
|
|
2215
|
+
type: "string"
|
|
2216
|
+
},
|
|
2217
|
+
timeout: {
|
|
2218
|
+
default: "900",
|
|
2219
|
+
description: "Give up after this many seconds (exit 2)",
|
|
2220
|
+
type: "string"
|
|
2221
|
+
},
|
|
2222
|
+
version: {
|
|
2223
|
+
default: "current",
|
|
2224
|
+
description: "Filter: current | all",
|
|
2225
|
+
type: "string"
|
|
2226
|
+
}
|
|
2227
|
+
},
|
|
2228
|
+
meta: {
|
|
2229
|
+
description: "Wait for new reviewer feedback (alias for comments --wait)",
|
|
2230
|
+
name: "listen"
|
|
2231
|
+
},
|
|
2232
|
+
async run({ args }) {
|
|
2233
|
+
try {
|
|
2234
|
+
const timeout = Number(args.timeout);
|
|
2235
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
2236
|
+
throw new Error(
|
|
2237
|
+
`--timeout must be a positive number of seconds (got "${args.timeout}").`
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
await comments({
|
|
2241
|
+
apiKey: args["api-key"],
|
|
2242
|
+
artifact: args.artifact,
|
|
2243
|
+
dir: args.dir,
|
|
2244
|
+
json: Boolean(args.json),
|
|
2245
|
+
status: args.status,
|
|
2246
|
+
timeout,
|
|
2247
|
+
version: args.version,
|
|
2248
|
+
wait: true
|
|
2249
|
+
});
|
|
2250
|
+
} catch (err) {
|
|
2251
|
+
printCliError(err, Boolean(args.json));
|
|
2252
|
+
process.exit(1);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
});
|
|
1574
2256
|
var protect2 = defineCommand({
|
|
1575
2257
|
args: {
|
|
2258
|
+
"api-key": {
|
|
2259
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
2260
|
+
type: "string"
|
|
2261
|
+
},
|
|
2262
|
+
artifact: {
|
|
2263
|
+
description: "Act on an artifact ID instead of the linked folder",
|
|
2264
|
+
type: "string"
|
|
2265
|
+
},
|
|
1576
2266
|
dir: {
|
|
1577
2267
|
default: ".",
|
|
1578
2268
|
description: "Published directory",
|
|
@@ -1589,6 +2279,8 @@ var protect2 = defineCommand({
|
|
|
1589
2279
|
async run({ args }) {
|
|
1590
2280
|
try {
|
|
1591
2281
|
await protect({
|
|
2282
|
+
apiKey: args["api-key"],
|
|
2283
|
+
artifact: args.artifact,
|
|
1592
2284
|
dir: args.dir,
|
|
1593
2285
|
json: Boolean(args.json),
|
|
1594
2286
|
off: Boolean(args.off),
|
|
@@ -1602,6 +2294,14 @@ var protect2 = defineCommand({
|
|
|
1602
2294
|
});
|
|
1603
2295
|
var open = defineCommand({
|
|
1604
2296
|
args: {
|
|
2297
|
+
"api-key": {
|
|
2298
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
2299
|
+
type: "string"
|
|
2300
|
+
},
|
|
2301
|
+
artifact: {
|
|
2302
|
+
description: "Open an artifact ID instead of the linked folder",
|
|
2303
|
+
type: "string"
|
|
2304
|
+
},
|
|
1605
2305
|
dir: {
|
|
1606
2306
|
default: ".",
|
|
1607
2307
|
description: "Published directory",
|
|
@@ -1609,9 +2309,13 @@ var open = defineCommand({
|
|
|
1609
2309
|
}
|
|
1610
2310
|
},
|
|
1611
2311
|
meta: { description: "Open the linked artifact's review URL", name: "open" },
|
|
1612
|
-
run({ args }) {
|
|
2312
|
+
async run({ args }) {
|
|
1613
2313
|
try {
|
|
1614
|
-
openArtifact({
|
|
2314
|
+
await openArtifact({
|
|
2315
|
+
apiKey: args["api-key"],
|
|
2316
|
+
artifact: args.artifact,
|
|
2317
|
+
dir: args.dir
|
|
2318
|
+
});
|
|
1615
2319
|
} catch (err) {
|
|
1616
2320
|
printCliError(err, false);
|
|
1617
2321
|
process.exit(1);
|
|
@@ -1644,16 +2348,31 @@ var main = defineCommand({
|
|
|
1644
2348
|
description: "Publish AI-generated artifacts for review \u2014 https://artifact.cafe",
|
|
1645
2349
|
name: "artifact-cafe"
|
|
1646
2350
|
},
|
|
2351
|
+
// Bare `artifact-cafe` (no subcommand) → brand banner + usage. citty
|
|
2352
|
+
// still calls this parent run *after* a matched subcommand, so bail when
|
|
2353
|
+
// a subcommand token is present (any non-flag arg; citty has already
|
|
2354
|
+
// rejected unknown ones with E_UNKNOWN_COMMAND before we get here).
|
|
2355
|
+
async run({ cmd, rawArgs }) {
|
|
2356
|
+
if (rawArgs.some((arg) => !arg.startsWith("-"))) {
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
printBanner();
|
|
2360
|
+
await showUsage(cmd);
|
|
2361
|
+
},
|
|
1647
2362
|
subCommands: {
|
|
1648
2363
|
comments: comments2,
|
|
1649
2364
|
dashboard,
|
|
2365
|
+
folders: folders2,
|
|
2366
|
+
link: link2,
|
|
1650
2367
|
list: list2,
|
|
2368
|
+
listen,
|
|
1651
2369
|
login: login2,
|
|
1652
2370
|
logout: logout2,
|
|
1653
2371
|
open,
|
|
1654
2372
|
protect: protect2,
|
|
1655
2373
|
publish: publish2,
|
|
1656
|
-
whoami: whoami2
|
|
2374
|
+
whoami: whoami2,
|
|
2375
|
+
workspaces: workspaces2
|
|
1657
2376
|
}
|
|
1658
2377
|
});
|
|
1659
2378
|
runMain(main);
|