codex-snapshots 0.1.0 → 0.1.1
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/README.md +101 -6
- package/bin/codex-snapshot.mjs +1 -6326
- package/deploy/aliyun/README.md +311 -0
- package/deploy/aliyun/backup-share-data.sh +109 -0
- package/deploy/aliyun/check-ecs-status.sh +149 -0
- package/deploy/aliyun/codex-snapshot-share.env.example +29 -0
- package/deploy/aliyun/codex-snapshot-share.service +26 -0
- package/deploy/aliyun/configure-github-pages-api.sh +141 -0
- package/deploy/aliyun/configure-local-publisher.sh +197 -0
- package/deploy/aliyun/deploy-to-ecs.sh +669 -0
- package/deploy/aliyun/deploy.env.example +52 -0
- package/deploy/aliyun/doctor.mjs +398 -0
- package/deploy/aliyun/install-share-api.sh +252 -0
- package/deploy/aliyun/install-system-deps.sh +84 -0
- package/deploy/aliyun/nginx-codex-snapshots.bootstrap.conf +34 -0
- package/deploy/aliyun/nginx-codex-snapshots.conf +52 -0
- package/deploy/aliyun/preflight.mjs +321 -0
- package/deploy/aliyun/restore-share-data.sh +141 -0
- package/deploy/aliyun/verify-public-share.mjs +404 -0
- package/dist/cli/codex-snapshot.mjs +2654 -0
- package/dist/core/privacy.js +81 -0
- package/dist/core/snapshot.js +1 -0
- package/dist/renderers/markdown.mjs +81 -0
- package/dist/renderers/transcript.js +195 -0
- package/dist/server/http.js +10 -0
- package/dist/server/local-security.js +66 -0
- package/dist/server/local-viewer-app.mjs +1670 -0
- package/dist/server/local-viewer.mjs +210 -0
- package/dist/server/share-api.mjs +1149 -0
- package/dist/server/share-store.js +136 -0
- package/dist/shared/sanitize.js +126 -0
- package/dist/shared/transcript.js +1 -0
- package/dist/sources/index.mjs +2 -0
- package/dist/sources/local-history.mjs +2221 -0
- package/package.json +42 -14
- package/scripts/build-site.mjs +71 -0
- package/scripts/launch-agent.mjs +19 -227
- package/scripts/serve-site.mjs +2 -2
- package/scripts/test-aliyun-deploy-config.sh +230 -0
- package/scripts/test-share-api.mjs +967 -0
- package/scripts/test-site-config.mjs +100 -0
- package/scripts/test-static-site.mjs +403 -0
- package/scripts/write-site-config.mjs +161 -0
- package/server/share-api.mjs +1 -771
- package/site/assets/config.js +3 -0
- package/site/assets/share.js +43 -106
- package/site/assets/site.css +3 -605
- package/site/assets/site.js +15 -92
- package/site/favicon.svg +7 -0
- package/site/index.html +3 -83
- package/site/share/index.html +3 -8
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
8
|
+
|
|
9
|
+
if (parsed.help) {
|
|
10
|
+
printHelp();
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const localPublisherConfig = readLocalPublisherConfig(parsed.options.tokenFile);
|
|
15
|
+
const apiUrl = normalizeUrl(
|
|
16
|
+
parsed.options.apiUrl ||
|
|
17
|
+
process.env.SNAPSHOT_SHARE_API_URL ||
|
|
18
|
+
process.env.SNAPSHOT_SHARE_PUBLIC_API_URL ||
|
|
19
|
+
localPublisherConfig.apiUrl
|
|
20
|
+
);
|
|
21
|
+
const siteUrl = normalizeUrl(
|
|
22
|
+
parsed.options.siteUrl ||
|
|
23
|
+
process.env.SNAPSHOT_SHARE_SITE_URL ||
|
|
24
|
+
localPublisherConfig.siteUrl ||
|
|
25
|
+
"https://ffffhx.github.io/codex-snapshots/"
|
|
26
|
+
);
|
|
27
|
+
const token = parsed.options.token || process.env.SNAPSHOT_SHARE_TOKEN || localPublisherConfig.token || "";
|
|
28
|
+
const shouldPublish = parsed.options.publish;
|
|
29
|
+
const shouldCheckSiteConfig = !parsed.options.skipSiteConfig;
|
|
30
|
+
const shouldCheckLocalConfig = parsed.options.checkLocalConfig;
|
|
31
|
+
|
|
32
|
+
main().catch((error) => {
|
|
33
|
+
console.error(`✗ ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
if (!apiUrl) {
|
|
39
|
+
throw new Error("Missing --api-url or SNAPSHOT_SHARE_API_URL");
|
|
40
|
+
}
|
|
41
|
+
if (!siteUrl) {
|
|
42
|
+
throw new Error("Missing --site-url or SNAPSHOT_SHARE_SITE_URL");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await checkHealth(apiUrl);
|
|
46
|
+
await checkList(apiUrl);
|
|
47
|
+
await checkCors(apiUrl, siteUrl);
|
|
48
|
+
|
|
49
|
+
if (shouldCheckSiteConfig) {
|
|
50
|
+
await checkSiteConfig(siteUrl, apiUrl);
|
|
51
|
+
} else {
|
|
52
|
+
console.log("• Skipped site config check");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (shouldCheckLocalConfig) {
|
|
56
|
+
checkLocalPublisherConfig(localPublisherConfig, apiUrl, siteUrl);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (shouldPublish) {
|
|
60
|
+
if (!token) {
|
|
61
|
+
throw new Error("--publish requires --token, SNAPSHOT_SHARE_TOKEN, or a local publisher config with snapshotShareToken.");
|
|
62
|
+
}
|
|
63
|
+
await checkPublish(apiUrl, siteUrl, token);
|
|
64
|
+
} else {
|
|
65
|
+
console.log("• Skipped publish check; pass --publish --token <token> for legacy token auth, or verify GitHub OAuth publishing in the browser.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log("✓ Public share deployment checks passed");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function checkHealth(baseUrl) {
|
|
72
|
+
const payload = await fetchJson(`${baseUrl}/api/snapshots/health`);
|
|
73
|
+
if (payload.ok !== true) {
|
|
74
|
+
throw new Error(`Health check did not return ok=true: ${JSON.stringify(payload)}`);
|
|
75
|
+
}
|
|
76
|
+
if (Object.hasOwn(payload, "storage")) {
|
|
77
|
+
throw new Error("Health check exposes the server storage path.");
|
|
78
|
+
}
|
|
79
|
+
console.log(`✓ Health check ok (${payload.shares ?? 0} shares)`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function checkList(baseUrl) {
|
|
83
|
+
const payload = await fetchJson(`${baseUrl}/api/snapshots?limit=3`);
|
|
84
|
+
if (!Array.isArray(payload.shares)) {
|
|
85
|
+
throw new Error("List endpoint did not return a shares array");
|
|
86
|
+
}
|
|
87
|
+
console.log(`✓ Public list ok (${payload.total ?? payload.shares.length} total shares)`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function checkCors(baseUrl, publicSiteUrl) {
|
|
91
|
+
const siteOrigin = new URL(publicSiteUrl).origin;
|
|
92
|
+
const response = await fetchWithTimeout(`${baseUrl}/api/snapshots?limit=1`, {
|
|
93
|
+
headers: {
|
|
94
|
+
origin: siteOrigin,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error(`CORS list check failed with HTTP ${response.status}`);
|
|
100
|
+
}
|
|
101
|
+
assertAllowedCorsOrigin(response.headers, siteOrigin, "CORS list response");
|
|
102
|
+
|
|
103
|
+
const preflight = await fetchWithTimeout(`${baseUrl}/api/snapshots`, {
|
|
104
|
+
method: "OPTIONS",
|
|
105
|
+
headers: {
|
|
106
|
+
"access-control-request-headers": "authorization,content-type",
|
|
107
|
+
"access-control-request-method": "POST",
|
|
108
|
+
origin: siteOrigin,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (preflight.status !== 204) {
|
|
113
|
+
throw new Error(`CORS preflight returned HTTP ${preflight.status}, expected 204`);
|
|
114
|
+
}
|
|
115
|
+
assertAllowedCorsOrigin(preflight.headers, siteOrigin, "CORS preflight");
|
|
116
|
+
|
|
117
|
+
console.log("✓ CORS allows the public site to read the API");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function assertAllowedCorsOrigin(headers, siteOrigin, label) {
|
|
121
|
+
const allowedOrigin = headers.get("access-control-allow-origin");
|
|
122
|
+
if (allowedOrigin !== "*" && allowedOrigin !== siteOrigin) {
|
|
123
|
+
throw new Error(`${label} allowed origin is ${allowedOrigin || "(missing)"}, expected * or ${siteOrigin}.`);
|
|
124
|
+
}
|
|
125
|
+
if (allowedOrigin === siteOrigin && headers.get("access-control-allow-credentials") !== "true") {
|
|
126
|
+
throw new Error(`${label} allows the site origin but is missing access-control-allow-credentials=true.`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function checkSiteConfig(publicSiteUrl, publicApiUrl) {
|
|
131
|
+
const configUrl = `${publicSiteUrl.replace(/\/+$/, "")}/assets/config.js`;
|
|
132
|
+
const response = await fetchWithTimeout(configUrl);
|
|
133
|
+
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
throw new Error(`Could not load site config at ${configUrl}: HTTP ${response.status}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const text = await response.text();
|
|
139
|
+
if (!text.includes(publicApiUrl)) {
|
|
140
|
+
throw new Error(`Site config at ${configUrl} does not include ${publicApiUrl}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log("✓ Site config points at the public API");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function checkPublish(baseUrl, publicSiteUrl, publishToken) {
|
|
147
|
+
const shareId = parsed.options.shareId || `snap_verify${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
148
|
+
const payload = await fetchJson(`${baseUrl}/api/snapshots`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
authorization: `Bearer ${publishToken}`,
|
|
152
|
+
"content-type": "application/json",
|
|
153
|
+
},
|
|
154
|
+
body: JSON.stringify({
|
|
155
|
+
shareId,
|
|
156
|
+
apiUrl: baseUrl,
|
|
157
|
+
siteUrl: publicSiteUrl,
|
|
158
|
+
snapshot: {
|
|
159
|
+
title: "Codex Snapshots public deployment verification",
|
|
160
|
+
engine: "codex",
|
|
161
|
+
engineLabel: "Codex",
|
|
162
|
+
redacted: true,
|
|
163
|
+
turns: [
|
|
164
|
+
{
|
|
165
|
+
role: "user",
|
|
166
|
+
text: "Verify that the public share API accepts writes.",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
role: "assistant",
|
|
170
|
+
text: "The public share API accepted this verification snapshot.",
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (payload.ok !== true || payload.id !== shareId || !payload.url) {
|
|
178
|
+
throw new Error(`Publish response was unexpected: ${JSON.stringify(payload)}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const url = new URL(payload.url);
|
|
182
|
+
if (url.origin !== new URL(publicSiteUrl).origin) {
|
|
183
|
+
throw new Error(`Published URL should point at the public site, got ${payload.url}`);
|
|
184
|
+
}
|
|
185
|
+
if (url.searchParams.get("api") !== baseUrl) {
|
|
186
|
+
throw new Error(`Published URL is missing api=${baseUrl}: ${payload.url}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const loaded = await fetchJson(`${baseUrl}/api/snapshots/${encodeURIComponent(shareId)}`);
|
|
190
|
+
if (loaded.share?.id !== shareId || !loaded.snapshot) {
|
|
191
|
+
throw new Error(`Published snapshot could not be loaded: ${JSON.stringify(loaded)}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (shouldCheckSiteConfig) {
|
|
195
|
+
await checkSharePageShell(payload.url);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log(`✓ Publish check ok: ${payload.url}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function checkSharePageShell(shareUrl) {
|
|
202
|
+
const response = await fetchWithTimeout(shareUrl);
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new Error(`Could not load public share page at ${shareUrl}: HTTP ${response.status}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const text = await response.text();
|
|
209
|
+
if (!text.includes("assets/share.js") && !text.includes("CODEX_SNAPSHOTS_CONFIG")) {
|
|
210
|
+
throw new Error(`Public share page at ${shareUrl} did not look like the Codex Snapshots viewer.`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log("✓ Public share page shell is reachable");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function fetchJson(url, options = {}) {
|
|
217
|
+
const response = await fetchWithTimeout(url, options);
|
|
218
|
+
const text = await response.text();
|
|
219
|
+
let payload;
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
payload = JSON.parse(text);
|
|
223
|
+
} catch {
|
|
224
|
+
throw new Error(`Expected JSON from ${url}, got: ${text.slice(0, 200)}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
throw new Error(`${url} failed with HTTP ${response.status}: ${payload.error || text}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return payload;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function fetchWithTimeout(url, options = {}) {
|
|
235
|
+
const timeoutMs = Number(parsed.options.timeoutMs || 8000);
|
|
236
|
+
return fetch(url, {
|
|
237
|
+
...options,
|
|
238
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function normalizeUrl(value) {
|
|
243
|
+
const text = String(value || "").trim().replace(/\/+$/, "");
|
|
244
|
+
if (!text) {
|
|
245
|
+
return "";
|
|
246
|
+
}
|
|
247
|
+
const url = new URL(text);
|
|
248
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
249
|
+
throw new Error(`URL must start with http:// or https://: ${text}`);
|
|
250
|
+
}
|
|
251
|
+
return url.toString().replace(/\/+$/, "");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function readLocalPublisherConfig(tokenFile) {
|
|
255
|
+
const candidates = [
|
|
256
|
+
tokenFile,
|
|
257
|
+
process.env.CODEX_SNAPSHOTS_AGENT_FILE,
|
|
258
|
+
process.env.SNAPSHOT_SHARE_TOKEN_FILE,
|
|
259
|
+
path.join(os.homedir(), ".codex-snapshots-agent.json"),
|
|
260
|
+
].filter(Boolean);
|
|
261
|
+
|
|
262
|
+
for (const filePath of candidates) {
|
|
263
|
+
try {
|
|
264
|
+
const payload = JSON.parse(readFileSync(filePath, "utf8"));
|
|
265
|
+
return {
|
|
266
|
+
apiUrl: firstNonEmptyString(
|
|
267
|
+
payload.snapshotShareApiUrl,
|
|
268
|
+
payload.snapshotSharePublicApiUrl,
|
|
269
|
+
payload.shareApiUrl,
|
|
270
|
+
payload.publicApiUrl,
|
|
271
|
+
payload.apiUrl
|
|
272
|
+
),
|
|
273
|
+
filePath,
|
|
274
|
+
siteUrl: firstNonEmptyString(payload.snapshotShareSiteUrl, payload.shareSiteUrl, payload.siteUrl),
|
|
275
|
+
token: firstNonEmptyString(payload.snapshotShareToken, payload.agentToken, payload.token, payload.uploadToken),
|
|
276
|
+
};
|
|
277
|
+
} catch {}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
apiUrl: "",
|
|
282
|
+
filePath: candidates[0] || "",
|
|
283
|
+
siteUrl: "",
|
|
284
|
+
token: "",
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function checkLocalPublisherConfig(config, publicApiUrl, publicSiteUrl) {
|
|
289
|
+
if (!config.filePath || (!config.apiUrl && !config.siteUrl && !config.token)) {
|
|
290
|
+
throw new Error("Local publisher config was not found. Run deploy/aliyun/configure-local-publisher.sh first.");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const configuredApiUrl = normalizeUrl(config.apiUrl);
|
|
294
|
+
const configuredSiteUrl = normalizeUrl(config.siteUrl);
|
|
295
|
+
|
|
296
|
+
if (!configuredApiUrl) {
|
|
297
|
+
throw new Error(`Local publisher config at ${config.filePath} does not include snapshotShareApiUrl.`);
|
|
298
|
+
}
|
|
299
|
+
if (configuredApiUrl !== publicApiUrl) {
|
|
300
|
+
throw new Error(`Local publisher config points at ${configuredApiUrl}, expected ${publicApiUrl}.`);
|
|
301
|
+
}
|
|
302
|
+
if (!configuredSiteUrl) {
|
|
303
|
+
throw new Error(`Local publisher config at ${config.filePath} does not include snapshotShareSiteUrl.`);
|
|
304
|
+
}
|
|
305
|
+
if (configuredSiteUrl !== publicSiteUrl) {
|
|
306
|
+
throw new Error(`Local publisher config site URL is ${configuredSiteUrl}, expected ${publicSiteUrl}.`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
console.log(`✓ Local publisher config points at the public API (${config.filePath})`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function firstNonEmptyString(...values) {
|
|
313
|
+
for (const value of values) {
|
|
314
|
+
if (typeof value === "string" && value.trim()) {
|
|
315
|
+
return value.trim();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return "";
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function parseArgs(args) {
|
|
322
|
+
const options = {
|
|
323
|
+
apiUrl: "",
|
|
324
|
+
checkLocalConfig: false,
|
|
325
|
+
help: false,
|
|
326
|
+
publish: false,
|
|
327
|
+
shareId: "",
|
|
328
|
+
skipSiteConfig: false,
|
|
329
|
+
siteUrl: "",
|
|
330
|
+
timeoutMs: "",
|
|
331
|
+
token: "",
|
|
332
|
+
tokenFile: "",
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
336
|
+
const arg = args[index];
|
|
337
|
+
if (arg === "--") continue;
|
|
338
|
+
if (arg === "-h" || arg === "--help") {
|
|
339
|
+
options.help = true;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (arg === "--publish") {
|
|
343
|
+
options.publish = true;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (arg === "--skip-site-config") {
|
|
347
|
+
options.skipSiteConfig = true;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (arg === "--check-local-config") {
|
|
351
|
+
options.checkLocalConfig = true;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (arg === "--api-url") {
|
|
355
|
+
options.apiUrl = String(args[++index] || "");
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (arg === "--site-url") {
|
|
359
|
+
options.siteUrl = String(args[++index] || "");
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (arg === "--token") {
|
|
363
|
+
options.token = String(args[++index] || "");
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (arg === "--token-file") {
|
|
367
|
+
options.tokenFile = String(args[++index] || "");
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (arg === "--share-id") {
|
|
371
|
+
options.shareId = String(args[++index] || "");
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (arg === "--timeout-ms") {
|
|
375
|
+
options.timeoutMs = String(args[++index] || "");
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
throw new Error(`unknown option: ${arg}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return { help: options.help, options };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function printHelp() {
|
|
385
|
+
console.log(`verify-public-share
|
|
386
|
+
|
|
387
|
+
Usage:
|
|
388
|
+
node deploy/aliyun/verify-public-share.mjs --api-url https://snapshots.example.com --site-url https://ffffhx.github.io/codex-snapshots/
|
|
389
|
+
SNAPSHOT_SHARE_TOKEN=<legacy-token> node deploy/aliyun/verify-public-share.mjs --api-url https://snapshots.example.com --publish
|
|
390
|
+
|
|
391
|
+
Checks:
|
|
392
|
+
- GET /api/snapshots/health
|
|
393
|
+
- GET /api/snapshots
|
|
394
|
+
- CORS headers for the public site
|
|
395
|
+
- site assets/config.js points at the public API
|
|
396
|
+
- optional local publisher config points at the public API
|
|
397
|
+
- optional legacy-token POST /api/snapshots, GET /api/snapshots/:id, and public share page URL
|
|
398
|
+
|
|
399
|
+
Options:
|
|
400
|
+
--skip-site-config Do not check the static site's assets/config.js
|
|
401
|
+
--check-local-config Check ~/.codex-snapshots-agent.json or --token-file
|
|
402
|
+
--token-file FILE Local publisher config file
|
|
403
|
+
`);
|
|
404
|
+
}
|