artifact-cafe 0.0.0 → 0.0.2
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 +1013 -0
- package/package.json +24 -6
- package/README.md +0 -11
package/dist/index.js
ADDED
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { defineCommand, runMain } from "citty";
|
|
5
|
+
|
|
6
|
+
// src/comments.ts
|
|
7
|
+
import path2 from "path";
|
|
8
|
+
|
|
9
|
+
// ../shared/src/comments.ts
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
var elementSnapshotSchema = z.object({
|
|
12
|
+
classes: z.string().max(256).optional(),
|
|
13
|
+
id: z.string().max(256).optional(),
|
|
14
|
+
tag: z.string().min(1).max(128),
|
|
15
|
+
text: z.string().max(200).optional()
|
|
16
|
+
});
|
|
17
|
+
var pointAnchorSchema = z.object({
|
|
18
|
+
element: elementSnapshotSchema.optional(),
|
|
19
|
+
// elementPath is the primary anchor; x/y are offsets within the
|
|
20
|
+
// element's box, normalized 0..1 (SPEC.md §8.1).
|
|
21
|
+
elementPath: z.string().max(2048),
|
|
22
|
+
type: z.literal("point"),
|
|
23
|
+
viewportHeight: z.number().int().positive().max(1e5),
|
|
24
|
+
viewportWidth: z.number().int().positive().max(1e5),
|
|
25
|
+
x: z.number().min(0).max(1),
|
|
26
|
+
y: z.number().min(0).max(1)
|
|
27
|
+
});
|
|
28
|
+
var textAnchorSchema = z.object({
|
|
29
|
+
elementPath: z.string().max(2048),
|
|
30
|
+
endOffset: z.number().int().nonnegative().max(1e8),
|
|
31
|
+
prefix: z.string().max(200).optional(),
|
|
32
|
+
quote: z.string().min(1).max(1e3),
|
|
33
|
+
startOffset: z.number().int().nonnegative().max(1e8),
|
|
34
|
+
suffix: z.string().max(200).optional(),
|
|
35
|
+
type: z.literal("text")
|
|
36
|
+
});
|
|
37
|
+
var anchorSchema = z.discriminatedUnion("type", [
|
|
38
|
+
pointAnchorSchema,
|
|
39
|
+
textAnchorSchema
|
|
40
|
+
]);
|
|
41
|
+
var guestSchema = z.object({
|
|
42
|
+
displayName: z.string().min(1).max(64),
|
|
43
|
+
guestId: z.string().min(16)
|
|
44
|
+
});
|
|
45
|
+
var createCommentThreadSchema = z.object({
|
|
46
|
+
anchor: anchorSchema,
|
|
47
|
+
body: z.string().min(1).max(1e4),
|
|
48
|
+
guest: guestSchema.optional(),
|
|
49
|
+
versionId: z.string()
|
|
50
|
+
});
|
|
51
|
+
var createCommentMessageSchema = z.object({
|
|
52
|
+
body: z.string().min(1).max(1e4),
|
|
53
|
+
guest: guestSchema.optional()
|
|
54
|
+
});
|
|
55
|
+
var threadActionSchema = z.object({
|
|
56
|
+
guest: guestSchema.optional()
|
|
57
|
+
});
|
|
58
|
+
var commentAuthorSchema = z.object({
|
|
59
|
+
displayName: z.string(),
|
|
60
|
+
guestId: z.string().nullable()
|
|
61
|
+
});
|
|
62
|
+
var commentMessageSchema = z.object({
|
|
63
|
+
author: commentAuthorSchema,
|
|
64
|
+
body: z.string(),
|
|
65
|
+
createdAt: z.string(),
|
|
66
|
+
id: z.string()
|
|
67
|
+
});
|
|
68
|
+
var commentThreadSchema = z.object({
|
|
69
|
+
anchor: anchorSchema,
|
|
70
|
+
artifactId: z.string(),
|
|
71
|
+
createdAt: z.string(),
|
|
72
|
+
createdBy: commentAuthorSchema,
|
|
73
|
+
id: z.string(),
|
|
74
|
+
messages: z.array(commentMessageSchema),
|
|
75
|
+
status: z.enum(["open", "resolved"]),
|
|
76
|
+
versionId: z.string()
|
|
77
|
+
});
|
|
78
|
+
var commentThreadListSchema = z.object({
|
|
79
|
+
threads: z.array(commentThreadSchema)
|
|
80
|
+
});
|
|
81
|
+
var commentExportQuerySchema = z.object({
|
|
82
|
+
status: z.enum(["open", "resolved", "all"]).default("open"),
|
|
83
|
+
version: z.enum(["current", "all"]).default("current")
|
|
84
|
+
});
|
|
85
|
+
var commentExportReplySchema = z.object({
|
|
86
|
+
author: z.string(),
|
|
87
|
+
body: z.string(),
|
|
88
|
+
createdAt: z.string()
|
|
89
|
+
});
|
|
90
|
+
var commentExportThreadSchema = z.object({
|
|
91
|
+
anchor: anchorSchema,
|
|
92
|
+
author: z.string(),
|
|
93
|
+
body: z.string(),
|
|
94
|
+
createdAt: z.string(),
|
|
95
|
+
id: z.string(),
|
|
96
|
+
replies: z.array(commentExportReplySchema),
|
|
97
|
+
status: z.enum(["open", "resolved"]),
|
|
98
|
+
versionNumber: z.number().int().positive()
|
|
99
|
+
});
|
|
100
|
+
var commentExportSchema = z.object({
|
|
101
|
+
artifactId: z.string(),
|
|
102
|
+
comments: z.array(commentExportThreadSchema),
|
|
103
|
+
// The artifact's current version (null before the first finalize).
|
|
104
|
+
versionNumber: z.number().int().positive().nullable()
|
|
105
|
+
});
|
|
106
|
+
function elementLabel(el) {
|
|
107
|
+
if (el.id) {
|
|
108
|
+
return `<${el.tag}#${el.id}>`;
|
|
109
|
+
}
|
|
110
|
+
const classes = el.classes ? el.classes.split(" ").slice(0, 2) : [];
|
|
111
|
+
return classes.length ? `<${el.tag}.${classes.join(".")}>` : `<${el.tag}>`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ../shared/src/errors.ts
|
|
115
|
+
import { z as z2 } from "zod";
|
|
116
|
+
var apiErrorSchema = z2.object({
|
|
117
|
+
code: z2.string(),
|
|
118
|
+
docsUrl: z2.string().url(),
|
|
119
|
+
message: z2.string(),
|
|
120
|
+
retryAfter: z2.number().int().positive().optional()
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// ../shared/src/owner.ts
|
|
124
|
+
import { z as z3 } from "zod";
|
|
125
|
+
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
|
|
126
|
+
var claimResponseSchema = z3.object({
|
|
127
|
+
artifactId: z3.string(),
|
|
128
|
+
slug: z3.string(),
|
|
129
|
+
title: z3.string(),
|
|
130
|
+
url: z3.string().url()
|
|
131
|
+
});
|
|
132
|
+
var updateArtifactSchema = z3.object({
|
|
133
|
+
slug: z3.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
134
|
+
message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
135
|
+
}).optional(),
|
|
136
|
+
title: z3.string().min(1).max(200).optional()
|
|
137
|
+
}).refine((body) => body.title !== void 0 || body.slug !== void 0, {
|
|
138
|
+
message: "provide at least one of title, slug"
|
|
139
|
+
});
|
|
140
|
+
var deleteArtifactResponseSchema = z3.object({
|
|
141
|
+
deleted: z3.literal(true)
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ../shared/src/protection.ts
|
|
145
|
+
import { z as z4 } from "zod";
|
|
146
|
+
var PASSWORD_MIN = 6;
|
|
147
|
+
var PASSWORD_MAX = 128;
|
|
148
|
+
var gatePasswordSchema = z4.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
|
|
149
|
+
var setProtectionSchema = z4.object({
|
|
150
|
+
password: gatePasswordSchema.nullable()
|
|
151
|
+
});
|
|
152
|
+
var protectionResponseSchema = z4.object({
|
|
153
|
+
protected: z4.boolean()
|
|
154
|
+
});
|
|
155
|
+
var unlockSchema = z4.object({
|
|
156
|
+
password: z4.string().min(1).max(1024)
|
|
157
|
+
});
|
|
158
|
+
var unlockResponseSchema = z4.object({
|
|
159
|
+
unlocked: z4.literal(true)
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// ../shared/src/publish.ts
|
|
163
|
+
import { z as z5 } from "zod";
|
|
164
|
+
function isSafeArtifactPath(p) {
|
|
165
|
+
if (p.length === 0 || p.length > 1024) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
if (p.includes("\\") || p.includes("\0")) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (p.startsWith("/") || p.startsWith("~")) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
return p.split("/").every((seg) => seg.length > 0 && seg !== "." && seg !== "..");
|
|
175
|
+
}
|
|
176
|
+
var manifestFileSchema = z5.object({
|
|
177
|
+
hash: z5.string().regex(/^sha256_[a-f0-9]{64}$/),
|
|
178
|
+
mimeType: z5.string().regex(/^[-\w.+]+\/[-\w.+]+$/, { message: "invalid MIME type" }),
|
|
179
|
+
path: z5.string().min(1).max(1024).refine(isSafeArtifactPath, {
|
|
180
|
+
message: "path must be a safe relative path"
|
|
181
|
+
}),
|
|
182
|
+
size: z5.number().int().positive()
|
|
183
|
+
});
|
|
184
|
+
var publishSourceSchema = z5.object({
|
|
185
|
+
agent: z5.string().optional(),
|
|
186
|
+
runId: z5.string().optional(),
|
|
187
|
+
type: z5.literal("cli")
|
|
188
|
+
});
|
|
189
|
+
var createPublishSessionSchema = z5.object({
|
|
190
|
+
artifactId: z5.string().nullable(),
|
|
191
|
+
entryPath: z5.string().optional(),
|
|
192
|
+
files: z5.array(manifestFileSchema).min(1).max(1e3),
|
|
193
|
+
source: publishSourceSchema,
|
|
194
|
+
title: z5.string().max(200).optional()
|
|
195
|
+
});
|
|
196
|
+
var publishSessionResponseSchema = z5.object({
|
|
197
|
+
artifactId: z5.string(),
|
|
198
|
+
expiresAt: z5.string(),
|
|
199
|
+
filesToUpload: z5.array(
|
|
200
|
+
z5.object({
|
|
201
|
+
method: z5.literal("PUT"),
|
|
202
|
+
path: z5.string(),
|
|
203
|
+
uploadUrl: z5.string().url()
|
|
204
|
+
})
|
|
205
|
+
),
|
|
206
|
+
publishSessionId: z5.string(),
|
|
207
|
+
skippedFiles: z5.array(z5.string()),
|
|
208
|
+
versionId: z5.string()
|
|
209
|
+
});
|
|
210
|
+
var finalizeResponseSchema = z5.object({
|
|
211
|
+
artifactId: z5.string(),
|
|
212
|
+
// Returned only on first publish of a new artifact (one-time).
|
|
213
|
+
claimUrl: z5.string().url().optional(),
|
|
214
|
+
publishToken: z5.string().optional(),
|
|
215
|
+
url: z5.string().url(),
|
|
216
|
+
versionId: z5.string(),
|
|
217
|
+
versionNumber: z5.number().int().positive()
|
|
218
|
+
});
|
|
219
|
+
var artifactResponseSchema = z5.object({
|
|
220
|
+
claimed: z5.boolean(),
|
|
221
|
+
currentVersion: z5.object({
|
|
222
|
+
entryUrl: z5.string().url(),
|
|
223
|
+
id: z5.string(),
|
|
224
|
+
versionNumber: z5.number().int().positive()
|
|
225
|
+
}).nullable(),
|
|
226
|
+
id: z5.string(),
|
|
227
|
+
slug: z5.string(),
|
|
228
|
+
title: z5.string()
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ../shared/src/report.ts
|
|
232
|
+
import { z as z6 } from "zod";
|
|
233
|
+
var reportRequestSchema = z6.object({
|
|
234
|
+
reason: z6.string().max(1e3).optional()
|
|
235
|
+
});
|
|
236
|
+
var reportResponseSchema = z6.object({
|
|
237
|
+
reported: z6.literal(true)
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ../shared/src/versions.ts
|
|
241
|
+
import { z as z7 } from "zod";
|
|
242
|
+
var versionSourceSchema = z7.object({
|
|
243
|
+
agentName: z7.string().nullable(),
|
|
244
|
+
type: z7.string()
|
|
245
|
+
});
|
|
246
|
+
var versionChangesSchema = z7.object({
|
|
247
|
+
added: z7.array(z7.string()),
|
|
248
|
+
changed: z7.array(z7.string()),
|
|
249
|
+
removed: z7.array(z7.string())
|
|
250
|
+
});
|
|
251
|
+
var versionSummarySchema = z7.object({
|
|
252
|
+
// Absent for the first version — there is no previous version to diff against.
|
|
253
|
+
changes: versionChangesSchema.nullable(),
|
|
254
|
+
createdAt: z7.string(),
|
|
255
|
+
entryUrl: z7.string().url(),
|
|
256
|
+
fileCount: z7.number().int().nonnegative(),
|
|
257
|
+
id: z7.string(),
|
|
258
|
+
isCurrent: z7.boolean(),
|
|
259
|
+
source: versionSourceSchema,
|
|
260
|
+
totalSizeBytes: z7.number().int().nonnegative(),
|
|
261
|
+
versionNumber: z7.number().int().positive()
|
|
262
|
+
});
|
|
263
|
+
var versionListResponseSchema = z7.object({
|
|
264
|
+
versions: z7.array(versionSummarySchema)
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// src/api.ts
|
|
268
|
+
var ApiRequestError = class extends Error {
|
|
269
|
+
constructor(status, code, message, retryAfter) {
|
|
270
|
+
super(message);
|
|
271
|
+
this.status = status;
|
|
272
|
+
this.code = code;
|
|
273
|
+
this.retryAfter = retryAfter;
|
|
274
|
+
this.name = "ApiRequestError";
|
|
275
|
+
}
|
|
276
|
+
status;
|
|
277
|
+
code;
|
|
278
|
+
retryAfter;
|
|
279
|
+
};
|
|
280
|
+
async function request(url, init, parse) {
|
|
281
|
+
let res;
|
|
282
|
+
try {
|
|
283
|
+
res = await fetch(url, init);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
`Could not reach ${new URL(url).origin} \u2014 is the API up? (${err instanceof Error ? err.message : err})`,
|
|
287
|
+
{ cause: err }
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
const body = await res.json().catch(() => null);
|
|
291
|
+
if (!res.ok) {
|
|
292
|
+
const parsed = apiErrorSchema.safeParse(body);
|
|
293
|
+
if (parsed.success) {
|
|
294
|
+
throw new ApiRequestError(
|
|
295
|
+
res.status,
|
|
296
|
+
parsed.data.code,
|
|
297
|
+
parsed.data.message,
|
|
298
|
+
parsed.data.retryAfter
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
throw new ApiRequestError(
|
|
302
|
+
res.status,
|
|
303
|
+
"unknown_error",
|
|
304
|
+
`API returned ${res.status}`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
return parse(body);
|
|
308
|
+
}
|
|
309
|
+
function createApiClient(apiUrl, publishToken) {
|
|
310
|
+
const base = apiUrl.replace(/\/$/, "");
|
|
311
|
+
const headers = {
|
|
312
|
+
"content-type": "application/json"
|
|
313
|
+
};
|
|
314
|
+
if (publishToken) {
|
|
315
|
+
headers.authorization = `Bearer ${publishToken}`;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
async createSession(body) {
|
|
319
|
+
return request(
|
|
320
|
+
`${base}/api/v1/publish`,
|
|
321
|
+
{ body: JSON.stringify(body), headers, method: "POST" },
|
|
322
|
+
(d) => publishSessionResponseSchema.parse(d)
|
|
323
|
+
);
|
|
324
|
+
},
|
|
325
|
+
async exportComments(artifactId, query) {
|
|
326
|
+
const params = new URLSearchParams({
|
|
327
|
+
status: query.status,
|
|
328
|
+
version: query.version
|
|
329
|
+
});
|
|
330
|
+
return request(
|
|
331
|
+
`${base}/api/v1/artifacts/${artifactId}/comments/export?${params}`,
|
|
332
|
+
{ method: "GET" },
|
|
333
|
+
(d) => commentExportSchema.parse(d)
|
|
334
|
+
);
|
|
335
|
+
},
|
|
336
|
+
async finalize(publishSessionId) {
|
|
337
|
+
return request(
|
|
338
|
+
`${base}/api/v1/publish/${publishSessionId}/finalize`,
|
|
339
|
+
{ headers, method: "POST" },
|
|
340
|
+
(d) => finalizeResponseSchema.parse(d)
|
|
341
|
+
);
|
|
342
|
+
},
|
|
343
|
+
async getArtifact(artifactId) {
|
|
344
|
+
return request(
|
|
345
|
+
`${base}/api/v1/artifacts/${artifactId}`,
|
|
346
|
+
{ method: "GET" },
|
|
347
|
+
(d) => artifactResponseSchema.parse(d)
|
|
348
|
+
);
|
|
349
|
+
},
|
|
350
|
+
// SPEC §6.16 — set a password (string) or clear it (null). Authorized
|
|
351
|
+
// by the publish token this client was created with.
|
|
352
|
+
async setProtection(artifactId, password) {
|
|
353
|
+
return request(
|
|
354
|
+
`${base}/api/v1/artifacts/${artifactId}/protection`,
|
|
355
|
+
{ body: JSON.stringify({ password }), headers, method: "PUT" },
|
|
356
|
+
(d) => protectionResponseSchema.parse(d)
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
async function uploadFile(uploadUrl, content, mimeType) {
|
|
362
|
+
const res = await fetch(uploadUrl, {
|
|
363
|
+
body: new Uint8Array(content),
|
|
364
|
+
headers: {
|
|
365
|
+
"content-length": String(content.byteLength),
|
|
366
|
+
"content-type": mimeType
|
|
367
|
+
},
|
|
368
|
+
method: "PUT"
|
|
369
|
+
});
|
|
370
|
+
if (!res.ok) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
`Upload failed (${res.status}): ${(await res.text()).slice(0, 200)}`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/config.ts
|
|
378
|
+
import {
|
|
379
|
+
appendFileSync,
|
|
380
|
+
existsSync,
|
|
381
|
+
mkdirSync,
|
|
382
|
+
readFileSync,
|
|
383
|
+
writeFileSync
|
|
384
|
+
} from "fs";
|
|
385
|
+
import path from "path";
|
|
386
|
+
import { z as z8 } from "zod";
|
|
387
|
+
var configSchema = z8.object({
|
|
388
|
+
apiUrl: z8.string(),
|
|
389
|
+
artifactId: z8.string(),
|
|
390
|
+
publishToken: z8.string(),
|
|
391
|
+
slug: z8.string(),
|
|
392
|
+
url: z8.string()
|
|
393
|
+
});
|
|
394
|
+
var CONFIG_DIR = ".artifactcafe";
|
|
395
|
+
var CONFIG_FILE = "config.json";
|
|
396
|
+
function readConfig(dir) {
|
|
397
|
+
const file = path.join(dir, CONFIG_DIR, CONFIG_FILE);
|
|
398
|
+
if (!existsSync(file)) {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
return configSchema.parse(JSON.parse(readFileSync(file, "utf8")));
|
|
403
|
+
} catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function writeConfig(dir, config) {
|
|
408
|
+
mkdirSync(path.join(dir, CONFIG_DIR), { recursive: true });
|
|
409
|
+
writeFileSync(
|
|
410
|
+
path.join(dir, CONFIG_DIR, CONFIG_FILE),
|
|
411
|
+
`${JSON.stringify(config, null, 2)}
|
|
412
|
+
`,
|
|
413
|
+
{ mode: 384 }
|
|
414
|
+
);
|
|
415
|
+
ensureGitignored(dir);
|
|
416
|
+
}
|
|
417
|
+
function ensureGitignored(dir) {
|
|
418
|
+
const gitignore = path.join(dir, ".gitignore");
|
|
419
|
+
if (!existsSync(gitignore)) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const lines = readFileSync(gitignore, "utf8").split(/\r?\n/);
|
|
423
|
+
const present = lines.some((l) => {
|
|
424
|
+
const t = l.trim();
|
|
425
|
+
return t === CONFIG_DIR || t === `${CONFIG_DIR}/` || t === `/${CONFIG_DIR}` || t === `/${CONFIG_DIR}/`;
|
|
426
|
+
});
|
|
427
|
+
if (!present) {
|
|
428
|
+
appendFileSync(
|
|
429
|
+
gitignore,
|
|
430
|
+
`
|
|
431
|
+
# artifact.cafe publish token
|
|
432
|
+
${CONFIG_DIR}/
|
|
433
|
+
`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/comments.ts
|
|
439
|
+
var STATUSES = ["open", "resolved", "all"];
|
|
440
|
+
var VERSIONS = ["current", "all"];
|
|
441
|
+
async function comments(opts) {
|
|
442
|
+
if (!STATUSES.includes(opts.status)) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
`--status must be one of: ${STATUSES.join(", ")} (got "${opts.status}").`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
if (!VERSIONS.includes(opts.version)) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`--version must be one of: ${VERSIONS.join(", ")} (got "${opts.version}").`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const status = opts.status;
|
|
453
|
+
const version = opts.version;
|
|
454
|
+
const dir = path2.resolve(opts.dir);
|
|
455
|
+
const config = readConfig(dir);
|
|
456
|
+
if (!config) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
`No artifact linked in ${dir} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
const apiUrl = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
|
|
462
|
+
const client = createApiClient(apiUrl);
|
|
463
|
+
const data = await client.exportComments(config.artifactId, {
|
|
464
|
+
status,
|
|
465
|
+
version
|
|
466
|
+
});
|
|
467
|
+
if (opts.json) {
|
|
468
|
+
console.log(JSON.stringify(data, null, 2));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
printHuman(data, config.url, status, version);
|
|
472
|
+
}
|
|
473
|
+
function printHuman(data, url, status, version) {
|
|
474
|
+
const statusLabel = status === "all" ? "" : `${status} `;
|
|
475
|
+
let scope = "across all versions";
|
|
476
|
+
if (version === "current") {
|
|
477
|
+
scope = data.versionNumber === null ? "on the current version" : `on v${data.versionNumber}`;
|
|
478
|
+
}
|
|
479
|
+
if (data.comments.length === 0) {
|
|
480
|
+
console.log(`No ${statusLabel}comments ${scope}.`);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const plural = data.comments.length === 1 ? "" : "s";
|
|
484
|
+
console.log(
|
|
485
|
+
`${data.comments.length} ${statusLabel}thread${plural} ${scope} \u2014 ${url}
|
|
486
|
+
`
|
|
487
|
+
);
|
|
488
|
+
for (const thread of data.comments) {
|
|
489
|
+
printThread(thread);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function printThread(t) {
|
|
493
|
+
const marker = t.status === "resolved" ? "\u2713" : "\u25CF";
|
|
494
|
+
const date = t.createdAt.slice(0, 10);
|
|
495
|
+
console.log(
|
|
496
|
+
`${marker} ${t.author} \xB7 v${t.versionNumber} \xB7 ${date} \xB7 ${t.id}`
|
|
497
|
+
);
|
|
498
|
+
if (t.anchor.type === "text") {
|
|
499
|
+
console.log(` \u21B3 "${t.anchor.quote}"`);
|
|
500
|
+
} else if (t.anchor.element) {
|
|
501
|
+
const el = t.anchor.element;
|
|
502
|
+
const text = el.text ? ` "${el.text}"` : "";
|
|
503
|
+
console.log(` \u21B3 at ${elementLabel(el)}${text} \u2014 ${t.anchor.elementPath}`);
|
|
504
|
+
} else {
|
|
505
|
+
console.log(` \u21B3 at ${t.anchor.elementPath}`);
|
|
506
|
+
}
|
|
507
|
+
console.log(indent(t.body, " "));
|
|
508
|
+
for (const reply of t.replies) {
|
|
509
|
+
console.log(indent(`${reply.author}: ${reply.body}`, " "));
|
|
510
|
+
}
|
|
511
|
+
console.log("");
|
|
512
|
+
}
|
|
513
|
+
function indent(text, pad) {
|
|
514
|
+
return text.split("\n").map((line) => pad + line).join("\n");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/errors.ts
|
|
518
|
+
function printCliError(err, json) {
|
|
519
|
+
if (err instanceof ApiRequestError) {
|
|
520
|
+
if (json) {
|
|
521
|
+
console.log(
|
|
522
|
+
JSON.stringify({
|
|
523
|
+
error: {
|
|
524
|
+
code: err.code,
|
|
525
|
+
message: err.message,
|
|
526
|
+
retryAfter: err.retryAfter
|
|
527
|
+
}
|
|
528
|
+
})
|
|
529
|
+
);
|
|
530
|
+
} else {
|
|
531
|
+
console.error(`\u2717 ${err.message} (${err.code})`);
|
|
532
|
+
if (err.retryAfter) {
|
|
533
|
+
console.error(` Retry in ${err.retryAfter}s.`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
539
|
+
if (json) {
|
|
540
|
+
console.log(JSON.stringify({ error: { code: "cli_error", message } }));
|
|
541
|
+
} else {
|
|
542
|
+
console.error(`\u2717 ${message}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/open.ts
|
|
547
|
+
import path5 from "path";
|
|
548
|
+
|
|
549
|
+
// src/publish.ts
|
|
550
|
+
import { exec } from "child_process";
|
|
551
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
552
|
+
import path4 from "path";
|
|
553
|
+
|
|
554
|
+
// src/manifest.ts
|
|
555
|
+
import { createHash } from "crypto";
|
|
556
|
+
import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
557
|
+
import path3 from "path";
|
|
558
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".artifactcafe"]);
|
|
559
|
+
var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
|
|
560
|
+
function isIgnoredFile(name) {
|
|
561
|
+
if (IGNORED_FILES.has(name)) {
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
if (name === ".env" || name.startsWith(".env.")) {
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
if (name.endsWith(".pem") || name.endsWith(".key")) {
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
if (name.startsWith(".git")) {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
var MIME_TYPES = {
|
|
576
|
+
".avif": "image/avif",
|
|
577
|
+
".css": "text/css",
|
|
578
|
+
".gif": "image/gif",
|
|
579
|
+
".htm": "text/html",
|
|
580
|
+
".html": "text/html",
|
|
581
|
+
".ico": "image/x-icon",
|
|
582
|
+
".jpeg": "image/jpeg",
|
|
583
|
+
".jpg": "image/jpeg",
|
|
584
|
+
".js": "text/javascript",
|
|
585
|
+
".json": "application/json",
|
|
586
|
+
".map": "application/json",
|
|
587
|
+
".md": "text/markdown",
|
|
588
|
+
".mjs": "text/javascript",
|
|
589
|
+
".mp3": "audio/mpeg",
|
|
590
|
+
".mp4": "video/mp4",
|
|
591
|
+
".otf": "font/otf",
|
|
592
|
+
".pdf": "application/pdf",
|
|
593
|
+
".png": "image/png",
|
|
594
|
+
".svg": "image/svg+xml",
|
|
595
|
+
".ttf": "font/ttf",
|
|
596
|
+
".txt": "text/plain",
|
|
597
|
+
".wasm": "application/wasm",
|
|
598
|
+
".wav": "audio/wav",
|
|
599
|
+
".webm": "video/webm",
|
|
600
|
+
".webp": "image/webp",
|
|
601
|
+
".woff": "font/woff",
|
|
602
|
+
".woff2": "font/woff2",
|
|
603
|
+
".xml": "application/xml"
|
|
604
|
+
};
|
|
605
|
+
function mimeFor(filePath) {
|
|
606
|
+
return MIME_TYPES[path3.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
607
|
+
}
|
|
608
|
+
function scanDirectory(dir) {
|
|
609
|
+
const files = [];
|
|
610
|
+
const skipped = [];
|
|
611
|
+
function walk(current, relPrefix) {
|
|
612
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
613
|
+
const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
|
|
614
|
+
const abs = path3.join(current, entry.name);
|
|
615
|
+
if (entry.isSymbolicLink()) {
|
|
616
|
+
skipped.push(rel);
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (entry.isDirectory()) {
|
|
620
|
+
if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith(".git")) {
|
|
621
|
+
skipped.push(`${rel}/`);
|
|
622
|
+
} else {
|
|
623
|
+
walk(abs, rel);
|
|
624
|
+
}
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (!entry.isFile()) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (isIgnoredFile(entry.name) || !isSafeArtifactPath(rel)) {
|
|
631
|
+
skipped.push(rel);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const { size } = statSync(abs);
|
|
635
|
+
if (size === 0) {
|
|
636
|
+
skipped.push(rel);
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
const content = readFileSync2(abs);
|
|
640
|
+
files.push({
|
|
641
|
+
absPath: abs,
|
|
642
|
+
hash: `sha256_${createHash("sha256").update(content).digest("hex")}`,
|
|
643
|
+
mimeType: mimeFor(rel),
|
|
644
|
+
path: rel,
|
|
645
|
+
size
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
walk(dir, "");
|
|
650
|
+
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
651
|
+
return { files, skipped };
|
|
652
|
+
}
|
|
653
|
+
function resolveEntry(files, explicit) {
|
|
654
|
+
if (explicit) {
|
|
655
|
+
if (!files.some((f) => f.path === explicit)) {
|
|
656
|
+
throw new Error(
|
|
657
|
+
`Entry file "${explicit}" not found in the publish folder.`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
return explicit;
|
|
661
|
+
}
|
|
662
|
+
if (files.some((f) => f.path === "index.html")) {
|
|
663
|
+
return "index.html";
|
|
664
|
+
}
|
|
665
|
+
const htmlFiles = files.filter(
|
|
666
|
+
(f) => f.path.endsWith(".html") && !f.path.includes("/")
|
|
667
|
+
);
|
|
668
|
+
const [onlyHtmlFile] = htmlFiles;
|
|
669
|
+
if (onlyHtmlFile && htmlFiles.length === 1) {
|
|
670
|
+
return onlyHtmlFile.path;
|
|
671
|
+
}
|
|
672
|
+
throw new Error(
|
|
673
|
+
htmlFiles.length === 0 ? "No entry file found. Add an index.html or pass --entry <file>." : `Multiple HTML files found (${htmlFiles.map((f) => f.path).join(", ")}). Pass --entry <file>.`
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/publish.ts
|
|
678
|
+
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
679
|
+
var UPLOAD_CONCURRENCY = 6;
|
|
680
|
+
async function publish(opts) {
|
|
681
|
+
const dir = path4.resolve(opts.dir);
|
|
682
|
+
const config = readConfig(dir);
|
|
683
|
+
if (opts.password !== void 0) {
|
|
684
|
+
const parsed = gatePasswordSchema.safeParse(opts.password);
|
|
685
|
+
if (!parsed.success) {
|
|
686
|
+
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const artifactId = opts.artifact ?? config?.artifactId ?? null;
|
|
690
|
+
const token = (artifactId !== null && artifactId === config?.artifactId ? config.publishToken : void 0) ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
691
|
+
const apiUrl = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
|
|
692
|
+
const { files, skipped } = scanDirectory(dir);
|
|
693
|
+
if (files.length === 0) {
|
|
694
|
+
throw new Error(`Nothing to publish in ${dir} (after ignore rules).`);
|
|
695
|
+
}
|
|
696
|
+
const entryPath = resolveEntry(files, opts.entry);
|
|
697
|
+
const log = (msg) => {
|
|
698
|
+
if (!opts.json) {
|
|
699
|
+
console.log(msg);
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
if (skipped.length) {
|
|
703
|
+
log(` ignoring ${skipped.length} item(s): ${preview(skipped)}`);
|
|
704
|
+
}
|
|
705
|
+
const client = createApiClient(apiUrl, token);
|
|
706
|
+
const session = await client.createSession({
|
|
707
|
+
artifactId,
|
|
708
|
+
entryPath,
|
|
709
|
+
files: files.map(({ absPath: _absPath, ...manifest }) => manifest),
|
|
710
|
+
source: {
|
|
711
|
+
agent: process.env.ARTIFACT_CAFE_AGENT,
|
|
712
|
+
runId: process.env.ARTIFACT_CAFE_RUN_ID,
|
|
713
|
+
type: "cli"
|
|
714
|
+
},
|
|
715
|
+
title: opts.title
|
|
716
|
+
});
|
|
717
|
+
const byPath = new Map(files.map((f) => [f.path, f]));
|
|
718
|
+
log(
|
|
719
|
+
` uploading ${session.filesToUpload.length} file(s)` + (session.skippedFiles.length ? `, ${session.skippedFiles.length} unchanged` : "")
|
|
720
|
+
);
|
|
721
|
+
await inBatches(session.filesToUpload, UPLOAD_CONCURRENCY, async (upload) => {
|
|
722
|
+
const file = byPath.get(upload.path);
|
|
723
|
+
if (!file) {
|
|
724
|
+
throw new Error(`Server asked for unknown file ${upload.path}`);
|
|
725
|
+
}
|
|
726
|
+
await uploadFile(
|
|
727
|
+
upload.uploadUrl,
|
|
728
|
+
readFileSync3(file.absPath),
|
|
729
|
+
file.mimeType
|
|
730
|
+
);
|
|
731
|
+
});
|
|
732
|
+
const result = await client.finalize(session.publishSessionId);
|
|
733
|
+
if (result.publishToken) {
|
|
734
|
+
writeConfig(dir, {
|
|
735
|
+
apiUrl,
|
|
736
|
+
artifactId: result.artifactId,
|
|
737
|
+
publishToken: result.publishToken,
|
|
738
|
+
slug: slugFromUrl(result.url),
|
|
739
|
+
url: result.url
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
if (opts.password !== void 0) {
|
|
743
|
+
const protectClient = createApiClient(apiUrl, token ?? result.publishToken);
|
|
744
|
+
await protectClient.setProtection(result.artifactId, opts.password);
|
|
745
|
+
}
|
|
746
|
+
if (opts.json) {
|
|
747
|
+
console.log(
|
|
748
|
+
JSON.stringify(
|
|
749
|
+
{
|
|
750
|
+
artifactId: result.artifactId,
|
|
751
|
+
url: result.url,
|
|
752
|
+
versionNumber: result.versionNumber,
|
|
753
|
+
...result.claimUrl ? { claimUrl: result.claimUrl } : {},
|
|
754
|
+
...opts.password === void 0 ? {} : { protected: true },
|
|
755
|
+
filesUploaded: session.filesToUpload.map((f) => f.path),
|
|
756
|
+
ignored: skipped,
|
|
757
|
+
skippedFiles: session.skippedFiles
|
|
758
|
+
},
|
|
759
|
+
null,
|
|
760
|
+
2
|
|
761
|
+
)
|
|
762
|
+
);
|
|
763
|
+
} else if (result.versionNumber === 1) {
|
|
764
|
+
console.log("\n\u2713 Published to artifact.cafe\n");
|
|
765
|
+
console.log(`Review URL:
|
|
766
|
+
${result.url}
|
|
767
|
+
`);
|
|
768
|
+
if (result.claimUrl) {
|
|
769
|
+
console.log(
|
|
770
|
+
`Claim URL (shown once \u2014 claim to keep this artifact):
|
|
771
|
+
${result.claimUrl}
|
|
772
|
+
`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
if (opts.password !== void 0) {
|
|
776
|
+
console.log("Password protection: on\n");
|
|
777
|
+
}
|
|
778
|
+
} else {
|
|
779
|
+
console.log(`
|
|
780
|
+
\u2713 Published v${result.versionNumber}
|
|
781
|
+
`);
|
|
782
|
+
console.log(`${result.url}
|
|
783
|
+
`);
|
|
784
|
+
if (opts.password !== void 0) {
|
|
785
|
+
console.log("Password protection: on\n");
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
if (opts.open && !opts.json && process.stdout.isTTY) {
|
|
789
|
+
openInBrowser(result.url);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
async function inBatches(items, concurrency, fn) {
|
|
793
|
+
let next = 0;
|
|
794
|
+
const workers = Array.from(
|
|
795
|
+
{ length: Math.min(concurrency, items.length) },
|
|
796
|
+
async () => {
|
|
797
|
+
while (next < items.length) {
|
|
798
|
+
const item = items[next];
|
|
799
|
+
next += 1;
|
|
800
|
+
if (item === void 0) {
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
await fn(item);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
);
|
|
807
|
+
await Promise.all(workers);
|
|
808
|
+
}
|
|
809
|
+
function slugFromUrl(url) {
|
|
810
|
+
return url.split("/a/")[1] ?? "";
|
|
811
|
+
}
|
|
812
|
+
function preview(items) {
|
|
813
|
+
return items.slice(0, 5).join(", ") + (items.length > 5 ? ", \u2026" : "");
|
|
814
|
+
}
|
|
815
|
+
function openInBrowser(url) {
|
|
816
|
+
let cmd = "xdg-open";
|
|
817
|
+
if (process.platform === "darwin") {
|
|
818
|
+
cmd = "open";
|
|
819
|
+
} else if (process.platform === "win32") {
|
|
820
|
+
cmd = "start";
|
|
821
|
+
}
|
|
822
|
+
exec(`${cmd} ${JSON.stringify(url)}`, () => {
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// src/open.ts
|
|
827
|
+
function openArtifact(opts) {
|
|
828
|
+
const dir = path5.resolve(opts.dir);
|
|
829
|
+
const config = readConfig(dir);
|
|
830
|
+
if (!config) {
|
|
831
|
+
throw new Error(
|
|
832
|
+
`No artifact linked in ${dir} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
console.log(config.url);
|
|
836
|
+
if (process.stdout.isTTY) {
|
|
837
|
+
openInBrowser(config.url);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/protect.ts
|
|
842
|
+
import path6 from "path";
|
|
843
|
+
async function protect(opts) {
|
|
844
|
+
if (opts.off && opts.password !== void 0) {
|
|
845
|
+
throw new Error("Pass either --password or --off, not both.");
|
|
846
|
+
}
|
|
847
|
+
if (!opts.off && opts.password === void 0) {
|
|
848
|
+
throw new Error(
|
|
849
|
+
"Provide --password <password> to set a gate, or --off to remove it."
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
const password = opts.off ? null : opts.password;
|
|
853
|
+
if (password !== null) {
|
|
854
|
+
const parsed = gatePasswordSchema.safeParse(password);
|
|
855
|
+
if (!parsed.success) {
|
|
856
|
+
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
const dir = path6.resolve(opts.dir);
|
|
860
|
+
const config = readConfig(dir);
|
|
861
|
+
if (!config) {
|
|
862
|
+
throw new Error(
|
|
863
|
+
`No artifact linked in ${dir} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
const apiUrl = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
|
|
867
|
+
const token = config.publishToken ?? process.env.ARTIFACT_CAFE_TOKEN;
|
|
868
|
+
const client = createApiClient(apiUrl, token);
|
|
869
|
+
const res = await client.setProtection(config.artifactId, password);
|
|
870
|
+
if (opts.json) {
|
|
871
|
+
console.log(JSON.stringify({ protected: res.protected }, null, 2));
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
console.log(
|
|
875
|
+
res.protected ? `
|
|
876
|
+
\u2713 Password protection on \u2014 ${config.url}
|
|
877
|
+
` : `
|
|
878
|
+
\u2713 Password protection removed \u2014 ${config.url}
|
|
879
|
+
`
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/index.ts
|
|
884
|
+
var publish2 = defineCommand({
|
|
885
|
+
args: {
|
|
886
|
+
artifact: {
|
|
887
|
+
description: "Publish to an existing artifact ID",
|
|
888
|
+
type: "string"
|
|
889
|
+
},
|
|
890
|
+
dir: {
|
|
891
|
+
default: ".",
|
|
892
|
+
description: "Directory to publish",
|
|
893
|
+
type: "positional"
|
|
894
|
+
},
|
|
895
|
+
entry: { description: "Entry file (default: auto-detect)", type: "string" },
|
|
896
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
897
|
+
"no-open": { description: "Don't open the review URL", type: "boolean" },
|
|
898
|
+
password: {
|
|
899
|
+
description: "Password-protect the artifact (min 6 chars)",
|
|
900
|
+
type: "string"
|
|
901
|
+
},
|
|
902
|
+
title: { description: "Artifact title", type: "string" }
|
|
903
|
+
},
|
|
904
|
+
meta: { description: "Publish a folder as an artifact", name: "publish" },
|
|
905
|
+
async run({ args }) {
|
|
906
|
+
try {
|
|
907
|
+
await publish({
|
|
908
|
+
artifact: args.artifact,
|
|
909
|
+
dir: args.dir,
|
|
910
|
+
entry: args.entry,
|
|
911
|
+
json: Boolean(args.json),
|
|
912
|
+
open: !args["no-open"],
|
|
913
|
+
password: args.password,
|
|
914
|
+
title: args.title
|
|
915
|
+
});
|
|
916
|
+
} catch (err) {
|
|
917
|
+
printCliError(err, Boolean(args.json));
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
var comments2 = defineCommand({
|
|
923
|
+
args: {
|
|
924
|
+
dir: {
|
|
925
|
+
default: ".",
|
|
926
|
+
description: "Published directory",
|
|
927
|
+
type: "positional"
|
|
928
|
+
},
|
|
929
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
930
|
+
status: {
|
|
931
|
+
default: "open",
|
|
932
|
+
description: "Filter: open | resolved | all",
|
|
933
|
+
type: "string"
|
|
934
|
+
},
|
|
935
|
+
version: {
|
|
936
|
+
default: "current",
|
|
937
|
+
description: "Filter: current | all",
|
|
938
|
+
type: "string"
|
|
939
|
+
}
|
|
940
|
+
},
|
|
941
|
+
meta: {
|
|
942
|
+
description: "Pull comments for the linked artifact",
|
|
943
|
+
name: "comments"
|
|
944
|
+
},
|
|
945
|
+
async run({ args }) {
|
|
946
|
+
try {
|
|
947
|
+
await comments({
|
|
948
|
+
dir: args.dir,
|
|
949
|
+
json: Boolean(args.json),
|
|
950
|
+
status: args.status,
|
|
951
|
+
version: args.version
|
|
952
|
+
});
|
|
953
|
+
} catch (err) {
|
|
954
|
+
printCliError(err, Boolean(args.json));
|
|
955
|
+
process.exit(1);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
var protect2 = defineCommand({
|
|
960
|
+
args: {
|
|
961
|
+
dir: {
|
|
962
|
+
default: ".",
|
|
963
|
+
description: "Published directory",
|
|
964
|
+
type: "positional"
|
|
965
|
+
},
|
|
966
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
967
|
+
off: { description: "Remove the password gate", type: "boolean" },
|
|
968
|
+
password: { description: "Gate password (min 6 chars)", type: "string" }
|
|
969
|
+
},
|
|
970
|
+
meta: {
|
|
971
|
+
description: "Password-protect the linked artifact (or --off to remove)",
|
|
972
|
+
name: "protect"
|
|
973
|
+
},
|
|
974
|
+
async run({ args }) {
|
|
975
|
+
try {
|
|
976
|
+
await protect({
|
|
977
|
+
dir: args.dir,
|
|
978
|
+
json: Boolean(args.json),
|
|
979
|
+
off: Boolean(args.off),
|
|
980
|
+
password: args.password
|
|
981
|
+
});
|
|
982
|
+
} catch (err) {
|
|
983
|
+
printCliError(err, Boolean(args.json));
|
|
984
|
+
process.exit(1);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
var open = defineCommand({
|
|
989
|
+
args: {
|
|
990
|
+
dir: {
|
|
991
|
+
default: ".",
|
|
992
|
+
description: "Published directory",
|
|
993
|
+
type: "positional"
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
meta: { description: "Open the linked artifact's review URL", name: "open" },
|
|
997
|
+
run({ args }) {
|
|
998
|
+
try {
|
|
999
|
+
openArtifact({ dir: args.dir });
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
printCliError(err, false);
|
|
1002
|
+
process.exit(1);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
var main = defineCommand({
|
|
1007
|
+
meta: {
|
|
1008
|
+
description: "Publish AI-generated artifacts for review \u2014 https://artifact.cafe",
|
|
1009
|
+
name: "artifact-cafe"
|
|
1010
|
+
},
|
|
1011
|
+
subCommands: { comments: comments2, open, protect: protect2, publish: publish2 }
|
|
1012
|
+
});
|
|
1013
|
+
runMain(main);
|
package/package.json
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "artifact-cafe",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Publish AI-generated artifacts for review on artifact.cafe
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Publish AI-generated artifacts for review on artifact.cafe",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"artifact-cafe": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"typecheck": "tsc --noEmit",
|
|
15
|
+
"dev": "tsup --watch"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"citty": "^0.1.6",
|
|
19
|
+
"zod": "^4.1.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@artifact-cafe/shared": "workspace:*",
|
|
23
|
+
"@types/node": "^24.0.0",
|
|
24
|
+
"tsup": "^8.5.0",
|
|
25
|
+
"typescript": "^5.9.0"
|
|
26
|
+
}
|
|
9
27
|
}
|
package/README.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# artifact-cafe
|
|
2
|
-
|
|
3
|
-
The home for AI-generated work. Agents publish interactive static artifacts
|
|
4
|
-
via CLI; humans review and comment through a share link.
|
|
5
|
-
|
|
6
|
-
```
|
|
7
|
-
npx artifact-cafe publish .
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
> **This package name is reserved.** `0.0.0` is a placeholder — the real CLI
|
|
11
|
-
> is in active development and will ship as `0.0.1`+. See https://artifact.cafe
|