0sec-cli 0.15.0 → 0.18.0
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/0sec.js +327 -0
- package/README.md +68 -48
- package/attacks/data-exfiltration/pii-leakage.yaml +27 -0
- package/attacks/encoding-bypass/base64-encoding.yaml +24 -0
- package/attacks/jailbreak/dan-roleplay.yaml +27 -0
- package/attacks/jailbreak/hypothetical-scenario.yaml +25 -0
- package/attacks/jailbreak/multilingual-bypass.yaml +22 -0
- package/attacks/output-manipulation/harmful-content.yaml +25 -0
- package/attacks/prompt-injection/context-manipulation.yaml +32 -0
- package/attacks/prompt-injection/direct-injection.yaml +28 -0
- package/attacks/prompt-injection/indirect-injection.yaml +33 -0
- package/attacks/system-prompt-extraction/direct-ask.yaml +30 -0
- package/attacks/system-prompt-extraction/markdown-exfil.yaml +26 -0
- package/attacks/tool-misuse/ssrf-via-tools.yaml +27 -0
- package/chunks/adapt-loop-SMH6V4PD.js +18 -0
- package/chunks/adgraph-JLGA6RYI.js +54 -0
- package/chunks/agent/skills/frameworks/entra-id.yaml +63 -0
- package/chunks/agent/skills/frameworks/graphql-introspection.yaml +116 -0
- package/chunks/agent/skills/frameworks/nextjs.yaml +82 -0
- package/chunks/agent/skills/frameworks/python-web.yaml +85 -0
- package/chunks/agent/skills/frameworks/supabase.yaml +83 -0
- package/chunks/agent/skills/frameworks/wordpress-deep.yaml +122 -0
- package/chunks/agent/skills/techniques/ad-attack-paths.yaml +57 -0
- package/chunks/agent/skills/techniques/advisory-disclosure.yaml +59 -0
- package/chunks/agent/skills/techniques/assumption-mining.yaml +53 -0
- package/chunks/agent/skills/techniques/blind-exploitation.yaml +143 -0
- package/chunks/agent/skills/techniques/crypto-misuse.yaml +88 -0
- package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +82 -0
- package/chunks/agent/skills/techniques/entra-attack-paths.yaml +55 -0
- package/chunks/agent/skills/techniques/http-conformance-diff.yaml +48 -0
- package/chunks/agent/skills/techniques/jwt-attacks.yaml +137 -0
- package/chunks/agent/skills/techniques/kernel-weaponization.yaml +53 -0
- package/chunks/agent/skills/techniques/llm-excessive-agency.yaml +55 -0
- package/chunks/agent/skills/techniques/llm-insecure-output-handling.yaml +56 -0
- package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +54 -0
- package/chunks/agent/skills/techniques/llm-prompt-layer-write.yaml +68 -0
- package/chunks/agent/skills/techniques/llm-rag-poisoning.yaml +57 -0
- package/chunks/agent/skills/techniques/llm-safety-eval.yaml +51 -0
- package/chunks/agent/skills/techniques/npm-ecosystem.yaml +51 -0
- package/chunks/agent/skills/techniques/poc-verification.yaml +55 -0
- package/chunks/agent/skills/techniques/race-condition.yaml +122 -0
- package/chunks/agent/skills/techniques/scoped-fix.yaml +49 -0
- package/chunks/agent/skills/techniques/seedless-depth-review.yaml +59 -0
- package/chunks/agent/skills/techniques/spec-differential.yaml +48 -0
- package/chunks/agent/skills/techniques/variant-hunting.yaml +55 -0
- package/chunks/agent/skills/vulnerabilities/cardano-eutxo-validators.yaml +72 -0
- package/chunks/agent/skills/vulnerabilities/command-injection.yaml +99 -0
- package/chunks/agent/skills/vulnerabilities/deserialization-chains.yaml +106 -0
- package/chunks/agent/skills/vulnerabilities/native-memory-safety.yaml +93 -0
- package/chunks/agent/skills/vulnerabilities/path-traversal.yaml +78 -0
- package/chunks/agent/skills/vulnerabilities/prototype-pollution.yaml +132 -0
- package/chunks/agent/skills/vulnerabilities/request-smuggling.yaml +129 -0
- package/chunks/agent/skills/vulnerabilities/sqli-advanced.yaml +102 -0
- package/chunks/agent/skills/vulnerabilities/ssrf-bypass.yaml +84 -0
- package/chunks/agent/skills/vulnerabilities/ssti-exploitation.yaml +112 -0
- package/chunks/agent/skills/vulnerabilities/structural-sqli.yaml +91 -0
- package/chunks/appsec-catalog-CBWHGTEL.js +24 -0
- package/chunks/artifact-scraper-HQ7HLP6V.js +34 -0
- package/chunks/assumption-mining-5SNIVVDN.js +79 -0
- package/chunks/chunk-242TMK6G.js +110 -0
- package/chunks/chunk-2JCCA2JL.js +381 -0
- package/chunks/chunk-2RMLOJVB.js +47 -0
- package/chunks/chunk-3KPWWJCI.js +1422 -0
- package/chunks/chunk-3MOLBTLS.js +953 -0
- package/chunks/chunk-53G27VPS.js +136 -0
- package/chunks/chunk-57ZENEX2.js +169 -0
- package/chunks/chunk-5G3ZXFBW.js +182 -0
- package/chunks/chunk-5JI7L7KV.js +74240 -0
- package/chunks/chunk-6L26OBWI.js +1094 -0
- package/chunks/chunk-6LKRLK2R.js +542 -0
- package/chunks/chunk-72CISA2D.js +2597 -0
- package/chunks/chunk-7DQEV5QI.js +287 -0
- package/chunks/chunk-AD7WXDH6.js +3250 -0
- package/chunks/chunk-ADOLI6DT.js +102 -0
- package/chunks/chunk-AHTBZC3E.js +596 -0
- package/chunks/chunk-ASUR522M.js +518 -0
- package/chunks/chunk-ATATQACO.js +471 -0
- package/chunks/chunk-BATRQBOR.js +1732 -0
- package/chunks/chunk-BFR2CDV5.js +5735 -0
- package/chunks/chunk-BFRB6HB2.js +235 -0
- package/chunks/chunk-BKZFDZ23.js +361 -0
- package/chunks/chunk-BVAZO4WA.js +193 -0
- package/chunks/chunk-BZNEY2ZS.js +33225 -0
- package/chunks/chunk-C2WXWFBB.js +1070 -0
- package/chunks/chunk-CMKXP5RR.js +917 -0
- package/chunks/chunk-DW5UWPFY.js +275 -0
- package/chunks/chunk-F3WBKITT.js +687 -0
- package/chunks/chunk-GMT2AZKM.js +2439 -0
- package/chunks/chunk-H2FFLZNK.js +3 -0
- package/chunks/chunk-IOL7D5YV.js +515 -0
- package/chunks/chunk-IR537GON.js +49 -0
- package/chunks/chunk-JQHOLLTD.js +3729 -0
- package/chunks/chunk-KLTNTE2Z.js +121 -0
- package/chunks/chunk-LP3HHYQU.js +245 -0
- package/chunks/chunk-MYVT64FN.js +16 -0
- package/chunks/chunk-N7BOUYEV.js +852 -0
- package/chunks/chunk-OEFNRYI2.js +150 -0
- package/chunks/chunk-P6WKNFWX.js +1968 -0
- package/chunks/chunk-QI233I24.js +333 -0
- package/chunks/chunk-RJWOYEOG.js +271 -0
- package/chunks/chunk-RRMJC3ZE.js +105 -0
- package/chunks/chunk-RWONANDA.js +973 -0
- package/chunks/chunk-SAFFWQW4.js +5921 -0
- package/chunks/chunk-SF4KZ4O3.js +502 -0
- package/chunks/chunk-SVJEDVYK.js +808 -0
- package/chunks/chunk-TSK2AG6J.js +5108 -0
- package/chunks/chunk-U5FLXGHT.js +2010 -0
- package/chunks/chunk-UCYRA73C.js +662 -0
- package/chunks/chunk-UR4ELX2Y.js +4784 -0
- package/chunks/chunk-VQG4FT5D.js +685 -0
- package/chunks/chunk-WEBMRWEG.js +9337 -0
- package/chunks/chunk-WU6AFRAZ.js +1167 -0
- package/chunks/chunk-WVTBZEQO.js +1531 -0
- package/chunks/chunk-WXQ6BJ6A.js +596 -0
- package/chunks/chunk-XM5NLHYU.js +600 -0
- package/chunks/chunk-YLMN3N25.js +4054 -0
- package/chunks/chunk-YYRBQVFE.js +2572 -0
- package/chunks/chunk-Z5FA2XOX.js +2798 -0
- package/chunks/commands-TBC2F5CE.js +16999 -0
- package/chunks/corpus-v1.json +403 -0
- package/chunks/cost-ledger-NU63MYZM.js +13 -0
- package/chunks/data/appsec-archetypes.json +102 -0
- package/chunks/data/chromium-archetypes.json +204 -0
- package/chunks/data/freebsd-archetypes.json +171 -0
- package/chunks/data/kernel-archetypes.json +611 -0
- package/chunks/db-KFWOAUUB.js +16 -0
- package/chunks/disclose-EKTWSCGW.js +132 -0
- package/chunks/dist-4MAW5X6L.js +74 -0
- package/chunks/dist-4YICS2J2.js +2776 -0
- package/chunks/dist-FMGSR3DW.js +159 -0
- package/chunks/eval-runner-7ZF472KV.js +27 -0
- package/chunks/example-manifest.json +95 -0
- package/chunks/exploit-agent-45YQOHPJ.js +98 -0
- package/chunks/exploit-autoclimb-UZGEMGYT.js +81 -0
- package/chunks/exploit-climb-SG23KB2G.js +358 -0
- package/chunks/fix-IG7LYXH3.js +12 -0
- package/chunks/github-issues-MJ6OYOOU.js +157 -0
- package/chunks/harness-7ODOLOWU.js +22 -0
- package/chunks/http-conformance-DU66MZIU.js +11 -0
- package/chunks/http-sender-GWH2IYEA.js +10 -0
- package/chunks/hunt-scan-MMKUOETU.js +38 -0
- package/chunks/identity-6ZAIIWOR.js +191 -0
- package/chunks/kernel-primitive-7U6CWHXD.js +44 -0
- package/chunks/kernel-vm-runner-3JAU4O6J.js +62 -0
- package/chunks/memsafety-scan-J7SECV5W.js +16 -0
- package/chunks/native-loop-RJCJPOUS.js +54 -0
- package/chunks/npm-detectors-KB5Y5ZFX.js +69 -0
- package/chunks/npm-dynamic-discovery-AHROOZDB.js +13 -0
- package/chunks/orchestrate-OC2VZN2X.js +62 -0
- package/chunks/pipeline-BZK5BLJR.js +14 -0
- package/chunks/pre-recon-cve-66EB6G4M.js +351 -0
- package/chunks/prepare-MYY743TK.js +13 -0
- package/chunks/process-LQRCS5OY.js +13 -0
- package/chunks/replay-runner-45G3VU7X.js +39 -0
- package/chunks/run-D56MPDKY.js +32716 -0
- package/chunks/runtime-G7YWIO3B.js +43 -0
- package/chunks/runtime-J7PLXZNM.js +12 -0
- package/chunks/scan-stream-FHI2FYZE.js +96 -0
- package/chunks/scope-BI7BF4ZY.js +18 -0
- package/chunks/session-store-GZ4A4KYT.js +28 -0
- package/chunks/source-files-PWRQR6LY.js +12 -0
- package/chunks/specdrift-WCLH6TTQ.js +19 -0
- package/chunks/variant-candidates-YERCYMPD.js +13 -0
- package/chunks/web-recon-prepass-JJGPEOLE.js +1148 -0
- package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
- package/dashboard/assets/bot-yILPnRgD.js +1 -0
- package/dashboard/assets/chevron-down-BeDJ-Vka.js +1 -0
- package/dashboard/assets/circle-alert-BEnMBMT-.js +1 -0
- package/dashboard/assets/client-DRYsuOGl.js +9 -0
- package/dashboard/assets/copy-Bi5vzoF9.js +1 -0
- package/dashboard/assets/desktop-CXTQonNm.js +32 -0
- package/dashboard/assets/desktop-NOekk4QS.css +1 -0
- package/dashboard/assets/dist-CmX-h7a4.js +1 -0
- package/dashboard/assets/dist-DAwYQmf_.js +45 -0
- package/dashboard/assets/findings-page-CACW9nw2.js +5 -0
- package/dashboard/assets/format-PoISqsES.js +1 -0
- package/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/dashboard/assets/jsx-runtime-C7oxC63R.js +1 -0
- package/dashboard/assets/live-page-DrgQqQK5.js +1 -0
- package/dashboard/assets/meta-tile-CcskIV_o.js +1 -0
- package/dashboard/assets/operations-BKE8T-vY.css +2 -0
- package/dashboard/assets/operations-D8nUP5_m.js +4 -0
- package/dashboard/assets/operations-app-CQQhG54n.js +2 -0
- package/dashboard/assets/overview-page-DTGS8dPo.js +1 -0
- package/dashboard/assets/page-header-MFVvEtZW.js +1 -0
- package/dashboard/assets/play-Dwo-9xt6.js +1 -0
- package/dashboard/assets/scans-page-ChVtq8Us.js +1 -0
- package/dashboard/assets/search-CiooTLck.js +1 -0
- package/dashboard/assets/siren-CCBwTSuk.js +1 -0
- package/dashboard/assets/table-BYS-nIVA.js +1 -0
- package/dashboard/assets/tabs-DivxbhQy.js +1 -0
- package/dashboard/desktop.html +21 -0
- package/dashboard/index.html +15 -0
- package/package.json +30 -18
- package/bin/0sec.cjs +0 -361
|
@@ -0,0 +1,2439 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __0secCreateRequire } from "node:module";
|
|
3
|
+
const require = __0secCreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
ANALYTICS_LEVEL_ENV,
|
|
6
|
+
analyticsPipeline,
|
|
7
|
+
levelAtLeast,
|
|
8
|
+
resolveAnalyticsLevel
|
|
9
|
+
} from "./chunk-BZNEY2ZS.js";
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
|
|
12
|
+
createPresentationEvent,
|
|
13
|
+
homeStateDir
|
|
14
|
+
} from "./chunk-6L26OBWI.js";
|
|
15
|
+
|
|
16
|
+
// packages/cli/src/tui/settings-store.ts
|
|
17
|
+
import { useSyncExternalStore } from "react";
|
|
18
|
+
|
|
19
|
+
// packages/cli/src/tui/settings.ts
|
|
20
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
21
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
22
|
+
|
|
23
|
+
// packages/cli/src/tui/keybindings.ts
|
|
24
|
+
var KEYBINDINGS = [
|
|
25
|
+
// ── Composer ──────────────────────────────────────────────────────────────
|
|
26
|
+
{
|
|
27
|
+
id: "composer.send",
|
|
28
|
+
keys: "Enter",
|
|
29
|
+
defaultChords: ["return"],
|
|
30
|
+
rebindable: false,
|
|
31
|
+
description: "Send the message or run the highlighted slash command; queues the line when a turn is already in flight.",
|
|
32
|
+
category: "Composer",
|
|
33
|
+
lockReason: "text entry",
|
|
34
|
+
handler: 'if (key.name === "return") { \u2026 send / queue / dequeue }'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "composer.newline",
|
|
38
|
+
keys: "Shift+Enter",
|
|
39
|
+
defaultChords: ["shift+return"],
|
|
40
|
+
rebindable: false,
|
|
41
|
+
description: "Insert a newline instead of sending (terminals without the kitty protocol fall through to send).",
|
|
42
|
+
category: "Composer",
|
|
43
|
+
lockReason: "text entry",
|
|
44
|
+
handler: 'if (key.name === "return" && key.shift) setComposerText(`${\u2026}\\n`)'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "composer.complete-command",
|
|
48
|
+
keys: "Tab",
|
|
49
|
+
defaultChords: ["tab"],
|
|
50
|
+
rebindable: false,
|
|
51
|
+
description: "Complete the highlighted slash command in the command menu.",
|
|
52
|
+
category: "Composer",
|
|
53
|
+
lockReason: "text entry",
|
|
54
|
+
handler: 'if (key.name === "tab") setComposerText(completionFor(selectedSlashCommand, \u2026))'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "composer.edit-queued",
|
|
58
|
+
keys: "Ctrl+Y",
|
|
59
|
+
defaultChords: ["ctrl+y"],
|
|
60
|
+
rebindable: true,
|
|
61
|
+
description: "Pull the most recently queued message back into the composer to edit, re-send, or drop.",
|
|
62
|
+
category: "Composer",
|
|
63
|
+
handler: 'if (matchesBinding(key, "composer.edit-queued", \u2026) && queuedRef.current.length > 0) \u2026'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "composer.delete-word",
|
|
67
|
+
keys: "Ctrl+W / Alt+Backspace",
|
|
68
|
+
defaultChords: ["ctrl+w", "option+backspace"],
|
|
69
|
+
rebindable: false,
|
|
70
|
+
description: "Delete the word before the cursor.",
|
|
71
|
+
category: "Composer",
|
|
72
|
+
lockReason: "text entry",
|
|
73
|
+
handler: 'if (key.ctrl && key.name === "w") / if (key.name === "backspace" && (key.meta || key.option || key.ctrl)) \u2192 deletePreviousWord'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "composer.delete-to-start",
|
|
77
|
+
keys: "Ctrl+U",
|
|
78
|
+
defaultChords: ["ctrl+u"],
|
|
79
|
+
rebindable: false,
|
|
80
|
+
description: "Delete from the cursor to the start of the line.",
|
|
81
|
+
category: "Composer",
|
|
82
|
+
lockReason: "text entry",
|
|
83
|
+
handler: 'if (key.ctrl && key.name === "u") \u2192 deleteToLineStart'
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "composer.accept-suggestion",
|
|
87
|
+
keys: "Right",
|
|
88
|
+
defaultChords: ["right"],
|
|
89
|
+
rebindable: false,
|
|
90
|
+
description: "Accept the inline autosuggestion, filling the ghost suffix into the draft without sending.",
|
|
91
|
+
category: "Composer",
|
|
92
|
+
lockReason: "text entry",
|
|
93
|
+
handler: 'if (key.name === "right") { const suffix = suggestCompletion(...); if (suffix) setComposerText(`${\u2026}${suffix}`) }'
|
|
94
|
+
},
|
|
95
|
+
// ── Navigation ──────────────────────────────────────────────────────────────
|
|
96
|
+
{
|
|
97
|
+
id: "nav.palette",
|
|
98
|
+
keys: "Ctrl+P / Ctrl+K",
|
|
99
|
+
defaultChords: ["ctrl+p", "ctrl+k"],
|
|
100
|
+
rebindable: true,
|
|
101
|
+
description: "Open the slash-command palette.",
|
|
102
|
+
category: "Navigation",
|
|
103
|
+
handler: 'if (matchesBinding(key, "nav.palette", \u2026)) setComposerText("/")'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "nav.history-prev",
|
|
107
|
+
keys: "Up",
|
|
108
|
+
defaultChords: ["up"],
|
|
109
|
+
rebindable: false,
|
|
110
|
+
description: "Recall the previous submitted message into the composer (also moves the selection in menus and overlays).",
|
|
111
|
+
category: "Navigation",
|
|
112
|
+
lockReason: "modal",
|
|
113
|
+
handler: 'if (key.name === "up") recallComposerHistory("up")'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "nav.history-next",
|
|
117
|
+
keys: "Down",
|
|
118
|
+
defaultChords: ["down"],
|
|
119
|
+
rebindable: false,
|
|
120
|
+
description: "Recall the next message, or \u2014 on an empty composer with workers running \u2014 drop into the active-subagents list.",
|
|
121
|
+
category: "Navigation",
|
|
122
|
+
lockReason: "modal",
|
|
123
|
+
handler: 'if (key.name === "down") { setAgentNavIndex(0) | recallComposerHistory("down") }'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "nav.jump-agents",
|
|
127
|
+
keys: "Ctrl+G",
|
|
128
|
+
defaultChords: ["ctrl+g"],
|
|
129
|
+
rebindable: true,
|
|
130
|
+
description: "Drop straight into the active-subagents list from the composer (a direct chord complementing Down).",
|
|
131
|
+
category: "Navigation",
|
|
132
|
+
handler: 'if (matchesBinding(key, "nav.jump-agents", \u2026) && workerRoster.length) setAgentNavIndex(0)'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: "nav.open-comms",
|
|
136
|
+
keys: "Ctrl+T",
|
|
137
|
+
defaultChords: ["ctrl+t"],
|
|
138
|
+
rebindable: true,
|
|
139
|
+
description: "Open the agent comms view (the live sub-agent fleet) from chat.",
|
|
140
|
+
category: "Navigation",
|
|
141
|
+
handler: 'if (matchesBinding(key, "nav.open-comms", \u2026)) onNavigate("comms")'
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "nav.escape",
|
|
145
|
+
keys: "Esc",
|
|
146
|
+
defaultChords: ["escape"],
|
|
147
|
+
rebindable: false,
|
|
148
|
+
description: "Step back one level: close the command menu, then clear the draft, then interrupt a running turn, then leave the screen.",
|
|
149
|
+
category: "Navigation",
|
|
150
|
+
lockReason: "modal",
|
|
151
|
+
handler: 'if (key.name === "escape") { \u2026 interruptTurn() \u2026 onGoBack() }'
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: "nav.scroll-up",
|
|
155
|
+
keys: "PageUp / Ctrl+Up",
|
|
156
|
+
defaultChords: ["pageup", "ctrl+up"],
|
|
157
|
+
rebindable: true,
|
|
158
|
+
description: "Scroll the transcript up by half a viewport.",
|
|
159
|
+
category: "Navigation",
|
|
160
|
+
handler: 'if (matchesBinding(key, "nav.scroll-up", \u2026)) transcriptRef.current?.scrollBy(-0.5, "viewport")'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "nav.scroll-down",
|
|
164
|
+
keys: "PageDown / Ctrl+Down",
|
|
165
|
+
defaultChords: ["pagedown", "ctrl+down"],
|
|
166
|
+
rebindable: true,
|
|
167
|
+
description: "Scroll the transcript down by half a viewport.",
|
|
168
|
+
category: "Navigation",
|
|
169
|
+
handler: 'if (matchesBinding(key, "nav.scroll-down", \u2026)) transcriptRef.current?.scrollBy(0.5, "viewport")'
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "nav.focus-subagent",
|
|
173
|
+
keys: "Enter",
|
|
174
|
+
defaultChords: ["return"],
|
|
175
|
+
rebindable: false,
|
|
176
|
+
description: "In the active-subagents list, drill into the highlighted subagent's live focus view.",
|
|
177
|
+
category: "Navigation",
|
|
178
|
+
lockReason: "modal",
|
|
179
|
+
handler: 'if (agentNavIndex >= 0) { if (key.name === "return") setFocusAgentId(agent.agent_id) }'
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "nav.leave-subagent",
|
|
183
|
+
keys: "Left / Esc",
|
|
184
|
+
defaultChords: ["left", "escape"],
|
|
185
|
+
rebindable: false,
|
|
186
|
+
description: "Return from the active-subagents list or a subagent focus view back to the composer.",
|
|
187
|
+
category: "Navigation",
|
|
188
|
+
lockReason: "modal",
|
|
189
|
+
handler: 'if (focusAgentId) / if (agentNavIndex >= 0) { if (key.name === "escape" || key.name === "left") \u2026 }'
|
|
190
|
+
},
|
|
191
|
+
// ── Session ─────────────────────────────────────────────────────────────────
|
|
192
|
+
{
|
|
193
|
+
id: "session.quit",
|
|
194
|
+
keys: "Ctrl+C",
|
|
195
|
+
defaultChords: ["ctrl+c"],
|
|
196
|
+
rebindable: false,
|
|
197
|
+
description: "Press twice within 3 seconds to quit; the first press arms and warns.",
|
|
198
|
+
category: "Session",
|
|
199
|
+
lockReason: "quit",
|
|
200
|
+
handler: 'if (key.ctrl && key.name === "c") requestExitRef.current()'
|
|
201
|
+
},
|
|
202
|
+
// ── View ────────────────────────────────────────────────────────────────────
|
|
203
|
+
// The only rebindable set: self-contained, stateless toggles that call
|
|
204
|
+
// `updateSetting(...)` and take no part in text entry or modal cycles.
|
|
205
|
+
{
|
|
206
|
+
id: "view.left-sidebar",
|
|
207
|
+
keys: "Ctrl+B",
|
|
208
|
+
defaultChords: ["ctrl+b"],
|
|
209
|
+
rebindable: true,
|
|
210
|
+
description: "Toggle the left sidebar (persists across the session).",
|
|
211
|
+
category: "View",
|
|
212
|
+
handler: 'if (matchesBinding(key, "view.left-sidebar", \u2026)) updateSetting("showLeftSidebar", \u2026)'
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: "view.right-sidebar",
|
|
216
|
+
keys: "Ctrl+L",
|
|
217
|
+
defaultChords: ["ctrl+l"],
|
|
218
|
+
rebindable: true,
|
|
219
|
+
description: "Toggle the right sidebar (persists across the session).",
|
|
220
|
+
category: "View",
|
|
221
|
+
handler: 'if (matchesBinding(key, "view.right-sidebar", \u2026)) updateSetting("showRightSidebar", \u2026)'
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: "view.transcript-detail",
|
|
225
|
+
keys: "Ctrl+R",
|
|
226
|
+
defaultChords: ["ctrl+r"],
|
|
227
|
+
rebindable: true,
|
|
228
|
+
description: "Toggle the whole transcript between collapsed and expanded tool/reasoning detail (persists across the session).",
|
|
229
|
+
category: "View",
|
|
230
|
+
handler: 'if (matchesBinding(key, "view.transcript-detail", \u2026)) updateSetting("transcriptDetail", \u2026)'
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: "overlay.review-toggle",
|
|
234
|
+
keys: "Ctrl+O",
|
|
235
|
+
defaultChords: ["ctrl+o"],
|
|
236
|
+
rebindable: true,
|
|
237
|
+
description: "Open (and close) the full-screen transcript-review overlay.",
|
|
238
|
+
category: "View",
|
|
239
|
+
handler: 'if (matchesBinding(key, "overlay.review-toggle", \u2026)) setReviewOpen(true) / (reviewOpen) close'
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "overlay.review-top",
|
|
243
|
+
keys: "Ctrl+Home",
|
|
244
|
+
defaultChords: ["ctrl+home"],
|
|
245
|
+
rebindable: false,
|
|
246
|
+
description: "Jump to the top of the transcript-review overlay.",
|
|
247
|
+
category: "View",
|
|
248
|
+
lockReason: "modal",
|
|
249
|
+
handler: 'if (reviewOpen && key.ctrl && key.name === "home") review.scrollY = 0'
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: "overlay.review-bottom",
|
|
253
|
+
keys: "Ctrl+End",
|
|
254
|
+
defaultChords: ["ctrl+end"],
|
|
255
|
+
rebindable: false,
|
|
256
|
+
description: "Jump to the bottom of the transcript-review overlay.",
|
|
257
|
+
category: "View",
|
|
258
|
+
lockReason: "modal",
|
|
259
|
+
handler: 'if (reviewOpen && key.ctrl && key.name === "end") review.scrollY = review.maxScrollY'
|
|
260
|
+
},
|
|
261
|
+
// ── Autonomy ─────────────────────────────────────────────────────────────────
|
|
262
|
+
{
|
|
263
|
+
id: "autonomy.cycle-mode",
|
|
264
|
+
keys: "Shift+Tab",
|
|
265
|
+
defaultChords: ["shift+tab"],
|
|
266
|
+
rebindable: false,
|
|
267
|
+
description: "Cycle the autonomy mode: Standard \u2192 Co-pilot \u2192 YOLO \u2192 Recon; no preconfigured scope is required.",
|
|
268
|
+
category: "Autonomy",
|
|
269
|
+
lockReason: "mode-cycle",
|
|
270
|
+
handler: "if (isAutonomyCycleKey(key)) routeSlashCommand(`/mode ${next}`)"
|
|
271
|
+
}
|
|
272
|
+
];
|
|
273
|
+
var KEYBINDING_CATEGORIES = [
|
|
274
|
+
"Composer",
|
|
275
|
+
"Navigation",
|
|
276
|
+
"Session",
|
|
277
|
+
"View",
|
|
278
|
+
"Autonomy"
|
|
279
|
+
];
|
|
280
|
+
function keybindingsByCategory(bindings = KEYBINDINGS) {
|
|
281
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
282
|
+
const push = (binding) => {
|
|
283
|
+
const existing = grouped.get(binding.category);
|
|
284
|
+
if (existing) existing.push(binding);
|
|
285
|
+
else grouped.set(binding.category, [binding]);
|
|
286
|
+
};
|
|
287
|
+
for (const category of KEYBINDING_CATEGORIES) {
|
|
288
|
+
for (const binding of bindings) {
|
|
289
|
+
if (binding.category === category) push(binding);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
for (const binding of bindings) {
|
|
293
|
+
if (!KEYBINDING_CATEGORIES.includes(binding.category)) push(binding);
|
|
294
|
+
}
|
|
295
|
+
return grouped;
|
|
296
|
+
}
|
|
297
|
+
var MODIFIER_ALIASES = {
|
|
298
|
+
ctrl: "ctrl",
|
|
299
|
+
control: "ctrl",
|
|
300
|
+
ctl: "ctrl",
|
|
301
|
+
shift: "shift",
|
|
302
|
+
meta: "meta",
|
|
303
|
+
cmd: "meta",
|
|
304
|
+
command: "meta",
|
|
305
|
+
super: "meta",
|
|
306
|
+
win: "meta",
|
|
307
|
+
option: "option",
|
|
308
|
+
opt: "option",
|
|
309
|
+
alt: "option"
|
|
310
|
+
};
|
|
311
|
+
var NAME_ALIASES = {
|
|
312
|
+
esc: "escape",
|
|
313
|
+
escape: "escape",
|
|
314
|
+
enter: "return",
|
|
315
|
+
return: "return",
|
|
316
|
+
ret: "return",
|
|
317
|
+
pgup: "pageup",
|
|
318
|
+
pageup: "pageup",
|
|
319
|
+
pgdn: "pagedown",
|
|
320
|
+
pgdown: "pagedown",
|
|
321
|
+
pagedown: "pagedown",
|
|
322
|
+
del: "delete",
|
|
323
|
+
ins: "insert",
|
|
324
|
+
space: "space",
|
|
325
|
+
spacebar: "space"
|
|
326
|
+
};
|
|
327
|
+
function formatChord(chord) {
|
|
328
|
+
const parts = [];
|
|
329
|
+
if (chord.ctrl) parts.push("ctrl");
|
|
330
|
+
if (chord.shift) parts.push("shift");
|
|
331
|
+
if (chord.meta) parts.push("meta");
|
|
332
|
+
if (chord.option) parts.push("option");
|
|
333
|
+
parts.push(chord.name);
|
|
334
|
+
return parts.join("+");
|
|
335
|
+
}
|
|
336
|
+
function parseChord(input) {
|
|
337
|
+
if (typeof input !== "string") return null;
|
|
338
|
+
const tokens = input.split("+").map((token) => token.trim().toLowerCase()).filter((token) => token.length > 0);
|
|
339
|
+
if (tokens.length === 0) return null;
|
|
340
|
+
let ctrl = false;
|
|
341
|
+
let shift = false;
|
|
342
|
+
let meta = false;
|
|
343
|
+
let option = false;
|
|
344
|
+
let name;
|
|
345
|
+
for (const token of tokens) {
|
|
346
|
+
const modifier = MODIFIER_ALIASES[token];
|
|
347
|
+
if (modifier) {
|
|
348
|
+
if (modifier === "ctrl") ctrl = true;
|
|
349
|
+
else if (modifier === "shift") shift = true;
|
|
350
|
+
else if (modifier === "meta") meta = true;
|
|
351
|
+
else option = true;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (name !== void 0) return null;
|
|
355
|
+
if (/\s/.test(token)) return null;
|
|
356
|
+
name = NAME_ALIASES[token] ?? token;
|
|
357
|
+
}
|
|
358
|
+
if (name === void 0 || name.length === 0) return null;
|
|
359
|
+
return { ctrl, shift, meta, option, name };
|
|
360
|
+
}
|
|
361
|
+
function chordFromKey(key) {
|
|
362
|
+
const name = typeof key.name === "string" ? key.name.toLowerCase() : "";
|
|
363
|
+
if (name.length === 0) return null;
|
|
364
|
+
return {
|
|
365
|
+
ctrl: Boolean(key.ctrl),
|
|
366
|
+
shift: Boolean(key.shift),
|
|
367
|
+
meta: Boolean(key.meta),
|
|
368
|
+
option: Boolean(key.option),
|
|
369
|
+
name
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function keyMatchesChord(key, chord) {
|
|
373
|
+
if (typeof chord !== "string" || chord.length === 0) return false;
|
|
374
|
+
const parsed = parseChord(chord);
|
|
375
|
+
if (!parsed) return false;
|
|
376
|
+
const pressed = chordFromKey(key);
|
|
377
|
+
if (!pressed) return false;
|
|
378
|
+
return formatChord(pressed) === formatChord(parsed);
|
|
379
|
+
}
|
|
380
|
+
function isAssignableChord(chord) {
|
|
381
|
+
if (!chord || chord.name.length === 0) return false;
|
|
382
|
+
return chord.ctrl || chord.meta || chord.option;
|
|
383
|
+
}
|
|
384
|
+
var REBINDABLE_IDS = new Set(
|
|
385
|
+
KEYBINDINGS.filter((binding) => binding.rebindable).map((binding) => binding.id)
|
|
386
|
+
);
|
|
387
|
+
function effectiveChords(binding, overrides) {
|
|
388
|
+
if (binding.rebindable && overrides) {
|
|
389
|
+
const raw = overrides[binding.id];
|
|
390
|
+
const parsed = parseChord(raw);
|
|
391
|
+
if (parsed && isAssignableChord(parsed)) return [formatChord(parsed)];
|
|
392
|
+
}
|
|
393
|
+
return [...binding.defaultChords];
|
|
394
|
+
}
|
|
395
|
+
function reservedChords(bindings = KEYBINDINGS) {
|
|
396
|
+
const reserved = /* @__PURE__ */ new Set();
|
|
397
|
+
for (const binding of bindings) {
|
|
398
|
+
if (binding.rebindable) continue;
|
|
399
|
+
for (const chord of binding.defaultChords) reserved.add(chord);
|
|
400
|
+
}
|
|
401
|
+
return reserved;
|
|
402
|
+
}
|
|
403
|
+
function matchesBinding(key, id, overrides, bindings = KEYBINDINGS) {
|
|
404
|
+
const binding = bindings.find((entry) => entry.id === id);
|
|
405
|
+
if (!binding) return false;
|
|
406
|
+
const chord = chordFromKey(key);
|
|
407
|
+
if (!chord) return false;
|
|
408
|
+
return effectiveChords(binding, overrides).includes(formatChord(chord));
|
|
409
|
+
}
|
|
410
|
+
function reservedOwner(bindings, chord) {
|
|
411
|
+
return bindings.find((binding) => !binding.rebindable && binding.defaultChords.includes(chord));
|
|
412
|
+
}
|
|
413
|
+
function assessChordAssignment(id, key, overrides, bindings = KEYBINDINGS) {
|
|
414
|
+
const target = bindings.find((entry) => entry.id === id);
|
|
415
|
+
if (!target || !target.rebindable) return null;
|
|
416
|
+
const chord = chordFromKey(key);
|
|
417
|
+
if (!chord) return null;
|
|
418
|
+
const canonical = formatChord(chord);
|
|
419
|
+
if (!isAssignableChord(chord)) return { kind: "unassignable", chord: canonical };
|
|
420
|
+
const owner = reservedOwner(bindings, canonical);
|
|
421
|
+
if (owner) {
|
|
422
|
+
return { kind: "conflict", chord: canonical, conflictId: owner.id, conflictLabel: owner.description };
|
|
423
|
+
}
|
|
424
|
+
for (const binding of bindings) {
|
|
425
|
+
if (binding.id === id || !binding.rebindable) continue;
|
|
426
|
+
if (effectiveChords(binding, overrides).includes(canonical)) {
|
|
427
|
+
return { kind: "conflict", chord: canonical, conflictId: binding.id, conflictLabel: binding.description };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return { kind: "ok", chord: canonical };
|
|
431
|
+
}
|
|
432
|
+
function sanitizeKeybindingOverrides(raw, bindings = KEYBINDINGS) {
|
|
433
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
434
|
+
const reserved = reservedChords(bindings);
|
|
435
|
+
const candidates = {};
|
|
436
|
+
for (const [id, value] of Object.entries(raw)) {
|
|
437
|
+
if (!REBINDABLE_IDS.has(id)) continue;
|
|
438
|
+
const parsed = parseChord(value);
|
|
439
|
+
if (!parsed || !isAssignableChord(parsed)) continue;
|
|
440
|
+
const canonical = formatChord(parsed);
|
|
441
|
+
if (reserved.has(canonical)) continue;
|
|
442
|
+
candidates[id] = canonical;
|
|
443
|
+
}
|
|
444
|
+
const occupied = /* @__PURE__ */ new Map();
|
|
445
|
+
for (const binding of bindings) {
|
|
446
|
+
if (!binding.rebindable) continue;
|
|
447
|
+
for (const chord of effectiveChords(binding, candidates)) {
|
|
448
|
+
if (!chord) continue;
|
|
449
|
+
const existing = occupied.get(chord);
|
|
450
|
+
if (existing) {
|
|
451
|
+
if (!existing.includes(binding.id)) existing.push(binding.id);
|
|
452
|
+
} else occupied.set(chord, [binding.id]);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
for (const [, ids] of occupied) {
|
|
456
|
+
if (ids.length <= 1) continue;
|
|
457
|
+
for (const id of ids) delete candidates[id];
|
|
458
|
+
}
|
|
459
|
+
return candidates;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// packages/cli/src/tui/themes.ts
|
|
463
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
464
|
+
import { dirname, join as join2 } from "node:path";
|
|
465
|
+
|
|
466
|
+
// packages/cli/src/tui/user-themes.ts
|
|
467
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
468
|
+
import { join } from "node:path";
|
|
469
|
+
function coerceUserTheme(id, raw) {
|
|
470
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
471
|
+
return { ok: false, reason: "file is not a JSON object" };
|
|
472
|
+
}
|
|
473
|
+
const record = raw;
|
|
474
|
+
const issues = validateTheme(record.palette);
|
|
475
|
+
if (issues.length > 0) {
|
|
476
|
+
return { ok: false, reason: issues.map((i) => i.message).join("; ") };
|
|
477
|
+
}
|
|
478
|
+
const label = typeof record.label === "string" && record.label.length > 0 ? record.label : id;
|
|
479
|
+
const description = typeof record.description === "string" ? record.description : "User theme.";
|
|
480
|
+
const mode = record.mode === "light" ? "light" : "dark";
|
|
481
|
+
return {
|
|
482
|
+
ok: true,
|
|
483
|
+
entry: { name: id, label, description, mode, palette: record.palette }
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function loadUserThemes(dir) {
|
|
487
|
+
const valid = [];
|
|
488
|
+
const rejected = [];
|
|
489
|
+
let files;
|
|
490
|
+
try {
|
|
491
|
+
files = readdirSync(dir);
|
|
492
|
+
} catch {
|
|
493
|
+
return { valid, rejected };
|
|
494
|
+
}
|
|
495
|
+
for (const file of [...files].sort()) {
|
|
496
|
+
if (!file.endsWith(".json")) continue;
|
|
497
|
+
const id = file.slice(0, -".json".length);
|
|
498
|
+
if (!isSafeThemeId(id)) {
|
|
499
|
+
rejected.push({
|
|
500
|
+
name: id,
|
|
501
|
+
file,
|
|
502
|
+
reason: "invalid theme name \u2014 must be a lowercase dotted/hyphenated identifier of at most 64 characters, with no path separators"
|
|
503
|
+
});
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
if (isThemeName(id)) {
|
|
507
|
+
rejected.push({
|
|
508
|
+
name: id,
|
|
509
|
+
file,
|
|
510
|
+
reason: `"${id}" is a built-in theme name and cannot be overridden \u2014 rename the file`
|
|
511
|
+
});
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
let parsed;
|
|
515
|
+
try {
|
|
516
|
+
parsed = JSON.parse(readFileSync(join(dir, file), "utf8"));
|
|
517
|
+
} catch (err) {
|
|
518
|
+
rejected.push({
|
|
519
|
+
name: id,
|
|
520
|
+
file,
|
|
521
|
+
reason: `could not read or parse: ${err instanceof Error ? err.message : String(err)}`
|
|
522
|
+
});
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const result = coerceUserTheme(id, parsed);
|
|
526
|
+
if (result.ok) valid.push(result.entry);
|
|
527
|
+
else rejected.push({ name: id, file, reason: result.reason });
|
|
528
|
+
}
|
|
529
|
+
valid.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
530
|
+
return { valid, rejected };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// packages/cli/src/tui/themes.ts
|
|
534
|
+
var BACKGROUND_TOKENS = ["CANVAS", "PANEL", "PANEL_ALT"];
|
|
535
|
+
var TEXT_TOKENS = [
|
|
536
|
+
"TEXT",
|
|
537
|
+
"MUTED",
|
|
538
|
+
"PRIMARY",
|
|
539
|
+
"ACCENT",
|
|
540
|
+
"BRAND",
|
|
541
|
+
"SUCCESS",
|
|
542
|
+
"WARNING",
|
|
543
|
+
"ERROR",
|
|
544
|
+
"INFO"
|
|
545
|
+
];
|
|
546
|
+
var CHROME_TOKENS = ["BORDER"];
|
|
547
|
+
var LAYER_TOKENS = ["background", "surface", "surfaceAlt", "overlay"];
|
|
548
|
+
var SYNTAX_TOKENS = [
|
|
549
|
+
"syntaxKeyword",
|
|
550
|
+
"syntaxString",
|
|
551
|
+
"syntaxNumber",
|
|
552
|
+
"syntaxComment",
|
|
553
|
+
"syntaxFunction",
|
|
554
|
+
"syntaxType",
|
|
555
|
+
"syntaxVariable",
|
|
556
|
+
"syntaxOperator",
|
|
557
|
+
"syntaxPunctuation",
|
|
558
|
+
"DIFF_ADD",
|
|
559
|
+
"DIFF_DEL",
|
|
560
|
+
"LINK"
|
|
561
|
+
];
|
|
562
|
+
var THEME_TOKENS = [
|
|
563
|
+
...BACKGROUND_TOKENS,
|
|
564
|
+
...TEXT_TOKENS,
|
|
565
|
+
...CHROME_TOKENS,
|
|
566
|
+
...LAYER_TOKENS
|
|
567
|
+
];
|
|
568
|
+
function resolveSyntaxColors(theme) {
|
|
569
|
+
const t = theme;
|
|
570
|
+
return {
|
|
571
|
+
keyword: t.syntaxKeyword ?? theme.PRIMARY,
|
|
572
|
+
string: t.syntaxString ?? theme.SUCCESS,
|
|
573
|
+
number: t.syntaxNumber ?? theme.WARNING,
|
|
574
|
+
comment: t.syntaxComment ?? theme.MUTED,
|
|
575
|
+
function: t.syntaxFunction ?? theme.INFO,
|
|
576
|
+
type: t.syntaxType ?? theme.BRAND,
|
|
577
|
+
variable: t.syntaxVariable ?? theme.TEXT,
|
|
578
|
+
operator: t.syntaxOperator ?? theme.MUTED,
|
|
579
|
+
punctuation: t.syntaxPunctuation ?? theme.MUTED,
|
|
580
|
+
diffAdd: t.DIFF_ADD ?? theme.SUCCESS,
|
|
581
|
+
diffDel: t.DIFF_DEL ?? theme.ERROR,
|
|
582
|
+
link: t.LINK ?? theme.INFO
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
var HEX_RE = /^#[0-9a-fA-F]{6}$/;
|
|
586
|
+
function parseHex(hex) {
|
|
587
|
+
if (typeof hex !== "string" || !HEX_RE.test(hex)) return null;
|
|
588
|
+
return {
|
|
589
|
+
r: Number.parseInt(hex.slice(1, 3), 16),
|
|
590
|
+
g: Number.parseInt(hex.slice(3, 5), 16),
|
|
591
|
+
b: Number.parseInt(hex.slice(5, 7), 16)
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function isHexColor(value) {
|
|
595
|
+
return typeof value === "string" && HEX_RE.test(value);
|
|
596
|
+
}
|
|
597
|
+
function linearize(channel) {
|
|
598
|
+
const c = channel / 255;
|
|
599
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
600
|
+
}
|
|
601
|
+
function relativeLuminance(hex) {
|
|
602
|
+
const rgb = parseHex(hex);
|
|
603
|
+
if (!rgb) throw new TypeError(`relativeLuminance: not a #RRGGBB colour: ${String(hex)}`);
|
|
604
|
+
return 0.2126 * linearize(rgb.r) + 0.7152 * linearize(rgb.g) + 0.0722 * linearize(rgb.b);
|
|
605
|
+
}
|
|
606
|
+
function contrastRatio(a, b) {
|
|
607
|
+
const la = relativeLuminance(a);
|
|
608
|
+
const lb = relativeLuminance(b);
|
|
609
|
+
const hi = Math.max(la, lb);
|
|
610
|
+
const lo = Math.min(la, lb);
|
|
611
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
612
|
+
}
|
|
613
|
+
function readableOnPrimary(theme) {
|
|
614
|
+
return contrastRatio(theme.CANVAS, theme.PRIMARY) >= contrastRatio(theme.TEXT, theme.PRIMARY) ? theme.CANVAS : theme.TEXT;
|
|
615
|
+
}
|
|
616
|
+
var MIN_TEXT_CONTRAST = 4.5;
|
|
617
|
+
var MIN_CHROME_CONTRAST = 3;
|
|
618
|
+
var MIN_SEMANTIC_CONTRAST = 1.15;
|
|
619
|
+
var DARK = {
|
|
620
|
+
CANVAS: "#080808",
|
|
621
|
+
PANEL: "#2B2825",
|
|
622
|
+
PANEL_ALT: "#363129",
|
|
623
|
+
BORDER: "#302E2C",
|
|
624
|
+
TEXT: "#F3EEE9",
|
|
625
|
+
MUTED: "#B8AC9E",
|
|
626
|
+
PRIMARY: "#FFFFFF",
|
|
627
|
+
ACCENT: "#F3EEE9",
|
|
628
|
+
BRAND: "#A78BFA",
|
|
629
|
+
SUCCESS: "#22C55E",
|
|
630
|
+
WARNING: "#EAB308",
|
|
631
|
+
ERROR: "#F4695E",
|
|
632
|
+
INFO: "#B8AFA6",
|
|
633
|
+
background: "#080808",
|
|
634
|
+
surface: "#111111",
|
|
635
|
+
surfaceAlt: "#171515",
|
|
636
|
+
overlay: "#1C1A18"
|
|
637
|
+
};
|
|
638
|
+
var LIGHT = {
|
|
639
|
+
CANVAS: "#FCFCFD",
|
|
640
|
+
PANEL: "#EFF1F4",
|
|
641
|
+
PANEL_ALT: "#E2E5EA",
|
|
642
|
+
BORDER: "#7A828F",
|
|
643
|
+
TEXT: "#191C22",
|
|
644
|
+
MUTED: "#565D68",
|
|
645
|
+
PRIMARY: "#111318",
|
|
646
|
+
ACCENT: "#2E333B",
|
|
647
|
+
BRAND: "#6D28D9",
|
|
648
|
+
SUCCESS: "#136B31",
|
|
649
|
+
WARNING: "#654A00",
|
|
650
|
+
ERROR: "#74130B",
|
|
651
|
+
INFO: "#1F4E7A",
|
|
652
|
+
background: "#FCFCFD",
|
|
653
|
+
surface: "#EFF1F4",
|
|
654
|
+
surfaceAlt: "#E2E5EA",
|
|
655
|
+
overlay: "#D7DBE1"
|
|
656
|
+
};
|
|
657
|
+
var HIGH_CONTRAST = {
|
|
658
|
+
CANVAS: "#000000",
|
|
659
|
+
PANEL: "#0B0B0B",
|
|
660
|
+
PANEL_ALT: "#161616",
|
|
661
|
+
BORDER: "#B3B3B3",
|
|
662
|
+
TEXT: "#FFFFFF",
|
|
663
|
+
MUTED: "#BCBCBC",
|
|
664
|
+
PRIMARY: "#FFFFFF",
|
|
665
|
+
ACCENT: "#E6E6E6",
|
|
666
|
+
BRAND: "#C4B5FD",
|
|
667
|
+
SUCCESS: "#3AE483",
|
|
668
|
+
WARNING: "#FFE635",
|
|
669
|
+
ERROR: "#FC8979",
|
|
670
|
+
INFO: "#82DAFF",
|
|
671
|
+
background: "#000000",
|
|
672
|
+
surface: "#0B0B0B",
|
|
673
|
+
surfaceAlt: "#161616",
|
|
674
|
+
overlay: "#1E1E1E"
|
|
675
|
+
};
|
|
676
|
+
var ANSI = {
|
|
677
|
+
CANVAS: "#000000",
|
|
678
|
+
PANEL: "#000000",
|
|
679
|
+
PANEL_ALT: "#000000",
|
|
680
|
+
BORDER: "#808080",
|
|
681
|
+
TEXT: "#FFFFFF",
|
|
682
|
+
MUTED: "#808080",
|
|
683
|
+
PRIMARY: "#FFFFFF",
|
|
684
|
+
ACCENT: "#C0C0C0",
|
|
685
|
+
BRAND: "#FF00FF",
|
|
686
|
+
SUCCESS: "#00FF00",
|
|
687
|
+
WARNING: "#FFFF00",
|
|
688
|
+
ERROR: "#FF0000",
|
|
689
|
+
INFO: "#C0C0C0",
|
|
690
|
+
background: "#000000",
|
|
691
|
+
surface: "#000000",
|
|
692
|
+
surfaceAlt: "#000000",
|
|
693
|
+
overlay: "#000000"
|
|
694
|
+
};
|
|
695
|
+
var MIDNIGHT = {
|
|
696
|
+
CANVAS: "#0A0E14",
|
|
697
|
+
PANEL: "#121824",
|
|
698
|
+
PANEL_ALT: "#1B2333",
|
|
699
|
+
BORDER: "#6B7688",
|
|
700
|
+
TEXT: "#E8ECF2",
|
|
701
|
+
MUTED: "#93A0B5",
|
|
702
|
+
PRIMARY: "#FFFFFF",
|
|
703
|
+
ACCENT: "#DDE3EC",
|
|
704
|
+
BRAND: "#A78BFA",
|
|
705
|
+
SUCCESS: "#57BE86",
|
|
706
|
+
WARNING: "#E2C14E",
|
|
707
|
+
ERROR: "#F0686C",
|
|
708
|
+
INFO: "#8FB4E0",
|
|
709
|
+
background: "#0A0E14",
|
|
710
|
+
surface: "#121824",
|
|
711
|
+
surfaceAlt: "#1B2333",
|
|
712
|
+
overlay: "#232D40"
|
|
713
|
+
};
|
|
714
|
+
var SLATE = {
|
|
715
|
+
CANVAS: "#141516",
|
|
716
|
+
PANEL: "#1D1F21",
|
|
717
|
+
PANEL_ALT: "#26292C",
|
|
718
|
+
BORDER: "#6E7379",
|
|
719
|
+
TEXT: "#E9EAEC",
|
|
720
|
+
MUTED: "#9A9EA4",
|
|
721
|
+
PRIMARY: "#FFFFFF",
|
|
722
|
+
ACCENT: "#D8DADD",
|
|
723
|
+
BRAND: "#A78BFA",
|
|
724
|
+
SUCCESS: "#5BC088",
|
|
725
|
+
WARNING: "#DEBB4E",
|
|
726
|
+
ERROR: "#EB767A",
|
|
727
|
+
INFO: "#9FB3C8",
|
|
728
|
+
background: "#141516",
|
|
729
|
+
surface: "#1D1F21",
|
|
730
|
+
surfaceAlt: "#26292C",
|
|
731
|
+
overlay: "#303438",
|
|
732
|
+
// Bespoke syntax palette — a real 9-hue code palette so highlighted code and
|
|
733
|
+
// diffs are polychrome even though the chrome stays a restrained grey. Every
|
|
734
|
+
// value is a light-band hue that reads clearly on the grey surfaces; none is
|
|
735
|
+
// the ERROR red (reserved for failures). DIFF_ADD/DIFF_DEL mirror
|
|
736
|
+
// SUCCESS/ERROR so diff semantics stay consistent with the rest of the TUI.
|
|
737
|
+
syntaxKeyword: "#7AA2F7",
|
|
738
|
+
syntaxString: "#9ECE6A",
|
|
739
|
+
syntaxNumber: "#E0AF68",
|
|
740
|
+
syntaxComment: "#7C828C",
|
|
741
|
+
syntaxFunction: "#E5C07B",
|
|
742
|
+
syntaxType: "#4EC9B0",
|
|
743
|
+
syntaxVariable: "#D4D8DE",
|
|
744
|
+
syntaxOperator: "#ABB2BF",
|
|
745
|
+
syntaxPunctuation: "#ABB2BF",
|
|
746
|
+
DIFF_ADD: "#5BC088",
|
|
747
|
+
DIFF_DEL: "#EB767A",
|
|
748
|
+
LINK: "#7AA2F7"
|
|
749
|
+
};
|
|
750
|
+
var PAPER = {
|
|
751
|
+
CANVAS: "#FBF7EF",
|
|
752
|
+
PANEL: "#F1EADC",
|
|
753
|
+
PANEL_ALT: "#EAE1CF",
|
|
754
|
+
BORDER: "#7C7158",
|
|
755
|
+
TEXT: "#201C15",
|
|
756
|
+
MUTED: "#5A5140",
|
|
757
|
+
PRIMARY: "#1A1712",
|
|
758
|
+
ACCENT: "#3A342A",
|
|
759
|
+
BRAND: "#5B2A86",
|
|
760
|
+
SUCCESS: "#155E2C",
|
|
761
|
+
WARNING: "#78560E",
|
|
762
|
+
ERROR: "#8A2015",
|
|
763
|
+
INFO: "#2A5578",
|
|
764
|
+
background: "#FBF7EF",
|
|
765
|
+
surface: "#F1EADC",
|
|
766
|
+
surfaceAlt: "#EAE1CF",
|
|
767
|
+
overlay: "#DACFB8"
|
|
768
|
+
};
|
|
769
|
+
var MONO_DIM = {
|
|
770
|
+
CANVAS: "#161616",
|
|
771
|
+
PANEL: "#1E1E1E",
|
|
772
|
+
PANEL_ALT: "#262626",
|
|
773
|
+
BORDER: "#787878",
|
|
774
|
+
TEXT: "#C9C9C9",
|
|
775
|
+
MUTED: "#8C8C8C",
|
|
776
|
+
PRIMARY: "#E4E4E4",
|
|
777
|
+
ACCENT: "#B8B8B8",
|
|
778
|
+
BRAND: "#A99BC9",
|
|
779
|
+
SUCCESS: "#63AE7B",
|
|
780
|
+
WARNING: "#C9B25E",
|
|
781
|
+
ERROR: "#D47878",
|
|
782
|
+
INFO: "#93A7BC",
|
|
783
|
+
background: "#161616",
|
|
784
|
+
surface: "#1E1E1E",
|
|
785
|
+
surfaceAlt: "#262626",
|
|
786
|
+
overlay: "#2E2E2E"
|
|
787
|
+
};
|
|
788
|
+
var SWISS = {
|
|
789
|
+
CANVAS: "#120809",
|
|
790
|
+
PANEL: "#1C0C0E",
|
|
791
|
+
PANEL_ALT: "#260F12",
|
|
792
|
+
BORDER: "#D9534C",
|
|
793
|
+
TEXT: "#FBF3F3",
|
|
794
|
+
MUTED: "#D69B9E",
|
|
795
|
+
PRIMARY: "#F26257",
|
|
796
|
+
ACCENT: "#F58379",
|
|
797
|
+
BRAND: "#F26257",
|
|
798
|
+
SUCCESS: "#4FB477",
|
|
799
|
+
WARNING: "#E0A83C",
|
|
800
|
+
ERROR: "#F26257",
|
|
801
|
+
INFO: "#8FB4E0",
|
|
802
|
+
background: "#120809",
|
|
803
|
+
surface: "#1C0C0E",
|
|
804
|
+
surfaceAlt: "#260F12",
|
|
805
|
+
overlay: "#301418"
|
|
806
|
+
};
|
|
807
|
+
var GOLDEN = {
|
|
808
|
+
CANVAS: "#0A0A0A",
|
|
809
|
+
PANEL: "#141414",
|
|
810
|
+
PANEL_ALT: "#1E1E1E",
|
|
811
|
+
BORDER: "#6E6E6E",
|
|
812
|
+
TEXT: "#EEEEEE",
|
|
813
|
+
MUTED: "#9A9A9A",
|
|
814
|
+
PRIMARY: "#FAB283",
|
|
815
|
+
ACCENT: "#B48EE8",
|
|
816
|
+
BRAND: "#9D7CD8",
|
|
817
|
+
SUCCESS: "#7FD88F",
|
|
818
|
+
WARNING: "#F5A742",
|
|
819
|
+
ERROR: "#EB7A82",
|
|
820
|
+
INFO: "#56B6C2",
|
|
821
|
+
background: "#0A0A0A",
|
|
822
|
+
surface: "#141414",
|
|
823
|
+
surfaceAlt: "#1E1E1E",
|
|
824
|
+
overlay: "#282828",
|
|
825
|
+
// Polychrome syntax mirroring OpenCode's code hues (keyword=purple,
|
|
826
|
+
// function=tan, string=green, number=orange, type=gold, variable=red).
|
|
827
|
+
syntaxKeyword: "#B48EE8",
|
|
828
|
+
syntaxString: "#7FD88F",
|
|
829
|
+
syntaxNumber: "#F5A742",
|
|
830
|
+
syntaxComment: "#9A9A9A",
|
|
831
|
+
syntaxFunction: "#FAB283",
|
|
832
|
+
syntaxType: "#E5C07B",
|
|
833
|
+
syntaxVariable: "#EB7A82",
|
|
834
|
+
syntaxOperator: "#56B6C2",
|
|
835
|
+
syntaxPunctuation: "#9A9A9A",
|
|
836
|
+
DIFF_ADD: "#7FD88F",
|
|
837
|
+
DIFF_DEL: "#EB7A82",
|
|
838
|
+
LINK: "#7AA2F7"
|
|
839
|
+
};
|
|
840
|
+
var OH_MY_PI = {
|
|
841
|
+
CANVAS: "#151820",
|
|
842
|
+
PANEL: "#0F1216",
|
|
843
|
+
PANEL_ALT: "#1F252D",
|
|
844
|
+
BORDER: "#6B7280",
|
|
845
|
+
TEXT: "#E8ECF4",
|
|
846
|
+
MUTED: "#9CA3B0",
|
|
847
|
+
PRIMARY: "#00B4FF",
|
|
848
|
+
ACCENT: "#D4C090",
|
|
849
|
+
BRAND: "#00B4FF",
|
|
850
|
+
SUCCESS: "#00FF88",
|
|
851
|
+
WARNING: "#FFB347",
|
|
852
|
+
ERROR: "#FF6B78",
|
|
853
|
+
// OMP alertRed #FF4757 nudged up for AA on the lightest surface
|
|
854
|
+
INFO: "#00B4FF",
|
|
855
|
+
background: "#151820",
|
|
856
|
+
surface: "#0F1216",
|
|
857
|
+
surfaceAlt: "#1F252D",
|
|
858
|
+
overlay: "#2A3038",
|
|
859
|
+
// OMP titanium's syntax hues, verbatim.
|
|
860
|
+
syntaxKeyword: "#00B4FF",
|
|
861
|
+
syntaxString: "#D4C090",
|
|
862
|
+
syntaxNumber: "#FFB347",
|
|
863
|
+
syntaxComment: "#6B7280",
|
|
864
|
+
syntaxFunction: "#00FF88",
|
|
865
|
+
syntaxType: "#00B4FF",
|
|
866
|
+
syntaxVariable: "#E8ECF4",
|
|
867
|
+
syntaxOperator: "#00B4FF",
|
|
868
|
+
syntaxPunctuation: "#9CA3B0",
|
|
869
|
+
DIFF_ADD: "#00FF88",
|
|
870
|
+
DIFF_DEL: "#FF6B78",
|
|
871
|
+
LINK: "#00B4FF"
|
|
872
|
+
};
|
|
873
|
+
var OSEC_THEME = {
|
|
874
|
+
CANVAS: "#1A1613",
|
|
875
|
+
PANEL: "#26221D",
|
|
876
|
+
PANEL_ALT: "#35302A",
|
|
877
|
+
BORDER: "#8A7D6B",
|
|
878
|
+
TEXT: "#F5EEE4",
|
|
879
|
+
MUTED: "#B0A493",
|
|
880
|
+
PRIMARY: "#FD802E",
|
|
881
|
+
ACCENT: "#F2A24C",
|
|
882
|
+
BRAND: "#FD802E",
|
|
883
|
+
SUCCESS: "#69C489",
|
|
884
|
+
WARNING: "#E4BC46",
|
|
885
|
+
ERROR: "#F47B72",
|
|
886
|
+
// signature-adjacent coral; lifted from a deeper red so it clears AA on PANEL_ALT
|
|
887
|
+
INFO: "#4FBAC6",
|
|
888
|
+
background: "#1A1613",
|
|
889
|
+
surface: "#26221D",
|
|
890
|
+
surfaceAlt: "#35302A",
|
|
891
|
+
overlay: "#403D39",
|
|
892
|
+
// the #403D39 graphite anchor itself, as the topmost raised surface tier
|
|
893
|
+
// Code palette HARMONIZED to the 0sec warm palette (oh-my-pi dark-volcanic
|
|
894
|
+
// philosophy: draw the syntax hues from the theme's OWN colours rather than a
|
|
895
|
+
// foreign rainbow, so code reads as part of the brand, not confetti). A warm
|
|
896
|
+
// family carries the meaningful tokens — keyword=amber (ACCENT), function=soft
|
|
897
|
+
// gold, string=yellow (WARNING hue), number=green (SUCCESS) — with ONE cool
|
|
898
|
+
// teal (INFO) for types as the single contrast note; everything structural
|
|
899
|
+
// (variable/operator/punctuation/comment) recedes into warm greys so the eye
|
|
900
|
+
// lands on the meaning, not the punctuation. Every token clears AA on PANEL.
|
|
901
|
+
syntaxKeyword: "#F2A24C",
|
|
902
|
+
// amber (ACCENT) — warm, in-brand
|
|
903
|
+
syntaxFunction: "#F7C97D",
|
|
904
|
+
// soft gold, a step lighter than keyword
|
|
905
|
+
syntaxString: "#E4BC46",
|
|
906
|
+
// saturated yellow (WARNING hue)
|
|
907
|
+
syntaxNumber: "#69C489",
|
|
908
|
+
// green (SUCCESS) — the warm-family break
|
|
909
|
+
syntaxType: "#4FBAC6",
|
|
910
|
+
// teal (INFO) — the single cool accent
|
|
911
|
+
syntaxVariable: "#D8CBBA",
|
|
912
|
+
// warm cream — plain identifiers stay calm
|
|
913
|
+
syntaxComment: "#9E927F",
|
|
914
|
+
// dim warm grey — dimmer than MUTED
|
|
915
|
+
syntaxOperator: "#B0A493",
|
|
916
|
+
// MUTED — structure recedes
|
|
917
|
+
syntaxPunctuation: "#9E9384",
|
|
918
|
+
// a touch dimmer than operators
|
|
919
|
+
DIFF_ADD: "#69C489",
|
|
920
|
+
// number/string green
|
|
921
|
+
DIFF_DEL: "#F47B72",
|
|
922
|
+
// ERROR coral
|
|
923
|
+
LINK: "#4FBAC6"
|
|
924
|
+
// teal (INFO)
|
|
925
|
+
};
|
|
926
|
+
var THEME_NAMES = [
|
|
927
|
+
"0sec",
|
|
928
|
+
"midnight",
|
|
929
|
+
"dark",
|
|
930
|
+
"light",
|
|
931
|
+
"high-contrast",
|
|
932
|
+
"ansi",
|
|
933
|
+
"slate",
|
|
934
|
+
"paper",
|
|
935
|
+
"mono-dim",
|
|
936
|
+
"swiss",
|
|
937
|
+
"golden-gate",
|
|
938
|
+
"blue-team"
|
|
939
|
+
];
|
|
940
|
+
var THEME_ALIASES = {
|
|
941
|
+
opencode: "golden-gate",
|
|
942
|
+
golden: "golden-gate",
|
|
943
|
+
"oh-my-pi": "blue-team",
|
|
944
|
+
ember: "0sec"
|
|
945
|
+
};
|
|
946
|
+
var DEFAULT_THEME_NAME = "0sec";
|
|
947
|
+
var THEMES = {
|
|
948
|
+
"0sec": {
|
|
949
|
+
name: "0sec",
|
|
950
|
+
label: "0sec",
|
|
951
|
+
description: "The flagship 0sec look: signature orange on warm graphite, with an amber accent.",
|
|
952
|
+
mode: "dark",
|
|
953
|
+
palette: OSEC_THEME
|
|
954
|
+
},
|
|
955
|
+
dark: {
|
|
956
|
+
name: "dark",
|
|
957
|
+
label: "Carbon",
|
|
958
|
+
description: "Warm dark surfaces, neutral white highlight. The original shipped palette.",
|
|
959
|
+
mode: "dark",
|
|
960
|
+
palette: DARK
|
|
961
|
+
},
|
|
962
|
+
light: {
|
|
963
|
+
name: "light",
|
|
964
|
+
label: "Standard",
|
|
965
|
+
description: "Light theme with clearly stepped section surfaces. Neutral highlight.",
|
|
966
|
+
mode: "light",
|
|
967
|
+
palette: LIGHT
|
|
968
|
+
},
|
|
969
|
+
"high-contrast": {
|
|
970
|
+
name: "high-contrast",
|
|
971
|
+
label: "Contrast",
|
|
972
|
+
description: "Pure black canvas, every text colour at WCAG AAA (7:1) or better.",
|
|
973
|
+
mode: "dark",
|
|
974
|
+
palette: HIGH_CONTRAST
|
|
975
|
+
},
|
|
976
|
+
ansi: {
|
|
977
|
+
name: "ansi",
|
|
978
|
+
label: "ANSI 16",
|
|
979
|
+
description: "Only the 16 standard terminal colours. For terminals without truecolor.",
|
|
980
|
+
mode: "dark",
|
|
981
|
+
palette: ANSI
|
|
982
|
+
},
|
|
983
|
+
midnight: {
|
|
984
|
+
name: "midnight",
|
|
985
|
+
label: "Midnight",
|
|
986
|
+
description: "Deep blue-black dark. Cool stepped surfaces, white highlight.",
|
|
987
|
+
mode: "dark",
|
|
988
|
+
palette: MIDNIGHT
|
|
989
|
+
},
|
|
990
|
+
slate: {
|
|
991
|
+
name: "slate",
|
|
992
|
+
label: "Slate",
|
|
993
|
+
description: "Neutral grey dark. No hue in the chrome, white highlight.",
|
|
994
|
+
mode: "dark",
|
|
995
|
+
palette: SLATE
|
|
996
|
+
},
|
|
997
|
+
paper: {
|
|
998
|
+
name: "paper",
|
|
999
|
+
label: "Paper",
|
|
1000
|
+
description: "Warm off-white light. Stepped paper surfaces, warm near-black highlight.",
|
|
1001
|
+
mode: "light",
|
|
1002
|
+
palette: PAPER
|
|
1003
|
+
},
|
|
1004
|
+
"mono-dim": {
|
|
1005
|
+
name: "mono-dim",
|
|
1006
|
+
label: "Mono Dim",
|
|
1007
|
+
description: "Very low-contrast dark, minimalist. Muted but still AA-legible.",
|
|
1008
|
+
mode: "dark",
|
|
1009
|
+
palette: MONO_DIM
|
|
1010
|
+
},
|
|
1011
|
+
swiss: {
|
|
1012
|
+
name: "swiss",
|
|
1013
|
+
label: "Swiss",
|
|
1014
|
+
description: "Swiss-flag red on near-black, crisp white text. For the Swiss Applied AI & Cybersecurity Research Lab.",
|
|
1015
|
+
mode: "dark",
|
|
1016
|
+
palette: SWISS
|
|
1017
|
+
},
|
|
1018
|
+
"golden-gate": {
|
|
1019
|
+
name: "golden-gate",
|
|
1020
|
+
label: "Golden Gate",
|
|
1021
|
+
description: "Warm tan-and-gold highlights on near-black surfaces, with a purple accent.",
|
|
1022
|
+
mode: "dark",
|
|
1023
|
+
palette: GOLDEN
|
|
1024
|
+
},
|
|
1025
|
+
"blue-team": {
|
|
1026
|
+
name: "blue-team",
|
|
1027
|
+
label: "Blue Team",
|
|
1028
|
+
description: "Electric-blue on brushed-titanium near-black, gold accent. A port of oh-my-pi's titanium theme.",
|
|
1029
|
+
mode: "dark",
|
|
1030
|
+
palette: OH_MY_PI
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
function isThemeName(value) {
|
|
1034
|
+
return typeof value === "string" && THEME_NAMES.includes(value);
|
|
1035
|
+
}
|
|
1036
|
+
function getTheme(name) {
|
|
1037
|
+
return getThemeEntry(name).palette;
|
|
1038
|
+
}
|
|
1039
|
+
function getThemeEntry(name) {
|
|
1040
|
+
if (isThemeName(name)) return THEMES[name];
|
|
1041
|
+
if (typeof name === "string" && name in THEME_ALIASES) return THEMES[THEME_ALIASES[name]];
|
|
1042
|
+
if (typeof name === "string" && installedThemes !== null) {
|
|
1043
|
+
const installed = installedThemes.get(name);
|
|
1044
|
+
if (installed) return installed;
|
|
1045
|
+
}
|
|
1046
|
+
return THEMES[DEFAULT_THEME_NAME];
|
|
1047
|
+
}
|
|
1048
|
+
function severityToneFor(palette, severity) {
|
|
1049
|
+
switch (String(severity).toLowerCase()) {
|
|
1050
|
+
case "critical":
|
|
1051
|
+
case "high":
|
|
1052
|
+
return palette.ERROR;
|
|
1053
|
+
case "medium":
|
|
1054
|
+
return palette.WARNING;
|
|
1055
|
+
case "low":
|
|
1056
|
+
return palette.INFO;
|
|
1057
|
+
default:
|
|
1058
|
+
return palette.MUTED;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
var CONTRAST_WAIVERS = [
|
|
1062
|
+
{
|
|
1063
|
+
theme: "dark",
|
|
1064
|
+
token: "BORDER",
|
|
1065
|
+
against: "CANVAS",
|
|
1066
|
+
ratio: 1.48,
|
|
1067
|
+
reason: "Preserved default: #302E2C is an intentional hairline, well below even the 3:1 non-text bar. #9C948C reaches 3:1 on all three surfaces if visible panel edges are wanted."
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
theme: "dark",
|
|
1071
|
+
token: "BORDER",
|
|
1072
|
+
against: "PANEL",
|
|
1073
|
+
ratio: 1.08,
|
|
1074
|
+
reason: "Preserved default: see the CANVAS waiver for BORDER."
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
theme: "dark",
|
|
1078
|
+
token: "BORDER",
|
|
1079
|
+
against: "PANEL_ALT",
|
|
1080
|
+
ratio: 1.05,
|
|
1081
|
+
reason: "Preserved default: see the CANVAS waiver for BORDER."
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
theme: "dark",
|
|
1085
|
+
token: "ERROR",
|
|
1086
|
+
against: "PANEL_ALT",
|
|
1087
|
+
ratio: 4.32,
|
|
1088
|
+
reason: "Preserved default: the coral #F4695E clears AA on CANVAS/PANEL but lands at 3.96:1 on the lightest surface. #FF9B93 clears 4.5:1 there if a visible change is acceptable."
|
|
1089
|
+
}
|
|
1090
|
+
];
|
|
1091
|
+
function isWaived(theme, token, against) {
|
|
1092
|
+
return CONTRAST_WAIVERS.some(
|
|
1093
|
+
(w) => w.theme === theme && w.token === token && w.against === against
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
var SEMANTIC_PAIRS = [
|
|
1097
|
+
["SUCCESS", "WARNING"],
|
|
1098
|
+
["SUCCESS", "ERROR"],
|
|
1099
|
+
["WARNING", "ERROR"]
|
|
1100
|
+
];
|
|
1101
|
+
function validateTheme(palette, options = {}) {
|
|
1102
|
+
const issues = [];
|
|
1103
|
+
if (typeof palette !== "object" || palette === null || Array.isArray(palette)) {
|
|
1104
|
+
return [{ kind: "missing", token: "*", message: "palette is not an object" }];
|
|
1105
|
+
}
|
|
1106
|
+
const record = palette;
|
|
1107
|
+
for (const token of THEME_TOKENS) {
|
|
1108
|
+
if (!(token in record)) {
|
|
1109
|
+
issues.push({ kind: "missing", token, message: `missing token ${token}` });
|
|
1110
|
+
} else if (!isHexColor(record[token])) {
|
|
1111
|
+
issues.push({
|
|
1112
|
+
kind: "malformed",
|
|
1113
|
+
token,
|
|
1114
|
+
message: `token ${token} is not a #RRGGBB colour: ${JSON.stringify(record[token])}`
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
for (const token of SYNTAX_TOKENS) {
|
|
1119
|
+
if (token in record && !isHexColor(record[token])) {
|
|
1120
|
+
issues.push({
|
|
1121
|
+
kind: "malformed",
|
|
1122
|
+
token,
|
|
1123
|
+
message: `token ${token} is not a #RRGGBB colour: ${JSON.stringify(record[token])}`
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
for (const key of Object.keys(record)) {
|
|
1128
|
+
if (!THEME_TOKENS.includes(key) && !SYNTAX_TOKENS.includes(key)) {
|
|
1129
|
+
issues.push({ kind: "extra", token: key, message: `unknown token ${key}` });
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (issues.some((i) => i.kind === "missing" || i.kind === "malformed")) return issues;
|
|
1133
|
+
const theme = record;
|
|
1134
|
+
const minText = options.minTextContrast ?? MIN_TEXT_CONTRAST;
|
|
1135
|
+
const minChrome = options.minChromeContrast ?? MIN_CHROME_CONTRAST;
|
|
1136
|
+
const minSemantic = options.minSemanticContrast ?? MIN_SEMANTIC_CONTRAST;
|
|
1137
|
+
const checkAgainstBackgrounds = (token, required) => {
|
|
1138
|
+
for (const bg of BACKGROUND_TOKENS) {
|
|
1139
|
+
if (options.name && isWaived(options.name, token, bg)) continue;
|
|
1140
|
+
const ratio = contrastRatio(theme[token], theme[bg]);
|
|
1141
|
+
if (ratio < required) {
|
|
1142
|
+
issues.push({
|
|
1143
|
+
kind: "contrast",
|
|
1144
|
+
token,
|
|
1145
|
+
against: bg,
|
|
1146
|
+
ratio,
|
|
1147
|
+
required,
|
|
1148
|
+
message: `${token} on ${bg} is ${ratio.toFixed(2)}:1, below ${required}:1`
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
for (const token of TEXT_TOKENS) checkAgainstBackgrounds(token, minText);
|
|
1154
|
+
for (const token of CHROME_TOKENS) checkAgainstBackgrounds(token, minChrome);
|
|
1155
|
+
for (const [a, b] of SEMANTIC_PAIRS) {
|
|
1156
|
+
const ratio = contrastRatio(theme[a], theme[b]);
|
|
1157
|
+
if (ratio < minSemantic) {
|
|
1158
|
+
issues.push({
|
|
1159
|
+
kind: "semantic",
|
|
1160
|
+
token: `${a}/${b}`,
|
|
1161
|
+
ratio,
|
|
1162
|
+
required: minSemantic,
|
|
1163
|
+
message: `${a} and ${b} differ by only ${ratio.toFixed(3)}:1 in luminance (need ${minSemantic}:1) \u2014 indistinguishable without colour vision`
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return issues;
|
|
1168
|
+
}
|
|
1169
|
+
var ANSI_16 = [
|
|
1170
|
+
{ index: 0, name: "black", hex: "#000000" },
|
|
1171
|
+
{ index: 1, name: "red", hex: "#800000" },
|
|
1172
|
+
{ index: 2, name: "green", hex: "#008000" },
|
|
1173
|
+
{ index: 3, name: "yellow", hex: "#808000" },
|
|
1174
|
+
{ index: 4, name: "blue", hex: "#000080" },
|
|
1175
|
+
{ index: 5, name: "magenta", hex: "#800080" },
|
|
1176
|
+
{ index: 6, name: "cyan", hex: "#008080" },
|
|
1177
|
+
{ index: 7, name: "white", hex: "#C0C0C0" },
|
|
1178
|
+
{ index: 8, name: "bright black", hex: "#808080" },
|
|
1179
|
+
{ index: 9, name: "bright red", hex: "#FF0000" },
|
|
1180
|
+
{ index: 10, name: "bright green", hex: "#00FF00" },
|
|
1181
|
+
{ index: 11, name: "bright yellow", hex: "#FFFF00" },
|
|
1182
|
+
{ index: 12, name: "bright blue", hex: "#0000FF" },
|
|
1183
|
+
{ index: 13, name: "bright magenta", hex: "#FF00FF" },
|
|
1184
|
+
{ index: 14, name: "bright cyan", hex: "#00FFFF" },
|
|
1185
|
+
{ index: 15, name: "bright white", hex: "#FFFFFF" }
|
|
1186
|
+
];
|
|
1187
|
+
function redmeanDistance(a, b) {
|
|
1188
|
+
const rmean = (a.r + b.r) / 2;
|
|
1189
|
+
const dr = a.r - b.r;
|
|
1190
|
+
const dg = a.g - b.g;
|
|
1191
|
+
const db = a.b - b.b;
|
|
1192
|
+
return (2 + rmean / 256) * dr * dr + 4 * dg * dg + (2 + (255 - rmean) / 256) * db * db;
|
|
1193
|
+
}
|
|
1194
|
+
function nearestAnsi16(hex) {
|
|
1195
|
+
const rgb = parseHex(hex);
|
|
1196
|
+
if (!rgb) throw new TypeError(`nearestAnsi16: not a #RRGGBB colour: ${String(hex)}`);
|
|
1197
|
+
let best = 0;
|
|
1198
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
1199
|
+
for (const entry of ANSI_16) {
|
|
1200
|
+
const d = redmeanDistance(rgb, parseHex(entry.hex));
|
|
1201
|
+
if (d < bestDistance) {
|
|
1202
|
+
bestDistance = d;
|
|
1203
|
+
best = entry.index;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return best;
|
|
1207
|
+
}
|
|
1208
|
+
var CUBE_LEVELS = [0, 95, 135, 175, 215, 255];
|
|
1209
|
+
function ansi256Hex(index) {
|
|
1210
|
+
if (index < 16) return ANSI_16[index].hex;
|
|
1211
|
+
const toHex = (n) => n.toString(16).padStart(2, "0").toUpperCase();
|
|
1212
|
+
if (index < 232) {
|
|
1213
|
+
const n = index - 16;
|
|
1214
|
+
const r = CUBE_LEVELS[Math.floor(n / 36) % 6];
|
|
1215
|
+
const g = CUBE_LEVELS[Math.floor(n / 6) % 6];
|
|
1216
|
+
const b = CUBE_LEVELS[n % 6];
|
|
1217
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
1218
|
+
}
|
|
1219
|
+
const level = 8 + (index - 232) * 10;
|
|
1220
|
+
return `#${toHex(level)}${toHex(level)}${toHex(level)}`;
|
|
1221
|
+
}
|
|
1222
|
+
function nearestAnsi256(hex) {
|
|
1223
|
+
const rgb = parseHex(hex);
|
|
1224
|
+
if (!rgb) throw new TypeError(`nearestAnsi256: not a #RRGGBB colour: ${String(hex)}`);
|
|
1225
|
+
let best = 0;
|
|
1226
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
1227
|
+
for (let i = 0; i < 256; i += 1) {
|
|
1228
|
+
const d = redmeanDistance(rgb, parseHex(ansi256Hex(i)));
|
|
1229
|
+
if (d < bestDistance) {
|
|
1230
|
+
bestDistance = d;
|
|
1231
|
+
best = i;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return best;
|
|
1235
|
+
}
|
|
1236
|
+
function detectColorDepth(env = {}) {
|
|
1237
|
+
const term = (env.TERM ?? "").toLowerCase();
|
|
1238
|
+
if (typeof env.NO_COLOR === "string") return "none";
|
|
1239
|
+
if (term === "dumb") return "none";
|
|
1240
|
+
const force = env.FORCE_COLOR;
|
|
1241
|
+
if (typeof force === "string") {
|
|
1242
|
+
if (force === "0" || force === "false") return "none";
|
|
1243
|
+
if (force === "1") return "ansi16";
|
|
1244
|
+
if (force === "2") return "ansi256";
|
|
1245
|
+
if (force === "3" || force === "true" || force === "") return "truecolor";
|
|
1246
|
+
}
|
|
1247
|
+
const colorterm = (env.COLORTERM ?? "").toLowerCase();
|
|
1248
|
+
if (colorterm.includes("truecolor") || colorterm.includes("24bit")) return "truecolor";
|
|
1249
|
+
if (term.includes("truecolor") || term.includes("direct")) return "truecolor";
|
|
1250
|
+
if (term.includes("256")) return "ansi256";
|
|
1251
|
+
return "ansi16";
|
|
1252
|
+
}
|
|
1253
|
+
function degradePalette(palette, depth) {
|
|
1254
|
+
if (depth === "truecolor" || depth === "none") return palette;
|
|
1255
|
+
const map = depth === "ansi16" ? nearestAnsi16 : nearestAnsi256;
|
|
1256
|
+
const snap = (hex) => depth === "ansi16" ? ANSI_16[map(hex)].hex : ansi256Hex(map(hex));
|
|
1257
|
+
const out = {};
|
|
1258
|
+
for (const token of THEME_TOKENS) {
|
|
1259
|
+
out[token] = snap(palette[token]);
|
|
1260
|
+
}
|
|
1261
|
+
const source = palette;
|
|
1262
|
+
for (const token of SYNTAX_TOKENS) {
|
|
1263
|
+
const value = source[token];
|
|
1264
|
+
if (typeof value === "string") out[token] = snap(value);
|
|
1265
|
+
}
|
|
1266
|
+
return out;
|
|
1267
|
+
}
|
|
1268
|
+
var INSTALLED_THEMES_DIRNAME = "themes";
|
|
1269
|
+
function installedThemesDir(homeDir2) {
|
|
1270
|
+
return join2(homeStateDir(homeDir2), INSTALLED_THEMES_DIRNAME);
|
|
1271
|
+
}
|
|
1272
|
+
function installedThemeFilePath(id, homeDir2) {
|
|
1273
|
+
return join2(installedThemesDir(homeDir2), `${id}.json`);
|
|
1274
|
+
}
|
|
1275
|
+
var THEME_ID_RE = /^[a-z][a-z0-9]*([._-][a-z0-9]+)*$/;
|
|
1276
|
+
var THEME_ID_MAX = 64;
|
|
1277
|
+
function isSafeThemeId(id) {
|
|
1278
|
+
return typeof id === "string" && id.length > 0 && id.length <= THEME_ID_MAX && THEME_ID_RE.test(id) && !id.includes("/") && !id.includes("\\") && id !== "." && id !== "..";
|
|
1279
|
+
}
|
|
1280
|
+
var installedThemes = null;
|
|
1281
|
+
var installedFromHome;
|
|
1282
|
+
function readInstalledThemes(homeDir2) {
|
|
1283
|
+
const out = /* @__PURE__ */ new Map();
|
|
1284
|
+
for (const entry of loadUserThemes(installedThemesDir(homeDir2)).valid) {
|
|
1285
|
+
out.set(entry.name, entry);
|
|
1286
|
+
}
|
|
1287
|
+
return out;
|
|
1288
|
+
}
|
|
1289
|
+
function ensureInstalledThemesLoaded(homeDir2) {
|
|
1290
|
+
if (installedThemes === null || installedFromHome !== homeDir2) {
|
|
1291
|
+
installedThemes = readInstalledThemes(homeDir2);
|
|
1292
|
+
installedFromHome = homeDir2;
|
|
1293
|
+
}
|
|
1294
|
+
return installedThemes;
|
|
1295
|
+
}
|
|
1296
|
+
function reloadInstalledThemes(homeDir2) {
|
|
1297
|
+
installedThemes = readInstalledThemes(homeDir2);
|
|
1298
|
+
installedFromHome = homeDir2;
|
|
1299
|
+
return installedThemes;
|
|
1300
|
+
}
|
|
1301
|
+
function installedThemeEntries(homeDir2) {
|
|
1302
|
+
return [...ensureInstalledThemesLoaded(homeDir2).values()];
|
|
1303
|
+
}
|
|
1304
|
+
function allThemeNames(homeDir2) {
|
|
1305
|
+
return [...THEME_NAMES, ...ensureInstalledThemesLoaded(homeDir2).keys()];
|
|
1306
|
+
}
|
|
1307
|
+
function isKnownTheme(value) {
|
|
1308
|
+
if (isThemeName(value)) return true;
|
|
1309
|
+
return typeof value === "string" && installedThemes !== null && installedThemes.has(value);
|
|
1310
|
+
}
|
|
1311
|
+
function writeInstalledTheme(file, homeDir2) {
|
|
1312
|
+
if (!isSafeThemeId(file.id)) return { ok: false, error: `unsafe theme id ${JSON.stringify(file.id)}` };
|
|
1313
|
+
if (isThemeName(file.id)) return { ok: false, error: `"${file.id}" is a built-in theme name and cannot be overridden` };
|
|
1314
|
+
const issues = validateTheme(file.palette);
|
|
1315
|
+
if (issues.length > 0) {
|
|
1316
|
+
return { ok: false, error: `invalid palette: ${issues.map((i) => i.message).join("; ")}` };
|
|
1317
|
+
}
|
|
1318
|
+
try {
|
|
1319
|
+
const path = installedThemeFilePath(file.id, homeDir2);
|
|
1320
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1321
|
+
const body = {
|
|
1322
|
+
id: file.id,
|
|
1323
|
+
...file.label !== void 0 ? { label: file.label } : {},
|
|
1324
|
+
...file.description !== void 0 ? { description: file.description } : {},
|
|
1325
|
+
...file.mode !== void 0 ? { mode: file.mode } : {},
|
|
1326
|
+
palette: file.palette
|
|
1327
|
+
};
|
|
1328
|
+
writeFileSync(path, `${JSON.stringify(body, null, 2)}
|
|
1329
|
+
`, "utf8");
|
|
1330
|
+
return { ok: true, path };
|
|
1331
|
+
} catch (err) {
|
|
1332
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
function removeInstalledTheme(id, homeDir2) {
|
|
1336
|
+
if (!isSafeThemeId(id)) return { ok: false, error: `unsafe theme id ${JSON.stringify(id)}` };
|
|
1337
|
+
if (isThemeName(id)) return { ok: false, error: `"${id}" is a built-in theme and cannot be removed` };
|
|
1338
|
+
const cache = ensureInstalledThemesLoaded(homeDir2);
|
|
1339
|
+
if (!cache.has(id)) return { ok: false, error: `theme "${id}" is not installed` };
|
|
1340
|
+
try {
|
|
1341
|
+
rmSync(installedThemeFilePath(id, homeDir2), { force: true });
|
|
1342
|
+
return { ok: true };
|
|
1343
|
+
} catch (err) {
|
|
1344
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// packages/cli/src/tui/settings.ts
|
|
1349
|
+
var THEME_CHOICES = [...THEME_NAMES];
|
|
1350
|
+
function syncThemeChoices(homeDir2) {
|
|
1351
|
+
ensureInstalledThemesLoaded(homeDir2);
|
|
1352
|
+
THEME_CHOICES.splice(0, THEME_CHOICES.length, ...allThemeNames(homeDir2));
|
|
1353
|
+
}
|
|
1354
|
+
var DEFS = [
|
|
1355
|
+
{
|
|
1356
|
+
key: "showStatusBar",
|
|
1357
|
+
label: "Status bar",
|
|
1358
|
+
description: "Optional bottom-bar telemetry: model, working directory, git state and token counters. Permission mode and the turn timer share that row and stay visible with this off.",
|
|
1359
|
+
kind: "boolean",
|
|
1360
|
+
default: true,
|
|
1361
|
+
group: "Display"
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
key: "showComposerHints",
|
|
1365
|
+
label: "Composer hints",
|
|
1366
|
+
description: "Keyboard-hint line under the input box.",
|
|
1367
|
+
kind: "boolean",
|
|
1368
|
+
default: true,
|
|
1369
|
+
group: "Display"
|
|
1370
|
+
},
|
|
1371
|
+
{
|
|
1372
|
+
key: "composerSuggestions",
|
|
1373
|
+
label: "Composer autosuggestions",
|
|
1374
|
+
description: "Inline ghost-text continuation from your submitted-message history, accepted with the Right arrow at end-of-input.",
|
|
1375
|
+
kind: "boolean",
|
|
1376
|
+
default: true,
|
|
1377
|
+
group: "Display"
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
key: "mouseSupport",
|
|
1381
|
+
label: "Mouse support",
|
|
1382
|
+
description: "Scroll-wheel scrolling, click-to-select on list rows, and clickable hints. Turn off for a keyboard-only console where the terminal's own text selection works.",
|
|
1383
|
+
kind: "boolean",
|
|
1384
|
+
default: true,
|
|
1385
|
+
group: "Display"
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
key: "showLogo",
|
|
1389
|
+
label: "Logo",
|
|
1390
|
+
description: 'Block "0SEC" mark shown on an empty transcript.',
|
|
1391
|
+
kind: "boolean",
|
|
1392
|
+
default: true,
|
|
1393
|
+
group: "Display"
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
key: "showRuntimeNotices",
|
|
1397
|
+
label: "Runtime notices",
|
|
1398
|
+
description: "Surface runtime stdout/stderr as transcript notices.",
|
|
1399
|
+
kind: "boolean",
|
|
1400
|
+
default: true,
|
|
1401
|
+
group: "Transcript"
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
key: "showTurnSummary",
|
|
1405
|
+
label: "Turn summary",
|
|
1406
|
+
description: 'Per-turn "N tool calls - in->out tok" line after each answer.',
|
|
1407
|
+
kind: "boolean",
|
|
1408
|
+
default: false,
|
|
1409
|
+
group: "Transcript"
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
key: "showSubagents",
|
|
1413
|
+
label: "Subagent list",
|
|
1414
|
+
description: "List the active subagents while workers are running.",
|
|
1415
|
+
kind: "boolean",
|
|
1416
|
+
default: true,
|
|
1417
|
+
group: "Transcript"
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
key: "showLeftSidebar",
|
|
1421
|
+
label: "Left sidebar",
|
|
1422
|
+
description: "Left sidebar in the chat view: recent sessions to resume, plus this run's findings. Hidden on narrow terminals.",
|
|
1423
|
+
kind: "boolean",
|
|
1424
|
+
default: false,
|
|
1425
|
+
group: "Display"
|
|
1426
|
+
},
|
|
1427
|
+
{
|
|
1428
|
+
key: "showRightSidebar",
|
|
1429
|
+
label: "Right sidebar",
|
|
1430
|
+
description: "Right sidebar: live agents, their activity, the current plan and findings. Hidden on narrow terminals.",
|
|
1431
|
+
kind: "boolean",
|
|
1432
|
+
default: false,
|
|
1433
|
+
group: "Display"
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
key: "showTimestamps",
|
|
1437
|
+
label: "Timestamps",
|
|
1438
|
+
description: "Relative timestamps on transcript entries.",
|
|
1439
|
+
kind: "boolean",
|
|
1440
|
+
default: false,
|
|
1441
|
+
group: "Transcript"
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
key: "showObjective",
|
|
1445
|
+
label: "Objective",
|
|
1446
|
+
description: `Bottom-bar "objective" pill: a short "what am I working on" title derived from the session's first message.`,
|
|
1447
|
+
kind: "boolean",
|
|
1448
|
+
default: true,
|
|
1449
|
+
group: "Display"
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
key: "showScope",
|
|
1453
|
+
label: "Scope",
|
|
1454
|
+
description: "Show the configured multi-host scope and exclusions. An absent scope is distinct from an explicit empty scope.",
|
|
1455
|
+
kind: "boolean",
|
|
1456
|
+
default: true,
|
|
1457
|
+
group: "Display"
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
key: "density",
|
|
1461
|
+
label: "Density",
|
|
1462
|
+
description: 'Transcript spacing: "comfortable" adds blank lines between entries.',
|
|
1463
|
+
kind: "enum",
|
|
1464
|
+
default: "comfortable",
|
|
1465
|
+
choices: ["comfortable", "compact"],
|
|
1466
|
+
group: "Display"
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
key: "composerStyle",
|
|
1470
|
+
label: "Composer style",
|
|
1471
|
+
description: "How the composer frame is drawn around the input.",
|
|
1472
|
+
kind: "enum",
|
|
1473
|
+
default: "border",
|
|
1474
|
+
choices: ["border", "rail", "plain"],
|
|
1475
|
+
group: "Display"
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
key: "busyInputMode",
|
|
1479
|
+
label: "Busy input",
|
|
1480
|
+
description: "Steer interrupts the main turn before sending the follow-up. Queue waits for it to finish. Background workers keep running.",
|
|
1481
|
+
kind: "enum",
|
|
1482
|
+
default: "steer",
|
|
1483
|
+
choices: ["steer", "queue"],
|
|
1484
|
+
group: "Display"
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
key: "allowSubagentPeerMessaging",
|
|
1488
|
+
label: "Subagent peer messaging",
|
|
1489
|
+
description: "A subagent runs attacker-influenced code, so a direct sibling channel is how one compromised subagent reaches another's context.",
|
|
1490
|
+
kind: "boolean",
|
|
1491
|
+
default: true,
|
|
1492
|
+
group: "Security"
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
key: "allowSubagentOperatorMessaging",
|
|
1496
|
+
label: "Subagent messages to you",
|
|
1497
|
+
description: "Messages arrive in your transcript sanitized and attributed, but a compromised subagent can use this channel to say things to you.",
|
|
1498
|
+
kind: "boolean",
|
|
1499
|
+
default: true,
|
|
1500
|
+
group: "Security"
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
key: "transcriptStyle",
|
|
1504
|
+
label: "Transcript style",
|
|
1505
|
+
description: "Minimal (the default) drops every bubble and box: your turns carry a thin coloured accent rail and the answer flows as plain body text, the OpenCode / oh-my-pi flat look. Bubble right-aligns your messages against left-aligned answers and titles each card on its border. Balanced: only your own messages are bubbled; everything else is flat. Rail, plain, compact and document offer alternative transcript layouts.",
|
|
1506
|
+
kind: "enum",
|
|
1507
|
+
default: "minimal",
|
|
1508
|
+
choices: ["minimal", "rail", "bubble", "balanced", "plain", "compact", "document"],
|
|
1509
|
+
group: "Display"
|
|
1510
|
+
},
|
|
1511
|
+
{
|
|
1512
|
+
key: "roleLabelStyle",
|
|
1513
|
+
label: "Role label",
|
|
1514
|
+
description: 'Speaker name on each message: "You" for your turns, "0sec" for answers. Full and short add the elapsed age when one is known; glyph shows the name alone; off omits the label entirely. Bubble cards carry it top-left on the card border, with your messages right-aligned and answers left.',
|
|
1515
|
+
kind: "enum",
|
|
1516
|
+
default: "full",
|
|
1517
|
+
choices: ["full", "short", "glyph", "off"],
|
|
1518
|
+
group: "Display"
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
key: "toolCardStyle",
|
|
1522
|
+
label: "Tool card",
|
|
1523
|
+
description: "How a tool or subagent call is drawn: compact, rail, inline or hidden (failures always show).",
|
|
1524
|
+
kind: "enum",
|
|
1525
|
+
default: "compact",
|
|
1526
|
+
choices: ["compact", "rail", "inline", "hidden"],
|
|
1527
|
+
group: "Display"
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
key: "richToolCards",
|
|
1531
|
+
label: "Rich tool cards",
|
|
1532
|
+
description: "Draw bash/run_command output and apply_patch edits as bordered cards (command + output + wall/exit footer, or an edit header + diff) instead of a plain line.",
|
|
1533
|
+
kind: "boolean",
|
|
1534
|
+
default: true,
|
|
1535
|
+
group: "Transcript"
|
|
1536
|
+
},
|
|
1537
|
+
{
|
|
1538
|
+
key: "transcriptDetail",
|
|
1539
|
+
label: "Transcript detail",
|
|
1540
|
+
description: "Expanded shows every step (thinking, tool calls + their output) inline \u2014 the default; collapsed folds each turn's successful steps into one-line summaries (failures always show). Ctrl+R toggles it live.",
|
|
1541
|
+
kind: "enum",
|
|
1542
|
+
default: "expanded",
|
|
1543
|
+
choices: ["expanded", "collapsed"],
|
|
1544
|
+
group: "Transcript"
|
|
1545
|
+
},
|
|
1546
|
+
{
|
|
1547
|
+
key: "autoCompaction",
|
|
1548
|
+
label: "Auto-compaction",
|
|
1549
|
+
description: "Automatically summarise older turns when the conversation nears the model's context window (the recap stays viewable with Ctrl+O).",
|
|
1550
|
+
kind: "boolean",
|
|
1551
|
+
default: true,
|
|
1552
|
+
group: "Context"
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
key: "compactionThreshold",
|
|
1556
|
+
label: "Compaction threshold",
|
|
1557
|
+
description: "How full the context window gets before auto-compaction runs.",
|
|
1558
|
+
kind: "enum",
|
|
1559
|
+
default: "80%",
|
|
1560
|
+
choices: ["70%", "75%", "80%", "85%"],
|
|
1561
|
+
group: "Context"
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
key: "theme",
|
|
1565
|
+
label: "Theme",
|
|
1566
|
+
description: "Colour palette. Slate (neutral grey, default) and Midnight (deep blue-black) and Carbon (warm dark), Standard/Paper (light), plus Contrast, Mono Dim and ANSI 16 for 16-colour terminals. Drop validated palettes in ~/.0sec/themes to add your own.",
|
|
1567
|
+
kind: "enum",
|
|
1568
|
+
default: DEFAULT_THEME_NAME,
|
|
1569
|
+
choices: THEME_CHOICES,
|
|
1570
|
+
group: "Display"
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
key: "allowModelSelfExtension",
|
|
1574
|
+
label: "Model self-extension",
|
|
1575
|
+
description: "Allow new sessions to add sandboxed tools and live harness generations. Existing disabled sessions stay disabled. Trusted harness code requires separate workspace trust; development engine updates are controlled independently.",
|
|
1576
|
+
kind: "boolean",
|
|
1577
|
+
default: DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
|
|
1578
|
+
group: "Security"
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
key: "allowDevSourceUpdates",
|
|
1582
|
+
label: "Development engine updates",
|
|
1583
|
+
description: "0dev only. Reload changed core engine code between turns without losing the session. Runs with host privileges, including credential access; independent of sandboxed tool extension.",
|
|
1584
|
+
kind: "boolean",
|
|
1585
|
+
default: false,
|
|
1586
|
+
group: "Security"
|
|
1587
|
+
},
|
|
1588
|
+
{
|
|
1589
|
+
key: "autoEvolveFinderLenses",
|
|
1590
|
+
label: "Auto-evolve finder lenses",
|
|
1591
|
+
description: "Start the TUI watcher for ~/.0sec/lens-synthesis/miss-input.json (or OSEC_TUI_LENS_SYNTH_INPUT) so each new curated revision can invoke the configured model.",
|
|
1592
|
+
kind: "boolean",
|
|
1593
|
+
default: false,
|
|
1594
|
+
group: "Security"
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
key: "autoPromoteFinderLenses",
|
|
1598
|
+
label: "Auto-promote validated lenses",
|
|
1599
|
+
description: "Permit the TUI watcher to persist a candidate only after its positive and negative-control corpus gate passes, otherwise automatic evaluation stays dry-run.",
|
|
1600
|
+
kind: "boolean",
|
|
1601
|
+
default: false,
|
|
1602
|
+
group: "Security"
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
key: "showTokenUsage",
|
|
1606
|
+
label: "Token usage",
|
|
1607
|
+
description: 'Per-turn "in\u2192out tok" line under each answer.',
|
|
1608
|
+
kind: "boolean",
|
|
1609
|
+
default: true,
|
|
1610
|
+
group: "Telemetry"
|
|
1611
|
+
},
|
|
1612
|
+
{
|
|
1613
|
+
key: "showCost",
|
|
1614
|
+
label: "Cost",
|
|
1615
|
+
description: "Estimated dollar cost, per turn and in the status bar.",
|
|
1616
|
+
kind: "boolean",
|
|
1617
|
+
default: true,
|
|
1618
|
+
group: "Telemetry"
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
key: "showContextMeter",
|
|
1622
|
+
label: "Context meter",
|
|
1623
|
+
description: "Visual context-usage bar in the status bar.",
|
|
1624
|
+
kind: "boolean",
|
|
1625
|
+
default: true,
|
|
1626
|
+
group: "Telemetry"
|
|
1627
|
+
},
|
|
1628
|
+
{
|
|
1629
|
+
key: "modelDisplay",
|
|
1630
|
+
label: "Model display",
|
|
1631
|
+
description: "Where the model name appears: status bar, per message, or hidden.",
|
|
1632
|
+
kind: "enum",
|
|
1633
|
+
default: "statusbar",
|
|
1634
|
+
choices: ["statusbar", "message", "off"],
|
|
1635
|
+
group: "Telemetry"
|
|
1636
|
+
},
|
|
1637
|
+
{
|
|
1638
|
+
key: "elapsedTimer",
|
|
1639
|
+
label: "Elapsed timer",
|
|
1640
|
+
description: "Show the running-turn elapsed time as a compact clock-glyphed pill next to the left status icon, or hide it.",
|
|
1641
|
+
kind: "enum",
|
|
1642
|
+
default: "left",
|
|
1643
|
+
choices: ["left", "off"],
|
|
1644
|
+
group: "Telemetry"
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
key: "logoAnimation",
|
|
1648
|
+
label: "Logo animation",
|
|
1649
|
+
description: 'Intro animation for the "0SEC" logo: glitch (a neon-flecked scramble that resolves \u2014 the default), rainbow (a looping hue sweep), matrix (a green matrix-rain cascade), wave (a rippling cyan wavefront), neon (a neon-sign warm-up flicker), shimmer (a bright comet with a gradient tail), pulse (the slash breathes), strike (an orange slash strikes through the 0), draw (letters draw in behind a pen tip), fade (a centre-out bloom), typein (per-cell reveal), sweep (a bright bar wipes across) or off (static).',
|
|
1650
|
+
kind: "enum",
|
|
1651
|
+
default: "glitch",
|
|
1652
|
+
choices: [
|
|
1653
|
+
"glitch",
|
|
1654
|
+
"rainbow",
|
|
1655
|
+
"matrix",
|
|
1656
|
+
"wave",
|
|
1657
|
+
"neon",
|
|
1658
|
+
"shimmer",
|
|
1659
|
+
"pulse",
|
|
1660
|
+
"strike",
|
|
1661
|
+
"draw",
|
|
1662
|
+
"fade",
|
|
1663
|
+
"typein",
|
|
1664
|
+
"sweep",
|
|
1665
|
+
"swiss",
|
|
1666
|
+
"off"
|
|
1667
|
+
],
|
|
1668
|
+
group: "Motion"
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
key: "reduceMotion",
|
|
1672
|
+
label: "Reduce motion",
|
|
1673
|
+
description: "Master reduce-motion: the console keeps essential feedback but disables decorative animations like the logo intro, shimmers and sweeps.",
|
|
1674
|
+
kind: "boolean",
|
|
1675
|
+
default: false,
|
|
1676
|
+
group: "Motion"
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
key: "analyticsLevel",
|
|
1680
|
+
label: "Analytics and training data",
|
|
1681
|
+
description: "Full is the new-install default. Usage shares feature counters and error categories, not tool content. Commands adds tool arguments/results and submitted code for model training and security research; Full also adds scope and findings. Recognized credentials are scrubbed; emails, URLs, identifiers and other content are retained. Sending uses authenticated Cloud storage and is not anonymous. Each tool/code content field is limited to 256 KiB after credential scrubbing; oversized records are reported locally, not silently truncated. Explicit 0SEC_ANALYTICS_LEVEL and offline/no-telemetry/DO_NOT_TRACK restrictions win over broader settings. Problem reports are separate. Applies to this computer, not this project.",
|
|
1682
|
+
kind: "enum",
|
|
1683
|
+
default: "full",
|
|
1684
|
+
choices: ["off", "usage", "commands", "full"],
|
|
1685
|
+
group: "Privacy"
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
key: "diagnosticReporting",
|
|
1689
|
+
label: "Problem reports",
|
|
1690
|
+
description: "Send limited diagnostics automatically by default, ask first, or turn reporting off. Uses Cloud sign-in or a configured HTTPS feedback endpoint. Never includes prompts, tool arguments or output. Applies to this computer, not this project.",
|
|
1691
|
+
kind: "enum",
|
|
1692
|
+
default: "automatic",
|
|
1693
|
+
choices: ["off", "ask", "automatic"],
|
|
1694
|
+
group: "Privacy"
|
|
1695
|
+
},
|
|
1696
|
+
{
|
|
1697
|
+
key: "updatePolicy",
|
|
1698
|
+
label: "Updates",
|
|
1699
|
+
description: "Off, notify about releases, or install updates before the console starts. Applies to this computer; project settings cannot enable installation.",
|
|
1700
|
+
kind: "enum",
|
|
1701
|
+
default: "automatic",
|
|
1702
|
+
choices: ["off", "notify", "automatic"],
|
|
1703
|
+
group: "Updates"
|
|
1704
|
+
},
|
|
1705
|
+
{
|
|
1706
|
+
key: "symbolPreset",
|
|
1707
|
+
label: "Symbols",
|
|
1708
|
+
description: "Glyph set for icons, status marks and checkboxes: Unicode (default, works everywhere), Nerd Font (crisp patched-font icons \u2014 requires a Nerd Font terminal) or ASCII (plain, single-cell).",
|
|
1709
|
+
kind: "enum",
|
|
1710
|
+
default: "unicode",
|
|
1711
|
+
choices: ["unicode", "nerd", "ascii"],
|
|
1712
|
+
group: "Display"
|
|
1713
|
+
},
|
|
1714
|
+
{
|
|
1715
|
+
key: "rosterSort",
|
|
1716
|
+
label: "Roster order",
|
|
1717
|
+
description: "Order the agent herd and comms fleet by attention (blocked and working agents float to the top \u2014 the default) or by fixed lifecycle status.",
|
|
1718
|
+
kind: "enum",
|
|
1719
|
+
default: "attention",
|
|
1720
|
+
choices: ["attention", "status"],
|
|
1721
|
+
group: "Display"
|
|
1722
|
+
},
|
|
1723
|
+
{
|
|
1724
|
+
key: "leaderKey",
|
|
1725
|
+
label: "Leader key",
|
|
1726
|
+
description: "Optional tmux-style prefix chord for the roster views: press it, then a number to focus that agent, or n/p to step. Off disables it (the direct number keys work regardless).",
|
|
1727
|
+
kind: "enum",
|
|
1728
|
+
default: "off",
|
|
1729
|
+
choices: ["off", "ctrl+a", "ctrl+b", "ctrl+space"],
|
|
1730
|
+
group: "Display"
|
|
1731
|
+
}
|
|
1732
|
+
];
|
|
1733
|
+
var SETTING_DEFS = DEFS;
|
|
1734
|
+
var DEF_BY_KEY = new Map(DEFS.map((def) => [def.key, def]));
|
|
1735
|
+
var DEFAULT_SETTINGS = {
|
|
1736
|
+
showStatusBar: true,
|
|
1737
|
+
showComposerHints: true,
|
|
1738
|
+
composerSuggestions: true,
|
|
1739
|
+
mouseSupport: true,
|
|
1740
|
+
showLogo: true,
|
|
1741
|
+
showRuntimeNotices: true,
|
|
1742
|
+
showTurnSummary: false,
|
|
1743
|
+
showSubagents: true,
|
|
1744
|
+
showLeftSidebar: false,
|
|
1745
|
+
showRightSidebar: false,
|
|
1746
|
+
showTimestamps: false,
|
|
1747
|
+
showObjective: true,
|
|
1748
|
+
showScope: true,
|
|
1749
|
+
density: "comfortable",
|
|
1750
|
+
composerStyle: "border",
|
|
1751
|
+
busyInputMode: "steer",
|
|
1752
|
+
onboardingCompleted: false,
|
|
1753
|
+
allowSubagentPeerMessaging: true,
|
|
1754
|
+
allowSubagentOperatorMessaging: true,
|
|
1755
|
+
transcriptStyle: "minimal",
|
|
1756
|
+
roleLabelStyle: "full",
|
|
1757
|
+
toolCardStyle: "compact",
|
|
1758
|
+
richToolCards: true,
|
|
1759
|
+
transcriptDetail: "expanded",
|
|
1760
|
+
autoCompaction: true,
|
|
1761
|
+
compactionThreshold: "80%",
|
|
1762
|
+
theme: DEFAULT_THEME_NAME,
|
|
1763
|
+
allowModelSelfExtension: DEFAULT_ALLOW_MODEL_SELF_EXTENSION,
|
|
1764
|
+
allowDevSourceUpdates: false,
|
|
1765
|
+
autoEvolveFinderLenses: false,
|
|
1766
|
+
autoPromoteFinderLenses: false,
|
|
1767
|
+
showTokenUsage: true,
|
|
1768
|
+
showCost: true,
|
|
1769
|
+
showContextMeter: true,
|
|
1770
|
+
modelDisplay: "statusbar",
|
|
1771
|
+
elapsedTimer: "left",
|
|
1772
|
+
logoAnimation: "glitch",
|
|
1773
|
+
reduceMotion: false,
|
|
1774
|
+
analyticsLevel: "full",
|
|
1775
|
+
diagnosticReporting: "automatic",
|
|
1776
|
+
diagnosticReportingPrompted: false,
|
|
1777
|
+
updatePolicy: "automatic",
|
|
1778
|
+
symbolPreset: "unicode",
|
|
1779
|
+
rosterSort: "attention",
|
|
1780
|
+
leaderKey: "off",
|
|
1781
|
+
keybindings: {}
|
|
1782
|
+
};
|
|
1783
|
+
var SETTINGS_FILENAME = "tui-settings.json";
|
|
1784
|
+
function settingsFilePath(homeDir2) {
|
|
1785
|
+
return join3(homeStateDir(homeDir2), SETTINGS_FILENAME);
|
|
1786
|
+
}
|
|
1787
|
+
function projectStateDir(projectDir2 = process.cwd()) {
|
|
1788
|
+
return join3(projectDir2, ".0sec");
|
|
1789
|
+
}
|
|
1790
|
+
function projectSettingsFilePath(projectDir2 = process.cwd()) {
|
|
1791
|
+
return join3(projectStateDir(projectDir2), SETTINGS_FILENAME);
|
|
1792
|
+
}
|
|
1793
|
+
function readRawSettingsFile(path) {
|
|
1794
|
+
try {
|
|
1795
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
1796
|
+
} catch {
|
|
1797
|
+
return void 0;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
function projectSettingsExist(projectDir2 = process.cwd()) {
|
|
1801
|
+
const raw = readRawSettingsFile(projectSettingsFilePath(projectDir2));
|
|
1802
|
+
return typeof raw === "object" && raw !== null && !Array.isArray(raw);
|
|
1803
|
+
}
|
|
1804
|
+
function strictValueAt(raw, key) {
|
|
1805
|
+
const value = rawValue(raw, key);
|
|
1806
|
+
if (value === void 0) return void 0;
|
|
1807
|
+
if (key === "onboardingCompleted" || key === "diagnosticReportingPrompted") {
|
|
1808
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1809
|
+
}
|
|
1810
|
+
if (value === false && (key === "diagnosticReporting" || key === "updatePolicy")) {
|
|
1811
|
+
return "off";
|
|
1812
|
+
}
|
|
1813
|
+
if (key === "theme") {
|
|
1814
|
+
return typeof value === "string" && isKnownTheme(value) ? value : void 0;
|
|
1815
|
+
}
|
|
1816
|
+
if (key === "keybindings") {
|
|
1817
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? sanitizeKeybindingOverrides(value) : void 0;
|
|
1818
|
+
}
|
|
1819
|
+
const def = DEF_BY_KEY.get(key);
|
|
1820
|
+
if (def?.kind === "boolean") {
|
|
1821
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1822
|
+
}
|
|
1823
|
+
const choices = def?.choices ?? [];
|
|
1824
|
+
return typeof value === "string" && choices.includes(value) ? value : void 0;
|
|
1825
|
+
}
|
|
1826
|
+
function isOperatorSetting(key) {
|
|
1827
|
+
return key === "onboardingCompleted" || key === "analyticsLevel" || key === "diagnosticReporting" || key === "diagnosticReportingPrompted" || key === "updatePolicy" || key === "allowDevSourceUpdates";
|
|
1828
|
+
}
|
|
1829
|
+
function resolveLayeredSettings(globalRaw, projectRaw) {
|
|
1830
|
+
const settings = {};
|
|
1831
|
+
const sources = {};
|
|
1832
|
+
for (const key of Object.keys(DEFAULT_SETTINGS)) {
|
|
1833
|
+
const projectValue = isOperatorSetting(key) ? void 0 : strictValueAt(projectRaw, key);
|
|
1834
|
+
if (projectValue !== void 0) {
|
|
1835
|
+
settings[key] = projectValue;
|
|
1836
|
+
sources[key] = "project";
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
const globalValue = strictValueAt(globalRaw, key);
|
|
1840
|
+
if (globalValue !== void 0) {
|
|
1841
|
+
settings[key] = globalValue;
|
|
1842
|
+
sources[key] = "global";
|
|
1843
|
+
continue;
|
|
1844
|
+
}
|
|
1845
|
+
settings[key] = DEFAULT_SETTINGS[key];
|
|
1846
|
+
sources[key] = "default";
|
|
1847
|
+
}
|
|
1848
|
+
return { settings, sources };
|
|
1849
|
+
}
|
|
1850
|
+
function loadLayeredSettings(opts = {}) {
|
|
1851
|
+
syncThemeChoices(opts.homeDir);
|
|
1852
|
+
const globalRaw = readRawSettingsFile(settingsFilePath(opts.homeDir));
|
|
1853
|
+
const projectRaw = readRawSettingsFile(projectSettingsFilePath(opts.projectDir));
|
|
1854
|
+
return resolveLayeredSettings(globalRaw, projectRaw);
|
|
1855
|
+
}
|
|
1856
|
+
function rawValue(raw, key) {
|
|
1857
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
1858
|
+
const value = raw[key];
|
|
1859
|
+
return key === "transcriptStyle" && value === "messenger" ? "bubble" : value;
|
|
1860
|
+
}
|
|
1861
|
+
function booleanAt(raw, key) {
|
|
1862
|
+
const value = rawValue(raw, key);
|
|
1863
|
+
return typeof value === "boolean" ? value : DEFAULT_SETTINGS[key];
|
|
1864
|
+
}
|
|
1865
|
+
function booleanWithLegacy(raw, key, legacyKey) {
|
|
1866
|
+
const value = rawValue(raw, key);
|
|
1867
|
+
if (typeof value === "boolean") return value;
|
|
1868
|
+
const legacy = rawValue(raw, legacyKey);
|
|
1869
|
+
if (typeof legacy === "boolean") return legacy;
|
|
1870
|
+
return DEFAULT_SETTINGS[key];
|
|
1871
|
+
}
|
|
1872
|
+
function enumAt(raw, key) {
|
|
1873
|
+
const def = DEF_BY_KEY.get(key);
|
|
1874
|
+
const value = rawValue(raw, key);
|
|
1875
|
+
const choices = def?.choices ?? [];
|
|
1876
|
+
return typeof value === "string" && choices.includes(value) ? value : DEFAULT_SETTINGS[key];
|
|
1877
|
+
}
|
|
1878
|
+
function themeAt(raw) {
|
|
1879
|
+
const value = rawValue(raw, "theme");
|
|
1880
|
+
return typeof value === "string" && isKnownTheme(value) ? value : DEFAULT_SETTINGS.theme;
|
|
1881
|
+
}
|
|
1882
|
+
function keybindingsAt(raw) {
|
|
1883
|
+
return sanitizeKeybindingOverrides(rawValue(raw, "keybindings"));
|
|
1884
|
+
}
|
|
1885
|
+
function normalizeSettings(raw) {
|
|
1886
|
+
return {
|
|
1887
|
+
showStatusBar: booleanAt(raw, "showStatusBar"),
|
|
1888
|
+
showComposerHints: booleanAt(raw, "showComposerHints"),
|
|
1889
|
+
composerSuggestions: booleanAt(raw, "composerSuggestions"),
|
|
1890
|
+
mouseSupport: booleanAt(raw, "mouseSupport"),
|
|
1891
|
+
showLogo: booleanAt(raw, "showLogo"),
|
|
1892
|
+
showRuntimeNotices: booleanAt(raw, "showRuntimeNotices"),
|
|
1893
|
+
showTurnSummary: booleanAt(raw, "showTurnSummary"),
|
|
1894
|
+
showSubagents: booleanAt(raw, "showSubagents"),
|
|
1895
|
+
showLeftSidebar: booleanAt(raw, "showLeftSidebar"),
|
|
1896
|
+
// Back-compat: the right sidebar was `showAgentRail` before it grew a
|
|
1897
|
+
// context strip and a left twin. A pre-rename file keeps its choice.
|
|
1898
|
+
showRightSidebar: booleanWithLegacy(raw, "showRightSidebar", "showAgentRail"),
|
|
1899
|
+
showTimestamps: booleanAt(raw, "showTimestamps"),
|
|
1900
|
+
showObjective: booleanAt(raw, "showObjective"),
|
|
1901
|
+
showScope: booleanAt(raw, "showScope"),
|
|
1902
|
+
density: enumAt(raw, "density"),
|
|
1903
|
+
composerStyle: enumAt(raw, "composerStyle"),
|
|
1904
|
+
busyInputMode: enumAt(raw, "busyInputMode"),
|
|
1905
|
+
onboardingCompleted: booleanAt(raw, "onboardingCompleted"),
|
|
1906
|
+
allowSubagentPeerMessaging: booleanAt(raw, "allowSubagentPeerMessaging"),
|
|
1907
|
+
allowSubagentOperatorMessaging: booleanAt(raw, "allowSubagentOperatorMessaging"),
|
|
1908
|
+
transcriptStyle: enumAt(raw, "transcriptStyle"),
|
|
1909
|
+
roleLabelStyle: enumAt(raw, "roleLabelStyle"),
|
|
1910
|
+
toolCardStyle: enumAt(raw, "toolCardStyle"),
|
|
1911
|
+
richToolCards: booleanAt(raw, "richToolCards"),
|
|
1912
|
+
transcriptDetail: enumAt(raw, "transcriptDetail"),
|
|
1913
|
+
autoCompaction: booleanAt(raw, "autoCompaction"),
|
|
1914
|
+
compactionThreshold: enumAt(raw, "compactionThreshold"),
|
|
1915
|
+
theme: themeAt(raw),
|
|
1916
|
+
allowModelSelfExtension: booleanAt(raw, "allowModelSelfExtension"),
|
|
1917
|
+
allowDevSourceUpdates: booleanAt(raw, "allowDevSourceUpdates"),
|
|
1918
|
+
autoEvolveFinderLenses: booleanAt(raw, "autoEvolveFinderLenses"),
|
|
1919
|
+
autoPromoteFinderLenses: booleanAt(raw, "autoPromoteFinderLenses"),
|
|
1920
|
+
showTokenUsage: booleanAt(raw, "showTokenUsage"),
|
|
1921
|
+
showCost: booleanAt(raw, "showCost"),
|
|
1922
|
+
showContextMeter: booleanAt(raw, "showContextMeter"),
|
|
1923
|
+
modelDisplay: enumAt(raw, "modelDisplay"),
|
|
1924
|
+
elapsedTimer: enumAt(raw, "elapsedTimer"),
|
|
1925
|
+
logoAnimation: enumAt(raw, "logoAnimation"),
|
|
1926
|
+
reduceMotion: booleanAt(raw, "reduceMotion"),
|
|
1927
|
+
analyticsLevel: enumAt(raw, "analyticsLevel"),
|
|
1928
|
+
diagnosticReporting: strictValueAt(raw, "diagnosticReporting") ?? DEFAULT_SETTINGS.diagnosticReporting,
|
|
1929
|
+
diagnosticReportingPrompted: booleanAt(raw, "diagnosticReportingPrompted"),
|
|
1930
|
+
updatePolicy: strictValueAt(raw, "updatePolicy") ?? DEFAULT_SETTINGS.updatePolicy,
|
|
1931
|
+
symbolPreset: enumAt(raw, "symbolPreset"),
|
|
1932
|
+
rosterSort: enumAt(raw, "rosterSort"),
|
|
1933
|
+
leaderKey: enumAt(raw, "leaderKey"),
|
|
1934
|
+
keybindings: keybindingsAt(raw)
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
function loadSettings(homeDir2, projectDir2) {
|
|
1938
|
+
return loadLayeredSettings({ homeDir: homeDir2, projectDir: projectDir2 }).settings;
|
|
1939
|
+
}
|
|
1940
|
+
function loadGlobalSettings(homeDir2) {
|
|
1941
|
+
syncThemeChoices(homeDir2);
|
|
1942
|
+
return normalizeSettings(readRawSettingsFile(settingsFilePath(homeDir2)));
|
|
1943
|
+
}
|
|
1944
|
+
function saveSettings(settings, homeDir2) {
|
|
1945
|
+
try {
|
|
1946
|
+
const path = settingsFilePath(homeDir2);
|
|
1947
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
1948
|
+
writeFileSync2(path, `${JSON.stringify(normalizeSettings(settings), null, 2)}
|
|
1949
|
+
`, "utf8");
|
|
1950
|
+
return true;
|
|
1951
|
+
} catch {
|
|
1952
|
+
return false;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
function sanitizeOverrides(raw) {
|
|
1956
|
+
const out = {};
|
|
1957
|
+
for (const key of Object.keys(DEFAULT_SETTINGS)) {
|
|
1958
|
+
if (isOperatorSetting(key)) continue;
|
|
1959
|
+
const value = strictValueAt(raw, key);
|
|
1960
|
+
if (value !== void 0) out[key] = value;
|
|
1961
|
+
}
|
|
1962
|
+
return out;
|
|
1963
|
+
}
|
|
1964
|
+
function readProjectOverrides(projectDir2) {
|
|
1965
|
+
return sanitizeOverrides(readRawSettingsFile(projectSettingsFilePath(projectDir2)));
|
|
1966
|
+
}
|
|
1967
|
+
function saveProjectOverrides(patch, projectDir2) {
|
|
1968
|
+
try {
|
|
1969
|
+
const path = projectSettingsFilePath(projectDir2);
|
|
1970
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
1971
|
+
writeFileSync2(path, `${JSON.stringify(sanitizeOverrides(patch), null, 2)}
|
|
1972
|
+
`, "utf8");
|
|
1973
|
+
return true;
|
|
1974
|
+
} catch {
|
|
1975
|
+
return false;
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
// packages/cli/src/tui/settings-store.ts
|
|
1980
|
+
var cached = null;
|
|
1981
|
+
var globalDraft = null;
|
|
1982
|
+
var projectDraft = null;
|
|
1983
|
+
var homeDir;
|
|
1984
|
+
var projectDir;
|
|
1985
|
+
var subscribers = /* @__PURE__ */ new Set();
|
|
1986
|
+
function notify(settings) {
|
|
1987
|
+
bridgeAnalyticsLevel(settings);
|
|
1988
|
+
for (const fn of [...subscribers]) {
|
|
1989
|
+
try {
|
|
1990
|
+
fn(settings);
|
|
1991
|
+
} catch {
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
function loadLayered() {
|
|
1996
|
+
return globalDraft === null && projectDraft === null ? loadLayeredSettings({ homeDir, projectDir }) : resolveLayeredSettings(globalDraft ?? loadGlobalSettings(homeDir), projectDraft ?? readProjectOverrides(projectDir));
|
|
1997
|
+
}
|
|
1998
|
+
function getSettings() {
|
|
1999
|
+
if (cached === null) {
|
|
2000
|
+
cached = loadLayered();
|
|
2001
|
+
bridgeAnalyticsLevel(cached.settings);
|
|
2002
|
+
}
|
|
2003
|
+
return cached.settings;
|
|
2004
|
+
}
|
|
2005
|
+
function setSettings(next) {
|
|
2006
|
+
const value = normalizeSettings(next);
|
|
2007
|
+
const saved = saveSettings(value, homeDir);
|
|
2008
|
+
globalDraft = saved ? null : value;
|
|
2009
|
+
cached = loadLayered();
|
|
2010
|
+
notify(cached.settings);
|
|
2011
|
+
return saved;
|
|
2012
|
+
}
|
|
2013
|
+
function defaultWriteLayer() {
|
|
2014
|
+
return projectSettingsExist(projectDir) ? "project" : "global";
|
|
2015
|
+
}
|
|
2016
|
+
function updateSetting(key, value, opts = {}) {
|
|
2017
|
+
const operatorOnly = isOperatorSetting(key);
|
|
2018
|
+
if (operatorOnly && opts.scope === "project") return false;
|
|
2019
|
+
const layer = operatorOnly ? "global" : opts.scope ?? defaultWriteLayer();
|
|
2020
|
+
if (layer === "global") {
|
|
2021
|
+
return setSettings({ ...globalDraft ?? loadGlobalSettings(homeDir), [key]: value });
|
|
2022
|
+
}
|
|
2023
|
+
const patch = { ...projectDraft ?? readProjectOverrides(projectDir), [key]: value };
|
|
2024
|
+
const saved = saveProjectOverrides(patch, projectDir);
|
|
2025
|
+
projectDraft = saved ? null : patch;
|
|
2026
|
+
cached = loadLayered();
|
|
2027
|
+
notify(cached.settings);
|
|
2028
|
+
return saved;
|
|
2029
|
+
}
|
|
2030
|
+
function resetSettings(next, keys) {
|
|
2031
|
+
const selected = keys.filter((key) => SETTING_DEFS.some((def) => def.key === key));
|
|
2032
|
+
if (selected.length === 0) return true;
|
|
2033
|
+
const global = { ...globalDraft ?? loadGlobalSettings(homeDir) };
|
|
2034
|
+
const project = { ...projectDraft ?? readProjectOverrides(projectDir) };
|
|
2035
|
+
let projectChanged = false;
|
|
2036
|
+
for (const key of selected) {
|
|
2037
|
+
global[key] = next[key];
|
|
2038
|
+
if (Object.hasOwn(project, key)) {
|
|
2039
|
+
delete project[key];
|
|
2040
|
+
projectChanged = true;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
const value = normalizeSettings(global);
|
|
2044
|
+
const globalSaved = saveSettings(value, homeDir);
|
|
2045
|
+
globalDraft = globalSaved ? null : value;
|
|
2046
|
+
const projectSaved = !projectChanged || saveProjectOverrides(project, projectDir);
|
|
2047
|
+
if (projectChanged) projectDraft = projectSaved ? null : project;
|
|
2048
|
+
cached = loadLayered();
|
|
2049
|
+
notify(cached.settings);
|
|
2050
|
+
return globalSaved && projectSaved;
|
|
2051
|
+
}
|
|
2052
|
+
function previewSetting(key, value) {
|
|
2053
|
+
if (cached === null) cached = loadLayered();
|
|
2054
|
+
cached = { settings: { ...cached.settings, [key]: value }, sources: cached.sources };
|
|
2055
|
+
notify(cached.settings);
|
|
2056
|
+
}
|
|
2057
|
+
function subscribeSettings(fn) {
|
|
2058
|
+
subscribers.add(fn);
|
|
2059
|
+
return () => {
|
|
2060
|
+
subscribers.delete(fn);
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
var bridgedAnalyticsEnv;
|
|
2064
|
+
function bridgeAnalyticsLevel(settings) {
|
|
2065
|
+
try {
|
|
2066
|
+
const current = process.env[ANALYTICS_LEVEL_ENV];
|
|
2067
|
+
if (current === void 0 || current === bridgedAnalyticsEnv) {
|
|
2068
|
+
process.env[ANALYTICS_LEVEL_ENV] = settings.analyticsLevel;
|
|
2069
|
+
bridgedAnalyticsEnv = settings.analyticsLevel;
|
|
2070
|
+
} else {
|
|
2071
|
+
bridgedAnalyticsEnv = void 0;
|
|
2072
|
+
}
|
|
2073
|
+
const environmentLevel = resolveAnalyticsLevel();
|
|
2074
|
+
analyticsPipeline.setLevel(levelAtLeast(environmentLevel, settings.analyticsLevel) ? settings.analyticsLevel : environmentLevel);
|
|
2075
|
+
} catch {
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
function reloadSettings() {
|
|
2079
|
+
globalDraft = null;
|
|
2080
|
+
projectDraft = null;
|
|
2081
|
+
cached = loadLayered();
|
|
2082
|
+
notify(cached.settings);
|
|
2083
|
+
return cached.settings;
|
|
2084
|
+
}
|
|
2085
|
+
function configureSettingsStore(options) {
|
|
2086
|
+
homeDir = options.homeDir;
|
|
2087
|
+
projectDir = options.projectDir;
|
|
2088
|
+
reloadSettings();
|
|
2089
|
+
}
|
|
2090
|
+
function useSettings() {
|
|
2091
|
+
return useSyncExternalStore(subscribeSettings, getSettings, getSettings);
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
// packages/cli/src/codex-auth.ts
|
|
2095
|
+
import { readFileSync as readFileSync3, existsSync } from "node:fs";
|
|
2096
|
+
import { homedir } from "node:os";
|
|
2097
|
+
import { join as join4 } from "node:path";
|
|
2098
|
+
var AUTH_FILE_ENV = "0SEC_CHATGPT_AUTH_FILE";
|
|
2099
|
+
var LEGACY_AUTH_FILE_ENV = "0SEC_CODEX_AUTH_JSON_PATH";
|
|
2100
|
+
function maybeLoadCodexAuth(options = {}) {
|
|
2101
|
+
const env = options.env ?? process.env;
|
|
2102
|
+
if (!options.force && (env["0SEC_CHATGPT_ACCESS_TOKEN"] || env["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"])) {
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
const home = options.home ?? homedir();
|
|
2106
|
+
const override = nonEmpty(env[AUTH_FILE_ENV]) ?? nonEmpty(env[LEGACY_AUTH_FILE_ENV]);
|
|
2107
|
+
const path = override ?? join4(home, ".codex", "auth.json");
|
|
2108
|
+
if (!existsSync(path)) return;
|
|
2109
|
+
try {
|
|
2110
|
+
const raw = JSON.parse(readFileSync3(path, "utf8"));
|
|
2111
|
+
const accessToken = nonEmpty(asString(raw.tokens?.access_token));
|
|
2112
|
+
const refreshToken = nonEmpty(asString(raw.tokens?.refresh_token));
|
|
2113
|
+
if (!accessToken && !refreshToken) return;
|
|
2114
|
+
if (options.force) {
|
|
2115
|
+
delete env["0SEC_CHATGPT_ACCESS_TOKEN"];
|
|
2116
|
+
delete env["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"];
|
|
2117
|
+
}
|
|
2118
|
+
if (accessToken) {
|
|
2119
|
+
env["0SEC_CHATGPT_ACCESS_TOKEN"] = accessToken;
|
|
2120
|
+
}
|
|
2121
|
+
if (refreshToken) {
|
|
2122
|
+
env["0SEC_CHATGPT_OAUTH_REFRESH_TOKEN"] = refreshToken;
|
|
2123
|
+
}
|
|
2124
|
+
} catch {
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
function nonEmpty(value) {
|
|
2128
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2129
|
+
}
|
|
2130
|
+
function asString(value) {
|
|
2131
|
+
return typeof value === "string" ? value : void 0;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
// packages/cli/src/presentation/event-bus.ts
|
|
2135
|
+
var PresentationEventBus = class {
|
|
2136
|
+
listeners = [];
|
|
2137
|
+
subscribe(listener) {
|
|
2138
|
+
this.listeners.push(listener);
|
|
2139
|
+
return () => {
|
|
2140
|
+
const index = this.listeners.indexOf(listener);
|
|
2141
|
+
if (index >= 0) this.listeners.splice(index, 1);
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
emit(event) {
|
|
2145
|
+
for (const listener of [...this.listeners]) {
|
|
2146
|
+
try {
|
|
2147
|
+
listener.emit(event);
|
|
2148
|
+
} catch {
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
get size() {
|
|
2153
|
+
return this.listeners.length;
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
var presentationEventBus = new PresentationEventBus();
|
|
2157
|
+
function createPresentationEmitter(options = {}) {
|
|
2158
|
+
const source = options.source ?? "cli";
|
|
2159
|
+
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
2160
|
+
const bus = options.bus ?? presentationEventBus;
|
|
2161
|
+
let sequence = 0;
|
|
2162
|
+
return {
|
|
2163
|
+
emit(eventType, payload, correlation = {}) {
|
|
2164
|
+
const event = createPresentationEvent({
|
|
2165
|
+
source,
|
|
2166
|
+
sequence: ++sequence,
|
|
2167
|
+
at: now(),
|
|
2168
|
+
eventType,
|
|
2169
|
+
payload,
|
|
2170
|
+
...correlation.scanId ? { scanId: correlation.scanId } : {},
|
|
2171
|
+
...correlation.sessionId ? { sessionId: correlation.sessionId } : {}
|
|
2172
|
+
});
|
|
2173
|
+
bus.emit(event);
|
|
2174
|
+
return event;
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
// packages/cli/src/presentation/process-output.ts
|
|
2180
|
+
import { format } from "node:util";
|
|
2181
|
+
var streamBridgeSuppressionDepth = 0;
|
|
2182
|
+
var activeStreamBridge = null;
|
|
2183
|
+
function currentPresentationTimestamp() {
|
|
2184
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2185
|
+
}
|
|
2186
|
+
function createProcessPresentationOutput(options = {}) {
|
|
2187
|
+
const stdout = options.stdout ?? { write: process.stdout.write.bind(process.stdout) };
|
|
2188
|
+
const stderr = options.stderr ?? { write: process.stderr.write.bind(process.stderr) };
|
|
2189
|
+
const source = options.source ?? "cli";
|
|
2190
|
+
const now = options.now ?? currentPresentationTimestamp;
|
|
2191
|
+
let sequence = 0;
|
|
2192
|
+
const write = (channel, target, text, eventType) => {
|
|
2193
|
+
const event = createPresentationEvent({
|
|
2194
|
+
source,
|
|
2195
|
+
sequence: ++sequence,
|
|
2196
|
+
at: now(),
|
|
2197
|
+
eventType,
|
|
2198
|
+
payload: { channel, text }
|
|
2199
|
+
});
|
|
2200
|
+
streamBridgeSuppressionDepth += 1;
|
|
2201
|
+
try {
|
|
2202
|
+
target.write(text);
|
|
2203
|
+
} finally {
|
|
2204
|
+
streamBridgeSuppressionDepth -= 1;
|
|
2205
|
+
}
|
|
2206
|
+
options.onEvent?.(event);
|
|
2207
|
+
return event;
|
|
2208
|
+
};
|
|
2209
|
+
return {
|
|
2210
|
+
stdout(text, eventType = "output.stdout") {
|
|
2211
|
+
return write("stdout", stdout, text, eventType);
|
|
2212
|
+
},
|
|
2213
|
+
stderr(text, eventType = "output.stderr") {
|
|
2214
|
+
return write("stderr", stderr, text, eventType);
|
|
2215
|
+
}
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
function createConsolePresentationOutput(options = {}) {
|
|
2219
|
+
const source = options.source ?? "cli";
|
|
2220
|
+
const now = options.now ?? currentPresentationTimestamp;
|
|
2221
|
+
let sequence = 0;
|
|
2222
|
+
const emit = (channel, text, eventType) => {
|
|
2223
|
+
const event = createPresentationEvent({
|
|
2224
|
+
source,
|
|
2225
|
+
sequence: ++sequence,
|
|
2226
|
+
at: now(),
|
|
2227
|
+
eventType,
|
|
2228
|
+
payload: { channel, text }
|
|
2229
|
+
});
|
|
2230
|
+
streamBridgeSuppressionDepth += 1;
|
|
2231
|
+
try {
|
|
2232
|
+
if (channel === "stdout") {
|
|
2233
|
+
(options.log ?? console.log)(text);
|
|
2234
|
+
} else {
|
|
2235
|
+
(options.error ?? console.error)(text);
|
|
2236
|
+
}
|
|
2237
|
+
} finally {
|
|
2238
|
+
streamBridgeSuppressionDepth -= 1;
|
|
2239
|
+
}
|
|
2240
|
+
options.onEvent?.(event);
|
|
2241
|
+
return event;
|
|
2242
|
+
};
|
|
2243
|
+
return {
|
|
2244
|
+
stdout(text, eventType = "output.stdout") {
|
|
2245
|
+
return emit("stdout", text, eventType);
|
|
2246
|
+
},
|
|
2247
|
+
stderr(text, eventType = "output.stderr") {
|
|
2248
|
+
return emit("stderr", text, eventType);
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
var consolePresentationOutput = createConsolePresentationOutput({
|
|
2253
|
+
onEvent(event) {
|
|
2254
|
+
presentationEventBus.emit(event);
|
|
2255
|
+
}
|
|
2256
|
+
});
|
|
2257
|
+
function installConsolePresentationBridge(options = {}) {
|
|
2258
|
+
const target = options.target ?? console;
|
|
2259
|
+
const originals = {
|
|
2260
|
+
log: target.log,
|
|
2261
|
+
info: target.info,
|
|
2262
|
+
warn: target.warn,
|
|
2263
|
+
error: target.error
|
|
2264
|
+
};
|
|
2265
|
+
const source = options.source ?? "cli";
|
|
2266
|
+
const now = options.now ?? currentPresentationTimestamp;
|
|
2267
|
+
let sequence = 0;
|
|
2268
|
+
const emit = (method, channel, args, eventType) => {
|
|
2269
|
+
const text = format(...args);
|
|
2270
|
+
if (streamBridgeSuppressionDepth > 0) {
|
|
2271
|
+
originals[method].call(target, text);
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
const event = createPresentationEvent({
|
|
2275
|
+
source,
|
|
2276
|
+
sequence: ++sequence,
|
|
2277
|
+
at: now(),
|
|
2278
|
+
eventType,
|
|
2279
|
+
payload: { channel, text }
|
|
2280
|
+
});
|
|
2281
|
+
streamBridgeSuppressionDepth += 1;
|
|
2282
|
+
try {
|
|
2283
|
+
originals[method].call(target, text);
|
|
2284
|
+
} finally {
|
|
2285
|
+
streamBridgeSuppressionDepth -= 1;
|
|
2286
|
+
}
|
|
2287
|
+
presentationEventBus.emit(event);
|
|
2288
|
+
options.onEvent?.(event);
|
|
2289
|
+
};
|
|
2290
|
+
const bridgedLog = (...args) => emit("log", "stdout", args, "output.console.log");
|
|
2291
|
+
const bridgedInfo = (...args) => emit("info", "stdout", args, "output.console.info");
|
|
2292
|
+
const bridgedWarn = (...args) => emit("warn", "stderr", args, "output.console.warn");
|
|
2293
|
+
const bridgedError = (...args) => emit("error", "stderr", args, "output.console.error");
|
|
2294
|
+
target.log = bridgedLog;
|
|
2295
|
+
target.info = bridgedInfo;
|
|
2296
|
+
target.warn = bridgedWarn;
|
|
2297
|
+
target.error = bridgedError;
|
|
2298
|
+
return {
|
|
2299
|
+
restore() {
|
|
2300
|
+
if (target.log === bridgedLog) target.log = originals.log;
|
|
2301
|
+
if (target.info === bridgedInfo) target.info = originals.info;
|
|
2302
|
+
if (target.warn === bridgedWarn) target.warn = originals.warn;
|
|
2303
|
+
if (target.error === bridgedError) target.error = originals.error;
|
|
2304
|
+
}
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
function installProcessPresentationStreamBridge(options = {}) {
|
|
2308
|
+
if (activeStreamBridge) return activeStreamBridge;
|
|
2309
|
+
const source = options.source ?? "cli";
|
|
2310
|
+
const now = options.now ?? currentPresentationTimestamp;
|
|
2311
|
+
let sequence = 0;
|
|
2312
|
+
let suspended = false;
|
|
2313
|
+
const streams = [];
|
|
2314
|
+
const emit = (channel, chunk, encoding) => {
|
|
2315
|
+
const text = typeof chunk === "string" ? chunk : chunk instanceof Uint8Array ? Buffer.from(chunk).toString(typeof encoding === "string" ? encoding : "utf8") : String(chunk ?? "");
|
|
2316
|
+
if (channel === "stdout" && text.startsWith("0SEC_EVENT_")) return;
|
|
2317
|
+
const event = createPresentationEvent({
|
|
2318
|
+
source,
|
|
2319
|
+
sequence: ++sequence,
|
|
2320
|
+
at: now(),
|
|
2321
|
+
eventType: channel === "stdout" ? "output.stream.stdout" : "output.stream.stderr",
|
|
2322
|
+
payload: { channel, text }
|
|
2323
|
+
});
|
|
2324
|
+
presentationEventBus.emit(event);
|
|
2325
|
+
options.onEvent?.(event);
|
|
2326
|
+
};
|
|
2327
|
+
for (const [channel, stream] of [
|
|
2328
|
+
["stdout", options.stdout ?? process.stdout],
|
|
2329
|
+
["stderr", options.stderr ?? process.stderr]
|
|
2330
|
+
]) {
|
|
2331
|
+
const original = stream.write;
|
|
2332
|
+
const patched = ((chunk, encoding, callback) => {
|
|
2333
|
+
const result = original.call(stream, chunk, encoding, callback);
|
|
2334
|
+
if (!suspended && streamBridgeSuppressionDepth === 0) emit(channel, chunk, encoding);
|
|
2335
|
+
return result;
|
|
2336
|
+
});
|
|
2337
|
+
streams.push({ channel, stream, original, patched });
|
|
2338
|
+
}
|
|
2339
|
+
const bridge = {
|
|
2340
|
+
suspend() {
|
|
2341
|
+
if (suspended) return;
|
|
2342
|
+
suspended = true;
|
|
2343
|
+
for (const item of streams) {
|
|
2344
|
+
if (item.stream.write === item.patched) item.stream.write = item.original;
|
|
2345
|
+
}
|
|
2346
|
+
},
|
|
2347
|
+
resume() {
|
|
2348
|
+
if (!suspended) return;
|
|
2349
|
+
suspended = false;
|
|
2350
|
+
for (const item of streams) {
|
|
2351
|
+
if (item.stream.write === item.original) item.stream.write = item.patched;
|
|
2352
|
+
}
|
|
2353
|
+
},
|
|
2354
|
+
restore() {
|
|
2355
|
+
for (const item of streams) {
|
|
2356
|
+
if (item.stream.write === item.patched) item.stream.write = item.original;
|
|
2357
|
+
}
|
|
2358
|
+
if (activeStreamBridge === bridge) activeStreamBridge = null;
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
for (const item of streams) item.stream.write = item.patched;
|
|
2362
|
+
activeStreamBridge = bridge;
|
|
2363
|
+
return bridge;
|
|
2364
|
+
}
|
|
2365
|
+
function suspendProcessPresentationStreamBridge() {
|
|
2366
|
+
activeStreamBridge?.suspend();
|
|
2367
|
+
}
|
|
2368
|
+
function resumeProcessPresentationStreamBridge() {
|
|
2369
|
+
activeStreamBridge?.resume();
|
|
2370
|
+
}
|
|
2371
|
+
var processPresentationOutput = createProcessPresentationOutput({
|
|
2372
|
+
onEvent(event) {
|
|
2373
|
+
presentationEventBus.emit(event);
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2376
|
+
function writePresentationLine(text, eventType = "output.stdout") {
|
|
2377
|
+
return processPresentationOutput.stdout(`${text}
|
|
2378
|
+
`, eventType);
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
export {
|
|
2382
|
+
KEYBINDINGS,
|
|
2383
|
+
keybindingsByCategory,
|
|
2384
|
+
parseChord,
|
|
2385
|
+
keyMatchesChord,
|
|
2386
|
+
effectiveChords,
|
|
2387
|
+
matchesBinding,
|
|
2388
|
+
assessChordAssignment,
|
|
2389
|
+
resolveSyntaxColors,
|
|
2390
|
+
parseHex,
|
|
2391
|
+
readableOnPrimary,
|
|
2392
|
+
THEME_NAMES,
|
|
2393
|
+
DEFAULT_THEME_NAME,
|
|
2394
|
+
THEMES,
|
|
2395
|
+
isThemeName,
|
|
2396
|
+
getTheme,
|
|
2397
|
+
getThemeEntry,
|
|
2398
|
+
severityToneFor,
|
|
2399
|
+
validateTheme,
|
|
2400
|
+
detectColorDepth,
|
|
2401
|
+
degradePalette,
|
|
2402
|
+
installedThemesDir,
|
|
2403
|
+
isSafeThemeId,
|
|
2404
|
+
reloadInstalledThemes,
|
|
2405
|
+
installedThemeEntries,
|
|
2406
|
+
isKnownTheme,
|
|
2407
|
+
writeInstalledTheme,
|
|
2408
|
+
removeInstalledTheme,
|
|
2409
|
+
SETTING_DEFS,
|
|
2410
|
+
DEFAULT_SETTINGS,
|
|
2411
|
+
settingsFilePath,
|
|
2412
|
+
projectSettingsFilePath,
|
|
2413
|
+
loadLayeredSettings,
|
|
2414
|
+
normalizeSettings,
|
|
2415
|
+
loadSettings,
|
|
2416
|
+
loadGlobalSettings,
|
|
2417
|
+
saveSettings,
|
|
2418
|
+
sanitizeOverrides,
|
|
2419
|
+
readProjectOverrides,
|
|
2420
|
+
saveProjectOverrides,
|
|
2421
|
+
getSettings,
|
|
2422
|
+
updateSetting,
|
|
2423
|
+
resetSettings,
|
|
2424
|
+
previewSetting,
|
|
2425
|
+
subscribeSettings,
|
|
2426
|
+
reloadSettings,
|
|
2427
|
+
configureSettingsStore,
|
|
2428
|
+
useSettings,
|
|
2429
|
+
maybeLoadCodexAuth,
|
|
2430
|
+
presentationEventBus,
|
|
2431
|
+
createPresentationEmitter,
|
|
2432
|
+
consolePresentationOutput,
|
|
2433
|
+
installConsolePresentationBridge,
|
|
2434
|
+
installProcessPresentationStreamBridge,
|
|
2435
|
+
suspendProcessPresentationStreamBridge,
|
|
2436
|
+
resumeProcessPresentationStreamBridge,
|
|
2437
|
+
processPresentationOutput,
|
|
2438
|
+
writePresentationLine
|
|
2439
|
+
};
|