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,53 @@
|
|
|
1
|
+
// Types for shared/usage.mjs, which the renderer imports and which is plain
|
|
2
|
+
// JavaScript because main imports it too. Hand-written and short: it is one
|
|
3
|
+
// rule about which coding agent the corner is about, the zone primitives the
|
|
4
|
+
// Claude reader is built on, and the words the panel prints.
|
|
5
|
+
|
|
6
|
+
export interface UsageLimit {
|
|
7
|
+
span: 'session' | 'week';
|
|
8
|
+
qualifier: string | null;
|
|
9
|
+
/** What the panel calls it, in her words, stamped on by whichever reader
|
|
10
|
+
* produced it. See renderer/src/types.ts for why it is not derived here. */
|
|
11
|
+
name: string;
|
|
12
|
+
percent: number;
|
|
13
|
+
resetsText: string | null;
|
|
14
|
+
resetsOn: string | null;
|
|
15
|
+
resetsAt: number | null;
|
|
16
|
+
zone: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** One live session as `Supervisor#status` reports it. Only the engine is read;
|
|
20
|
+
* it is already one of exactly two words by the time it arrives. */
|
|
21
|
+
export interface RunningSession {
|
|
22
|
+
engine?: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export declare function usageEngine(facts?: {
|
|
26
|
+
workspace?: string;
|
|
27
|
+
running?: RunningSession[] | null;
|
|
28
|
+
}): string;
|
|
29
|
+
|
|
30
|
+
export declare function localZone(): string | null;
|
|
31
|
+
export declare function wallIn(ms: number, zone: string | null): {
|
|
32
|
+
year: number; month: number; day: number; hour: number; minute: number; second: number;
|
|
33
|
+
} | null;
|
|
34
|
+
export declare function instantIn(
|
|
35
|
+
year: number, monthIndex: number, day: number, hour: number, minute: number, zone: string | null,
|
|
36
|
+
): number;
|
|
37
|
+
|
|
38
|
+
export declare function sessionLimit(limits: UsageLimit[] | null | undefined): UsageLimit | null;
|
|
39
|
+
export declare function headlineLimit(limits: UsageLimit[] | null | undefined): UsageLimit | null;
|
|
40
|
+
export declare const ANY_MOMENT: string;
|
|
41
|
+
export declare function spanLeft(ms: number): string;
|
|
42
|
+
export declare function untilReset(ms: number): string;
|
|
43
|
+
|
|
44
|
+
export declare function limitRows(
|
|
45
|
+
limits: UsageLimit[] | null | undefined,
|
|
46
|
+
now?: number,
|
|
47
|
+
): { key: string; name: string; used: number; when: string | null }[];
|
|
48
|
+
|
|
49
|
+
export declare function usageSentence(
|
|
50
|
+
limits: UsageLimit[] | null | undefined,
|
|
51
|
+
now?: number,
|
|
52
|
+
engineWord?: string | null,
|
|
53
|
+
): string | null;
|
package/shared/usage.mjs
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// WHAT IS LEFT OF A LIMIT, WHICHEVER CODING AGENT REPORTED IT.
|
|
2
|
+
//
|
|
3
|
+
// The corner meter and its dropdown were built against `claude -p /usage` and
|
|
4
|
+
// nothing else. They are drawn on every screen, unlabelled, and they describe a
|
|
5
|
+
// Claude Code subscription whether or not the work in front of her is billing
|
|
6
|
+
// one. That is the same class of defect he had already caught on the Settings
|
|
7
|
+
// Model row: a control that looks as though it belongs to the coding agent named
|
|
8
|
+
// elsewhere on the screen, and does not.
|
|
9
|
+
//
|
|
10
|
+
// SO THE PANEL'S WORDS LEFT THE CLAUDE FILE, and that move is the point rather
|
|
11
|
+
// than tidying. A Codex reading flowing through a module called `claude-usage`
|
|
12
|
+
// is the same defect written in source: the next person to add a Claude-shaped
|
|
13
|
+
// assumption to it would have no reason not to. What is Claude Code's stays
|
|
14
|
+
// there -- the text format, the timezone stamp it prints, `readUsage` --
|
|
15
|
+
// and what belongs to a LIMIT, any limit, is here.
|
|
16
|
+
//
|
|
17
|
+
// TWO THINGS DID NOT CARRY OVER, AND SAYING SO IS THE POINT OF THIS PARAGRAPH.
|
|
18
|
+
// Claude Code prints a clock time in a named zone and no instant, so the whole
|
|
19
|
+
// of `resetAt` / `wallIn` / `instantIn` exists to turn "Sep 3 at 3:59pm
|
|
20
|
+
// (America/Los_Angeles)" into a moment, and to REFUSE when it cannot be done
|
|
21
|
+
// honestly. Codex hands over an absolute instant and no text at all. Those are
|
|
22
|
+
// different problems and neither reader may be forced into the other's shape.
|
|
23
|
+
// What is genuinely shared is everything after the reading: the countdown, the
|
|
24
|
+
// rows, the sentence, and the rule for which agent the corner is about.
|
|
25
|
+
//
|
|
26
|
+
// AND A LIMIT NOW CARRIES ITS OWN NAME. `limitName` used to derive one from
|
|
27
|
+
// Claude Code's three spans and its qualifier, which is how "This week, Fable"
|
|
28
|
+
// -- a Claude model -- could have appeared over a Codex reading. Each reader
|
|
29
|
+
// names its own limits, in her words, and this file prints the name it is
|
|
30
|
+
// given.
|
|
31
|
+
|
|
32
|
+
import { DEFAULT_ENGINE } from './engines.mjs';
|
|
33
|
+
|
|
34
|
+
/* ------------------ WHICH CODING AGENT THE CORNER IS ABOUT ---------------- */
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* THE AGENT MOST OF HER LIVE WORK IS ON, or the workspace's own when nothing is
|
|
38
|
+
* running and when the fleet is level.
|
|
39
|
+
*
|
|
40
|
+
* IT IS THE RULE THE BYLINE ALREADY FOLLOWS, one level up. renderer/src/byline.ts
|
|
41
|
+
* settled this for a row a slice ago -- "WHAT IS RUNNING BEATS WHAT WOULD RUN" --
|
|
42
|
+
* and the corner asks the same question about the whole app. Two rules for one
|
|
43
|
+
* question is how "Medium" and "medium" ended up on adjacent screens.
|
|
44
|
+
*
|
|
45
|
+
* WHY NOT THE WORKSPACE AGENT ALWAYS. It is stabler, and it is wrong in exactly
|
|
46
|
+
* the case that was reported: four Codex workers under a workspace defaulting to
|
|
47
|
+
* Claude Code would draw Claude Code's meter at 12% while the thing about to
|
|
48
|
+
* stop the fleet sits at 96% on the other subscription. A meter that is stable
|
|
49
|
+
* and describes the wrong account is the defect, not the fix.
|
|
50
|
+
*
|
|
51
|
+
* WHY IT COUNTS RATHER THAN ASKING "IS ANY CODEX RUNNING". A rule that flipped
|
|
52
|
+
* the moment one worker of the other kind started would change the SUBJECT of
|
|
53
|
+
* the corner several times a minute on a mixed fleet, and she would be reading a
|
|
54
|
+
* history of two subscriptions in one bar. Counting moves it only when the
|
|
55
|
+
* balance of the fleet really moves. The level case has no answer of its own, so
|
|
56
|
+
* it falls back to the one answer that is always available.
|
|
57
|
+
*
|
|
58
|
+
* The engine on a session is already one of exactly two words: `Supervisor#status`
|
|
59
|
+
* puts every record through `engineOf` first, and a record written before the
|
|
60
|
+
* second engine existed comes out as Claude Code. A session with no engine on it
|
|
61
|
+
* at all is not counted here rather than counted as a third thing.
|
|
62
|
+
*/
|
|
63
|
+
export function usageEngine({ workspace = DEFAULT_ENGINE, running = [] } = {}) {
|
|
64
|
+
const tally = new Map();
|
|
65
|
+
for (const session of running ?? []) {
|
|
66
|
+
const id = session?.engine;
|
|
67
|
+
if (typeof id !== 'string' || !id) continue;
|
|
68
|
+
tally.set(id, (tally.get(id) ?? 0) + 1);
|
|
69
|
+
}
|
|
70
|
+
let best = null;
|
|
71
|
+
let most = 0;
|
|
72
|
+
let level = false;
|
|
73
|
+
for (const [id, count] of tally) {
|
|
74
|
+
if (count > most) { best = id; most = count; level = false; } else if (count === most) level = true;
|
|
75
|
+
}
|
|
76
|
+
return best === null || level ? workspace : best;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* --------------------- reading a clock in a named zone -------------------- */
|
|
80
|
+
// The two primitives the zone-honesty of shared/claude-usage.mjs rests on, and
|
|
81
|
+
// the one question this file asks about a day. They exist because
|
|
82
|
+
// `new Date(y, m, d, h, min)` and `Date#getDate` are the PROCESS's zone and
|
|
83
|
+
// nothing else, and every question about a reset is about hers.
|
|
84
|
+
//
|
|
85
|
+
// A ZONE WE CANNOT USE FALLS BACK TO THE PROCESS'S, DELIBERATELY. `zone` is null
|
|
86
|
+
// when `Intl` would not answer at all, and a name Intl rejects throws; in both
|
|
87
|
+
// cases the old behaviour is the best guess left and is exactly right on the only
|
|
88
|
+
// machine that matters, hers. It is a fallback and never a first choice: when we
|
|
89
|
+
// are told the zone we use the zone.
|
|
90
|
+
|
|
91
|
+
export function localZone() {
|
|
92
|
+
try { return Intl.DateTimeFormat().resolvedOptions().timeZone || null; } catch { return null; }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const WALL = {
|
|
96
|
+
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
97
|
+
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** What a clock in `zone` reads at instant `ms`, or null if it cannot be asked. */
|
|
101
|
+
export function wallIn(ms, zone) {
|
|
102
|
+
if (!zone) return null;
|
|
103
|
+
try {
|
|
104
|
+
const parts = new Intl.DateTimeFormat('en-US', { timeZone: zone, ...WALL }).formatToParts(new Date(ms));
|
|
105
|
+
const at = (type) => Number(parts.find((p) => p.type === type)?.value);
|
|
106
|
+
// `hour12: false` prints midnight as 24 in some engines and 0 in others.
|
|
107
|
+
const read = {
|
|
108
|
+
year: at('year'), month: at('month'), day: at('day'),
|
|
109
|
+
hour: at('hour') % 24, minute: at('minute'), second: at('second'),
|
|
110
|
+
};
|
|
111
|
+
return Object.values(read).every(Number.isFinite) ? read : null;
|
|
112
|
+
} catch { return null; }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* THE INSTANT AT WHICH A CLOCK IN `zone` READS THIS WALL TIME.
|
|
117
|
+
*
|
|
118
|
+
* Two passes and not one, and the second is the DST correction rather than
|
|
119
|
+
* defensiveness: the offset has to be sampled at an instant, the first sample
|
|
120
|
+
* is taken at the wall time pretending to be UTC, and on the two days a year
|
|
121
|
+
* that lands on the far side of a transition the sample is the wrong offset by
|
|
122
|
+
* an hour. Sampling again at the instant the first pass produced puts it inside
|
|
123
|
+
* the same offset as the answer. A wall time that does not exist (the spring
|
|
124
|
+
* gap) or happens twice (the autumn overlap) settles on one of the two, which is
|
|
125
|
+
* what every other reader of a bare clock time does and is a whole hour better
|
|
126
|
+
* than being in the wrong zone.
|
|
127
|
+
*/
|
|
128
|
+
export function instantIn(year, monthIndex, day, hour, minute, zone) {
|
|
129
|
+
const local = () => new Date(year, monthIndex, day, hour, minute, 0, 0).getTime();
|
|
130
|
+
if (!zone) return local();
|
|
131
|
+
const wanted = Date.UTC(year, monthIndex, day, hour, minute, 0);
|
|
132
|
+
let ms = wanted;
|
|
133
|
+
for (let pass = 0; pass < 2; pass += 1) {
|
|
134
|
+
const w = wallIn(ms, zone);
|
|
135
|
+
if (!w) return local();
|
|
136
|
+
ms = wanted - (Date.UTC(w.year, w.month - 1, w.day, w.hour, w.minute, w.second) - ms);
|
|
137
|
+
}
|
|
138
|
+
return ms;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* WHETHER TWO MOMENTS ARE THE SAME DAY WHERE SHE IS, which is what decides
|
|
143
|
+
* whether a reset needs its date said out loud. By calendar date rather than by
|
|
144
|
+
* arithmetic on hours, for the same reason the repeating tasks are: DST and a
|
|
145
|
+
* clock correction have to be boring.
|
|
146
|
+
*
|
|
147
|
+
* IN A NAMED ZONE, for the same reason `resetAt` builds its instant in one
|
|
148
|
+
* (2026-09-04). "Is this reset today" is a question about her calendar, and
|
|
149
|
+
* reading it off the process's clock answered it about the machine's instead:
|
|
150
|
+
* the same instant is Sep 1 in Los Angeles and Sep 2 in Berlin, so the panel's
|
|
151
|
+
* "Resets 4pm" became "Resets Sep 1 at 4pm" purely by moving desks.
|
|
152
|
+
*/
|
|
153
|
+
function sameDay(a, b, zone) {
|
|
154
|
+
const x = wallIn(a, zone), y = wallIn(b, zone);
|
|
155
|
+
if (x && y) return x.year === y.year && x.month === y.month && x.day === y.day;
|
|
156
|
+
const p = new Date(a), q = new Date(b);
|
|
157
|
+
return p.getFullYear() === q.getFullYear() && p.getMonth() === q.getMonth() && p.getDate() === q.getDate();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* ------------------------------ the countdown ----------------------------- */
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The five hour limit, which is the one that stops her today. Claude Code
|
|
164
|
+
* prints it as "Current session"; Codex reports it as the `primary` window,
|
|
165
|
+
* measured at 300 minutes. Both readers mark it `span: 'session'`. */
|
|
166
|
+
export function sessionLimit(limits) {
|
|
167
|
+
return (limits ?? []).find((l) => l.span === 'session') ?? null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* THE ONE LIMIT THE CORNER IS ABOUT, on an account that may not have a five hour
|
|
172
|
+
* window at all.
|
|
173
|
+
*
|
|
174
|
+
* The bar used to be the session limit or nothing, and on 2026-09-18 that turned
|
|
175
|
+
* out to mean NOTHING on the founder's own Codex plan: measured across the 1,140
|
|
176
|
+
* rate-limit records in her twenty-five most recent Codex sessions, every window
|
|
177
|
+
* she has is a seven day one and `secondary` is never populated. Her weekly limit
|
|
178
|
+
* was at 100% used that day, which is exactly the moment a person needs the
|
|
179
|
+
* corner, and the corner would have been blank.
|
|
180
|
+
*
|
|
181
|
+
* The session window still wins whenever there is one, because it is the one that
|
|
182
|
+
* stops her today. When there is not, the fullest window she does have is drawn
|
|
183
|
+
* instead: a limit at 100% decides whether she can start six agents, whatever
|
|
184
|
+
* span it runs over. Null only when there is truly nothing, which stays the
|
|
185
|
+
* honest empty corner it has always been.
|
|
186
|
+
*/
|
|
187
|
+
export function headlineLimit(limits) {
|
|
188
|
+
const session = sessionLimit(limits);
|
|
189
|
+
if (session) return session;
|
|
190
|
+
let best = null;
|
|
191
|
+
for (const limit of limits ?? []) {
|
|
192
|
+
if (!Number.isFinite(limit?.percent)) continue;
|
|
193
|
+
if (!best || limit.percent > best.percent) best = limit;
|
|
194
|
+
}
|
|
195
|
+
return best;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* HOW LONG UNTIL IT STARTS OVER, in her house style: whole minutes, then hours
|
|
200
|
+
* and minutes, and never a seconds counter.
|
|
201
|
+
*/
|
|
202
|
+
export function untilReset(ms) {
|
|
203
|
+
const span = spanLeft(ms);
|
|
204
|
+
return span === ANY_MOMENT ? span : `${span} left`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* THE SAME COUNTDOWN WITHOUT THE WORD "left" ON THE END, for the callers that
|
|
209
|
+
* put a preposition in front of it instead. It is the same rule and the same
|
|
210
|
+
* rounding; `untilReset` is this plus a suffix, so the two cannot drift into
|
|
211
|
+
* disagreeing about what 90 minutes is. */
|
|
212
|
+
export const ANY_MOMENT = 'any moment';
|
|
213
|
+
export function spanLeft(ms) {
|
|
214
|
+
const minutes = Math.max(0, Math.floor(ms / 60_000));
|
|
215
|
+
if (minutes < 1) return ANY_MOMENT;
|
|
216
|
+
if (minutes < 60) return `${minutes}m`;
|
|
217
|
+
const hours = Math.floor(minutes / 60);
|
|
218
|
+
// AND DAYS PAST A DAY, which the session limit never needs and the WEEKLY ones
|
|
219
|
+
// always do. The first version of this stopped at hours and read "71h 12m
|
|
220
|
+
// left" on her week, which is a number a person has to do arithmetic on before
|
|
221
|
+
// it means anything. The minutes come off at that range too: nobody reads a
|
|
222
|
+
// weekly allowance to the minute.
|
|
223
|
+
if (hours >= 24) {
|
|
224
|
+
const days = Math.floor(hours / 24);
|
|
225
|
+
const rest = hours % 24;
|
|
226
|
+
return rest ? `${days}d ${rest}h` : `${days}d`;
|
|
227
|
+
}
|
|
228
|
+
return `${hours}h ${minutes % 60}m`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* -------------------------------- the panel ------------------------------- */
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* THE PANEL'S ROWS, ONE PER LIMIT, COUNTING WHAT HAS BEEN SPENT.
|
|
235
|
+
*
|
|
236
|
+
* IT COUNTED WHAT WAS LEFT FOR ABOUT AN HOUR AND THAT WAS WRONG.
|
|
237
|
+
*
|
|
238
|
+
* That is the QUESTION, and "45% used" is a fine answer to it. A bar is not a
|
|
239
|
+
* sentence, and every bar anybody has used fills as the thing is consumed.
|
|
240
|
+
*
|
|
241
|
+
* SO `used`, STRAIGHT FROM THE READING, and no `left` anywhere for a caller to
|
|
242
|
+
* reach for by mistake.
|
|
243
|
+
*
|
|
244
|
+
* THE ORDER IS THE READER'S OWN, which is the five hour one first and the one
|
|
245
|
+
* that stops her today.
|
|
246
|
+
*
|
|
247
|
+
* AND `when` IS A CLOCK TIME, WITH THE DAY WHEN THE DAY IS NOT TODAY. Claude
|
|
248
|
+
* Code prints "Resets 9:59pm" for the session and "Resets Sep 3 at 3:59pm" for
|
|
249
|
+
* the week, and that solves the ambiguity a countdown was invented to dodge on
|
|
250
|
+
* the round before: the week's stamp is "4pm" and reads as today when it is
|
|
251
|
+
* Thursday's. Saying the day is the better answer and it is the one she is
|
|
252
|
+
* pointing at.
|
|
253
|
+
*
|
|
254
|
+
* A READING THAT CARRIES AN INSTANT AND NO CLOCK TEXT COUNTS DOWN INSTEAD,
|
|
255
|
+
* which is the Codex shape and is unreachable for Claude Code: `resetsAt` is
|
|
256
|
+
* only ever non-null there when the same regex already produced `resetsText`.
|
|
257
|
+
* Formatting Codex's instant into a clock time would mean inventing a second
|
|
258
|
+
* copy of the zone machinery next door, to print a fact we already hold exactly
|
|
259
|
+
* -- so the fact we hold is what gets printed, in the countdown wording this
|
|
260
|
+
* file already owns.
|
|
261
|
+
*
|
|
262
|
+
* `when` stays null rather than invented when there is no reset at all, which is
|
|
263
|
+
* what Claude Code gives for a limit at 0% and what Codex's nullable `resetsAt`
|
|
264
|
+
* gives. And when a Claude stamp was computed in another timezone we cannot say
|
|
265
|
+
* which local day it lands on, so the whole stamp goes out as the command wrote
|
|
266
|
+
* it: true either way, and still refusing to guess.
|
|
267
|
+
*
|
|
268
|
+
* WHICH DAY "TODAY" IS gets asked in the limit's OWN zone, which is the zone the
|
|
269
|
+
* CLI printed beside the stamp. That is not a third source of truth: `resetsAt`
|
|
270
|
+
* is null unless that zone is the one `readUsage` was told this machine is in
|
|
271
|
+
* (`resetAt`'s standing refusal), so by the time this line is reached the two
|
|
272
|
+
* are the same zone and the stamp's copy of it is the one in hand. A reading
|
|
273
|
+
* with no zone on it at all falls back to the process's, which is what it always
|
|
274
|
+
* was, and is every Codex reading.
|
|
275
|
+
*/
|
|
276
|
+
export function limitRows(limits, now = Date.now()) {
|
|
277
|
+
const here = localZone();
|
|
278
|
+
return (limits ?? []).map((l) => ({
|
|
279
|
+
// Stable across redraws, and it has to carry the qualifier: Claude Code's
|
|
280
|
+
// two week rows are otherwise the same string.
|
|
281
|
+
key: `${l.span}:${l.qualifier ?? ''}`,
|
|
282
|
+
name: l.name,
|
|
283
|
+
used: l.percent,
|
|
284
|
+
when: whenItStartsOver(l, now, here),
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function whenItStartsOver(l, now, here) {
|
|
289
|
+
if (l.resetsText) {
|
|
290
|
+
if (l.resetsAt && sameDay(l.resetsAt, now, l.zone ?? here)) return `Resets ${l.resetsText}`;
|
|
291
|
+
return l.resetsOn ? `Resets ${l.resetsOn} at ${l.resetsText}` : `Resets ${l.resetsText}`;
|
|
292
|
+
}
|
|
293
|
+
if (!l.resetsAt) return null;
|
|
294
|
+
const soon = spanLeft(l.resetsAt - now);
|
|
295
|
+
return soon === ANY_MOMENT ? `Resets ${soon}` : `Resets in ${soon}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* THE WHOLE SENTENCE, for whatever reads the corner aloud and for the tooltip.
|
|
300
|
+
* It says every limit, because the corner itself is a bar with no words in it at
|
|
301
|
+
* all and a summary has to expand into the thing it summarised.
|
|
302
|
+
*
|
|
303
|
+
* AND IT NAMES THE CODING AGENT WHERE THERE IS ONE TO NAME (2026-09-05). The
|
|
304
|
+
* bar has no words in it, so this sentence IS the corner for anybody who cannot
|
|
305
|
+
* see it; labelling the dropdown and leaving this unlabelled would answer the
|
|
306
|
+
* tester's ask for one reader and not the other. `engineWord` is null on every
|
|
307
|
+
* Mac with one coding agent -- `engineWordFor` in renderer/src/byline.ts is the
|
|
308
|
+
* one rule for that, and it is the same rule the panel's heading follows -- and
|
|
309
|
+
* then this is byte for byte the sentence she has been reading since
|
|
310
|
+
* 2026-09-01.
|
|
311
|
+
*/
|
|
312
|
+
export function usageSentence(limits, now = Date.now(), engineWord = null) {
|
|
313
|
+
if (!limits?.length) return null;
|
|
314
|
+
const said = limits.map((l) => {
|
|
315
|
+
const when = l.resetsAt ? `, ${untilReset(l.resetsAt - now)}` : l.resetsText ? `, resets ${l.resetsText}` : '';
|
|
316
|
+
return `${l.name}: ${l.percent}% used${when}.`;
|
|
317
|
+
}).join(' ');
|
|
318
|
+
return engineWord ? `${engineWord}. ${said}` : said;
|
|
319
|
+
}
|