@bakapiano/ccsm 0.21.0 → 0.21.2
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/lib/codexSeed.js +36 -8
- package/lib/codexThemes/ccsm-light.tmTheme +77 -0
- package/lib/config.js +16 -1
- package/lib/webTerminal.js +7 -1
- package/package.json +1 -1
- package/public/js/api.js +5 -1
- package/public/js/components/TerminalView.js +24 -0
- package/public/js/pages/ConfigurePage.js +1 -1
- package/public/js/pages/LaunchPage.js +1 -1
- package/public/js/streaming.js +4 -1
- package/server.js +61 -5
package/lib/codexSeed.js
CHANGED
|
@@ -52,16 +52,23 @@ function execWithTimeout(exe, args, { timeoutMs = 8000 } = {}) {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
// the
|
|
59
|
-
//
|
|
60
|
-
//
|
|
55
|
+
// Pull CODEX_HOME out of a wrapper's `doctor` (or `--version`) output. Two
|
|
56
|
+
// shapes appear, and we must handle BOTH:
|
|
57
|
+
// 1. Diagnostic table: `CODEX_HOME <path> (dir)` — only when `doctor`
|
|
58
|
+
// fully succeeds. Variable whitespace; the `(dir)`/`(file)` suffix marks
|
|
59
|
+
// the path end.
|
|
60
|
+
// 2. Wrapper banner: `CODEX_HOME=<path>` — printed on EVERY invocation.
|
|
61
|
+
// Critical because in ccsm's non-interactive spawn `doctor` often exits
|
|
62
|
+
// non-zero (skipping the table) yet still prints this banner line, so it's
|
|
63
|
+
// the reliable source. Without it the probe returns null and the seed
|
|
64
|
+
// lands in ~/.codex instead of the wrapper's relocated home, breaking
|
|
65
|
+
// `resume <id>` ("No saved session found with ID …").
|
|
66
|
+
// Some wrappers colour the label (`\x1b[7mCODEX_HOME\x1b[0m`); strip ANSI first.
|
|
61
67
|
function parseCodexHomeFromDoctor(text) {
|
|
62
68
|
if (!text) return null;
|
|
63
69
|
const clean = String(text).replace(/\x1b\[[0-9;]*m/g, '');
|
|
64
|
-
|
|
70
|
+
let m = clean.match(/\bCODEX_HOME\s+(.+?)\s*\((?:dir|file)\)/); // table form
|
|
71
|
+
if (!m) m = clean.match(/\bCODEX_HOME=(.+?)\s*$/m); // banner form
|
|
65
72
|
if (!m) return null;
|
|
66
73
|
const p = m[1].trim();
|
|
67
74
|
return p || null;
|
|
@@ -151,5 +158,26 @@ async function seedCodexSession({ id, cwd, cli }) {
|
|
|
151
158
|
return file;
|
|
152
159
|
}
|
|
153
160
|
|
|
154
|
-
|
|
161
|
+
// Copy ccsm's bundled light codex syntax theme into the codex home's themes/
|
|
162
|
+
// dir so `-c tui.theme=ccsm-light` resolves. This theme carries light
|
|
163
|
+
// markup.inserted/deleted backgrounds, which at true-color level override
|
|
164
|
+
// codex's diff palette — the only way to get a LIGHT diff on Windows, where
|
|
165
|
+
// codex's own background detection (default_bg()) is compiled out and always
|
|
166
|
+
// falls back to a dark diff. Idempotent (writes only when missing/changed).
|
|
167
|
+
async function ensureCodexLightTheme(home) {
|
|
168
|
+
if (!home) return false;
|
|
169
|
+
const src = path.join(__dirname, 'codexThemes', 'ccsm-light.tmTheme');
|
|
170
|
+
const dstDir = path.join(home, 'themes');
|
|
171
|
+
const dst = path.join(dstDir, 'ccsm-light.tmTheme');
|
|
172
|
+
try {
|
|
173
|
+
const content = await fs.readFile(src, 'utf8');
|
|
174
|
+
await fs.mkdir(dstDir, { recursive: true });
|
|
175
|
+
let existing = null;
|
|
176
|
+
try { existing = await fs.readFile(dst, 'utf8'); } catch {}
|
|
177
|
+
if (existing !== content) await fs.writeFile(dst, content, 'utf8');
|
|
178
|
+
return true;
|
|
179
|
+
} catch { return false; }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = { seedCodexSession, probeCodexHome, parseCodexHomeFromDoctor, ensureCodexLightTheme };
|
|
155
183
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<!-- ccsm-light: a GitHub-Light-flavoured codex syntax theme whose
|
|
4
|
+
markup.inserted / markup.deleted scopes carry LIGHT diff backgrounds.
|
|
5
|
+
ccsm injects this (via `-c tui.theme=ccsm-light`) when the ccsm terminal
|
|
6
|
+
is in light mode, because codex's own diff theme detection (default_bg())
|
|
7
|
+
is compiled out on Windows and always falls back to a DARK diff palette —
|
|
8
|
+
which buries readability on a white terminal. At true-color level codex
|
|
9
|
+
lets a syntax theme's markup scope backgrounds override that default, so
|
|
10
|
+
these two scopes are what flip the diff to light. -->
|
|
11
|
+
<plist version="1.0">
|
|
12
|
+
<dict>
|
|
13
|
+
<key>name</key>
|
|
14
|
+
<string>ccsm-light</string>
|
|
15
|
+
<key>settings</key>
|
|
16
|
+
<array>
|
|
17
|
+
<dict>
|
|
18
|
+
<key>settings</key>
|
|
19
|
+
<dict>
|
|
20
|
+
<key>background</key><string>#ffffff</string>
|
|
21
|
+
<key>foreground</key><string>#1f2328</string>
|
|
22
|
+
<key>caret</key><string>#1f2328</string>
|
|
23
|
+
<key>selection</key><string>#b6d6fd</string>
|
|
24
|
+
<key>lineHighlight</key><string>#eaeef2</string>
|
|
25
|
+
</dict>
|
|
26
|
+
</dict>
|
|
27
|
+
<dict>
|
|
28
|
+
<key>scope</key><string>comment, punctuation.definition.comment</string>
|
|
29
|
+
<key>settings</key><dict><key>foreground</key><string>#6e7781</string></dict>
|
|
30
|
+
</dict>
|
|
31
|
+
<dict>
|
|
32
|
+
<key>scope</key><string>string, string.quoted, punctuation.definition.string</string>
|
|
33
|
+
<key>settings</key><dict><key>foreground</key><string>#0a3069</string></dict>
|
|
34
|
+
</dict>
|
|
35
|
+
<dict>
|
|
36
|
+
<key>scope</key><string>constant.numeric, constant.language, constant.character, constant.other</string>
|
|
37
|
+
<key>settings</key><dict><key>foreground</key><string>#0550ae</string></dict>
|
|
38
|
+
</dict>
|
|
39
|
+
<dict>
|
|
40
|
+
<key>scope</key><string>keyword, keyword.control, keyword.operator, storage, storage.type, storage.modifier</string>
|
|
41
|
+
<key>settings</key><dict><key>foreground</key><string>#cf222e</string></dict>
|
|
42
|
+
</dict>
|
|
43
|
+
<dict>
|
|
44
|
+
<key>scope</key><string>entity.name.function, support.function, meta.function-call</string>
|
|
45
|
+
<key>settings</key><dict><key>foreground</key><string>#8250df</string></dict>
|
|
46
|
+
</dict>
|
|
47
|
+
<dict>
|
|
48
|
+
<key>scope</key><string>entity.name.type, entity.name.class, entity.name.namespace, support.type, support.class</string>
|
|
49
|
+
<key>settings</key><dict><key>foreground</key><string>#953800</string></dict>
|
|
50
|
+
</dict>
|
|
51
|
+
<dict>
|
|
52
|
+
<key>scope</key><string>variable, variable.other, variable.parameter</string>
|
|
53
|
+
<key>settings</key><dict><key>foreground</key><string>#1f2328</string></dict>
|
|
54
|
+
</dict>
|
|
55
|
+
<dict>
|
|
56
|
+
<key>scope</key><string>entity.name.tag, support.type.property-name</string>
|
|
57
|
+
<key>settings</key><dict><key>foreground</key><string>#116329</string></dict>
|
|
58
|
+
</dict>
|
|
59
|
+
<dict>
|
|
60
|
+
<key>scope</key><string>markup.inserted, markup.inserted.diff, meta.diff.header.to-file</string>
|
|
61
|
+
<key>settings</key>
|
|
62
|
+
<dict>
|
|
63
|
+
<key>background</key><string>#e6ffec</string>
|
|
64
|
+
<key>foreground</key><string>#1a7f37</string>
|
|
65
|
+
</dict>
|
|
66
|
+
</dict>
|
|
67
|
+
<dict>
|
|
68
|
+
<key>scope</key><string>markup.deleted, markup.deleted.diff, meta.diff.header.from-file</string>
|
|
69
|
+
<key>settings</key>
|
|
70
|
+
<dict>
|
|
71
|
+
<key>background</key><string>#ffebe9</string>
|
|
72
|
+
<key>foreground</key><string>#cf222e</string>
|
|
73
|
+
</dict>
|
|
74
|
+
</dict>
|
|
75
|
+
</array>
|
|
76
|
+
</dict>
|
|
77
|
+
</plist>
|
package/lib/config.js
CHANGED
|
@@ -56,8 +56,13 @@ const DEFAULT_CLIS = [
|
|
|
56
56
|
name: 'GitHub Copilot',
|
|
57
57
|
command: 'copilot',
|
|
58
58
|
args: [],
|
|
59
|
+
// copilot has no `--resume <id>` (space) flag — that parses as `--resume`
|
|
60
|
+
// (valueless → session picker) plus a stray positional arg, which hangs on
|
|
61
|
+
// a blank screen. `--session-id <id>` both creates AND resumes a session by
|
|
62
|
+
// id (`copilot --help`: "Resume an existing session or task by ID"), so we
|
|
63
|
+
// use it for both new and resume.
|
|
59
64
|
newSessionIdArgs: ['--session-id', '<id>'],
|
|
60
|
-
resumeIdArgs: ['--
|
|
65
|
+
resumeIdArgs: ['--session-id', '<id>'],
|
|
61
66
|
shell: 'direct',
|
|
62
67
|
type: 'copilot',
|
|
63
68
|
builtin: true,
|
|
@@ -149,6 +154,16 @@ function mergeWithDefaults(partial) {
|
|
|
149
154
|
const needsBackfill = (v) => v == null || (Array.isArray(v) && v.length === 0);
|
|
150
155
|
if (needsBackfill(existing.resumeIdArgs)) existing.resumeIdArgs = def.resumeIdArgs;
|
|
151
156
|
if (needsBackfill(existing.newSessionIdArgs)) existing.newSessionIdArgs = def.newSessionIdArgs;
|
|
157
|
+
// Heal the old broken copilot resume flag: `--resume <id>` (space form)
|
|
158
|
+
// isn't valid copilot syntax — it parses as a valueless `--resume`
|
|
159
|
+
// (session picker) plus a stray positional, hanging on a blank screen.
|
|
160
|
+
// The builtin now uses `--session-id <id>`. Migrate configs still
|
|
161
|
+
// carrying the old default; genuine user customisations are left alone.
|
|
162
|
+
if (existing.id === 'copilot'
|
|
163
|
+
&& Array.isArray(existing.resumeIdArgs)
|
|
164
|
+
&& existing.resumeIdArgs.join(' ') === '--resume <id>') {
|
|
165
|
+
existing.resumeIdArgs = def.resumeIdArgs;
|
|
166
|
+
}
|
|
152
167
|
// Drop the v0.x `resumeArgs` fallback — every builtin now has a
|
|
153
168
|
// pre-assigned UUID (claude/copilot via flag, codex via seed) so
|
|
154
169
|
// resumeIdArgs always applies on resume; the field is dead weight.
|
package/lib/webTerminal.js
CHANGED
|
@@ -54,7 +54,13 @@ function spawn({ command, args = [], cwd, env, cols = 120, rows = 30, meta = {},
|
|
|
54
54
|
name: 'xterm-256color',
|
|
55
55
|
cols, rows,
|
|
56
56
|
cwd: cwd ? path.resolve(cwd) : process.cwd(),
|
|
57
|
-
|
|
57
|
+
// Advertise 24-bit colour. xterm.js renders truecolor, and CLIs like
|
|
58
|
+
// claude paint their diffs with 24-bit DARK red/green backgrounds +
|
|
59
|
+
// white text when COLORTERM says so. Without it they downgrade to the
|
|
60
|
+
// bright ANSI-16 red/green backgrounds, which wash the white foreground
|
|
61
|
+
// out (the "white text gets buried" report). VSCode sets the same on its
|
|
62
|
+
// terminal PTYs; our ANSI-16 palette already matches VSCode's verbatim.
|
|
63
|
+
env: { ...process.env, ...(env || {}), COLORTERM: 'truecolor' },
|
|
58
64
|
};
|
|
59
65
|
if (process.platform === 'win32') {
|
|
60
66
|
ptyOpts.useConpty = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bakapiano/ccsm",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"description": "Claude Code Session Manager — Windows web UI to manage many concurrent claude sessions: live list, snapshot/restore, focus existing window, new session in an isolated workspace with repo clones",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "server.js",
|
package/public/js/api.js
CHANGED
|
@@ -282,7 +282,11 @@ export function resumeSession(sessionId) {
|
|
|
282
282
|
const cached = resumeInFlight.get(sessionId);
|
|
283
283
|
if (cached) return cached;
|
|
284
284
|
const p = (async () => {
|
|
285
|
-
const r = await api('POST', `/api/sessions/${sessionId}/resume
|
|
285
|
+
const r = await api('POST', `/api/sessions/${sessionId}/resume`, {
|
|
286
|
+
// Resolved terminal theme → backend sets a matching COLORFGBG so the
|
|
287
|
+
// CLI's light/dark auto-detection follows the ccsm terminal.
|
|
288
|
+
theme: document.documentElement.dataset.theme,
|
|
289
|
+
});
|
|
286
290
|
await loadSessions();
|
|
287
291
|
return r.launched;
|
|
288
292
|
})();
|
|
@@ -183,6 +183,30 @@ export function TerminalView({ terminalId, cliType }) {
|
|
|
183
183
|
|
|
184
184
|
const host = hostRef.current;
|
|
185
185
|
term.open(host);
|
|
186
|
+
|
|
187
|
+
// Answer OSC 10/11 (default foreground / background colour) queries with
|
|
188
|
+
// the LIVE theme colours. CLIs like claude probe the terminal background
|
|
189
|
+
// (`OSC 11 ; ? ST`) to pick a light- or dark-tuned syntax theme. xterm.js
|
|
190
|
+
// doesn't answer these by default, so claude assumes a dark terminal and
|
|
191
|
+
// paints near-white tokens (comments, f-string interpolations, call
|
|
192
|
+
// names) that vanish on our light background — the "字体颜色和背景重复"
|
|
193
|
+
// bug. VSCode answers them; we match. Reply format is the xterm/X11
|
|
194
|
+
// `rgb:RRRR/GGGG/BBBB` (16-bit-per-channel) the query expects.
|
|
195
|
+
const answerColorOsc = (code, getHex) => (data) => {
|
|
196
|
+
if (data !== '?') return false; // only the query form
|
|
197
|
+
const hex = getHex(); // '#rrggbb'
|
|
198
|
+
const ch = (i) => parseInt(hex.slice(i, i + 2), 16);
|
|
199
|
+
const w = (v) => (v * 257).toString(16).padStart(4, '0'); // 8-bit → 16-bit
|
|
200
|
+
const reply = `\x1b]${code};rgb:${w(ch(1))}/${w(ch(3))}/${w(ch(5))}\x07`;
|
|
201
|
+
const ws = wsRef.current;
|
|
202
|
+
if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'input', data: reply }));
|
|
203
|
+
return true;
|
|
204
|
+
};
|
|
205
|
+
try {
|
|
206
|
+
term.parser.registerOscHandler(11, answerColorOsc(11, () => themeRef.current.background));
|
|
207
|
+
term.parser.registerOscHandler(10, answerColorOsc(10, () => themeRef.current.foreground));
|
|
208
|
+
} catch {}
|
|
209
|
+
|
|
186
210
|
// Robust fit scheduler. A single requestAnimationFrame works most
|
|
187
211
|
// of the time but races on tab/session switches: the .tab-panel
|
|
188
212
|
// just flipped from display:none to display:flex and although the
|
|
@@ -47,7 +47,7 @@ function tokenizeCliArgs(v) {
|
|
|
47
47
|
const CLI_TYPE_DEFAULTS = {
|
|
48
48
|
claude: { command: 'claude', resumeIdArgs: '--resume <id>', newSessionIdArgs: '--session-id <id>' },
|
|
49
49
|
codex: { command: 'codex', resumeIdArgs: 'resume <id>', newSessionIdArgs: 'resume <id>' },
|
|
50
|
-
copilot: { command: 'copilot', resumeIdArgs: '--
|
|
50
|
+
copilot: { command: 'copilot', resumeIdArgs: '--session-id <id>', newSessionIdArgs: '--session-id <id>' },
|
|
51
51
|
other: { resumeIdArgs: '', newSessionIdArgs: '' },
|
|
52
52
|
};
|
|
53
53
|
|
|
@@ -171,7 +171,7 @@ function LaunchHero() {
|
|
|
171
171
|
onChange: (v, next) => {
|
|
172
172
|
const presets = { claude: { command: 'claude', resumeArgs: '--continue', resumeIdArgs: '--resume <id>', name: 'Claude Code' },
|
|
173
173
|
codex: { command: 'codex', resumeArgs: 'resume --last', resumeIdArgs: 'resume <id>', name: 'OpenAI Codex' },
|
|
174
|
-
copilot: { command: 'copilot', resumeArgs: '--continue', resumeIdArgs: '--
|
|
174
|
+
copilot: { command: 'copilot', resumeArgs: '--continue', resumeIdArgs: '--session-id <id>', name: 'GitHub Copilot' },
|
|
175
175
|
other: {} }[v] || {};
|
|
176
176
|
const patch = {};
|
|
177
177
|
if (presets.resumeArgs != null) patch.resumeArgs = presets.resumeArgs;
|
package/public/js/streaming.js
CHANGED
|
@@ -60,10 +60,13 @@ function applyEvent(ev, rootId) {
|
|
|
60
60
|
// onMeta(event) is called for workspace/launched/done events so the caller can
|
|
61
61
|
// surface them in their own result text area.
|
|
62
62
|
export async function streamNewSession(body, { progressRootId = 'newSessionProgress', onMeta } = {}) {
|
|
63
|
+
// Pass the resolved terminal theme so the backend can hand CLIs a matching
|
|
64
|
+
// COLORFGBG (light/dark detection). dataset.theme is set by applyTheme().
|
|
65
|
+
const theme = document.documentElement.dataset.theme;
|
|
63
66
|
const res = await fetch(httpBase() + '/api/sessions/new', {
|
|
64
67
|
method: 'POST',
|
|
65
68
|
headers: { 'Content-Type': 'application/json' },
|
|
66
|
-
body: JSON.stringify(body),
|
|
69
|
+
body: JSON.stringify({ ...body, theme }),
|
|
67
70
|
});
|
|
68
71
|
if (!res.ok && res.headers.get('content-type')?.startsWith('application/json')) {
|
|
69
72
|
const j = await res.json();
|
package/server.js
CHANGED
|
@@ -281,7 +281,7 @@ function quoteForCmd(s) {
|
|
|
281
281
|
return s;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
function spawnCliSession({ cli, cwd, sessionId, meta, extraArgs = [] }) {
|
|
284
|
+
function spawnCliSession({ cli, cwd, sessionId, meta, extraArgs = [], theme }) {
|
|
285
285
|
if (!webTerminal.available) {
|
|
286
286
|
const e = new Error('node-pty unavailable · cannot spawn web terminal');
|
|
287
287
|
e.code = 'PTY_UNAVAILABLE';
|
|
@@ -291,19 +291,46 @@ function spawnCliSession({ cli, cwd, sessionId, meta, extraArgs = [] }) {
|
|
|
291
291
|
// extraArgs into the single quoted command string — otherwise extraArgs
|
|
292
292
|
// would become args to the shell itself, not the wrapped command.
|
|
293
293
|
// Re-resolve here when extraArgs is present so the quoting is correct.
|
|
294
|
+
// Force a session-scoped theme=auto for claude so its syntax/diff colours
|
|
295
|
+
// follow the ccsm terminal background (which we report via OSC 10/11 in
|
|
296
|
+
// TerminalView). claude's DEFAULT theme is *dark*, whose near-white tokens
|
|
297
|
+
// (comments, f-string interpolations, call names) vanish on our light
|
|
298
|
+
// terminal — the "字体颜色和背景重复" bug. --settings is session-scoped, so
|
|
299
|
+
// the user's global ~/.claude/settings.json is left untouched, and ccsm
|
|
300
|
+
// sessions Just Work on a fresh machine without anyone running /theme auto.
|
|
301
|
+
// (Injected here as an integration arg, like --session-id — not via the
|
|
302
|
+
// user-editable cli.args, so it reaches existing configs too.)
|
|
303
|
+
// Skip the injection entirely if the user already put their own --settings
|
|
304
|
+
// in cli.args — claude deep-merges multiple --settings (verified: later ones
|
|
305
|
+
// win per-key), so ours would silently override a theme they set on purpose.
|
|
306
|
+
// Respect the user's explicit choice instead.
|
|
307
|
+
const userHasSettings = (cli.args || []).some(
|
|
308
|
+
(a) => a === '--settings' || String(a).startsWith('--settings='));
|
|
309
|
+
const baseArgs = [...(cli.args || [])];
|
|
310
|
+
if (cli.type === 'claude' && !userHasSettings) baseArgs.push('--settings', '{"theme":"auto"}');
|
|
294
311
|
const resolved = resolveCommand(
|
|
295
312
|
cli.command,
|
|
296
|
-
[...
|
|
313
|
+
[...baseArgs, ...extraArgs],
|
|
297
314
|
cli.shell || 'direct',
|
|
298
315
|
);
|
|
299
316
|
const { exe, prefixArgs, fallbackExe, consumesUserArgs } = resolved;
|
|
300
317
|
const args = consumesUserArgs
|
|
301
318
|
? prefixArgs
|
|
302
|
-
: [...prefixArgs, ...
|
|
319
|
+
: [...prefixArgs, ...baseArgs, ...extraArgs];
|
|
303
320
|
// Merge user-scope PATH from registry into the env we hand the PTY.
|
|
304
321
|
// spawnEnv() also strips duplicate path-case keys so our override
|
|
305
322
|
// doesn't get shadowed by the inherited `Path` from process.env.
|
|
306
323
|
const env = spawnEnv(cli.env);
|
|
324
|
+
// Tell background-aware CLIs which way the ccsm terminal is painted, so
|
|
325
|
+
// their light/dark auto-detection matches it. COLORFGBG (fg;bg ANSI indices)
|
|
326
|
+
// is the de-facto signal that codex (its DiffTheme probes it), copilot, and
|
|
327
|
+
// claude all read — bg 15 = light, 0 = dark. claude additionally gets OSC
|
|
328
|
+
// 10/11 answers + --settings auto; this covers codex/copilot, which detect
|
|
329
|
+
// via COLORFGBG, not OSC. The frontend passes its resolved theme on spawn;
|
|
330
|
+
// a theme switch is picked up on the next resume.
|
|
331
|
+
if (theme === 'light' || theme === 'dark') {
|
|
332
|
+
env.COLORFGBG = theme === 'light' ? '0;15' : '15;0';
|
|
333
|
+
}
|
|
307
334
|
const trySpawn = (executable) => webTerminal.spawn({
|
|
308
335
|
id: sessionId,
|
|
309
336
|
command: executable,
|
|
@@ -832,12 +859,14 @@ app.post('/api/sessions/new', async (req, res) => {
|
|
|
832
859
|
cliSessionId: preAssignedId || undefined,
|
|
833
860
|
});
|
|
834
861
|
try {
|
|
862
|
+
const themeArgs = await codexThemeArgs(cli, req.body && req.body.theme);
|
|
835
863
|
const entry = spawnCliSession({
|
|
836
864
|
cli,
|
|
837
865
|
cwd: workspace.path,
|
|
838
866
|
sessionId: record.id,
|
|
839
867
|
meta: { title: workspace.name, workspace: workspace.name, cwd: workspace.path },
|
|
840
|
-
extraArgs: newSessionArgs,
|
|
868
|
+
extraArgs: [...themeArgs, ...newSessionArgs],
|
|
869
|
+
theme: req.body && req.body.theme,
|
|
841
870
|
});
|
|
842
871
|
await persistedSessions.markRunning(record.id, entry.meta.pid);
|
|
843
872
|
launched = { id: record.id, pid: entry.meta.pid, cliId: cli.id };
|
|
@@ -966,13 +995,15 @@ app.post('/api/sessions/:id/resume', asyncH(async (req, res) => {
|
|
|
966
995
|
// pre-assignment refactor every ccsm-launched session has one (via
|
|
967
996
|
// newSessionIdArgs flag or the codex seed trick), and adopted
|
|
968
997
|
// sessions inherit theirs from the disk scan.
|
|
998
|
+
const themeArgs = await codexThemeArgs(cli, req.body && req.body.theme);
|
|
969
999
|
const extraArgs = buildResumeArgs(cli, record);
|
|
970
1000
|
const entry = spawnCliSession({
|
|
971
1001
|
cli,
|
|
972
1002
|
cwd: record.cwd,
|
|
973
1003
|
sessionId: record.id,
|
|
974
1004
|
meta: { title: record.title || record.workspace, workspace: record.workspace, cwd: record.cwd },
|
|
975
|
-
extraArgs,
|
|
1005
|
+
extraArgs: [...themeArgs, ...extraArgs],
|
|
1006
|
+
theme: req.body && req.body.theme,
|
|
976
1007
|
});
|
|
977
1008
|
await persistedSessions.markRunning(record.id, entry.meta.pid);
|
|
978
1009
|
res.json({ launched: { id: record.id, pid: entry.meta.pid, cliId: cli.id } });
|
|
@@ -981,6 +1012,31 @@ app.post('/api/sessions/:id/resume', asyncH(async (req, res) => {
|
|
|
981
1012
|
}
|
|
982
1013
|
}));
|
|
983
1014
|
|
|
1015
|
+
// codex-only: when the ccsm terminal is in LIGHT mode, inject a session-scoped
|
|
1016
|
+
// `-c tui.theme=ccsm-light`. codex's diff theme detection (default_bg()) is
|
|
1017
|
+
// compiled out on Windows and always falls back to a DARK diff palette, which
|
|
1018
|
+
// reads poorly on a white terminal — and it ignores COLORFGBG/OSC. The only
|
|
1019
|
+
// lever is a syntax theme whose markup.inserted/deleted scopes carry light
|
|
1020
|
+
// backgrounds (they override the diff palette at true-color level). We ship
|
|
1021
|
+
// that theme (ccsm-light.tmTheme), copy it into the codex home, and point
|
|
1022
|
+
// tui.theme at it. Returns the args to prepend (before `resume <id>` so the
|
|
1023
|
+
// global -c lands before the subcommand), or [] when not applicable. Skipped
|
|
1024
|
+
// in dark mode (codex's dark default is already correct on a dark terminal)
|
|
1025
|
+
// and when the user configured their own tui.theme in cli.args.
|
|
1026
|
+
async function codexThemeArgs(cli, theme) {
|
|
1027
|
+
if (!cli || cli.type !== 'codex' || theme !== 'light') return [];
|
|
1028
|
+
const args = cli.args || [];
|
|
1029
|
+
const userSet = args.some((a, i) =>
|
|
1030
|
+
String(a).includes('tui.theme') || (a === '-c' && String(args[i + 1] || '').includes('tui.theme')));
|
|
1031
|
+
if (userSet) return [];
|
|
1032
|
+
try {
|
|
1033
|
+
const { probeCodexHome, ensureCodexLightTheme } = require('./lib/codexSeed');
|
|
1034
|
+
const home = await probeCodexHome({ command: cli.command, shell: cli.shell });
|
|
1035
|
+
if (!(await ensureCodexLightTheme(home))) return [];
|
|
1036
|
+
return ['-c', 'tui.theme="ccsm-light"'];
|
|
1037
|
+
} catch { return []; }
|
|
1038
|
+
}
|
|
1039
|
+
|
|
984
1040
|
// Build the args appended on resume: substitute the captured upstream
|
|
985
1041
|
// session UUID into cli.resumeIdArgs (e.g. ['--resume', '<id>'] →
|
|
986
1042
|
// ['--resume', '7c28...']). Throws if either piece is missing — by
|