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.
Files changed (51) hide show
  1. package/README.md +101 -6
  2. package/bin/codex-snapshot.mjs +1 -6326
  3. package/deploy/aliyun/README.md +311 -0
  4. package/deploy/aliyun/backup-share-data.sh +109 -0
  5. package/deploy/aliyun/check-ecs-status.sh +149 -0
  6. package/deploy/aliyun/codex-snapshot-share.env.example +29 -0
  7. package/deploy/aliyun/codex-snapshot-share.service +26 -0
  8. package/deploy/aliyun/configure-github-pages-api.sh +141 -0
  9. package/deploy/aliyun/configure-local-publisher.sh +197 -0
  10. package/deploy/aliyun/deploy-to-ecs.sh +669 -0
  11. package/deploy/aliyun/deploy.env.example +52 -0
  12. package/deploy/aliyun/doctor.mjs +398 -0
  13. package/deploy/aliyun/install-share-api.sh +252 -0
  14. package/deploy/aliyun/install-system-deps.sh +84 -0
  15. package/deploy/aliyun/nginx-codex-snapshots.bootstrap.conf +34 -0
  16. package/deploy/aliyun/nginx-codex-snapshots.conf +52 -0
  17. package/deploy/aliyun/preflight.mjs +321 -0
  18. package/deploy/aliyun/restore-share-data.sh +141 -0
  19. package/deploy/aliyun/verify-public-share.mjs +404 -0
  20. package/dist/cli/codex-snapshot.mjs +2654 -0
  21. package/dist/core/privacy.js +81 -0
  22. package/dist/core/snapshot.js +1 -0
  23. package/dist/renderers/markdown.mjs +81 -0
  24. package/dist/renderers/transcript.js +195 -0
  25. package/dist/server/http.js +10 -0
  26. package/dist/server/local-security.js +66 -0
  27. package/dist/server/local-viewer-app.mjs +1670 -0
  28. package/dist/server/local-viewer.mjs +210 -0
  29. package/dist/server/share-api.mjs +1149 -0
  30. package/dist/server/share-store.js +136 -0
  31. package/dist/shared/sanitize.js +126 -0
  32. package/dist/shared/transcript.js +1 -0
  33. package/dist/sources/index.mjs +2 -0
  34. package/dist/sources/local-history.mjs +2221 -0
  35. package/package.json +42 -14
  36. package/scripts/build-site.mjs +71 -0
  37. package/scripts/launch-agent.mjs +19 -227
  38. package/scripts/serve-site.mjs +2 -2
  39. package/scripts/test-aliyun-deploy-config.sh +230 -0
  40. package/scripts/test-share-api.mjs +967 -0
  41. package/scripts/test-site-config.mjs +100 -0
  42. package/scripts/test-static-site.mjs +403 -0
  43. package/scripts/write-site-config.mjs +161 -0
  44. package/server/share-api.mjs +1 -771
  45. package/site/assets/config.js +3 -0
  46. package/site/assets/share.js +43 -106
  47. package/site/assets/site.css +3 -605
  48. package/site/assets/site.js +15 -92
  49. package/site/favicon.svg +7 -0
  50. package/site/index.html +3 -83
  51. package/site/share/index.html +3 -8
@@ -0,0 +1,210 @@
1
+ // @ts-nocheck
2
+ import http from "node:http";
3
+ import { send, sendJson } from "./http.js";
4
+ import { allowMutationRequest, createMutationCsrfToken, isAllowedSnapshotServerRequest, setSnapshotServerCorsHeaders, } from "./local-security.js";
5
+ import { renderServerApp } from "./local-viewer-app.mjs";
6
+ export async function serveLocalViewer({ codexHome, claudeHome, traeHome, traeAppHome, traeRecordingsDir, host, port, defaultServerLimit, snapshotLogoSvg, shareConfig, listSessions, loadSnapshot, applySafetyChecksOption, snapshotApiResponse, publishAllSnapshots, publishSnapshot, createShareRequestPayload, stableSnapshotShareId, renderMarkdown, renderHtml, readPositiveInteger, readNonNegativeInteger, safeFileName, }) {
7
+ const csrfToken = createMutationCsrfToken();
8
+ const server = http.createServer(async (request, response) => {
9
+ try {
10
+ setSnapshotServerCorsHeaders(request, response);
11
+ if (request.method === "OPTIONS") {
12
+ response.writeHead(204);
13
+ response.end();
14
+ return;
15
+ }
16
+ if (!isAllowedSnapshotServerRequest(request)) {
17
+ sendJson(response, { error: "origin is not allowed to access this local snapshot server" }, 403);
18
+ return;
19
+ }
20
+ const url = new URL(request.url || "/", `http://${request.headers.host || `${host}:${port}`}`);
21
+ if (url.pathname === "/") {
22
+ send(response, 200, "text/html; charset=utf-8", renderServerApp(csrfToken, shareConfig));
23
+ return;
24
+ }
25
+ if (url.pathname === "/favicon.svg" || url.pathname === "/favicon.ico") {
26
+ send(response, 200, "image/svg+xml; charset=utf-8", snapshotLogoSvg);
27
+ return;
28
+ }
29
+ if (url.pathname === "/api/sessions") {
30
+ const limit = url.searchParams.get("all") === "1"
31
+ ? Number.POSITIVE_INFINITY
32
+ : readPositiveInteger(url.searchParams.get("limit") || String(defaultServerLimit), "limit");
33
+ const offset = readNonNegativeInteger(url.searchParams.get("offset") || "0", "offset");
34
+ const scanLimit = Number.isFinite(limit) ? limit + offset : Number.POSITIVE_INFINITY;
35
+ const sessions = await listSessions({
36
+ codexHome,
37
+ claudeHome,
38
+ traeHome,
39
+ traeAppHome,
40
+ traeRecordingsDir,
41
+ limit: scanLimit,
42
+ cwd: url.searchParams.get("cwd") || "",
43
+ includeArchived: url.searchParams.get("liveOnly") !== "1",
44
+ source: url.searchParams.get("source") || "codex",
45
+ completeOnly: url.searchParams.get("completeOnly") !== "0",
46
+ });
47
+ sendJson(response, Number.isFinite(limit) ? sessions.slice(offset, offset + limit) : sessions.slice(offset));
48
+ return;
49
+ }
50
+ if (url.pathname === "/api/snapshot") {
51
+ const id = url.searchParams.get("id");
52
+ if (!id) {
53
+ sendJson(response, { error: "missing id" }, 400);
54
+ return;
55
+ }
56
+ const snapshot = await loadSnapshot(id, {
57
+ codexHome,
58
+ claudeHome,
59
+ traeHome,
60
+ traeAppHome,
61
+ traeRecordingsDir,
62
+ includeTools: url.searchParams.get("includeTools") === "1" || url.searchParams.get("includeToolOutput") === "1",
63
+ includeToolOutput: url.searchParams.get("includeToolOutput") === "1",
64
+ redact: url.searchParams.get("redact") !== "0",
65
+ });
66
+ applySafetyChecksOption(snapshot, url.searchParams.get("safety") !== "0");
67
+ sendJson(response, snapshotApiResponse(snapshot));
68
+ return;
69
+ }
70
+ if (url.pathname === "/api/publish-all") {
71
+ if (!allowMutationRequest(request, response, csrfToken)) {
72
+ return;
73
+ }
74
+ if (url.searchParams.get("redact") === "0") {
75
+ sendJson(response, { error: "Cloud publish requires Redact enabled in the local viewer." }, 400);
76
+ return;
77
+ }
78
+ const result = await publishAllSnapshots({
79
+ codexHome,
80
+ claudeHome,
81
+ traeHome,
82
+ traeAppHome,
83
+ traeRecordingsDir,
84
+ cwd: url.searchParams.get("cwd") || "",
85
+ includeArchived: url.searchParams.get("liveOnly") !== "1",
86
+ source: "all",
87
+ completeOnly: url.searchParams.get("completeOnly") !== "0",
88
+ limit: url.searchParams.get("limit")
89
+ ? readPositiveInteger(url.searchParams.get("limit"), "limit")
90
+ : Number.POSITIVE_INFINITY,
91
+ includeTools: url.searchParams.get("includeTools") === "1" || url.searchParams.get("includeToolOutput") === "1",
92
+ includeToolOutput: url.searchParams.get("includeToolOutput") === "1",
93
+ safety: url.searchParams.get("safety") === "1",
94
+ });
95
+ sendJson(response, result);
96
+ return;
97
+ }
98
+ if (url.pathname === "/api/publish") {
99
+ if (!allowMutationRequest(request, response, csrfToken)) {
100
+ return;
101
+ }
102
+ const id = url.searchParams.get("id");
103
+ if (!id) {
104
+ sendJson(response, { error: "missing id" }, 400);
105
+ return;
106
+ }
107
+ if (url.searchParams.get("redact") === "0") {
108
+ sendJson(response, { error: "Cloud publish requires Redact enabled in the local viewer." }, 400);
109
+ return;
110
+ }
111
+ const snapshot = await loadSnapshot(id, {
112
+ codexHome,
113
+ claudeHome,
114
+ traeHome,
115
+ traeAppHome,
116
+ traeRecordingsDir,
117
+ includeTools: url.searchParams.get("includeTools") === "1" || url.searchParams.get("includeToolOutput") === "1",
118
+ includeToolOutput: url.searchParams.get("includeToolOutput") === "1",
119
+ redact: true,
120
+ });
121
+ applySafetyChecksOption(snapshot, url.searchParams.get("safety") === "1");
122
+ const result = await publishSnapshot(snapshot, {
123
+ apiUrl: "",
124
+ token: "",
125
+ siteUrl: "",
126
+ expiresInDays: 0,
127
+ shareId: stableSnapshotShareId(snapshot),
128
+ });
129
+ sendJson(response, result);
130
+ return;
131
+ }
132
+ if (url.pathname === "/api/share-payload") {
133
+ if (!allowMutationRequest(request, response, csrfToken)) {
134
+ return;
135
+ }
136
+ const id = url.searchParams.get("id");
137
+ if (!id) {
138
+ sendJson(response, { error: "missing id" }, 400);
139
+ return;
140
+ }
141
+ if (url.searchParams.get("redact") === "0") {
142
+ sendJson(response, { error: "Cloud publish requires Redact enabled in the local viewer." }, 400);
143
+ return;
144
+ }
145
+ const snapshot = await loadSnapshot(id, {
146
+ codexHome,
147
+ claudeHome,
148
+ traeHome,
149
+ traeAppHome,
150
+ traeRecordingsDir,
151
+ includeTools: url.searchParams.get("includeTools") === "1" || url.searchParams.get("includeToolOutput") === "1",
152
+ includeToolOutput: url.searchParams.get("includeToolOutput") === "1",
153
+ redact: true,
154
+ });
155
+ applySafetyChecksOption(snapshot, url.searchParams.get("safety") === "1");
156
+ const result = createShareRequestPayload(snapshot, {
157
+ apiUrl: "",
158
+ siteUrl: "",
159
+ expiresInDays: 0,
160
+ shareId: stableSnapshotShareId(snapshot),
161
+ });
162
+ sendJson(response, result);
163
+ return;
164
+ }
165
+ if (url.pathname === "/export") {
166
+ const id = url.searchParams.get("id");
167
+ const format = url.searchParams.get("format") === "md" ? "md" : "html";
168
+ if (!id) {
169
+ send(response, 400, "text/plain; charset=utf-8", "missing id");
170
+ return;
171
+ }
172
+ const snapshot = await loadSnapshot(id, {
173
+ codexHome,
174
+ claudeHome,
175
+ traeHome,
176
+ traeAppHome,
177
+ traeRecordingsDir,
178
+ includeTools: url.searchParams.get("includeTools") === "1" || url.searchParams.get("includeToolOutput") === "1",
179
+ includeToolOutput: url.searchParams.get("includeToolOutput") === "1",
180
+ redact: url.searchParams.get("redact") !== "0",
181
+ });
182
+ applySafetyChecksOption(snapshot, url.searchParams.get("safety") !== "0");
183
+ const body = format === "md" ? renderMarkdown(snapshot) : renderHtml(snapshot);
184
+ const fileName = `${safeFileName(snapshot.title || snapshot.id)}.${format === "md" ? "md" : "html"}`;
185
+ response.writeHead(200, {
186
+ "content-type": format === "md" ? "text/markdown; charset=utf-8" : "text/html; charset=utf-8",
187
+ "content-disposition": `attachment; filename="${fileName}"`,
188
+ "cache-control": "no-store",
189
+ });
190
+ response.end(body);
191
+ return;
192
+ }
193
+ send(response, 404, "text/plain; charset=utf-8", "not found");
194
+ }
195
+ catch (error) {
196
+ sendJson(response, { error: error instanceof Error ? error.message : String(error) }, 500);
197
+ }
198
+ });
199
+ await new Promise((resolve, reject) => {
200
+ server.once("error", reject);
201
+ server.listen(port, host, resolve);
202
+ });
203
+ const url = `http://${host}:${port}`;
204
+ console.log(`Codex Snapshot is running at ${url}`);
205
+ console.log(`Codex home: ${codexHome}`);
206
+ console.log(`Claude Code home: ${claudeHome}`);
207
+ console.log(`Trae home: ${traeHome}`);
208
+ console.log(`Trae app home: ${traeAppHome}`);
209
+ console.log(`Trae recordings: ${traeRecordingsDir}`);
210
+ }