artifact-cafe 0.0.3 → 0.0.5
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 +773 -325
- 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";
|
|
@@ -167,47 +194,82 @@ var apiErrorSchema = z3.object({
|
|
|
167
194
|
retryAfter: z3.number().int().positive().optional()
|
|
168
195
|
});
|
|
169
196
|
|
|
170
|
-
// ../shared/src/
|
|
197
|
+
// ../shared/src/me.ts
|
|
171
198
|
import { z as z4 } from "zod";
|
|
172
|
-
var
|
|
173
|
-
|
|
174
|
-
|
|
199
|
+
var ownArtifactSchema = z4.object({
|
|
200
|
+
createdAt: z4.string(),
|
|
201
|
+
currentVersionNumber: z4.number().nullable(),
|
|
202
|
+
id: z4.string(),
|
|
203
|
+
openThreads: z4.number(),
|
|
204
|
+
protected: z4.boolean(),
|
|
175
205
|
slug: z4.string(),
|
|
176
206
|
title: z4.string(),
|
|
177
|
-
|
|
207
|
+
totalViews: z4.number(),
|
|
208
|
+
updatedAt: z4.string(),
|
|
209
|
+
url: z4.string(),
|
|
210
|
+
versionCount: z4.number()
|
|
211
|
+
});
|
|
212
|
+
var ownArtifactsResponseSchema = z4.object({
|
|
213
|
+
artifacts: z4.array(ownArtifactSchema)
|
|
178
214
|
});
|
|
179
|
-
|
|
180
|
-
|
|
215
|
+
|
|
216
|
+
// ../shared/src/owner.ts
|
|
217
|
+
import { z as z5 } from "zod";
|
|
218
|
+
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
|
|
219
|
+
var claimResponseSchema = z5.object({
|
|
220
|
+
artifactId: z5.string(),
|
|
221
|
+
slug: z5.string(),
|
|
222
|
+
title: z5.string(),
|
|
223
|
+
url: z5.string().url()
|
|
224
|
+
});
|
|
225
|
+
var updateArtifactSchema = z5.object({
|
|
226
|
+
slug: z5.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
181
227
|
message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
182
228
|
}).optional(),
|
|
183
|
-
title:
|
|
229
|
+
title: z5.string().min(1).max(200).optional()
|
|
184
230
|
}).refine((body) => body.title !== void 0 || body.slug !== void 0, {
|
|
185
231
|
message: "provide at least one of title, slug"
|
|
186
232
|
});
|
|
187
|
-
var deleteArtifactResponseSchema =
|
|
188
|
-
deleted:
|
|
233
|
+
var deleteArtifactResponseSchema = z5.object({
|
|
234
|
+
deleted: z5.literal(true)
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// ../shared/src/presence.ts
|
|
238
|
+
import { z as z6 } from "zod";
|
|
239
|
+
var AGENT_PRESENCE = {
|
|
240
|
+
heartbeatSeconds: 15,
|
|
241
|
+
shellPollSeconds: 15,
|
|
242
|
+
ttlSeconds: 45
|
|
243
|
+
};
|
|
244
|
+
var agentHeartbeatResponseSchema = z6.object({
|
|
245
|
+
listening: z6.literal(true),
|
|
246
|
+
ttlSeconds: z6.number().int().positive()
|
|
247
|
+
});
|
|
248
|
+
var agentPresenceSchema = z6.object({
|
|
249
|
+
listening: z6.boolean(),
|
|
250
|
+
versionNumber: z6.number().int().positive().nullable()
|
|
189
251
|
});
|
|
190
252
|
|
|
191
253
|
// ../shared/src/protection.ts
|
|
192
|
-
import { z as
|
|
254
|
+
import { z as z7 } from "zod";
|
|
193
255
|
var PASSWORD_MIN = 6;
|
|
194
256
|
var PASSWORD_MAX = 128;
|
|
195
|
-
var gatePasswordSchema =
|
|
196
|
-
var setProtectionSchema =
|
|
257
|
+
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.`);
|
|
258
|
+
var setProtectionSchema = z7.object({
|
|
197
259
|
password: gatePasswordSchema.nullable()
|
|
198
260
|
});
|
|
199
|
-
var protectionResponseSchema =
|
|
200
|
-
protected:
|
|
261
|
+
var protectionResponseSchema = z7.object({
|
|
262
|
+
protected: z7.boolean()
|
|
201
263
|
});
|
|
202
|
-
var unlockSchema =
|
|
203
|
-
password:
|
|
264
|
+
var unlockSchema = z7.object({
|
|
265
|
+
password: z7.string().min(1).max(1024)
|
|
204
266
|
});
|
|
205
|
-
var unlockResponseSchema =
|
|
206
|
-
unlocked:
|
|
267
|
+
var unlockResponseSchema = z7.object({
|
|
268
|
+
unlocked: z7.literal(true)
|
|
207
269
|
});
|
|
208
270
|
|
|
209
271
|
// ../shared/src/publish.ts
|
|
210
|
-
import { z as
|
|
272
|
+
import { z as z8 } from "zod";
|
|
211
273
|
function isSafeArtifactPath(p) {
|
|
212
274
|
if (p.length === 0 || p.length > 1024) {
|
|
213
275
|
return false;
|
|
@@ -220,128 +282,128 @@ function isSafeArtifactPath(p) {
|
|
|
220
282
|
}
|
|
221
283
|
return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
|
|
222
284
|
}
|
|
223
|
-
var manifestFileSchema =
|
|
224
|
-
hash:
|
|
225
|
-
mimeType:
|
|
226
|
-
path:
|
|
285
|
+
var manifestFileSchema = z8.object({
|
|
286
|
+
hash: z8.string().regex(/^sha256_[a-f0-9]{64}$/),
|
|
287
|
+
mimeType: z8.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
|
|
288
|
+
path: z8.string().min(1).max(1024).refine(isSafeArtifactPath, {
|
|
227
289
|
message: "path must be a safe relative path"
|
|
228
290
|
}),
|
|
229
|
-
size:
|
|
291
|
+
size: z8.number().int().positive()
|
|
230
292
|
});
|
|
231
|
-
var publishSourceSchema =
|
|
232
|
-
agent:
|
|
233
|
-
runId:
|
|
234
|
-
type:
|
|
293
|
+
var publishSourceSchema = z8.object({
|
|
294
|
+
agent: z8.string().optional(),
|
|
295
|
+
runId: z8.string().optional(),
|
|
296
|
+
type: z8.literal("cli")
|
|
235
297
|
});
|
|
236
|
-
var createPublishSessionSchema =
|
|
237
|
-
artifactId:
|
|
238
|
-
entryPath:
|
|
239
|
-
files:
|
|
298
|
+
var createPublishSessionSchema = z8.object({
|
|
299
|
+
artifactId: z8.string().nullable(),
|
|
300
|
+
entryPath: z8.string().optional(),
|
|
301
|
+
files: z8.array(manifestFileSchema).min(1).max(1e3),
|
|
240
302
|
source: publishSourceSchema,
|
|
241
|
-
title:
|
|
303
|
+
title: z8.string().max(200).optional()
|
|
242
304
|
});
|
|
243
|
-
var publishSessionResponseSchema =
|
|
244
|
-
artifactId:
|
|
245
|
-
expiresAt:
|
|
246
|
-
filesToUpload:
|
|
247
|
-
|
|
248
|
-
method:
|
|
249
|
-
path:
|
|
250
|
-
uploadUrl:
|
|
305
|
+
var publishSessionResponseSchema = z8.object({
|
|
306
|
+
artifactId: z8.string(),
|
|
307
|
+
expiresAt: z8.string(),
|
|
308
|
+
filesToUpload: z8.array(
|
|
309
|
+
z8.object({
|
|
310
|
+
method: z8.literal("PUT"),
|
|
311
|
+
path: z8.string(),
|
|
312
|
+
uploadUrl: z8.string().url()
|
|
251
313
|
})
|
|
252
314
|
),
|
|
253
|
-
publishSessionId:
|
|
254
|
-
skippedFiles:
|
|
255
|
-
versionId:
|
|
315
|
+
publishSessionId: z8.string(),
|
|
316
|
+
skippedFiles: z8.array(z8.string()),
|
|
317
|
+
versionId: z8.string()
|
|
256
318
|
});
|
|
257
|
-
var finalizeResponseSchema =
|
|
258
|
-
artifactId:
|
|
319
|
+
var finalizeResponseSchema = z8.object({
|
|
320
|
+
artifactId: z8.string(),
|
|
259
321
|
// API-key publishes create the artifact already claimed (SPEC §5.4) —
|
|
260
322
|
// the CLI uses this to tell "claim URL pending" from "already yours".
|
|
261
|
-
claimed:
|
|
323
|
+
claimed: z8.boolean(),
|
|
262
324
|
// Returned only on first publish of a new artifact (one-time).
|
|
263
|
-
claimUrl:
|
|
264
|
-
publishToken:
|
|
265
|
-
url:
|
|
266
|
-
versionId:
|
|
267
|
-
versionNumber:
|
|
325
|
+
claimUrl: z8.string().url().optional(),
|
|
326
|
+
publishToken: z8.string().optional(),
|
|
327
|
+
url: z8.string().url(),
|
|
328
|
+
versionId: z8.string(),
|
|
329
|
+
versionNumber: z8.number().int().positive()
|
|
268
330
|
});
|
|
269
|
-
var artifactResponseSchema =
|
|
270
|
-
claimed:
|
|
271
|
-
currentVersion:
|
|
272
|
-
entryUrl:
|
|
273
|
-
id:
|
|
274
|
-
versionNumber:
|
|
331
|
+
var artifactResponseSchema = z8.object({
|
|
332
|
+
claimed: z8.boolean(),
|
|
333
|
+
currentVersion: z8.object({
|
|
334
|
+
entryUrl: z8.string().url(),
|
|
335
|
+
id: z8.string(),
|
|
336
|
+
versionNumber: z8.number().int().positive()
|
|
275
337
|
}).nullable(),
|
|
276
|
-
id:
|
|
277
|
-
slug:
|
|
278
|
-
title:
|
|
338
|
+
id: z8.string(),
|
|
339
|
+
slug: z8.string(),
|
|
340
|
+
title: z8.string()
|
|
279
341
|
});
|
|
280
342
|
|
|
281
343
|
// ../shared/src/report.ts
|
|
282
|
-
import { z as
|
|
283
|
-
var reportRequestSchema =
|
|
284
|
-
reason:
|
|
344
|
+
import { z as z9 } from "zod";
|
|
345
|
+
var reportRequestSchema = z9.object({
|
|
346
|
+
reason: z9.string().max(1e3).optional()
|
|
285
347
|
});
|
|
286
|
-
var reportResponseSchema =
|
|
287
|
-
reported:
|
|
348
|
+
var reportResponseSchema = z9.object({
|
|
349
|
+
reported: z9.literal(true)
|
|
288
350
|
});
|
|
289
351
|
|
|
290
352
|
// ../shared/src/stats.ts
|
|
291
|
-
import { z as
|
|
292
|
-
var avgViewSecondsSchema =
|
|
293
|
-
var versionViewStatsSchema =
|
|
353
|
+
import { z as z10 } from "zod";
|
|
354
|
+
var avgViewSecondsSchema = z10.number().int().nonnegative().nullable();
|
|
355
|
+
var versionViewStatsSchema = z10.object({
|
|
294
356
|
avgViewSeconds: avgViewSecondsSchema,
|
|
295
357
|
// Views from inside the review shell's iframe vs. direct visits to
|
|
296
358
|
// the render URL. views = embeddedViews + directViews.
|
|
297
|
-
directViews:
|
|
298
|
-
embeddedViews:
|
|
359
|
+
directViews: z10.number().int().nonnegative(),
|
|
360
|
+
embeddedViews: z10.number().int().nonnegative(),
|
|
299
361
|
// Distinct hashed viewer IPs across the version's lifetime.
|
|
300
|
-
uniqueViewers:
|
|
301
|
-
versionNumber:
|
|
302
|
-
views:
|
|
362
|
+
uniqueViewers: z10.number().int().nonnegative(),
|
|
363
|
+
versionNumber: z10.number().int().positive(),
|
|
364
|
+
views: z10.number().int().nonnegative()
|
|
303
365
|
});
|
|
304
|
-
var dailyViewsSchema =
|
|
366
|
+
var dailyViewsSchema = z10.object({
|
|
305
367
|
avgViewSeconds: avgViewSecondsSchema,
|
|
306
368
|
// UTC day, YYYY-MM-DD.
|
|
307
|
-
day:
|
|
308
|
-
uniqueViewers:
|
|
309
|
-
views:
|
|
369
|
+
day: z10.string(),
|
|
370
|
+
uniqueViewers: z10.number().int().nonnegative(),
|
|
371
|
+
views: z10.number().int().nonnegative()
|
|
310
372
|
});
|
|
311
|
-
var artifactStatsResponseSchema =
|
|
373
|
+
var artifactStatsResponseSchema = z10.object({
|
|
312
374
|
avgViewSeconds: avgViewSecondsSchema,
|
|
313
|
-
daily:
|
|
375
|
+
daily: z10.array(dailyViewsSchema),
|
|
314
376
|
// Distinct hashed viewer IPs across all versions and days.
|
|
315
|
-
totalUniqueViewers:
|
|
316
|
-
totalViews:
|
|
317
|
-
versions:
|
|
377
|
+
totalUniqueViewers: z10.number().int().nonnegative(),
|
|
378
|
+
totalViews: z10.number().int().nonnegative(),
|
|
379
|
+
versions: z10.array(versionViewStatsSchema)
|
|
318
380
|
});
|
|
319
381
|
|
|
320
382
|
// ../shared/src/versions.ts
|
|
321
|
-
import { z as
|
|
322
|
-
var versionSourceSchema =
|
|
323
|
-
agentName:
|
|
324
|
-
type:
|
|
383
|
+
import { z as z11 } from "zod";
|
|
384
|
+
var versionSourceSchema = z11.object({
|
|
385
|
+
agentName: z11.string().nullable(),
|
|
386
|
+
type: z11.string()
|
|
325
387
|
});
|
|
326
|
-
var versionChangesSchema =
|
|
327
|
-
added:
|
|
328
|
-
changed:
|
|
329
|
-
removed:
|
|
388
|
+
var versionChangesSchema = z11.object({
|
|
389
|
+
added: z11.array(z11.string()),
|
|
390
|
+
changed: z11.array(z11.string()),
|
|
391
|
+
removed: z11.array(z11.string())
|
|
330
392
|
});
|
|
331
|
-
var versionSummarySchema =
|
|
393
|
+
var versionSummarySchema = z11.object({
|
|
332
394
|
// Absent for the first version — there is no previous version to diff against.
|
|
333
395
|
changes: versionChangesSchema.nullable(),
|
|
334
|
-
createdAt:
|
|
335
|
-
entryUrl:
|
|
336
|
-
fileCount:
|
|
337
|
-
id:
|
|
338
|
-
isCurrent:
|
|
396
|
+
createdAt: z11.string(),
|
|
397
|
+
entryUrl: z11.string().url(),
|
|
398
|
+
fileCount: z11.number().int().nonnegative(),
|
|
399
|
+
id: z11.string(),
|
|
400
|
+
isCurrent: z11.boolean(),
|
|
339
401
|
source: versionSourceSchema,
|
|
340
|
-
totalSizeBytes:
|
|
341
|
-
versionNumber:
|
|
402
|
+
totalSizeBytes: z11.number().int().nonnegative(),
|
|
403
|
+
versionNumber: z11.number().int().positive()
|
|
342
404
|
});
|
|
343
|
-
var versionListResponseSchema =
|
|
344
|
-
versions:
|
|
405
|
+
var versionListResponseSchema = z11.object({
|
|
406
|
+
versions: z11.array(versionSummarySchema)
|
|
345
407
|
});
|
|
346
408
|
|
|
347
409
|
// src/api.ts
|
|
@@ -395,6 +457,15 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
395
457
|
headers.authorization = `Bearer ${publishToken}`;
|
|
396
458
|
}
|
|
397
459
|
return {
|
|
460
|
+
// SPEC §6.19 — mark the agent as actively waiting for feedback.
|
|
461
|
+
// Authorized by the publish token this client was created with.
|
|
462
|
+
async agentHeartbeat(artifactId) {
|
|
463
|
+
return request(
|
|
464
|
+
`${base}/api/v1/artifacts/${artifactId}/agent-presence`,
|
|
465
|
+
{ headers, method: "POST" },
|
|
466
|
+
(d) => agentHeartbeatResponseSchema.parse(d)
|
|
467
|
+
);
|
|
468
|
+
},
|
|
398
469
|
async createSession(body) {
|
|
399
470
|
return request(
|
|
400
471
|
`${base}/api/v1/publish`,
|
|
@@ -475,37 +546,99 @@ async function uploadFile(uploadUrl, content, mimeType) {
|
|
|
475
546
|
// src/config.ts
|
|
476
547
|
import {
|
|
477
548
|
appendFileSync,
|
|
478
|
-
existsSync,
|
|
479
|
-
mkdirSync,
|
|
480
|
-
readFileSync,
|
|
481
|
-
writeFileSync
|
|
549
|
+
existsSync as existsSync2,
|
|
550
|
+
mkdirSync as mkdirSync2,
|
|
551
|
+
readFileSync as readFileSync2,
|
|
552
|
+
writeFileSync as writeFileSync2
|
|
482
553
|
} from "fs";
|
|
554
|
+
import path2 from "path";
|
|
555
|
+
import { z as z13 } from "zod";
|
|
556
|
+
|
|
557
|
+
// src/credentials.ts
|
|
558
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
559
|
+
import { homedir } from "os";
|
|
483
560
|
import path from "path";
|
|
484
|
-
import { z as
|
|
485
|
-
var
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
561
|
+
import { z as z12 } from "zod";
|
|
562
|
+
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
563
|
+
var credentialsSchema = z12.object({
|
|
564
|
+
keys: z12.record(z12.string(), z12.string()),
|
|
565
|
+
version: z12.literal(1)
|
|
566
|
+
});
|
|
567
|
+
function credentialsPath() {
|
|
568
|
+
return path.join(homedir(), ".artifactcafe", "credentials");
|
|
569
|
+
}
|
|
570
|
+
function apiOrigin(apiUrl2) {
|
|
571
|
+
return new URL(apiUrl2).origin;
|
|
572
|
+
}
|
|
573
|
+
function readCredentials() {
|
|
574
|
+
const file = credentialsPath();
|
|
575
|
+
if (!existsSync(file)) {
|
|
576
|
+
return { keys: {}, version: 1 };
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
return credentialsSchema.parse(JSON.parse(readFileSync(file, "utf8")));
|
|
580
|
+
} catch {
|
|
581
|
+
return { keys: {}, version: 1 };
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function writeCredentials(credentials) {
|
|
585
|
+
const file = credentialsPath();
|
|
586
|
+
mkdirSync(path.dirname(file), { mode: 448, recursive: true });
|
|
587
|
+
writeFileSync(file, `${JSON.stringify(credentials, null, 2)}
|
|
588
|
+
`, {
|
|
589
|
+
mode: 384
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function storedApiKey(apiUrl2) {
|
|
593
|
+
return readCredentials().keys[apiOrigin(apiUrl2)] ?? null;
|
|
594
|
+
}
|
|
595
|
+
function storeApiKey(apiUrl2, key) {
|
|
596
|
+
const credentials = readCredentials();
|
|
597
|
+
credentials.keys[apiOrigin(apiUrl2)] = key;
|
|
598
|
+
writeCredentials(credentials);
|
|
599
|
+
}
|
|
600
|
+
function removeApiKey(apiUrl2) {
|
|
601
|
+
const credentials = readCredentials();
|
|
602
|
+
const origin = apiOrigin(apiUrl2);
|
|
603
|
+
if (!(origin in credentials.keys)) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
delete credentials.keys[origin];
|
|
607
|
+
writeCredentials(credentials);
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
function resolveApiKey(apiUrl2, flag) {
|
|
611
|
+
return flag ?? process.env.ARTIFACT_CAFE_API_KEY ?? storedApiKey(apiUrl2) ?? void 0;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/config.ts
|
|
615
|
+
var configSchema = z13.object({
|
|
616
|
+
apiUrl: z13.string(),
|
|
617
|
+
artifactId: z13.string(),
|
|
618
|
+
// Optional: a folder linked by `link` to an artifact you own carries no
|
|
619
|
+
// token — owner actions authorize via your stored login instead. A first
|
|
620
|
+
// publish still writes the anonymous-mode token here.
|
|
621
|
+
publishToken: z13.string().optional(),
|
|
622
|
+
slug: z13.string(),
|
|
623
|
+
url: z13.string()
|
|
491
624
|
});
|
|
492
625
|
var CONFIG_DIR = ".artifactcafe";
|
|
493
626
|
var CONFIG_FILE = "config.json";
|
|
494
627
|
function readConfig(dir) {
|
|
495
|
-
const file =
|
|
496
|
-
if (!
|
|
628
|
+
const file = path2.join(dir, CONFIG_DIR, CONFIG_FILE);
|
|
629
|
+
if (!existsSync2(file)) {
|
|
497
630
|
return null;
|
|
498
631
|
}
|
|
499
632
|
try {
|
|
500
|
-
return configSchema.parse(JSON.parse(
|
|
633
|
+
return configSchema.parse(JSON.parse(readFileSync2(file, "utf8")));
|
|
501
634
|
} catch {
|
|
502
635
|
return null;
|
|
503
636
|
}
|
|
504
637
|
}
|
|
505
638
|
function writeConfig(dir, config) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
639
|
+
mkdirSync2(path2.join(dir, CONFIG_DIR), { recursive: true });
|
|
640
|
+
writeFileSync2(
|
|
641
|
+
path2.join(dir, CONFIG_DIR, CONFIG_FILE),
|
|
509
642
|
`${JSON.stringify(config, null, 2)}
|
|
510
643
|
`,
|
|
511
644
|
{ mode: 384 }
|
|
@@ -513,11 +646,11 @@ function writeConfig(dir, config) {
|
|
|
513
646
|
ensureGitignored(dir);
|
|
514
647
|
}
|
|
515
648
|
function ensureGitignored(dir) {
|
|
516
|
-
const gitignore =
|
|
517
|
-
if (!
|
|
649
|
+
const gitignore = path2.join(dir, ".gitignore");
|
|
650
|
+
if (!existsSync2(gitignore)) {
|
|
518
651
|
return;
|
|
519
652
|
}
|
|
520
|
-
const lines =
|
|
653
|
+
const lines = readFileSync2(gitignore, "utf8").split(/\r?\n/);
|
|
521
654
|
const present = lines.some((l) => {
|
|
522
655
|
const t = l.trim();
|
|
523
656
|
return t === CONFIG_DIR || t === `${CONFIG_DIR}/` || t === `/${CONFIG_DIR}` || t === `/${CONFIG_DIR}/`;
|
|
@@ -532,10 +665,30 @@ ${CONFIG_DIR}/
|
|
|
532
665
|
);
|
|
533
666
|
}
|
|
534
667
|
}
|
|
668
|
+
function reviewUrl(apiUrl2, slug) {
|
|
669
|
+
return `${apiUrl2.replace(/\/$/, "")}/a/${slug}`;
|
|
670
|
+
}
|
|
671
|
+
function resolveArtifactTarget(dir, opts = {}) {
|
|
672
|
+
const config = readConfig(dir);
|
|
673
|
+
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
|
|
674
|
+
const artifactId = opts.artifact ?? process.env.ARTIFACT_CAFE_ARTIFACT_ID ?? config?.artifactId ?? null;
|
|
675
|
+
if (!artifactId) {
|
|
676
|
+
throw new Error(
|
|
677
|
+
`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.`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
const folderToken = artifactId === config?.artifactId ? config.publishToken : void 0;
|
|
681
|
+
const credential = folderToken ?? process.env.ARTIFACT_CAFE_TOKEN ?? resolveApiKey(apiUrl2, opts.apiKey);
|
|
682
|
+
const url = artifactId === config?.artifactId ? config.url : void 0;
|
|
683
|
+
return { apiUrl: apiUrl2, artifactId, credential, url };
|
|
684
|
+
}
|
|
535
685
|
|
|
536
686
|
// src/comments.ts
|
|
537
687
|
var STATUSES = ["open", "resolved", "all"];
|
|
538
688
|
var VERSIONS = ["current", "all"];
|
|
689
|
+
var pollSeconds = () => Number(process.env.AC_WAIT_POLL_SECONDS ?? 5);
|
|
690
|
+
var settleSeconds = () => Number(process.env.AC_WAIT_SETTLE_SECONDS ?? 20);
|
|
691
|
+
var WAIT_TIMEOUT_EXIT = 2;
|
|
539
692
|
async function comments(opts) {
|
|
540
693
|
if (!STATUSES.includes(opts.status)) {
|
|
541
694
|
throw new Error(
|
|
@@ -549,16 +702,24 @@ async function comments(opts) {
|
|
|
549
702
|
}
|
|
550
703
|
const status = opts.status;
|
|
551
704
|
const version = opts.version;
|
|
552
|
-
const dir =
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
705
|
+
const dir = path3.resolve(opts.dir);
|
|
706
|
+
const target = resolveArtifactTarget(dir, {
|
|
707
|
+
apiKey: opts.apiKey,
|
|
708
|
+
artifact: opts.artifact
|
|
709
|
+
});
|
|
710
|
+
const client = createApiClient(target.apiUrl, target.credential);
|
|
711
|
+
const url = target.url ?? (opts.json ? "" : await deriveReviewUrl(client, target));
|
|
712
|
+
const ref = { artifactId: target.artifactId, url };
|
|
713
|
+
if (opts.wait) {
|
|
714
|
+
await waitForFeedback(client, ref, {
|
|
715
|
+
json: opts.json,
|
|
716
|
+
status,
|
|
717
|
+
timeoutSeconds: opts.timeout,
|
|
718
|
+
version
|
|
719
|
+
});
|
|
720
|
+
return;
|
|
558
721
|
}
|
|
559
|
-
const
|
|
560
|
-
const client = createApiClient(apiUrl2);
|
|
561
|
-
const data = await client.exportComments(config.artifactId, {
|
|
722
|
+
const data = await client.exportComments(target.artifactId, {
|
|
562
723
|
status,
|
|
563
724
|
version
|
|
564
725
|
});
|
|
@@ -566,7 +727,117 @@ async function comments(opts) {
|
|
|
566
727
|
console.log(JSON.stringify(data, null, 2));
|
|
567
728
|
return;
|
|
568
729
|
}
|
|
569
|
-
printHuman(data,
|
|
730
|
+
printHuman(data, url, status, version);
|
|
731
|
+
}
|
|
732
|
+
async function deriveReviewUrl(client, target) {
|
|
733
|
+
const artifact = await client.getArtifact(target.artifactId);
|
|
734
|
+
return reviewUrl(target.apiUrl, artifact.slug);
|
|
735
|
+
}
|
|
736
|
+
var sleep = (s) => new Promise((r) => setTimeout(r, s * 1e3));
|
|
737
|
+
function messageCounts(data) {
|
|
738
|
+
return new Map(data.comments.map((t) => [t.id, 1 + t.replies.length]));
|
|
739
|
+
}
|
|
740
|
+
function diffThreads(baseline, data) {
|
|
741
|
+
const changed = [];
|
|
742
|
+
for (const t of data.comments) {
|
|
743
|
+
const before = baseline.get(t.id);
|
|
744
|
+
if (before === void 0) {
|
|
745
|
+
changed.push({ ...t, change: "new" });
|
|
746
|
+
} else if (1 + t.replies.length > before) {
|
|
747
|
+
changed.push({ ...t, change: "updated" });
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return changed;
|
|
751
|
+
}
|
|
752
|
+
async function waitForFeedback(client, config, opts) {
|
|
753
|
+
const query = { status: opts.status, version: opts.version };
|
|
754
|
+
const baselineExport = await client.exportComments(config.artifactId, query);
|
|
755
|
+
const baseline = messageCounts(baselineExport);
|
|
756
|
+
if (!opts.json) {
|
|
757
|
+
const mins = Math.round(opts.timeoutSeconds / 60);
|
|
758
|
+
console.log(
|
|
759
|
+
`Waiting for feedback on ${config.url} \u2014 times out after ${mins}m. Ctrl-C to stop.`
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
const deadline = Date.now() + opts.timeoutSeconds * 1e3;
|
|
763
|
+
let heartbeatWarned = false;
|
|
764
|
+
let lastHeartbeat = 0;
|
|
765
|
+
const heartbeat = async () => {
|
|
766
|
+
if (Date.now() - lastHeartbeat < AGENT_PRESENCE.heartbeatSeconds * 1e3) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
lastHeartbeat = Date.now();
|
|
770
|
+
try {
|
|
771
|
+
await client.agentHeartbeat(config.artifactId);
|
|
772
|
+
} catch (err) {
|
|
773
|
+
if (!(opts.json || heartbeatWarned)) {
|
|
774
|
+
heartbeatWarned = true;
|
|
775
|
+
console.error(
|
|
776
|
+
`(presence heartbeat failed \u2014 reviewers won't see "Agent is listening": ${err instanceof Error ? err.message : err})`
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
await heartbeat();
|
|
782
|
+
while (Date.now() < deadline) {
|
|
783
|
+
await sleep(Math.min(pollSeconds(), (deadline - Date.now()) / 1e3));
|
|
784
|
+
await heartbeat();
|
|
785
|
+
const data = await client.exportComments(config.artifactId, query);
|
|
786
|
+
if (diffThreads(baseline, data).length === 0) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
await sleep(settleSeconds());
|
|
790
|
+
const settled = await client.exportComments(config.artifactId, query);
|
|
791
|
+
const changed = diffThreads(baseline, settled);
|
|
792
|
+
printWaitResult(config, settled, changed, opts.json);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
if (opts.json) {
|
|
796
|
+
console.log(
|
|
797
|
+
JSON.stringify(
|
|
798
|
+
{
|
|
799
|
+
artifactId: config.artifactId,
|
|
800
|
+
threads: [],
|
|
801
|
+
timedOut: true,
|
|
802
|
+
versionNumber: baselineExport.versionNumber
|
|
803
|
+
},
|
|
804
|
+
null,
|
|
805
|
+
2
|
|
806
|
+
)
|
|
807
|
+
);
|
|
808
|
+
} else {
|
|
809
|
+
console.log(
|
|
810
|
+
`No new feedback after ${opts.timeoutSeconds}s. Run the command again to keep waiting.`
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
process.exit(WAIT_TIMEOUT_EXIT);
|
|
814
|
+
}
|
|
815
|
+
function printWaitResult(config, data, changed, json) {
|
|
816
|
+
if (json) {
|
|
817
|
+
console.log(
|
|
818
|
+
JSON.stringify(
|
|
819
|
+
{
|
|
820
|
+
artifactId: config.artifactId,
|
|
821
|
+
threads: changed,
|
|
822
|
+
timedOut: false,
|
|
823
|
+
versionNumber: data.versionNumber
|
|
824
|
+
},
|
|
825
|
+
null,
|
|
826
|
+
2
|
|
827
|
+
)
|
|
828
|
+
);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const plural = changed.length === 1 ? "" : "s";
|
|
832
|
+
console.log(
|
|
833
|
+
`
|
|
834
|
+
${changed.length} thread${plural} with new feedback \u2014 ${config.url}
|
|
835
|
+
`
|
|
836
|
+
);
|
|
837
|
+
for (const thread of changed) {
|
|
838
|
+
console.log(thread.change === "new" ? "NEW" : "UPDATED");
|
|
839
|
+
printThread(thread);
|
|
840
|
+
}
|
|
570
841
|
}
|
|
571
842
|
function printHuman(data, url, status, version) {
|
|
572
843
|
const statusLabel = status === "all" ? "" : `${status} `;
|
|
@@ -589,173 +860,31 @@ function printHuman(data, url, status, version) {
|
|
|
589
860
|
}
|
|
590
861
|
function printThread(t) {
|
|
591
862
|
const marker = t.status === "resolved" ? "\u2713" : "\u25CF";
|
|
592
|
-
const date = t.createdAt.slice(0, 10);
|
|
593
|
-
console.log(
|
|
594
|
-
`${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}`
|
|
595
|
-
);
|
|
596
|
-
if (t.anchor.type === "text") {
|
|
597
|
-
console.log(` \u21B3 "${t.anchor.quote}"`);
|
|
598
|
-
} else if (t.anchor.element) {
|
|
599
|
-
const el = t.anchor.element;
|
|
600
|
-
const text = el.text ? ` "${el.text}"` : "";
|
|
601
|
-
console.log(` \u21B3 at ${elementLabel(el)}${text} \u2014 ${t.anchor.elementPath}`);
|
|
602
|
-
} else {
|
|
603
|
-
console.log(` \u21B3 at ${t.anchor.elementPath}`);
|
|
604
|
-
}
|
|
605
|
-
console.log(indent(t.body, " "));
|
|
606
|
-
for (const reply of t.replies) {
|
|
607
|
-
console.log(indent(`${reply.author}: ${reply.body}`, " "));
|
|
608
|
-
}
|
|
609
|
-
console.log("");
|
|
610
|
-
}
|
|
611
|
-
function indent(text, pad) {
|
|
612
|
-
return text.split("\n").map((line) => pad + line).join("\n");
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// src/errors.ts
|
|
616
|
-
function printCliError(err, json) {
|
|
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
|
-
}
|
|
643
|
-
|
|
644
|
-
// src/credentials.ts
|
|
645
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
646
|
-
import { homedir } from "os";
|
|
647
|
-
import path3 from "path";
|
|
648
|
-
import { z as z11 } from "zod";
|
|
649
|
-
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
650
|
-
var credentialsSchema = z11.object({
|
|
651
|
-
keys: z11.record(z11.string(), z11.string()),
|
|
652
|
-
version: z11.literal(1)
|
|
653
|
-
});
|
|
654
|
-
function credentialsPath() {
|
|
655
|
-
return path3.join(homedir(), ".artifactcafe", "credentials");
|
|
656
|
-
}
|
|
657
|
-
function apiOrigin(apiUrl2) {
|
|
658
|
-
return new URL(apiUrl2).origin;
|
|
659
|
-
}
|
|
660
|
-
function readCredentials() {
|
|
661
|
-
const file = credentialsPath();
|
|
662
|
-
if (!existsSync2(file)) {
|
|
663
|
-
return { keys: {}, version: 1 };
|
|
664
|
-
}
|
|
665
|
-
try {
|
|
666
|
-
return credentialsSchema.parse(JSON.parse(readFileSync2(file, "utf8")));
|
|
667
|
-
} catch {
|
|
668
|
-
return { keys: {}, version: 1 };
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
function writeCredentials(credentials) {
|
|
672
|
-
const file = credentialsPath();
|
|
673
|
-
mkdirSync2(path3.dirname(file), { mode: 448, recursive: true });
|
|
674
|
-
writeFileSync2(file, `${JSON.stringify(credentials, null, 2)}
|
|
675
|
-
`, {
|
|
676
|
-
mode: 384
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
function storedApiKey(apiUrl2) {
|
|
680
|
-
return readCredentials().keys[apiOrigin(apiUrl2)] ?? null;
|
|
681
|
-
}
|
|
682
|
-
function storeApiKey(apiUrl2, key) {
|
|
683
|
-
const credentials = readCredentials();
|
|
684
|
-
credentials.keys[apiOrigin(apiUrl2)] = key;
|
|
685
|
-
writeCredentials(credentials);
|
|
686
|
-
}
|
|
687
|
-
function removeApiKey(apiUrl2) {
|
|
688
|
-
const credentials = readCredentials();
|
|
689
|
-
const origin = apiOrigin(apiUrl2);
|
|
690
|
-
if (!(origin in credentials.keys)) {
|
|
691
|
-
return false;
|
|
692
|
-
}
|
|
693
|
-
delete credentials.keys[origin];
|
|
694
|
-
writeCredentials(credentials);
|
|
695
|
-
return true;
|
|
696
|
-
}
|
|
697
|
-
function resolveApiKey(apiUrl2, flag) {
|
|
698
|
-
return flag ?? process.env.ARTIFACT_CAFE_API_KEY ?? storedApiKey(apiUrl2) ?? void 0;
|
|
699
|
-
}
|
|
700
|
-
|
|
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`;
|
|
863
|
+
const date = t.createdAt.slice(0, 10);
|
|
864
|
+
console.log(
|
|
865
|
+
`${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}`
|
|
866
|
+
);
|
|
867
|
+
if (t.anchor.type === "text") {
|
|
868
|
+
console.log(` \u21B3 "${t.anchor.quote}"`);
|
|
869
|
+
} else if (t.anchor.element) {
|
|
870
|
+
const el = t.anchor.element;
|
|
871
|
+
const text = el.text ? ` "${el.text}"` : "";
|
|
872
|
+
console.log(` \u21B3 at ${elementLabel(el)}${text} \u2014 ${t.anchor.elementPath}`);
|
|
873
|
+
} else {
|
|
874
|
+
console.log(` \u21B3 at ${t.anchor.elementPath}`);
|
|
750
875
|
}
|
|
751
|
-
|
|
752
|
-
|
|
876
|
+
console.log(indent(t.body, " "));
|
|
877
|
+
for (const reply of t.replies) {
|
|
878
|
+
console.log(indent(`${reply.author}: ${reply.body}`, " "));
|
|
753
879
|
}
|
|
754
|
-
|
|
880
|
+
console.log("");
|
|
881
|
+
}
|
|
882
|
+
function indent(text, pad) {
|
|
883
|
+
return text.split("\n").map((line) => pad + line).join("\n");
|
|
755
884
|
}
|
|
756
885
|
|
|
757
|
-
// src/
|
|
758
|
-
import
|
|
886
|
+
// src/dashboard.ts
|
|
887
|
+
import path6 from "path";
|
|
759
888
|
|
|
760
889
|
// src/publish.ts
|
|
761
890
|
import { exec } from "child_process";
|
|
@@ -1053,13 +1182,205 @@ function openInBrowser(url) {
|
|
|
1053
1182
|
});
|
|
1054
1183
|
}
|
|
1055
1184
|
|
|
1185
|
+
// src/dashboard.ts
|
|
1186
|
+
function openDashboard(opts) {
|
|
1187
|
+
const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path6.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
|
|
1188
|
+
const url = `${base.replace(/\/$/, "")}/dashboard`;
|
|
1189
|
+
console.log(url);
|
|
1190
|
+
if (process.stdout.isTTY) {
|
|
1191
|
+
openInBrowser(url);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// src/errors.ts
|
|
1196
|
+
function printCliError(err, json) {
|
|
1197
|
+
if (err instanceof ApiRequestError) {
|
|
1198
|
+
if (json) {
|
|
1199
|
+
console.log(
|
|
1200
|
+
JSON.stringify({
|
|
1201
|
+
error: {
|
|
1202
|
+
code: err.code,
|
|
1203
|
+
message: err.message,
|
|
1204
|
+
retryAfter: err.retryAfter
|
|
1205
|
+
}
|
|
1206
|
+
})
|
|
1207
|
+
);
|
|
1208
|
+
} else {
|
|
1209
|
+
console.error(`\u2717 ${err.message} (${err.code})`);
|
|
1210
|
+
if (err.retryAfter) {
|
|
1211
|
+
console.error(` Retry in ${err.retryAfter}s.`);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1217
|
+
if (json) {
|
|
1218
|
+
console.log(JSON.stringify({ error: { code: "cli_error", message } }));
|
|
1219
|
+
} else {
|
|
1220
|
+
console.error(`\u2717 ${message}`);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// src/link.ts
|
|
1225
|
+
import path7 from "path";
|
|
1226
|
+
import { createInterface } from "readline";
|
|
1227
|
+
async function link(opts) {
|
|
1228
|
+
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
1229
|
+
/\/$/,
|
|
1230
|
+
""
|
|
1231
|
+
);
|
|
1232
|
+
const key = resolveApiKey(apiUrl2, opts.apiKey);
|
|
1233
|
+
if (!key) {
|
|
1234
|
+
throw new Error(
|
|
1235
|
+
`Linking needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
const { artifacts } = await createApiClient(apiUrl2, key).listArtifacts();
|
|
1239
|
+
if (artifacts.length === 0) {
|
|
1240
|
+
throw new Error(
|
|
1241
|
+
"No artifacts in your account yet \u2014 publish one first, or claim one via its claim link."
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
const selector = opts.artifact ?? opts.slug;
|
|
1245
|
+
let chosen;
|
|
1246
|
+
if (opts.artifact) {
|
|
1247
|
+
chosen = artifacts.find((a) => a.id === opts.artifact);
|
|
1248
|
+
} else if (opts.slug) {
|
|
1249
|
+
chosen = artifacts.find((a) => a.slug === opts.slug);
|
|
1250
|
+
}
|
|
1251
|
+
if (selector && !chosen) {
|
|
1252
|
+
throw new Error(
|
|
1253
|
+
`No artifact "${selector}" in your account \u2014 run \`artifact-cafe list\` to see them.`
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
if (opts.json && !chosen) {
|
|
1257
|
+
console.log(JSON.stringify({ artifacts }, null, 2));
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
if (!chosen) {
|
|
1261
|
+
if (!process.stdin.isTTY) {
|
|
1262
|
+
throw new Error(
|
|
1263
|
+
"Pick an artifact with --artifact <id> or --slug <slug> (or --json to list them). Interactive selection needs a terminal."
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
chosen = await pickArtifact(artifacts);
|
|
1267
|
+
}
|
|
1268
|
+
const dir = path7.resolve(opts.dir);
|
|
1269
|
+
writeConfig(dir, {
|
|
1270
|
+
apiUrl: apiUrl2,
|
|
1271
|
+
artifactId: chosen.id,
|
|
1272
|
+
slug: chosen.slug,
|
|
1273
|
+
url: chosen.url
|
|
1274
|
+
// No publishToken — this folder authorizes via your login, not a secret.
|
|
1275
|
+
});
|
|
1276
|
+
if (opts.json) {
|
|
1277
|
+
console.log(
|
|
1278
|
+
JSON.stringify(
|
|
1279
|
+
{
|
|
1280
|
+
artifactId: chosen.id,
|
|
1281
|
+
linked: true,
|
|
1282
|
+
slug: chosen.slug,
|
|
1283
|
+
url: chosen.url
|
|
1284
|
+
},
|
|
1285
|
+
null,
|
|
1286
|
+
2
|
|
1287
|
+
)
|
|
1288
|
+
);
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
console.log(`
|
|
1292
|
+
\u2713 Linked ${dir} \u2192 "${chosen.title}" \u2014 ${chosen.url}`);
|
|
1293
|
+
console.log(
|
|
1294
|
+
" Owner actions (publish, comments --wait, protect) use your login.\n"
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
function pickArtifact(artifacts) {
|
|
1298
|
+
console.log("Your artifacts:\n");
|
|
1299
|
+
artifacts.forEach((a, i) => {
|
|
1300
|
+
const v = a.currentVersionNumber ? `v${a.currentVersionNumber}` : "\u2014";
|
|
1301
|
+
console.log(
|
|
1302
|
+
` ${String(i + 1).padStart(2)}. ${a.title} \xB7 ${v} \xB7 ${a.url}`
|
|
1303
|
+
);
|
|
1304
|
+
});
|
|
1305
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1306
|
+
return new Promise((resolve, reject) => {
|
|
1307
|
+
rl.question(`
|
|
1308
|
+
Link which? [1-${artifacts.length}]: `, (answer) => {
|
|
1309
|
+
rl.close();
|
|
1310
|
+
const n = Number(answer.trim());
|
|
1311
|
+
if (!(Number.isInteger(n) && n >= 1 && n <= artifacts.length)) {
|
|
1312
|
+
reject(new Error(`Enter a number between 1 and ${artifacts.length}.`));
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
resolve(artifacts[n - 1]);
|
|
1316
|
+
});
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// src/list.ts
|
|
1321
|
+
async function list(opts) {
|
|
1322
|
+
const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
|
|
1323
|
+
/\/$/,
|
|
1324
|
+
""
|
|
1325
|
+
);
|
|
1326
|
+
const key = resolveApiKey(apiUrl2, opts.apiKey);
|
|
1327
|
+
if (!key) {
|
|
1328
|
+
throw new Error(
|
|
1329
|
+
`Listing your artifacts needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
const data = await createApiClient(apiUrl2, key).listArtifacts();
|
|
1333
|
+
if (opts.json) {
|
|
1334
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (data.artifacts.length === 0) {
|
|
1338
|
+
console.log(
|
|
1339
|
+
"No artifacts in your account yet \u2014 publish while logged in, or claim one via its claim link."
|
|
1340
|
+
);
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const plural = data.artifacts.length === 1 ? "" : "s";
|
|
1344
|
+
console.log(`${data.artifacts.length} artifact${plural}
|
|
1345
|
+
`);
|
|
1346
|
+
for (const artifact of data.artifacts) {
|
|
1347
|
+
printArtifact(artifact);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function printArtifact(a) {
|
|
1351
|
+
const version = a.currentVersionNumber === null ? "no version" : `v${a.currentVersionNumber}`;
|
|
1352
|
+
const open2 = a.openCommentCount === 0 ? "no open comments" : `${a.openCommentCount} open comment${a.openCommentCount === 1 ? "" : "s"}`;
|
|
1353
|
+
const marker = a.openCommentCount > 0 ? "\u25CF" : "\u25CB";
|
|
1354
|
+
console.log(`${marker} ${a.title}`);
|
|
1355
|
+
console.log(` ${version} \xB7 ${open2} \xB7 updated ${relativeTime(a.updatedAt)}`);
|
|
1356
|
+
console.log(` ${a.url}
|
|
1357
|
+
`);
|
|
1358
|
+
}
|
|
1359
|
+
function relativeTime(iso) {
|
|
1360
|
+
const seconds = Math.max(0, (Date.now() - new Date(iso).getTime()) / 1e3);
|
|
1361
|
+
if (seconds < 60) {
|
|
1362
|
+
return "just now";
|
|
1363
|
+
}
|
|
1364
|
+
if (seconds < 3600) {
|
|
1365
|
+
return `${Math.floor(seconds / 60)}m ago`;
|
|
1366
|
+
}
|
|
1367
|
+
if (seconds < 86400) {
|
|
1368
|
+
return `${Math.floor(seconds / 3600)}h ago`;
|
|
1369
|
+
}
|
|
1370
|
+
if (seconds < 30 * 86400) {
|
|
1371
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
1372
|
+
}
|
|
1373
|
+
return iso.slice(0, 10);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1056
1376
|
// src/login.ts
|
|
1377
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1057
1378
|
function apiUrl() {
|
|
1058
1379
|
return (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
1059
1380
|
}
|
|
1060
1381
|
function promptSecret(question) {
|
|
1061
1382
|
return new Promise((resolve) => {
|
|
1062
|
-
const rl =
|
|
1383
|
+
const rl = createInterface2({
|
|
1063
1384
|
input: process.stdin,
|
|
1064
1385
|
output: process.stdout
|
|
1065
1386
|
});
|
|
@@ -1167,23 +1488,27 @@ function logout(opts) {
|
|
|
1167
1488
|
}
|
|
1168
1489
|
|
|
1169
1490
|
// src/open.ts
|
|
1170
|
-
import
|
|
1171
|
-
function openArtifact(opts) {
|
|
1172
|
-
const dir =
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1491
|
+
import path8 from "path";
|
|
1492
|
+
async function openArtifact(opts) {
|
|
1493
|
+
const dir = path8.resolve(opts.dir);
|
|
1494
|
+
const target = resolveArtifactTarget(dir, {
|
|
1495
|
+
apiKey: opts.apiKey,
|
|
1496
|
+
artifact: opts.artifact
|
|
1497
|
+
});
|
|
1498
|
+
const url = target.url ?? reviewUrl(
|
|
1499
|
+
target.apiUrl,
|
|
1500
|
+
(await createApiClient(target.apiUrl, target.credential).getArtifact(
|
|
1501
|
+
target.artifactId
|
|
1502
|
+
)).slug
|
|
1503
|
+
);
|
|
1504
|
+
console.log(url);
|
|
1180
1505
|
if (process.stdout.isTTY) {
|
|
1181
|
-
openInBrowser(
|
|
1506
|
+
openInBrowser(url);
|
|
1182
1507
|
}
|
|
1183
1508
|
}
|
|
1184
1509
|
|
|
1185
1510
|
// src/protect.ts
|
|
1186
|
-
import
|
|
1511
|
+
import path9 from "path";
|
|
1187
1512
|
async function protect(opts) {
|
|
1188
1513
|
if (opts.off && opts.password !== void 0) {
|
|
1189
1514
|
throw new Error("Pass either --password or --off, not both.");
|
|
@@ -1200,26 +1525,26 @@ async function protect(opts) {
|
|
|
1200
1525
|
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
1201
1526
|
}
|
|
1202
1527
|
}
|
|
1203
|
-
const dir =
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
|
|
1211
|
-
const token = config.publishToken ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
1212
|
-
const client = createApiClient(apiUrl2, token);
|
|
1213
|
-
const res = await client.setProtection(config.artifactId, password);
|
|
1528
|
+
const dir = path9.resolve(opts.dir);
|
|
1529
|
+
const target = resolveArtifactTarget(dir, {
|
|
1530
|
+
apiKey: opts.apiKey,
|
|
1531
|
+
artifact: opts.artifact
|
|
1532
|
+
});
|
|
1533
|
+
const client = createApiClient(target.apiUrl, target.credential);
|
|
1534
|
+
const res = await client.setProtection(target.artifactId, password);
|
|
1214
1535
|
if (opts.json) {
|
|
1215
1536
|
console.log(JSON.stringify({ protected: res.protected }, null, 2));
|
|
1216
1537
|
return;
|
|
1217
1538
|
}
|
|
1539
|
+
const url = target.url ?? reviewUrl(
|
|
1540
|
+
target.apiUrl,
|
|
1541
|
+
(await client.getArtifact(target.artifactId)).slug
|
|
1542
|
+
);
|
|
1218
1543
|
console.log(
|
|
1219
1544
|
res.protected ? `
|
|
1220
|
-
\u2713 Password protection on \u2014 ${
|
|
1545
|
+
\u2713 Password protection on \u2014 ${url}
|
|
1221
1546
|
` : `
|
|
1222
|
-
\u2713 Password protection removed \u2014 ${
|
|
1547
|
+
\u2713 Password protection removed \u2014 ${url}
|
|
1223
1548
|
`
|
|
1224
1549
|
);
|
|
1225
1550
|
}
|
|
@@ -1344,8 +1669,56 @@ var list2 = defineCommand({
|
|
|
1344
1669
|
}
|
|
1345
1670
|
}
|
|
1346
1671
|
});
|
|
1672
|
+
var link2 = defineCommand({
|
|
1673
|
+
args: {
|
|
1674
|
+
"api-key": {
|
|
1675
|
+
description: "Link with this API key (default: stored login)",
|
|
1676
|
+
type: "string"
|
|
1677
|
+
},
|
|
1678
|
+
artifact: {
|
|
1679
|
+
description: "Link this artifact id (skip the picker)",
|
|
1680
|
+
type: "string"
|
|
1681
|
+
},
|
|
1682
|
+
dir: {
|
|
1683
|
+
default: ".",
|
|
1684
|
+
description: "Directory to link",
|
|
1685
|
+
type: "positional"
|
|
1686
|
+
},
|
|
1687
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
1688
|
+
slug: {
|
|
1689
|
+
description: "Link this artifact slug (skip the picker)",
|
|
1690
|
+
type: "string"
|
|
1691
|
+
}
|
|
1692
|
+
},
|
|
1693
|
+
meta: {
|
|
1694
|
+
description: "Link a folder to one of your artifacts (pick from your account)",
|
|
1695
|
+
name: "link"
|
|
1696
|
+
},
|
|
1697
|
+
async run({ args }) {
|
|
1698
|
+
try {
|
|
1699
|
+
await link({
|
|
1700
|
+
apiKey: args["api-key"],
|
|
1701
|
+
artifact: args.artifact,
|
|
1702
|
+
dir: args.dir,
|
|
1703
|
+
json: Boolean(args.json),
|
|
1704
|
+
slug: args.slug
|
|
1705
|
+
});
|
|
1706
|
+
} catch (err) {
|
|
1707
|
+
printCliError(err, Boolean(args.json));
|
|
1708
|
+
process.exit(1);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1347
1712
|
var comments2 = defineCommand({
|
|
1348
1713
|
args: {
|
|
1714
|
+
"api-key": {
|
|
1715
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
1716
|
+
type: "string"
|
|
1717
|
+
},
|
|
1718
|
+
artifact: {
|
|
1719
|
+
description: "Act on an artifact ID instead of the linked folder",
|
|
1720
|
+
type: "string"
|
|
1721
|
+
},
|
|
1349
1722
|
dir: {
|
|
1350
1723
|
default: ".",
|
|
1351
1724
|
description: "Published directory",
|
|
@@ -1357,10 +1730,19 @@ var comments2 = defineCommand({
|
|
|
1357
1730
|
description: "Filter: open | resolved | all",
|
|
1358
1731
|
type: "string"
|
|
1359
1732
|
},
|
|
1733
|
+
timeout: {
|
|
1734
|
+
default: "900",
|
|
1735
|
+
description: "With --wait: give up after this many seconds (exit 2)",
|
|
1736
|
+
type: "string"
|
|
1737
|
+
},
|
|
1360
1738
|
version: {
|
|
1361
1739
|
default: "current",
|
|
1362
1740
|
description: "Filter: current | all",
|
|
1363
1741
|
type: "string"
|
|
1742
|
+
},
|
|
1743
|
+
wait: {
|
|
1744
|
+
description: "Block until a reviewer leaves new feedback",
|
|
1745
|
+
type: "boolean"
|
|
1364
1746
|
}
|
|
1365
1747
|
},
|
|
1366
1748
|
meta: {
|
|
@@ -1369,11 +1751,21 @@ var comments2 = defineCommand({
|
|
|
1369
1751
|
},
|
|
1370
1752
|
async run({ args }) {
|
|
1371
1753
|
try {
|
|
1754
|
+
const timeout = Number(args.timeout);
|
|
1755
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
1756
|
+
throw new Error(
|
|
1757
|
+
`--timeout must be a positive number of seconds (got "${args.timeout}").`
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1372
1760
|
await comments({
|
|
1761
|
+
apiKey: args["api-key"],
|
|
1762
|
+
artifact: args.artifact,
|
|
1373
1763
|
dir: args.dir,
|
|
1374
1764
|
json: Boolean(args.json),
|
|
1375
1765
|
status: args.status,
|
|
1376
|
-
|
|
1766
|
+
timeout,
|
|
1767
|
+
version: args.version,
|
|
1768
|
+
wait: Boolean(args.wait)
|
|
1377
1769
|
});
|
|
1378
1770
|
} catch (err) {
|
|
1379
1771
|
printCliError(err, Boolean(args.json));
|
|
@@ -1383,6 +1775,14 @@ var comments2 = defineCommand({
|
|
|
1383
1775
|
});
|
|
1384
1776
|
var protect2 = defineCommand({
|
|
1385
1777
|
args: {
|
|
1778
|
+
"api-key": {
|
|
1779
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
1780
|
+
type: "string"
|
|
1781
|
+
},
|
|
1782
|
+
artifact: {
|
|
1783
|
+
description: "Act on an artifact ID instead of the linked folder",
|
|
1784
|
+
type: "string"
|
|
1785
|
+
},
|
|
1386
1786
|
dir: {
|
|
1387
1787
|
default: ".",
|
|
1388
1788
|
description: "Published directory",
|
|
@@ -1399,6 +1799,8 @@ var protect2 = defineCommand({
|
|
|
1399
1799
|
async run({ args }) {
|
|
1400
1800
|
try {
|
|
1401
1801
|
await protect({
|
|
1802
|
+
apiKey: args["api-key"],
|
|
1803
|
+
artifact: args.artifact,
|
|
1402
1804
|
dir: args.dir,
|
|
1403
1805
|
json: Boolean(args.json),
|
|
1404
1806
|
off: Boolean(args.off),
|
|
@@ -1412,6 +1814,14 @@ var protect2 = defineCommand({
|
|
|
1412
1814
|
});
|
|
1413
1815
|
var open = defineCommand({
|
|
1414
1816
|
args: {
|
|
1817
|
+
"api-key": {
|
|
1818
|
+
description: "Authorize with an account API key (default: stored login)",
|
|
1819
|
+
type: "string"
|
|
1820
|
+
},
|
|
1821
|
+
artifact: {
|
|
1822
|
+
description: "Open an artifact ID instead of the linked folder",
|
|
1823
|
+
type: "string"
|
|
1824
|
+
},
|
|
1415
1825
|
dir: {
|
|
1416
1826
|
default: ".",
|
|
1417
1827
|
description: "Published directory",
|
|
@@ -1419,9 +1829,34 @@ var open = defineCommand({
|
|
|
1419
1829
|
}
|
|
1420
1830
|
},
|
|
1421
1831
|
meta: { description: "Open the linked artifact's review URL", name: "open" },
|
|
1832
|
+
async run({ args }) {
|
|
1833
|
+
try {
|
|
1834
|
+
await openArtifact({
|
|
1835
|
+
apiKey: args["api-key"],
|
|
1836
|
+
artifact: args.artifact,
|
|
1837
|
+
dir: args.dir
|
|
1838
|
+
});
|
|
1839
|
+
} catch (err) {
|
|
1840
|
+
printCliError(err, false);
|
|
1841
|
+
process.exit(1);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
});
|
|
1845
|
+
var dashboard = defineCommand({
|
|
1846
|
+
args: {
|
|
1847
|
+
dir: {
|
|
1848
|
+
default: ".",
|
|
1849
|
+
description: "Directory whose linked instance to open (optional)",
|
|
1850
|
+
type: "positional"
|
|
1851
|
+
}
|
|
1852
|
+
},
|
|
1853
|
+
meta: {
|
|
1854
|
+
description: "Open your personal dashboard in the browser",
|
|
1855
|
+
name: "dashboard"
|
|
1856
|
+
},
|
|
1422
1857
|
run({ args }) {
|
|
1423
1858
|
try {
|
|
1424
|
-
|
|
1859
|
+
openDashboard({ dir: args.dir });
|
|
1425
1860
|
} catch (err) {
|
|
1426
1861
|
printCliError(err, false);
|
|
1427
1862
|
process.exit(1);
|
|
@@ -1433,8 +1868,21 @@ var main = defineCommand({
|
|
|
1433
1868
|
description: "Publish AI-generated artifacts for review \u2014 https://artifact.cafe",
|
|
1434
1869
|
name: "artifact-cafe"
|
|
1435
1870
|
},
|
|
1871
|
+
// Bare `artifact-cafe` (no subcommand) → brand banner + usage. citty
|
|
1872
|
+
// still calls this parent run *after* a matched subcommand, so bail when
|
|
1873
|
+
// a subcommand token is present (any non-flag arg; citty has already
|
|
1874
|
+
// rejected unknown ones with E_UNKNOWN_COMMAND before we get here).
|
|
1875
|
+
async run({ cmd, rawArgs }) {
|
|
1876
|
+
if (rawArgs.some((arg) => !arg.startsWith("-"))) {
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
printBanner();
|
|
1880
|
+
await showUsage(cmd);
|
|
1881
|
+
},
|
|
1436
1882
|
subCommands: {
|
|
1437
1883
|
comments: comments2,
|
|
1884
|
+
dashboard,
|
|
1885
|
+
link: link2,
|
|
1438
1886
|
list: list2,
|
|
1439
1887
|
login: login2,
|
|
1440
1888
|
logout: logout2,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "artifact-cafe",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Publish AI-generated artifacts for review on artifact.cafe",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,19 +9,19 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
|
-
"scripts": {
|
|
13
|
-
"build": "tsup",
|
|
14
|
-
"typecheck": "tsc --noEmit",
|
|
15
|
-
"dev": "tsup --watch"
|
|
16
|
-
},
|
|
17
12
|
"dependencies": {
|
|
18
13
|
"citty": "^0.1.6",
|
|
19
14
|
"zod": "^4.1.0"
|
|
20
15
|
},
|
|
21
16
|
"devDependencies": {
|
|
22
|
-
"@artifact-cafe/shared": "workspace:*",
|
|
23
17
|
"@types/node": "^24.0.0",
|
|
24
18
|
"tsup": "^8.5.0",
|
|
25
|
-
"typescript": "^5.9.0"
|
|
19
|
+
"typescript": "^5.9.0",
|
|
20
|
+
"@artifact-cafe/shared": "0.1.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"dev": "tsup --watch"
|
|
26
26
|
}
|
|
27
|
-
}
|
|
27
|
+
}
|