artifact-cafe 0.0.3 → 0.0.4
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 +418 -206
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -167,47 +167,82 @@ var apiErrorSchema = z3.object({
|
|
|
167
167
|
retryAfter: z3.number().int().positive().optional()
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
-
// ../shared/src/
|
|
170
|
+
// ../shared/src/me.ts
|
|
171
171
|
import { z as z4 } from "zod";
|
|
172
|
-
var
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
var ownArtifactSchema = z4.object({
|
|
173
|
+
createdAt: z4.string(),
|
|
174
|
+
currentVersionNumber: z4.number().nullable(),
|
|
175
|
+
id: z4.string(),
|
|
176
|
+
openThreads: z4.number(),
|
|
177
|
+
protected: z4.boolean(),
|
|
175
178
|
slug: z4.string(),
|
|
176
179
|
title: z4.string(),
|
|
177
|
-
|
|
180
|
+
totalViews: z4.number(),
|
|
181
|
+
updatedAt: z4.string(),
|
|
182
|
+
url: z4.string(),
|
|
183
|
+
versionCount: z4.number()
|
|
184
|
+
});
|
|
185
|
+
var ownArtifactsResponseSchema = z4.object({
|
|
186
|
+
artifacts: z4.array(ownArtifactSchema)
|
|
178
187
|
});
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
|
|
189
|
+
// ../shared/src/owner.ts
|
|
190
|
+
import { z as z5 } from "zod";
|
|
191
|
+
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
|
|
192
|
+
var claimResponseSchema = z5.object({
|
|
193
|
+
artifactId: z5.string(),
|
|
194
|
+
slug: z5.string(),
|
|
195
|
+
title: z5.string(),
|
|
196
|
+
url: z5.string().url()
|
|
197
|
+
});
|
|
198
|
+
var updateArtifactSchema = z5.object({
|
|
199
|
+
slug: z5.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
181
200
|
message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
182
201
|
}).optional(),
|
|
183
|
-
title:
|
|
202
|
+
title: z5.string().min(1).max(200).optional()
|
|
184
203
|
}).refine((body) => body.title !== void 0 || body.slug !== void 0, {
|
|
185
204
|
message: "provide at least one of title, slug"
|
|
186
205
|
});
|
|
187
|
-
var deleteArtifactResponseSchema =
|
|
188
|
-
deleted:
|
|
206
|
+
var deleteArtifactResponseSchema = z5.object({
|
|
207
|
+
deleted: z5.literal(true)
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// ../shared/src/presence.ts
|
|
211
|
+
import { z as z6 } from "zod";
|
|
212
|
+
var AGENT_PRESENCE = {
|
|
213
|
+
heartbeatSeconds: 15,
|
|
214
|
+
shellPollSeconds: 15,
|
|
215
|
+
ttlSeconds: 45
|
|
216
|
+
};
|
|
217
|
+
var agentHeartbeatResponseSchema = z6.object({
|
|
218
|
+
listening: z6.literal(true),
|
|
219
|
+
ttlSeconds: z6.number().int().positive()
|
|
220
|
+
});
|
|
221
|
+
var agentPresenceSchema = z6.object({
|
|
222
|
+
listening: z6.boolean(),
|
|
223
|
+
versionNumber: z6.number().int().positive().nullable()
|
|
189
224
|
});
|
|
190
225
|
|
|
191
226
|
// ../shared/src/protection.ts
|
|
192
|
-
import { z as
|
|
227
|
+
import { z as z7 } from "zod";
|
|
193
228
|
var PASSWORD_MIN = 6;
|
|
194
229
|
var PASSWORD_MAX = 128;
|
|
195
|
-
var gatePasswordSchema =
|
|
196
|
-
var setProtectionSchema =
|
|
230
|
+
var gatePasswordSchema = z7.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
|
|
231
|
+
var setProtectionSchema = z7.object({
|
|
197
232
|
password: gatePasswordSchema.nullable()
|
|
198
233
|
});
|
|
199
|
-
var protectionResponseSchema =
|
|
200
|
-
protected:
|
|
234
|
+
var protectionResponseSchema = z7.object({
|
|
235
|
+
protected: z7.boolean()
|
|
201
236
|
});
|
|
202
|
-
var unlockSchema =
|
|
203
|
-
password:
|
|
237
|
+
var unlockSchema = z7.object({
|
|
238
|
+
password: z7.string().min(1).max(1024)
|
|
204
239
|
});
|
|
205
|
-
var unlockResponseSchema =
|
|
206
|
-
unlocked:
|
|
240
|
+
var unlockResponseSchema = z7.object({
|
|
241
|
+
unlocked: z7.literal(true)
|
|
207
242
|
});
|
|
208
243
|
|
|
209
244
|
// ../shared/src/publish.ts
|
|
210
|
-
import { z as
|
|
245
|
+
import { z as z8 } from "zod";
|
|
211
246
|
function isSafeArtifactPath(p) {
|
|
212
247
|
if (p.length === 0 || p.length > 1024) {
|
|
213
248
|
return false;
|
|
@@ -220,128 +255,128 @@ function isSafeArtifactPath(p) {
|
|
|
220
255
|
}
|
|
221
256
|
return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
|
|
222
257
|
}
|
|
223
|
-
var manifestFileSchema =
|
|
224
|
-
hash:
|
|
225
|
-
mimeType:
|
|
226
|
-
path:
|
|
258
|
+
var manifestFileSchema = z8.object({
|
|
259
|
+
hash: z8.string().regex(/^sha256_[a-f0-9]{64}$/),
|
|
260
|
+
mimeType: z8.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
|
|
261
|
+
path: z8.string().min(1).max(1024).refine(isSafeArtifactPath, {
|
|
227
262
|
message: "path must be a safe relative path"
|
|
228
263
|
}),
|
|
229
|
-
size:
|
|
264
|
+
size: z8.number().int().positive()
|
|
230
265
|
});
|
|
231
|
-
var publishSourceSchema =
|
|
232
|
-
agent:
|
|
233
|
-
runId:
|
|
234
|
-
type:
|
|
266
|
+
var publishSourceSchema = z8.object({
|
|
267
|
+
agent: z8.string().optional(),
|
|
268
|
+
runId: z8.string().optional(),
|
|
269
|
+
type: z8.literal("cli")
|
|
235
270
|
});
|
|
236
|
-
var createPublishSessionSchema =
|
|
237
|
-
artifactId:
|
|
238
|
-
entryPath:
|
|
239
|
-
files:
|
|
271
|
+
var createPublishSessionSchema = z8.object({
|
|
272
|
+
artifactId: z8.string().nullable(),
|
|
273
|
+
entryPath: z8.string().optional(),
|
|
274
|
+
files: z8.array(manifestFileSchema).min(1).max(1e3),
|
|
240
275
|
source: publishSourceSchema,
|
|
241
|
-
title:
|
|
276
|
+
title: z8.string().max(200).optional()
|
|
242
277
|
});
|
|
243
|
-
var publishSessionResponseSchema =
|
|
244
|
-
artifactId:
|
|
245
|
-
expiresAt:
|
|
246
|
-
filesToUpload:
|
|
247
|
-
|
|
248
|
-
method:
|
|
249
|
-
path:
|
|
250
|
-
uploadUrl:
|
|
278
|
+
var publishSessionResponseSchema = z8.object({
|
|
279
|
+
artifactId: z8.string(),
|
|
280
|
+
expiresAt: z8.string(),
|
|
281
|
+
filesToUpload: z8.array(
|
|
282
|
+
z8.object({
|
|
283
|
+
method: z8.literal("PUT"),
|
|
284
|
+
path: z8.string(),
|
|
285
|
+
uploadUrl: z8.string().url()
|
|
251
286
|
})
|
|
252
287
|
),
|
|
253
|
-
publishSessionId:
|
|
254
|
-
skippedFiles:
|
|
255
|
-
versionId:
|
|
288
|
+
publishSessionId: z8.string(),
|
|
289
|
+
skippedFiles: z8.array(z8.string()),
|
|
290
|
+
versionId: z8.string()
|
|
256
291
|
});
|
|
257
|
-
var finalizeResponseSchema =
|
|
258
|
-
artifactId:
|
|
292
|
+
var finalizeResponseSchema = z8.object({
|
|
293
|
+
artifactId: z8.string(),
|
|
259
294
|
// API-key publishes create the artifact already claimed (SPEC §5.4) —
|
|
260
295
|
// the CLI uses this to tell "claim URL pending" from "already yours".
|
|
261
|
-
claimed:
|
|
296
|
+
claimed: z8.boolean(),
|
|
262
297
|
// Returned only on first publish of a new artifact (one-time).
|
|
263
|
-
claimUrl:
|
|
264
|
-
publishToken:
|
|
265
|
-
url:
|
|
266
|
-
versionId:
|
|
267
|
-
versionNumber:
|
|
298
|
+
claimUrl: z8.string().url().optional(),
|
|
299
|
+
publishToken: z8.string().optional(),
|
|
300
|
+
url: z8.string().url(),
|
|
301
|
+
versionId: z8.string(),
|
|
302
|
+
versionNumber: z8.number().int().positive()
|
|
268
303
|
});
|
|
269
|
-
var artifactResponseSchema =
|
|
270
|
-
claimed:
|
|
271
|
-
currentVersion:
|
|
272
|
-
entryUrl:
|
|
273
|
-
id:
|
|
274
|
-
versionNumber:
|
|
304
|
+
var artifactResponseSchema = z8.object({
|
|
305
|
+
claimed: z8.boolean(),
|
|
306
|
+
currentVersion: z8.object({
|
|
307
|
+
entryUrl: z8.string().url(),
|
|
308
|
+
id: z8.string(),
|
|
309
|
+
versionNumber: z8.number().int().positive()
|
|
275
310
|
}).nullable(),
|
|
276
|
-
id:
|
|
277
|
-
slug:
|
|
278
|
-
title:
|
|
311
|
+
id: z8.string(),
|
|
312
|
+
slug: z8.string(),
|
|
313
|
+
title: z8.string()
|
|
279
314
|
});
|
|
280
315
|
|
|
281
316
|
// ../shared/src/report.ts
|
|
282
|
-
import { z as
|
|
283
|
-
var reportRequestSchema =
|
|
284
|
-
reason:
|
|
317
|
+
import { z as z9 } from "zod";
|
|
318
|
+
var reportRequestSchema = z9.object({
|
|
319
|
+
reason: z9.string().max(1e3).optional()
|
|
285
320
|
});
|
|
286
|
-
var reportResponseSchema =
|
|
287
|
-
reported:
|
|
321
|
+
var reportResponseSchema = z9.object({
|
|
322
|
+
reported: z9.literal(true)
|
|
288
323
|
});
|
|
289
324
|
|
|
290
325
|
// ../shared/src/stats.ts
|
|
291
|
-
import { z as
|
|
292
|
-
var avgViewSecondsSchema =
|
|
293
|
-
var versionViewStatsSchema =
|
|
326
|
+
import { z as z10 } from "zod";
|
|
327
|
+
var avgViewSecondsSchema = z10.number().int().nonnegative().nullable();
|
|
328
|
+
var versionViewStatsSchema = z10.object({
|
|
294
329
|
avgViewSeconds: avgViewSecondsSchema,
|
|
295
330
|
// Views from inside the review shell's iframe vs. direct visits to
|
|
296
331
|
// the render URL. views = embeddedViews + directViews.
|
|
297
|
-
directViews:
|
|
298
|
-
embeddedViews:
|
|
332
|
+
directViews: z10.number().int().nonnegative(),
|
|
333
|
+
embeddedViews: z10.number().int().nonnegative(),
|
|
299
334
|
// Distinct hashed viewer IPs across the version's lifetime.
|
|
300
|
-
uniqueViewers:
|
|
301
|
-
versionNumber:
|
|
302
|
-
views:
|
|
335
|
+
uniqueViewers: z10.number().int().nonnegative(),
|
|
336
|
+
versionNumber: z10.number().int().positive(),
|
|
337
|
+
views: z10.number().int().nonnegative()
|
|
303
338
|
});
|
|
304
|
-
var dailyViewsSchema =
|
|
339
|
+
var dailyViewsSchema = z10.object({
|
|
305
340
|
avgViewSeconds: avgViewSecondsSchema,
|
|
306
341
|
// UTC day, YYYY-MM-DD.
|
|
307
|
-
day:
|
|
308
|
-
uniqueViewers:
|
|
309
|
-
views:
|
|
342
|
+
day: z10.string(),
|
|
343
|
+
uniqueViewers: z10.number().int().nonnegative(),
|
|
344
|
+
views: z10.number().int().nonnegative()
|
|
310
345
|
});
|
|
311
|
-
var artifactStatsResponseSchema =
|
|
346
|
+
var artifactStatsResponseSchema = z10.object({
|
|
312
347
|
avgViewSeconds: avgViewSecondsSchema,
|
|
313
|
-
daily:
|
|
348
|
+
daily: z10.array(dailyViewsSchema),
|
|
314
349
|
// Distinct hashed viewer IPs across all versions and days.
|
|
315
|
-
totalUniqueViewers:
|
|
316
|
-
totalViews:
|
|
317
|
-
versions:
|
|
350
|
+
totalUniqueViewers: z10.number().int().nonnegative(),
|
|
351
|
+
totalViews: z10.number().int().nonnegative(),
|
|
352
|
+
versions: z10.array(versionViewStatsSchema)
|
|
318
353
|
});
|
|
319
354
|
|
|
320
355
|
// ../shared/src/versions.ts
|
|
321
|
-
import { z as
|
|
322
|
-
var versionSourceSchema =
|
|
323
|
-
agentName:
|
|
324
|
-
type:
|
|
356
|
+
import { z as z11 } from "zod";
|
|
357
|
+
var versionSourceSchema = z11.object({
|
|
358
|
+
agentName: z11.string().nullable(),
|
|
359
|
+
type: z11.string()
|
|
325
360
|
});
|
|
326
|
-
var versionChangesSchema =
|
|
327
|
-
added:
|
|
328
|
-
changed:
|
|
329
|
-
removed:
|
|
361
|
+
var versionChangesSchema = z11.object({
|
|
362
|
+
added: z11.array(z11.string()),
|
|
363
|
+
changed: z11.array(z11.string()),
|
|
364
|
+
removed: z11.array(z11.string())
|
|
330
365
|
});
|
|
331
|
-
var versionSummarySchema =
|
|
366
|
+
var versionSummarySchema = z11.object({
|
|
332
367
|
// Absent for the first version — there is no previous version to diff against.
|
|
333
368
|
changes: versionChangesSchema.nullable(),
|
|
334
|
-
createdAt:
|
|
335
|
-
entryUrl:
|
|
336
|
-
fileCount:
|
|
337
|
-
id:
|
|
338
|
-
isCurrent:
|
|
369
|
+
createdAt: z11.string(),
|
|
370
|
+
entryUrl: z11.string().url(),
|
|
371
|
+
fileCount: z11.number().int().nonnegative(),
|
|
372
|
+
id: z11.string(),
|
|
373
|
+
isCurrent: z11.boolean(),
|
|
339
374
|
source: versionSourceSchema,
|
|
340
|
-
totalSizeBytes:
|
|
341
|
-
versionNumber:
|
|
375
|
+
totalSizeBytes: z11.number().int().nonnegative(),
|
|
376
|
+
versionNumber: z11.number().int().positive()
|
|
342
377
|
});
|
|
343
|
-
var versionListResponseSchema =
|
|
344
|
-
versions:
|
|
378
|
+
var versionListResponseSchema = z11.object({
|
|
379
|
+
versions: z11.array(versionSummarySchema)
|
|
345
380
|
});
|
|
346
381
|
|
|
347
382
|
// src/api.ts
|
|
@@ -395,6 +430,15 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
395
430
|
headers.authorization = `Bearer ${publishToken}`;
|
|
396
431
|
}
|
|
397
432
|
return {
|
|
433
|
+
// SPEC §6.19 — mark the agent as actively waiting for feedback.
|
|
434
|
+
// Authorized by the publish token this client was created with.
|
|
435
|
+
async agentHeartbeat(artifactId) {
|
|
436
|
+
return request(
|
|
437
|
+
`${base}/api/v1/artifacts/${artifactId}/agent-presence`,
|
|
438
|
+
{ headers, method: "POST" },
|
|
439
|
+
(d) => agentHeartbeatResponseSchema.parse(d)
|
|
440
|
+
);
|
|
441
|
+
},
|
|
398
442
|
async createSession(body) {
|
|
399
443
|
return request(
|
|
400
444
|
`${base}/api/v1/publish`,
|
|
@@ -481,13 +525,13 @@ import {
|
|
|
481
525
|
writeFileSync
|
|
482
526
|
} from "fs";
|
|
483
527
|
import path from "path";
|
|
484
|
-
import { z as
|
|
485
|
-
var configSchema =
|
|
486
|
-
apiUrl:
|
|
487
|
-
artifactId:
|
|
488
|
-
publishToken:
|
|
489
|
-
slug:
|
|
490
|
-
url:
|
|
528
|
+
import { z as z12 } from "zod";
|
|
529
|
+
var configSchema = z12.object({
|
|
530
|
+
apiUrl: z12.string(),
|
|
531
|
+
artifactId: z12.string(),
|
|
532
|
+
publishToken: z12.string(),
|
|
533
|
+
slug: z12.string(),
|
|
534
|
+
url: z12.string()
|
|
491
535
|
});
|
|
492
536
|
var CONFIG_DIR = ".artifactcafe";
|
|
493
537
|
var CONFIG_FILE = "config.json";
|
|
@@ -536,6 +580,9 @@ ${CONFIG_DIR}/
|
|
|
536
580
|
// src/comments.ts
|
|
537
581
|
var STATUSES = ["open", "resolved", "all"];
|
|
538
582
|
var VERSIONS = ["current", "all"];
|
|
583
|
+
var pollSeconds = () => Number(process.env.AC_WAIT_POLL_SECONDS ?? 5);
|
|
584
|
+
var settleSeconds = () => Number(process.env.AC_WAIT_SETTLE_SECONDS ?? 20);
|
|
585
|
+
var WAIT_TIMEOUT_EXIT = 2;
|
|
539
586
|
async function comments(opts) {
|
|
540
587
|
if (!STATUSES.includes(opts.status)) {
|
|
541
588
|
throw new Error(
|
|
@@ -557,7 +604,16 @@ async function comments(opts) {
|
|
|
557
604
|
);
|
|
558
605
|
}
|
|
559
606
|
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
|
|
560
|
-
const client = createApiClient(apiUrl2);
|
|
607
|
+
const client = createApiClient(apiUrl2, config.publishToken);
|
|
608
|
+
if (opts.wait) {
|
|
609
|
+
await waitForFeedback(client, config, {
|
|
610
|
+
json: opts.json,
|
|
611
|
+
status,
|
|
612
|
+
timeoutSeconds: opts.timeout,
|
|
613
|
+
version
|
|
614
|
+
});
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
561
617
|
const data = await client.exportComments(config.artifactId, {
|
|
562
618
|
status,
|
|
563
619
|
version
|
|
@@ -568,6 +624,112 @@ async function comments(opts) {
|
|
|
568
624
|
}
|
|
569
625
|
printHuman(data, config.url, status, version);
|
|
570
626
|
}
|
|
627
|
+
var sleep = (s) => new Promise((r) => setTimeout(r, s * 1e3));
|
|
628
|
+
function messageCounts(data) {
|
|
629
|
+
return new Map(data.comments.map((t) => [t.id, 1 + t.replies.length]));
|
|
630
|
+
}
|
|
631
|
+
function diffThreads(baseline, data) {
|
|
632
|
+
const changed = [];
|
|
633
|
+
for (const t of data.comments) {
|
|
634
|
+
const before = baseline.get(t.id);
|
|
635
|
+
if (before === void 0) {
|
|
636
|
+
changed.push({ ...t, change: "new" });
|
|
637
|
+
} else if (1 + t.replies.length > before) {
|
|
638
|
+
changed.push({ ...t, change: "updated" });
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return changed;
|
|
642
|
+
}
|
|
643
|
+
async function waitForFeedback(client, config, opts) {
|
|
644
|
+
const query = { status: opts.status, version: opts.version };
|
|
645
|
+
const baselineExport = await client.exportComments(config.artifactId, query);
|
|
646
|
+
const baseline = messageCounts(baselineExport);
|
|
647
|
+
if (!opts.json) {
|
|
648
|
+
const mins = Math.round(opts.timeoutSeconds / 60);
|
|
649
|
+
console.log(
|
|
650
|
+
`Waiting for feedback on ${config.url} \u2014 times out after ${mins}m. Ctrl-C to stop.`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
const deadline = Date.now() + opts.timeoutSeconds * 1e3;
|
|
654
|
+
let heartbeatWarned = false;
|
|
655
|
+
let lastHeartbeat = 0;
|
|
656
|
+
const heartbeat = async () => {
|
|
657
|
+
if (Date.now() - lastHeartbeat < AGENT_PRESENCE.heartbeatSeconds * 1e3) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
lastHeartbeat = Date.now();
|
|
661
|
+
try {
|
|
662
|
+
await client.agentHeartbeat(config.artifactId);
|
|
663
|
+
} catch (err) {
|
|
664
|
+
if (!(opts.json || heartbeatWarned)) {
|
|
665
|
+
heartbeatWarned = true;
|
|
666
|
+
console.error(
|
|
667
|
+
`(presence heartbeat failed \u2014 reviewers won't see "Agent is listening": ${err instanceof Error ? err.message : err})`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
await heartbeat();
|
|
673
|
+
while (Date.now() < deadline) {
|
|
674
|
+
await sleep(Math.min(pollSeconds(), (deadline - Date.now()) / 1e3));
|
|
675
|
+
await heartbeat();
|
|
676
|
+
const data = await client.exportComments(config.artifactId, query);
|
|
677
|
+
if (diffThreads(baseline, data).length === 0) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
await sleep(settleSeconds());
|
|
681
|
+
const settled = await client.exportComments(config.artifactId, query);
|
|
682
|
+
const changed = diffThreads(baseline, settled);
|
|
683
|
+
printWaitResult(config, settled, changed, opts.json);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (opts.json) {
|
|
687
|
+
console.log(
|
|
688
|
+
JSON.stringify(
|
|
689
|
+
{
|
|
690
|
+
artifactId: config.artifactId,
|
|
691
|
+
threads: [],
|
|
692
|
+
timedOut: true,
|
|
693
|
+
versionNumber: baselineExport.versionNumber
|
|
694
|
+
},
|
|
695
|
+
null,
|
|
696
|
+
2
|
|
697
|
+
)
|
|
698
|
+
);
|
|
699
|
+
} else {
|
|
700
|
+
console.log(
|
|
701
|
+
`No new feedback after ${opts.timeoutSeconds}s. Run the command again to keep waiting.`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
process.exit(WAIT_TIMEOUT_EXIT);
|
|
705
|
+
}
|
|
706
|
+
function printWaitResult(config, data, changed, json) {
|
|
707
|
+
if (json) {
|
|
708
|
+
console.log(
|
|
709
|
+
JSON.stringify(
|
|
710
|
+
{
|
|
711
|
+
artifactId: config.artifactId,
|
|
712
|
+
threads: changed,
|
|
713
|
+
timedOut: false,
|
|
714
|
+
versionNumber: data.versionNumber
|
|
715
|
+
},
|
|
716
|
+
null,
|
|
717
|
+
2
|
|
718
|
+
)
|
|
719
|
+
);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const plural = changed.length === 1 ? "" : "s";
|
|
723
|
+
console.log(
|
|
724
|
+
`
|
|
725
|
+
${changed.length} thread${plural} with new feedback \u2014 ${config.url}
|
|
726
|
+
`
|
|
727
|
+
);
|
|
728
|
+
for (const thread of changed) {
|
|
729
|
+
console.log(thread.change === "new" ? "NEW" : "UPDATED");
|
|
730
|
+
printThread(thread);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
571
733
|
function printHuman(data, url, status, version) {
|
|
572
734
|
const statusLabel = status === "all" ? "" : `${status} `;
|
|
573
735
|
let scope = "across all versions";
|
|
@@ -612,44 +774,18 @@ function indent(text, pad) {
|
|
|
612
774
|
return text.split("\n").map((line) => pad + line).join("\n");
|
|
613
775
|
}
|
|
614
776
|
|
|
615
|
-
// src/
|
|
616
|
-
|
|
617
|
-
if (err instanceof ApiRequestError) {
|
|
618
|
-
if (json) {
|
|
619
|
-
console.log(
|
|
620
|
-
JSON.stringify({
|
|
621
|
-
error: {
|
|
622
|
-
code: err.code,
|
|
623
|
-
message: err.message,
|
|
624
|
-
retryAfter: err.retryAfter
|
|
625
|
-
}
|
|
626
|
-
})
|
|
627
|
-
);
|
|
628
|
-
} else {
|
|
629
|
-
console.error(`\u2717 ${err.message} (${err.code})`);
|
|
630
|
-
if (err.retryAfter) {
|
|
631
|
-
console.error(` Retry in ${err.retryAfter}s.`);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
637
|
-
if (json) {
|
|
638
|
-
console.log(JSON.stringify({ error: { code: "cli_error", message } }));
|
|
639
|
-
} else {
|
|
640
|
-
console.error(`\u2717 ${message}`);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
777
|
+
// src/dashboard.ts
|
|
778
|
+
import path6 from "path";
|
|
643
779
|
|
|
644
780
|
// src/credentials.ts
|
|
645
781
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
646
782
|
import { homedir } from "os";
|
|
647
783
|
import path3 from "path";
|
|
648
|
-
import { z as
|
|
784
|
+
import { z as z13 } from "zod";
|
|
649
785
|
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
650
|
-
var credentialsSchema =
|
|
651
|
-
keys:
|
|
652
|
-
version:
|
|
786
|
+
var credentialsSchema = z13.object({
|
|
787
|
+
keys: z13.record(z13.string(), z13.string()),
|
|
788
|
+
version: z13.literal(1)
|
|
653
789
|
});
|
|
654
790
|
function credentialsPath() {
|
|
655
791
|
return path3.join(homedir(), ".artifactcafe", "credentials");
|
|
@@ -698,65 +834,6 @@ function resolveApiKey(apiUrl2, flag) {
|
|
|
698
834
|
return flag ?? process.env.ARTIFACT_CAFE_API_KEY ?? storedApiKey(apiUrl2) ?? void 0;
|
|
699
835
|
}
|
|
700
836
|
|
|
701
|
-
// src/list.ts
|
|
702
|
-
async function list(opts) {
|
|
703
|
-
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
704
|
-
/\/$/,
|
|
705
|
-
""
|
|
706
|
-
);
|
|
707
|
-
const key = resolveApiKey(apiUrl2, opts.apiKey);
|
|
708
|
-
if (!key) {
|
|
709
|
-
throw new Error(
|
|
710
|
-
`Listing your artifacts needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
const data = await createApiClient(apiUrl2, key).listArtifacts();
|
|
714
|
-
if (opts.json) {
|
|
715
|
-
console.log(JSON.stringify(data, null, 2));
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
if (data.artifacts.length === 0) {
|
|
719
|
-
console.log(
|
|
720
|
-
"No artifacts in your account yet \u2014 publish while logged in, or claim one via its claim link."
|
|
721
|
-
);
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
724
|
-
const plural = data.artifacts.length === 1 ? "" : "s";
|
|
725
|
-
console.log(`${data.artifacts.length} artifact${plural}
|
|
726
|
-
`);
|
|
727
|
-
for (const artifact of data.artifacts) {
|
|
728
|
-
printArtifact(artifact);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
function printArtifact(a) {
|
|
732
|
-
const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
|
|
733
|
-
const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
|
|
734
|
-
const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
|
|
735
|
-
console.log(`${marker} ${a.title}`);
|
|
736
|
-
console.log(` ${version} \xB7 ${open2} \xB7 updated ${relativeTime(a.updatedAt)}`);
|
|
737
|
-
console.log(` ${a.url}
|
|
738
|
-
`);
|
|
739
|
-
}
|
|
740
|
-
function relativeTime(iso) {
|
|
741
|
-
const seconds = Math.max(0, (Date.now() - new Date(iso).getTime()) / 1e3);
|
|
742
|
-
if (seconds < 60) {
|
|
743
|
-
return "just now";
|
|
744
|
-
}
|
|
745
|
-
if (seconds < 3600) {
|
|
746
|
-
return `${Math.floor(seconds / 60)}m ago`;
|
|
747
|
-
}
|
|
748
|
-
if (seconds < 86400) {
|
|
749
|
-
return `${Math.floor(seconds / 3600)}h ago`;
|
|
750
|
-
}
|
|
751
|
-
if (seconds < 30 * 86400) {
|
|
752
|
-
return `${Math.floor(seconds / 86400)}d ago`;
|
|
753
|
-
}
|
|
754
|
-
return iso.slice(0, 10);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// src/login.ts
|
|
758
|
-
import { createInterface } from "readline";
|
|
759
|
-
|
|
760
837
|
// src/publish.ts
|
|
761
838
|
import { exec } from "child_process";
|
|
762
839
|
import { readFileSync as readFileSync4 } from "fs";
|
|
@@ -1053,7 +1130,103 @@ function openInBrowser(url) {
|
|
|
1053
1130
|
});
|
|
1054
1131
|
}
|
|
1055
1132
|
|
|
1133
|
+
// src/dashboard.ts
|
|
1134
|
+
function openDashboard(opts) {
|
|
1135
|
+
const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path6.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
|
|
1136
|
+
const url = `${base.replace(/\/$/, "")}/dashboard`;
|
|
1137
|
+
console.log(url);
|
|
1138
|
+
if (process.stdout.isTTY) {
|
|
1139
|
+
openInBrowser(url);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// src/errors.ts
|
|
1144
|
+
function printCliError(err, json) {
|
|
1145
|
+
if (err instanceof ApiRequestError) {
|
|
1146
|
+
if (json) {
|
|
1147
|
+
console.log(
|
|
1148
|
+
JSON.stringify({
|
|
1149
|
+
error: {
|
|
1150
|
+
code: err.code,
|
|
1151
|
+
message: err.message,
|
|
1152
|
+
retryAfter: err.retryAfter
|
|
1153
|
+
}
|
|
1154
|
+
})
|
|
1155
|
+
);
|
|
1156
|
+
} else {
|
|
1157
|
+
console.error(`\u2717 ${err.message} (${err.code})`);
|
|
1158
|
+
if (err.retryAfter) {
|
|
1159
|
+
console.error(` Retry in ${err.retryAfter}s.`);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1165
|
+
if (json) {
|
|
1166
|
+
console.log(JSON.stringify({ error: { code: "cli_error", message } }));
|
|
1167
|
+
} else {
|
|
1168
|
+
console.error(`\u2717 ${message}`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// src/list.ts
|
|
1173
|
+
async function list(opts) {
|
|
1174
|
+
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
1175
|
+
/\/$/,
|
|
1176
|
+
""
|
|
1177
|
+
);
|
|
1178
|
+
const key = resolveApiKey(apiUrl2, opts.apiKey);
|
|
1179
|
+
if (!key) {
|
|
1180
|
+
throw new Error(
|
|
1181
|
+
`Listing your artifacts needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
const data = await createApiClient(apiUrl2, key).listArtifacts();
|
|
1185
|
+
if (opts.json) {
|
|
1186
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
if (data.artifacts.length === 0) {
|
|
1190
|
+
console.log(
|
|
1191
|
+
"No artifacts in your account yet \u2014 publish while logged in, or claim one via its claim link."
|
|
1192
|
+
);
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
const plural = data.artifacts.length === 1 ? "" : "s";
|
|
1196
|
+
console.log(`${data.artifacts.length} artifact${plural}
|
|
1197
|
+
`);
|
|
1198
|
+
for (const artifact of data.artifacts) {
|
|
1199
|
+
printArtifact(artifact);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
function printArtifact(a) {
|
|
1203
|
+
const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
|
|
1204
|
+
const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
|
|
1205
|
+
const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
|
|
1206
|
+
console.log(`${marker} ${a.title}`);
|
|
1207
|
+
console.log(` ${version} \xB7 ${open2} \xB7 updated ${relativeTime(a.updatedAt)}`);
|
|
1208
|
+
console.log(` ${a.url}
|
|
1209
|
+
`);
|
|
1210
|
+
}
|
|
1211
|
+
function relativeTime(iso) {
|
|
1212
|
+
const seconds = Math.max(0, (Date.now() - new Date(iso).getTime()) / 1e3);
|
|
1213
|
+
if (seconds < 60) {
|
|
1214
|
+
return "just now";
|
|
1215
|
+
}
|
|
1216
|
+
if (seconds < 3600) {
|
|
1217
|
+
return `${Math.floor(seconds / 60)}m ago`;
|
|
1218
|
+
}
|
|
1219
|
+
if (seconds < 86400) {
|
|
1220
|
+
return `${Math.floor(seconds / 3600)}h ago`;
|
|
1221
|
+
}
|
|
1222
|
+
if (seconds < 30 * 86400) {
|
|
1223
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
1224
|
+
}
|
|
1225
|
+
return iso.slice(0, 10);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1056
1228
|
// src/login.ts
|
|
1229
|
+
import { createInterface } from "readline";
|
|
1057
1230
|
function apiUrl() {
|
|
1058
1231
|
return (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
1059
1232
|
}
|
|
@@ -1167,9 +1340,9 @@ function logout(opts) {
|
|
|
1167
1340
|
}
|
|
1168
1341
|
|
|
1169
1342
|
// src/open.ts
|
|
1170
|
-
import
|
|
1343
|
+
import path7 from "path";
|
|
1171
1344
|
function openArtifact(opts) {
|
|
1172
|
-
const dir =
|
|
1345
|
+
const dir = path7.resolve(opts.dir);
|
|
1173
1346
|
const config = readConfig(dir);
|
|
1174
1347
|
if (!config) {
|
|
1175
1348
|
throw new Error(
|
|
@@ -1183,7 +1356,7 @@ function openArtifact(opts) {
|
|
|
1183
1356
|
}
|
|
1184
1357
|
|
|
1185
1358
|
// src/protect.ts
|
|
1186
|
-
import
|
|
1359
|
+
import path8 from "path";
|
|
1187
1360
|
async function protect(opts) {
|
|
1188
1361
|
if (opts.off && opts.password !== void 0) {
|
|
1189
1362
|
throw new Error("Pass either --password or --off, not both.");
|
|
@@ -1200,7 +1373,7 @@ async function protect(opts) {
|
|
|
1200
1373
|
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
1201
1374
|
}
|
|
1202
1375
|
}
|
|
1203
|
-
const dir =
|
|
1376
|
+
const dir = path8.resolve(opts.dir);
|
|
1204
1377
|
const config = readConfig(dir);
|
|
1205
1378
|
if (!config) {
|
|
1206
1379
|
throw new Error(
|
|
@@ -1357,10 +1530,19 @@ var comments2 = defineCommand({
|
|
|
1357
1530
|
description: "Filter: open | resolved | all",
|
|
1358
1531
|
type: "string"
|
|
1359
1532
|
},
|
|
1533
|
+
timeout: {
|
|
1534
|
+
default: "900",
|
|
1535
|
+
description: "With --wait: give up after this many seconds (exit 2)",
|
|
1536
|
+
type: "string"
|
|
1537
|
+
},
|
|
1360
1538
|
version: {
|
|
1361
1539
|
default: "current",
|
|
1362
1540
|
description: "Filter: current | all",
|
|
1363
1541
|
type: "string"
|
|
1542
|
+
},
|
|
1543
|
+
wait: {
|
|
1544
|
+
description: "Block until a reviewer leaves new feedback",
|
|
1545
|
+
type: "boolean"
|
|
1364
1546
|
}
|
|
1365
1547
|
},
|
|
1366
1548
|
meta: {
|
|
@@ -1369,11 +1551,19 @@ var comments2 = defineCommand({
|
|
|
1369
1551
|
},
|
|
1370
1552
|
async run({ args }) {
|
|
1371
1553
|
try {
|
|
1554
|
+
const timeout = Number(args.timeout);
|
|
1555
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
1556
|
+
throw new Error(
|
|
1557
|
+
`--timeout must be a positive number of seconds (got "${args.timeout}").`
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1372
1560
|
await comments({
|
|
1373
1561
|
dir: args.dir,
|
|
1374
1562
|
json: Boolean(args.json),
|
|
1375
1563
|
status: args.status,
|
|
1376
|
-
|
|
1564
|
+
timeout,
|
|
1565
|
+
version: args.version,
|
|
1566
|
+
wait: Boolean(args.wait)
|
|
1377
1567
|
});
|
|
1378
1568
|
} catch (err) {
|
|
1379
1569
|
printCliError(err, Boolean(args.json));
|
|
@@ -1428,6 +1618,27 @@ var open = defineCommand({
|
|
|
1428
1618
|
}
|
|
1429
1619
|
}
|
|
1430
1620
|
});
|
|
1621
|
+
var dashboard = defineCommand({
|
|
1622
|
+
args: {
|
|
1623
|
+
dir: {
|
|
1624
|
+
default: ".",
|
|
1625
|
+
description: "Directory whose linked instance to open (optional)",
|
|
1626
|
+
type: "positional"
|
|
1627
|
+
}
|
|
1628
|
+
},
|
|
1629
|
+
meta: {
|
|
1630
|
+
description: "Open your personal dashboard in the browser",
|
|
1631
|
+
name: "dashboard"
|
|
1632
|
+
},
|
|
1633
|
+
run({ args }) {
|
|
1634
|
+
try {
|
|
1635
|
+
openDashboard({ dir: args.dir });
|
|
1636
|
+
} catch (err) {
|
|
1637
|
+
printCliError(err, false);
|
|
1638
|
+
process.exit(1);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1431
1642
|
var main = defineCommand({
|
|
1432
1643
|
meta: {
|
|
1433
1644
|
description: "Publish AI-generated artifacts for review \u2014 https://artifact.cafe",
|
|
@@ -1435,6 +1646,7 @@ var main = defineCommand({
|
|
|
1435
1646
|
},
|
|
1436
1647
|
subCommands: {
|
|
1437
1648
|
comments: comments2,
|
|
1649
|
+
dashboard,
|
|
1438
1650
|
list: list2,
|
|
1439
1651
|
login: login2,
|
|
1440
1652
|
logout: logout2,
|