agentbox-app 0.1.6
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/LICENSE +674 -0
- package/README.md +176 -0
- package/bin/agentbox.mjs +103 -0
- package/briefs/adhd-mode.md +5 -0
- package/briefs/message-rules.md +95 -0
- package/briefs/worker.md +230 -0
- package/briefs/writing-rules.md +336 -0
- package/main/account-discovery.mjs +145 -0
- package/main/account-tooling.mjs +261 -0
- package/main/agent-activity.mjs +56 -0
- package/main/agent-files.mjs +422 -0
- package/main/agent-schedule.mjs +254 -0
- package/main/agent-sessions.mjs +435 -0
- package/main/agents.mjs +1095 -0
- package/main/analytics.mjs +206 -0
- package/main/answer-modes.mjs +206 -0
- package/main/approval-prompt-server.mjs +160 -0
- package/main/approvals.mjs +487 -0
- package/main/artifact-path.mjs +144 -0
- package/main/claude-bin.mjs +311 -0
- package/main/claude-input.mjs +117 -0
- package/main/claude-models.mjs +244 -0
- package/main/claude-plan.mjs +148 -0
- package/main/claude-usage.mjs +149 -0
- package/main/code-change.mjs +383 -0
- package/main/code-file.mjs +116 -0
- package/main/codex-account.mjs +146 -0
- package/main/codex-app-server.mjs +796 -0
- package/main/codex-approvals.mjs +712 -0
- package/main/codex-bin.mjs +261 -0
- package/main/codex-compaction.mjs +49 -0
- package/main/codex-models.mjs +194 -0
- package/main/codex-session.mjs +797 -0
- package/main/codex-threads.mjs +268 -0
- package/main/codex-usage-file.mjs +192 -0
- package/main/codex-usage.mjs +149 -0
- package/main/codex-watch.mjs +35 -0
- package/main/codex.mjs +757 -0
- package/main/config.mjs +345 -0
- package/main/copy-selection.mjs +55 -0
- package/main/crash-report.mjs +158 -0
- package/main/demo.mjs +254 -0
- package/main/dev-window.mjs +103 -0
- package/main/doc-file.mjs +99 -0
- package/main/doc-scheme.mjs +126 -0
- package/main/first-run.mjs +190 -0
- package/main/folders.mjs +91 -0
- package/main/fresh-user.mjs +120 -0
- package/main/git-change.mjs +323 -0
- package/main/img-scheme.mjs +90 -0
- package/main/instruction-settings.mjs +205 -0
- package/main/ipc.mjs +1558 -0
- package/main/live-replies.mjs +77 -0
- package/main/machine.mjs +113 -0
- package/main/main.mjs +972 -0
- package/main/menu.mjs +177 -0
- package/main/notify.mjs +155 -0
- package/main/open-source-file.mjs +8 -0
- package/main/project-identity.mjs +149 -0
- package/main/provider-commands.mjs +32 -0
- package/main/rail-note.mjs +74 -0
- package/main/repeats.mjs +261 -0
- package/main/row-label.mjs +185 -0
- package/main/screen-detail.mjs +44 -0
- package/main/serve.mjs +262 -0
- package/main/settings.mjs +912 -0
- package/main/staleness.mjs +76 -0
- package/main/store/chats.mjs +188 -0
- package/main/store/home.mjs +383 -0
- package/main/store/migrate.mjs +40 -0
- package/main/store/paths.mjs +82 -0
- package/main/store/pending-writes.mjs +122 -0
- package/main/store/project-lock.mjs +200 -0
- package/main/store/project.mjs +523 -0
- package/main/store/queues.mjs +31 -0
- package/main/store/work-items.mjs +299 -0
- package/main/store/write-queue.mjs +14 -0
- package/main/store-modules.mjs +23 -0
- package/main/store.mjs +1269 -0
- package/main/supervisor.mjs +6875 -0
- package/main/task-commands.mjs +161 -0
- package/main/task-folders-offthread.mjs +84 -0
- package/main/task-folders-thread.mjs +13 -0
- package/main/task-folders.mjs +409 -0
- package/main/task-remote-control.mjs +65 -0
- package/main/task-terminals.mjs +169 -0
- package/main/terminal-access.mjs +6 -0
- package/main/updater.mjs +240 -0
- package/main/zoom-keys.mjs +54 -0
- package/main/zoom-readout.mjs +20 -0
- package/mcp/agentbox-mcp.sh +34 -0
- package/mcp/core/account.mjs +77 -0
- package/mcp/core/chat.mjs +61 -0
- package/mcp/core/documents.mjs +238 -0
- package/mcp/core/page.mjs +152 -0
- package/mcp/core/products.mjs +110 -0
- package/mcp/core/skills.mjs +126 -0
- package/mcp/core/work.mjs +377 -0
- package/mcp/stdio.mjs +70 -0
- package/mcp/tools.mjs +257 -0
- package/package.json +186 -0
- package/renderer/dist/assets/Geist-Variable-Bj2R_7yk.woff2 +0 -0
- package/renderer/dist/assets/GeistMono-Variable-Dispecij.woff2 +0 -0
- package/renderer/dist/assets/addon-fit-YJmn1quW.js +12 -0
- package/renderer/dist/assets/addon-serialize-CsFQTvP_.js +13 -0
- package/renderer/dist/assets/cel-dusk-CS5I7kJS.webp +0 -0
- package/renderer/dist/assets/cel-dusk-DUslqS6B.webp +0 -0
- package/renderer/dist/assets/cel-dusk-Jt_xVGxp.webp +0 -0
- package/renderer/dist/assets/cel-dusk-sm-BBkV-tWT.webp +0 -0
- package/renderer/dist/assets/frost-haze-CxPSOBEg.webp +0 -0
- package/renderer/dist/assets/frost-haze-sm-C_w21qgd.webp +0 -0
- package/renderer/dist/assets/gouache-valley-ChSu14-t.webp +0 -0
- package/renderer/dist/assets/gouache-valley-d9qasI6k.webp +0 -0
- package/renderer/dist/assets/gouache-valley-sm-CdLjT5Ec.webp +0 -0
- package/renderer/dist/assets/ice-BC8luINc.webp +0 -0
- package/renderer/dist/assets/ice-CP9PrKHo.webp +0 -0
- package/renderer/dist/assets/ice-river-D6ziOhLY.webp +0 -0
- package/renderer/dist/assets/ice-river-UVM2-JY_.webp +0 -0
- package/renderer/dist/assets/ice-river-bErJwYWW.webp +0 -0
- package/renderer/dist/assets/ice-river-sm-Bg1U-SIj.webp +0 -0
- package/renderer/dist/assets/ice-sm-CbVcg3_D.webp +0 -0
- package/renderer/dist/assets/index-CwiDv5bL.css +32 -0
- package/renderer/dist/assets/index-D9hCAVPl.js +684 -0
- package/renderer/dist/assets/lake-C0WaW-6E.webp +0 -0
- package/renderer/dist/assets/lake-CSKjLC5K.webp +0 -0
- package/renderer/dist/assets/lake-ETGcOb25.webp +0 -0
- package/renderer/dist/assets/lake-harbour-BCQRbrHb.webp +0 -0
- package/renderer/dist/assets/lake-harbour-B_wfXTmE.webp +0 -0
- package/renderer/dist/assets/lake-harbour-Te1pEvBh.webp +0 -0
- package/renderer/dist/assets/lake-harbour-sm-Cme_ShEg.webp +0 -0
- package/renderer/dist/assets/lake-sm-CbG72Hbv.webp +0 -0
- package/renderer/dist/assets/lino-coast-CUZHcucI.webp +0 -0
- package/renderer/dist/assets/lino-coast-DP4J7cgd.webp +0 -0
- package/renderer/dist/assets/lino-coast-kOIAffr_.webp +0 -0
- package/renderer/dist/assets/lino-coast-sm-C1Mdbgit.webp +0 -0
- package/renderer/dist/assets/moon-pines-BskgnnXR.webp +0 -0
- package/renderer/dist/assets/moon-pines-CFSIIwt0.webp +0 -0
- package/renderer/dist/assets/moon-pines-PNhdOYww.webp +0 -0
- package/renderer/dist/assets/moon-pines-sm-C6K1NomK.webp +0 -0
- package/renderer/dist/assets/orbit-rings-2XV36Ch3.webp +0 -0
- package/renderer/dist/assets/orbit-rings-C4NrcYGQ.webp +0 -0
- package/renderer/dist/assets/orbit-rings-DGLk07dT.webp +0 -0
- package/renderer/dist/assets/orbit-rings-sm-Dky3YdvL.webp +0 -0
- package/renderer/dist/assets/peach-haze-2-C6f7xnWw.webp +0 -0
- package/renderer/dist/assets/peach-haze-2-sm-Dj_Ez_Sb.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-DQjqMROg.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-Dw71H_bc.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-kOZfm-GM.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-sm-Dturjuky.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-C296JVMO.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-C4oMRDSx.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-CqVmQ4eh.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-sm-CHagC_nO.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-C6LYu6uE.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-CIUCEESs.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-sm-BnE0F_w-.webp +0 -0
- package/renderer/dist/assets/riso-hills-7heKVtry.webp +0 -0
- package/renderer/dist/assets/riso-hills-CIjAL3J6.webp +0 -0
- package/renderer/dist/assets/riso-hills-sm-DHsnbCfP.webp +0 -0
- package/renderer/dist/assets/slate-haze-BPWdkNsw.webp +0 -0
- package/renderer/dist/assets/slate-haze-sm-B0Cb5a2W.webp +0 -0
- package/renderer/dist/assets/source-sans-3-cyrillic-ext-wght-normal-DzyfIafT.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-cyrillic-wght-normal-BMDVbyM7.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-greek-ext-wght-normal-BWSLJLk6.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-greek-wght-normal-C9H9m1vD.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-latin-ext-wght-normal-C8iNium2.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-latin-wght-normal-BqRLTx4X.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-vietnamese-wght-normal-C1uRvKPU.woff2 +0 -0
- package/renderer/dist/assets/valley-haze-BDHPqoJy.webp +0 -0
- package/renderer/dist/assets/valley-haze-sm-CTyjmPAG.webp +0 -0
- package/renderer/dist/assets/vector-dunes-DzzWLdKo.webp +0 -0
- package/renderer/dist/assets/vector-dunes-Yr9NBQUy.webp +0 -0
- package/renderer/dist/assets/vector-dunes-sm-D8MYY6ms.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-BCGKN4u4.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-BF-jk5MC.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-sm-Ds1xzTw9.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-B1um67Z9.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-DKQdYs32.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-sm-BJzNq8xh.webp +0 -0
- package/renderer/dist/assets/xterm-BT7tBf8A.js +27 -0
- package/renderer/dist/emulator/audio-rules.mjs +74 -0
- package/renderer/dist/emulator/data/compression/README.md +11 -0
- package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
- package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
- package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
- package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
- package/renderer/dist/emulator/data/cores/README.md +22 -0
- package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/package.json +65 -0
- package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
- package/renderer/dist/emulator/data/emulator.css +1614 -0
- package/renderer/dist/emulator/data/loader.js +170 -0
- package/renderer/dist/emulator/data/localization/README.md +63 -0
- package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
- package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
- package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
- package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
- package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
- package/renderer/dist/emulator/data/localization/en-US.json +339 -0
- package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
- package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
- package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
- package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
- package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
- package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
- package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
- package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
- package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
- package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
- package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
- package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
- package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
- package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
- package/renderer/dist/emulator/data/src/GameManager.js +464 -0
- package/renderer/dist/emulator/data/src/compression.js +142 -0
- package/renderer/dist/emulator/data/src/emulator.js +6158 -0
- package/renderer/dist/emulator/data/src/gamepad.js +167 -0
- package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
- package/renderer/dist/emulator/data/src/shaders.js +236 -0
- package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
- package/renderer/dist/emulator/data/src/storage.js +125 -0
- package/renderer/dist/emulator/data/version.json +5 -0
- package/renderer/dist/emulator/host.js +299 -0
- package/renderer/dist/emulator/index.html +28 -0
- package/renderer/dist/emulator/provenance.json +6 -0
- package/renderer/dist/fixtures/onboard/designs/w-n13/the-first-row.html +1 -0
- package/renderer/dist/index.html +13 -0
- package/scripts/after-install.mjs +59 -0
- package/scripts/approval-server.sh +43 -0
- package/scripts/before-publish.mjs +133 -0
- package/shared/agent-import.d.mts +13 -0
- package/shared/agent-import.mjs +252 -0
- package/shared/agents.d.mts +190 -0
- package/shared/agents.mjs +937 -0
- package/shared/analytics-events.mjs +63 -0
- package/shared/answers.d.mts +12 -0
- package/shared/answers.mjs +80 -0
- package/shared/artifact-keys.d.mts +28 -0
- package/shared/artifact-keys.mjs +140 -0
- package/shared/bridge-map.mjs +128 -0
- package/shared/claude-commands.d.mts +20 -0
- package/shared/claude-commands.generated.mjs +149 -0
- package/shared/claude-commands.mjs +131 -0
- package/shared/claude-models.generated.d.mts +15 -0
- package/shared/claude-models.generated.mjs +26 -0
- package/shared/claude-usage.d.mts +16 -0
- package/shared/claude-usage.mjs +172 -0
- package/shared/codex-commands.d.mts +4 -0
- package/shared/codex-commands.mjs +17 -0
- package/shared/codex-import.d.mts +38 -0
- package/shared/codex-import.mjs +232 -0
- package/shared/codex-modes.mjs +159 -0
- package/shared/codex-usage.mjs +180 -0
- package/shared/contracts.mjs +412 -0
- package/shared/crash-scrub.mjs +182 -0
- package/shared/dashboard.mjs +481 -0
- package/shared/dead-run-trace.mjs +127 -0
- package/shared/demo-world.mjs +415 -0
- package/shared/effort-levels.d.mts +15 -0
- package/shared/effort-levels.mjs +76 -0
- package/shared/engines.d.mts +62 -0
- package/shared/engines.mjs +389 -0
- package/shared/first-run-examples.mjs +60 -0
- package/shared/first-run-line.mjs +149 -0
- package/shared/first-run-practice.d.mts +32 -0
- package/shared/first-run-practice.mjs +254 -0
- package/shared/first-run-shape.mjs +100 -0
- package/shared/fresh-user-home.mjs +189 -0
- package/shared/hook-failure.mjs +22 -0
- package/shared/install-id.mjs +39 -0
- package/shared/instruction-defaults.json +5 -0
- package/shared/library-folder.mjs +28 -0
- package/shared/notify-rules.mjs +120 -0
- package/shared/product-name.d.mts +6 -0
- package/shared/product-name.mjs +144 -0
- package/shared/project-folder-check.d.mts +22 -0
- package/shared/project-folder-check.mjs +152 -0
- package/shared/provider-commands.d.mts +8 -0
- package/shared/provider-commands.mjs +58 -0
- package/shared/rank.d.mts +19 -0
- package/shared/rank.mjs +146 -0
- package/shared/recovery.mjs +26 -0
- package/shared/referenced-files.d.mts +11 -0
- package/shared/referenced-files.mjs +122 -0
- package/shared/repeats.d.mts +45 -0
- package/shared/repeats.mjs +193 -0
- package/shared/schemes.mjs +33 -0
- package/shared/settings-terminal.d.mts +1 -0
- package/shared/settings-terminal.mjs +14 -0
- package/shared/side-build.mjs +57 -0
- package/shared/spawn-trouble.d.mts +8 -0
- package/shared/spawn-trouble.mjs +599 -0
- package/shared/terminal-state.d.mts +1 -0
- package/shared/terminal-state.mjs +58 -0
- package/shared/usage.d.mts +53 -0
- package/shared/usage.mjs +319 -0
- package/shared/work-items.mjs +561 -0
- package/shared/work-lines.mjs +360 -0
- package/worker-permissions.json +6 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// READING AND WRITING ONE FILE INSIDE A CHANGE.
|
|
2
|
+
//
|
|
3
|
+
// The pane already types onto a markdown file in the product's own folders
|
|
4
|
+
// (main/doc-file.mjs). This is the same job for the other kind of artifact, and
|
|
5
|
+
// it is a harder one for two reasons.
|
|
6
|
+
//
|
|
7
|
+
// 1. THE FILE IS NOT IN THE PRODUCT'S FOLDERS. It is source in a checkout the
|
|
8
|
+
// run worked in, and the change records it as a short path like
|
|
9
|
+
// `renderer/src/api.ts`, which on its own does not say which checkout.
|
|
10
|
+
// So a change now carries `roots` and each file carries `abs`
|
|
11
|
+
// (main/code-change.mjs). This finds the file from those and refuses if it
|
|
12
|
+
// cannot, rather than guessing at a path and writing over a stranger's.
|
|
13
|
+
// 2. WHAT IS ON THE SCREEN IS NOT THE WHOLE FILE. The splice back into the
|
|
14
|
+
// real bytes is pure and lives in renderer/src/code-edit.ts; by the time
|
|
15
|
+
// anything here runs, the caller has the file's WHOLE new text.
|
|
16
|
+
//
|
|
17
|
+
// THE RULE ON WHERE A WRITE MAY LAND: inside one of the change's own roots, or
|
|
18
|
+
// inside the product's folders. Nowhere else. That is the same shape as
|
|
19
|
+
// doc-file.mjs, keyed to the change instead of to a fixed list, because the
|
|
20
|
+
// checkout is the run's and not the app's.
|
|
21
|
+
import fsDefault from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { artifactRoots } from './artifact-path.mjs';
|
|
24
|
+
import { isInside } from './doc-file.mjs';
|
|
25
|
+
|
|
26
|
+
// Half a megabyte of one source file. Past that the change artifact is not what
|
|
27
|
+
// she should be editing in, and a truncation would eat the file on save.
|
|
28
|
+
export const MAX_CODE_BYTES = 800_000;
|
|
29
|
+
|
|
30
|
+
/** The roots a change's files may sit under: the run's own, plus the product's. */
|
|
31
|
+
export function rootsFor(change, product) {
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const r of change?.roots ?? []) if (typeof r === 'string' && r) out.push(r);
|
|
34
|
+
for (const r of artifactRoots(product)) if (r) out.push(r);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Where one file of a change really is.
|
|
40
|
+
*
|
|
41
|
+
* `abs` is taken when the change carries it, because that is what the run
|
|
42
|
+
* actually wrote and needs no guessing. Otherwise the short path is tried
|
|
43
|
+
* against each root and the first one that EXISTS wins — existence rather than
|
|
44
|
+
* order, because a product's docs dir and its checkout can both plausibly hold
|
|
45
|
+
* `designs/x.html` and only one of them does.
|
|
46
|
+
*
|
|
47
|
+
* A change written before 2026-08-24 has neither, and then there is nothing
|
|
48
|
+
* honest to do but say so.
|
|
49
|
+
*/
|
|
50
|
+
export function findFile({ change, product, filePath, fs = fsDefault }) {
|
|
51
|
+
if (!filePath) return { ok: false, error: 'No file was named.' };
|
|
52
|
+
const entry = (change?.files ?? []).find((f) => f?.path === filePath) ?? null;
|
|
53
|
+
if (!entry) return { ok: false, error: 'That file is not part of this change.' };
|
|
54
|
+
|
|
55
|
+
const tries = [];
|
|
56
|
+
if (typeof entry.abs === 'string' && entry.abs) tries.push(entry.abs);
|
|
57
|
+
if (path.isAbsolute(filePath)) tries.push(filePath);
|
|
58
|
+
for (const root of rootsFor(change, product)) tries.push(path.join(root, filePath));
|
|
59
|
+
|
|
60
|
+
for (const candidate of tries) {
|
|
61
|
+
try { if (fs.statSync(candidate).isFile()) return { ok: true, path: candidate }; } catch { /* next */ }
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
error: 'That file could not be found on this machine, so it cannot be edited here.',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** May the pane write onto this file, for this product? */
|
|
70
|
+
export function canWriteCode({ file, change, product, fs = fsDefault }) {
|
|
71
|
+
const roots = rootsFor(change, product);
|
|
72
|
+
if (!roots.length) return { ok: false, error: 'This change does not say which folder it came from, so nothing was saved.' };
|
|
73
|
+
if (!roots.some((root) => isInside(root, file))) {
|
|
74
|
+
return { ok: false, error: 'That file is outside the folders this run worked in, so nothing was saved.' };
|
|
75
|
+
}
|
|
76
|
+
if (!fs.existsSync(file)) return { ok: false, error: 'That file is no longer there, so nothing was saved.' };
|
|
77
|
+
return { ok: true };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The bytes of one file of a change, with when it last moved. */
|
|
81
|
+
export function readCodeFile({ file, fs = fsDefault }) {
|
|
82
|
+
let stat;
|
|
83
|
+
try { stat = fs.statSync(file); } catch { return { ok: false, error: 'That file is no longer there.' }; }
|
|
84
|
+
if (stat.size > MAX_CODE_BYTES) {
|
|
85
|
+
return { ok: false, error: 'That file is too big to edit here.' };
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return { ok: true, path: file, text: fs.readFileSync(file, 'utf8'), mtime: stat.mtimeMs };
|
|
89
|
+
} catch (err) {
|
|
90
|
+
return { ok: false, error: String(err?.message ?? err).slice(0, 200) };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Her edit, onto the source file.
|
|
96
|
+
*/
|
|
97
|
+
export function writeCodeFile({ file, change, product, text, mtime, fs = fsDefault }) {
|
|
98
|
+
const allowed = canWriteCode({ file, change, product, fs });
|
|
99
|
+
if (!allowed.ok) return allowed;
|
|
100
|
+
if (typeof text !== 'string') return { ok: false, error: 'Nothing to save.' };
|
|
101
|
+
if (mtime) {
|
|
102
|
+
let now = 0;
|
|
103
|
+
try { now = fs.statSync(file).mtimeMs; } catch { /* handled above */ }
|
|
104
|
+
if (now && now - mtime > 1000) {
|
|
105
|
+
return { ok: false, stale: true, error: 'Something else changed this file while you were typing, so nothing was saved.' };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
fs.writeFileSync(file, text, 'utf8');
|
|
110
|
+
let after = 0;
|
|
111
|
+
try { after = fs.statSync(file).mtimeMs; } catch { /* the write succeeded */ }
|
|
112
|
+
return { ok: true, mtime: after, path: file };
|
|
113
|
+
} catch (err) {
|
|
114
|
+
return { ok: false, error: String(err?.message ?? err).slice(0, 200) };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// WHICH CODEX ACCOUNT THIS MAC IS SIGNED INTO.
|
|
2
|
+
//
|
|
3
|
+
// THE CARD USED TO HAVE A REASON FOR SAYING NOTHING, AND THE REASON HAS EXPIRED.
|
|
4
|
+
// renderer/src/components/Settings.tsx carried this, written when the Codex card
|
|
5
|
+
// was built: "`~/.codex/auth.json` carries a mode and an opaque account id, no
|
|
6
|
+
// email and no plan, and a uuid is not a person." That was true of the Codex CLI
|
|
7
|
+
// of the day. It is not true of 0.153.4, measured on her Mac 2026-09-18: the
|
|
8
|
+
// `id_token` beside those fields carries `email`, `name`, and a
|
|
9
|
+
// `chatgpt_plan_type` of "pro". So the half of "connected, and signed in as"
|
|
10
|
+
// that had no honest answer now has one, and Codex's card can say what Claude
|
|
11
|
+
// Code's account rows have always said.
|
|
12
|
+
//
|
|
13
|
+
// THE CLI ITSELF WILL NOT TELL YOU. `codex login status` answers "Logged in
|
|
14
|
+
// using ChatGPT" and names nobody, so there is nothing to shell out to even if
|
|
15
|
+
// we wanted to pay for a process. This is a read of one small file.
|
|
16
|
+
//
|
|
17
|
+
// NOTHING SECRET LEAVES THIS MODULE. `auth.json` holds an access token, a
|
|
18
|
+
// refresh token and an API key, and none of them are returned, logged, or put on
|
|
19
|
+
// any wire. The id_token's signature is not checked and does not need to be:
|
|
20
|
+
// this is not authenticating anybody, it is reading the name off a credential
|
|
21
|
+
// the machine already trusts, to print it back to the person who put it there.
|
|
22
|
+
// A forged one would only mislabel her own screen.
|
|
23
|
+
//
|
|
24
|
+
// IT IS READ WHEN THE PAGE ASKS, NOT KEPT. Settings is opened by hand a few
|
|
25
|
+
// times a day and she can sign in or out between two of those, so a cached
|
|
26
|
+
// answer is a screen that can be quietly wrong. Same judgement main/claude-plan.
|
|
27
|
+
// mjs makes for the Claude plan, and for the same reason.
|
|
28
|
+
|
|
29
|
+
import fs from 'node:fs';
|
|
30
|
+
import os from 'node:os';
|
|
31
|
+
import path from 'node:path';
|
|
32
|
+
|
|
33
|
+
/** The claim OpenAI hangs its ChatGPT facts off, verbatim. */
|
|
34
|
+
const AUTH_CLAIM = 'https://api.openai.com/auth';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The plan ids whose proper names are known. Everything else is printed exactly
|
|
38
|
+
* as Codex spells it, and that is deliberate: her own token reads "prolite",
|
|
39
|
+
* which title-cases into "Prolite", a product name nobody at OpenAI has ever
|
|
40
|
+
* used. Inventing a label is how a screen tells a confident lie about what
|
|
41
|
+
* somebody is paying for, so an id we do not know is shown as the id. */
|
|
42
|
+
const PLANS = { free: 'Free', plus: 'Plus', pro: 'Pro', business: 'Business', team: 'Team', enterprise: 'Enterprise', edu: 'Edu' };
|
|
43
|
+
|
|
44
|
+
function planLabel(raw) {
|
|
45
|
+
const s = String(raw ?? '').trim().toLowerCase();
|
|
46
|
+
if (!s) return null;
|
|
47
|
+
return PLANS[s] ?? s;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether that label is a name anybody uses, or the raw id. The screen says
|
|
52
|
+
* "On the Pro plan" for one and "Codex reports the plan as prolite" for the
|
|
53
|
+
* other, because "On the prolite plan" reads like our typo rather than like
|
|
54
|
+
* Codex's word. */
|
|
55
|
+
function planIsNamed(raw) {
|
|
56
|
+
const s = String(raw ?? '').trim().toLowerCase();
|
|
57
|
+
return !!s && !!PLANS[s];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The middle of a JWT, which is plain base64url JSON. No verification: see the
|
|
62
|
+
* header of this file for why there is nothing here to verify. */
|
|
63
|
+
function claims(token) {
|
|
64
|
+
if (typeof token !== 'string') return null;
|
|
65
|
+
const part = token.split('.')[1];
|
|
66
|
+
if (!part) return null;
|
|
67
|
+
try {
|
|
68
|
+
const padded = part + '='.repeat((4 - (part.length % 4)) % 4);
|
|
69
|
+
const json = JSON.parse(Buffer.from(padded, 'base64url').toString('utf8'));
|
|
70
|
+
return json && typeof json === 'object' ? json : null;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Where this login lives. `configured` is the workspace's CODEX_HOME when it has
|
|
78
|
+
* one, which is the same rule main/supervisor.mjs `_codexHome` follows. */
|
|
79
|
+
export function codexAuthFile(home = null) {
|
|
80
|
+
return path.join(home || path.join(os.homedir(), '.codex'), 'auth.json');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* WHO IS SIGNED INTO CODEX UNDER THIS HOME, or null because nobody is.
|
|
85
|
+
*
|
|
86
|
+
* Null is an ordinary answer and the card falls back to saying only that Codex is
|
|
87
|
+
* connected, exactly as it did before this existed. Every field is independently
|
|
88
|
+
* nullable: an account signed in with an API key has no email and no plan, and
|
|
89
|
+
* the card then says the one true thing it can.
|
|
90
|
+
*
|
|
91
|
+
* @returns {{ email: string|null, name: string|null, plan: string|null,
|
|
92
|
+
* mode: string|null, accountId: string|null }|null}
|
|
93
|
+
*/
|
|
94
|
+
export function codexAccount(home = null, { file = null } = {}) {
|
|
95
|
+
const at = file ?? codexAuthFile(home);
|
|
96
|
+
let auth = null;
|
|
97
|
+
try { auth = JSON.parse(fs.readFileSync(at, 'utf8')); } catch { return null; }
|
|
98
|
+
if (!auth || typeof auth !== 'object') return null;
|
|
99
|
+
|
|
100
|
+
const id = claims(auth?.tokens?.id_token);
|
|
101
|
+
const chatgpt = id?.[AUTH_CLAIM] ?? null;
|
|
102
|
+
const email = typeof id?.email === 'string' && id.email.trim() ? id.email.trim() : null;
|
|
103
|
+
const name = typeof id?.name === 'string' && id.name.trim() ? id.name.trim() : null;
|
|
104
|
+
const plan = planLabel(chatgpt?.chatgpt_plan_type);
|
|
105
|
+
// The uuid, which is nobody's name and is only worth drawing when there is no
|
|
106
|
+
// email to draw instead. Claude Code's rows make the same choice.
|
|
107
|
+
const accountId = typeof auth?.tokens?.account_id === 'string' && auth.tokens.account_id
|
|
108
|
+
? auth.tokens.account_id
|
|
109
|
+
: (typeof chatgpt?.chatgpt_account_id === 'string' ? chatgpt.chatgpt_account_id : null);
|
|
110
|
+
const mode = typeof auth?.auth_mode === 'string' && auth.auth_mode ? auth.auth_mode : null;
|
|
111
|
+
|
|
112
|
+
if (!email && !name && !plan && !accountId) return null;
|
|
113
|
+
return { email, name, plan, planNamed: planIsNamed(chatgpt?.chatgpt_plan_type), mode, accountId };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A FOLDER FOR THE NEXT CODEX LOGIN, MADE BEFORE ANYTHING IS ASKED TO USE IT.
|
|
118
|
+
*
|
|
119
|
+
* The command the card handed her was simply wrong: codex-cli will not create
|
|
120
|
+
* the home it is pointed at, so the folder has to exist first. Making it here,
|
|
121
|
+
* in the app, is better than telling her to run one more line.
|
|
122
|
+
*
|
|
123
|
+
* THE NAME IS COUNTED, NEVER GUESSED. `~/.codex-2` and up, skipping anything
|
|
124
|
+
* that exists, so pressing Add twice cannot hand the second login the first
|
|
125
|
+
* one's folder and overwrite a working account.
|
|
126
|
+
*
|
|
127
|
+
* @returns {{ home: string, profile: string }} `profile` is the word the fleet
|
|
128
|
+
* spawns on, which for every home but her primary one IS the path.
|
|
129
|
+
*/
|
|
130
|
+
export function makeCodexHome({ home = os.homedir(), mkdir = fs.mkdirSync, exists = fs.existsSync } = {}) {
|
|
131
|
+
for (let n = 2; n < 100; n += 1) {
|
|
132
|
+
const dir = path.join(home, `.codex-${n}`);
|
|
133
|
+
if (exists(dir)) continue;
|
|
134
|
+
mkdir(dir, { recursive: true });
|
|
135
|
+
return { home: dir, profile: dir };
|
|
136
|
+
}
|
|
137
|
+
throw new Error('There are already a lot of Codex folders in your home directory.');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The one line that signs a new account in, for the terminal to run. The home
|
|
142
|
+
* is spelled out rather than left to the shell, because `~` inside a variable
|
|
143
|
+
* assignment is not expanded by every shell the same way. */
|
|
144
|
+
export function codexLoginCommand(home) {
|
|
145
|
+
return `CODEX_HOME=${home} codex login`;
|
|
146
|
+
}
|