@bakapiano/ccsm 0.22.6 → 0.22.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CLAUDE.md +521 -540
  2. package/README.md +186 -189
  3. package/bin/ccsm.js +235 -235
  4. package/lib/cliActivity.js +36 -139
  5. package/lib/codexSeed.js +126 -183
  6. package/lib/config.js +277 -274
  7. package/lib/devices.js +229 -229
  8. package/lib/folders.js +124 -124
  9. package/lib/persistedSessions.js +179 -139
  10. package/lib/tunnel.js +621 -621
  11. package/lib/webTerminal.js +225 -225
  12. package/lib/winPath.js +1 -1
  13. package/lib/workspace.js +233 -233
  14. package/package.json +57 -57
  15. package/public/css/base.css +99 -99
  16. package/public/css/cards.css +183 -183
  17. package/public/css/feedback.css +504 -504
  18. package/public/css/forms.css +453 -453
  19. package/public/css/layout.css +154 -154
  20. package/public/css/modal.css +190 -190
  21. package/public/css/responsive.css +176 -176
  22. package/public/css/sidebar.css +707 -707
  23. package/public/css/terminals.css +546 -546
  24. package/public/css/tokens.css +81 -81
  25. package/public/css/wco.css +196 -196
  26. package/public/css/widgets.css +2347 -2725
  27. package/public/index.html +152 -152
  28. package/public/js/api.js +349 -371
  29. package/public/js/backend.js +149 -149
  30. package/public/js/components/App.js +73 -73
  31. package/public/js/components/DirectoryPicker.js +203 -203
  32. package/public/js/components/EntityFormModal.js +153 -153
  33. package/public/js/components/Modal.js +57 -57
  34. package/public/js/components/OfflineBanner.js +67 -67
  35. package/public/js/components/PageTitleBar.js +13 -13
  36. package/public/js/components/PendingApprovalOverlay.js +128 -128
  37. package/public/js/components/Picker.js +179 -179
  38. package/public/js/components/Popover.js +55 -55
  39. package/public/js/components/RestartOverlay.js +36 -36
  40. package/public/js/components/Sidebar.js +380 -380
  41. package/public/js/components/TerminalInstance.js +28 -0
  42. package/public/js/components/useDragSort.js +67 -67
  43. package/public/js/dialog.js +67 -67
  44. package/public/js/icons.js +212 -212
  45. package/public/js/main.js +296 -296
  46. package/public/js/pages/AboutPage.js +90 -90
  47. package/public/js/pages/ConfigurePage.js +730 -713
  48. package/public/js/pages/LaunchPage.js +403 -421
  49. package/public/js/pages/RemotePage.js +743 -743
  50. package/public/js/pages/SessionsPage.js +54 -54
  51. package/public/js/state.js +335 -335
  52. package/public/js/util.js +1 -1
  53. package/scripts/dev.js +149 -149
  54. package/scripts/install.js +153 -153
  55. package/scripts/restart-helper.js +96 -96
  56. package/scripts/upgrade-helper.js +687 -687
  57. package/server.js +1748 -1817
  58. package/lib/localCliSessions.js +0 -519
  59. package/public/js/components/AdoptModal.js +0 -261
  60. package/public/manifest.webmanifest +0 -25
  61. package/public/setup/index.html +0 -567
package/lib/codexSeed.js CHANGED
@@ -1,183 +1,126 @@
1
- 'use strict';
2
-
3
- // Seed a fake codex rollout file so `codex resume <uuid>` works from the
4
- // VERY FIRST launch the same trick claude/copilot's `--session-id` flag
5
- // gives us natively. codex has no equivalent flag; its only "set the id"
6
- // surface is `resume <SESSION_ID>` against a file that already exists on
7
- // disk. We pre-write that file with one `session_meta` line carrying the
8
- // id + cwd ccsm pre-assigned, then spawn `codex resume <id>`. Codex picks
9
- // up our seed and appends its actual conversation events to it.
10
- //
11
- // Path layout (matches codex's own scheme):
12
- // ~/.codex/sessions/YYYY/MM/DD/rollout-<iso-ts>-<uuid>.jsonl
13
- //
14
- // Filename timestamp uses dashes-only (codex's convention), but it's
15
- // purely cosmetic codex looks up sessions by UUID, not filename.
16
- //
17
- // CODEX_HOME resolution. Some wrappers relocate CODEX_HOME to a
18
- // non-default dir (e.g. %LOCALAPPDATA%\<wrapper>\codex-home) so the seed has
19
- // to land there or `resume <id>` won't find it. We probe by running
20
- // `<cli.command> doctor` once per (command, shell) pair and parsing the
21
- // "CODEX_HOME ... (dir)" line out of its output. Cached for the life of
22
- // the process.
23
-
24
- const fs = require('node:fs/promises');
25
- const path = require('node:path');
26
- const os = require('node:os');
27
- const { execFile } = require('node:child_process');
28
- const { spawnEnv } = require('./winPath');
29
-
30
- function isoForFilename(d = new Date()) {
31
- // 2026-05-25T15:39:11 → 2026-05-25T15-39-11 (codex strips ms + colons)
32
- return d.toISOString().replace(/\.\d+Z$/, '').replace(/:/g, '-');
33
- }
34
-
35
- // command+shell CODEX_HOME (or null if probe failed / not detected).
36
- // Module-scope so we probe at most once per (command, shell) per server.
37
- const codexHomeCache = new Map();
38
- function cacheKey(command, shell) { return `${shell || 'direct'}|${command}`; }
39
-
40
- function execWithTimeout(exe, args, { timeoutMs = 8000 } = {}) {
41
- return new Promise((resolve) => {
42
- execFile(exe, args, {
43
- windowsHide: true,
44
- timeout: timeoutMs,
45
- maxBuffer: 1024 * 1024,
46
- // Use the registry-merged user PATH so wrapper commands resolve
47
- // even when the long-running server inherited a stale PATH at boot.
48
- env: spawnEnv(),
49
- }, (err, stdout, stderr) => {
50
- resolve({ err, stdout: String(stdout || ''), stderr: String(stderr || '') });
51
- });
52
- });
53
- }
54
-
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.
67
- function parseCodexHomeFromDoctor(text) {
68
- if (!text) return null;
69
- const clean = String(text).replace(/\x1b\[[0-9;]*m/g, '');
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
72
- if (!m) return null;
73
- const p = m[1].trim();
74
- return p || null;
75
- }
76
-
77
- // Build the [exe, args] needed to run `<cli.command> doctor` honouring
78
- // the same shell-wrapping rules webTerminal uses. Mirrors the relevant
79
- // bits of server.js' resolveCommand kept local so this module doesn't
80
- // drag a dependency on server.js.
81
- function buildDoctorInvocation(command, shell) {
82
- const cmd = String(command || '').replace(/^\.[\\/]/, '');
83
- if (!cmd) return null;
84
- if (shell === 'pwsh') {
85
- return {
86
- exe: 'pwsh.exe',
87
- args: ['-NoLogo', '-NonInteractive', '-Command', `& { ${cmd} doctor }`],
88
- };
89
- }
90
- if (shell === 'cmd') {
91
- return {
92
- exe: process.env.ComSpec || 'cmd.exe',
93
- args: ['/d', '/s', '/c', `${cmd} doctor`],
94
- };
95
- }
96
- // direct
97
- if (path.isAbsolute(cmd)) {
98
- const ext = path.extname(cmd).toLowerCase();
99
- if (ext === '.cmd' || ext === '.bat') {
100
- return { exe: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', `"${cmd}" doctor`] };
101
- }
102
- if (ext === '.ps1') {
103
- return { exe: 'powershell.exe', args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', cmd, 'doctor'] };
104
- }
105
- return { exe: cmd, args: ['doctor'] };
106
- }
107
- // bare name on direct defer to cmd.exe so Windows resolves via PATH
108
- return { exe: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', `${cmd} doctor`] };
109
- }
110
-
111
- async function probeCodexHome({ command, shell }) {
112
- const key = cacheKey(command, shell);
113
- if (codexHomeCache.has(key)) return codexHomeCache.get(key);
114
- const inv = buildDoctorInvocation(command, shell);
115
- if (!inv) { codexHomeCache.set(key, null); return null; }
116
- const { stdout, stderr } = await execWithTimeout(inv.exe, inv.args);
117
- // Some wrappers print their banner to stderr; doctor itself prints
118
- // the CODEX_HOME line to stdout. Search both to be safe.
119
- const home = parseCodexHomeFromDoctor(stdout) || parseCodexHomeFromDoctor(stderr);
120
- codexHomeCache.set(key, home);
121
- return home;
122
- }
123
-
124
- async function seedCodexSession({ id, cwd, cli }) {
125
- if (!id || !cwd) throw new Error('seedCodexSession: id and cwd required');
126
- // Resolution order:
127
- // 1. `<cli.command> doctor` probe (handles wrappers that
128
- // relocate CODEX_HOME)
129
- // 2. process.env.CODEX_HOME (global override)
130
- // 3. ~/.codex (codex's own default)
131
- let home = null;
132
- if (cli?.command) {
133
- try { home = await probeCodexHome({ command: cli.command, shell: cli.shell }); }
134
- catch (_) { /* probe is best-effort */ }
135
- }
136
- if (!home) home = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
137
-
138
- const now = new Date();
139
- const yyyy = String(now.getUTCFullYear());
140
- const mm = String(now.getUTCMonth() + 1).padStart(2, '0');
141
- const dd = String(now.getUTCDate()).padStart(2, '0');
142
- const dir = path.join(home, 'sessions', yyyy, mm, dd);
143
- await fs.mkdir(dir, { recursive: true });
144
- const file = path.join(dir, `rollout-${isoForFilename(now)}-${id}.jsonl`);
145
- const meta = {
146
- timestamp: now.toISOString(),
147
- type: 'session_meta',
148
- payload: {
149
- id,
150
- timestamp: now.toISOString(),
151
- cwd,
152
- originator: 'ccsm',
153
- cli_version: '0.0.0',
154
- source: 'ccsm-seed',
155
- },
156
- };
157
- await fs.writeFile(file, JSON.stringify(meta) + '\n', 'utf8');
158
- return file;
159
- }
160
-
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 };
183
-
1
+ 'use strict';
2
+
3
+ // Codex light-theme helper. Some wrappers relocate CODEX_HOME to a
4
+ // non-default dir (e.g. %LOCALAPPDATA%\<wrapper>\codex-home), so the bundled
5
+ // ccsm-light theme has to be installed there. We probe by running
6
+ // `<cli.command> doctor` once per (command, shell) pair and parsing the
7
+ // "CODEX_HOME ... (dir)" line out of its output. Cached for the life of
8
+ // the process.
9
+
10
+ const fs = require('node:fs/promises');
11
+ const path = require('node:path');
12
+ const { execFile } = require('node:child_process');
13
+ const { spawnEnv } = require('./winPath');
14
+
15
+ // command+shell CODEX_HOME (or null if probe failed / not detected).
16
+ // Module-scope so we probe at most once per (command, shell) per server.
17
+ const codexHomeCache = new Map();
18
+ function cacheKey(command, shell) { return `${shell || 'direct'}|${command}`; }
19
+
20
+ function execWithTimeout(exe, args, { timeoutMs = 8000 } = {}) {
21
+ return new Promise((resolve) => {
22
+ execFile(exe, args, {
23
+ windowsHide: true,
24
+ timeout: timeoutMs,
25
+ maxBuffer: 1024 * 1024,
26
+ // Use the registry-merged user PATH so wrapper commands resolve
27
+ // even when the long-running server inherited a stale PATH at boot.
28
+ env: spawnEnv(),
29
+ }, (err, stdout, stderr) => {
30
+ resolve({ err, stdout: String(stdout || ''), stderr: String(stderr || '') });
31
+ });
32
+ });
33
+ }
34
+
35
+ // Pull CODEX_HOME out of a wrapper's `doctor` (or `--version`) output. Two
36
+ // shapes appear, and we must handle BOTH:
37
+ // 1. Diagnostic table: `CODEX_HOME <path> (dir)` — only when `doctor`
38
+ // fully succeeds. Variable whitespace; the `(dir)`/`(file)` suffix marks
39
+ // the path end.
40
+ // 2. Wrapper banner: `CODEX_HOME=<path>` — printed on EVERY invocation.
41
+ // Critical because in ccsm's non-interactive spawn `doctor` often exits
42
+ // non-zero (skipping the table) yet still prints this banner line, so it's
43
+ // the reliable source. Without it the probe returns null and the seed
44
+ // lands in ~/.codex instead of the wrapper's relocated home, breaking
45
+ // `resume <id>` ("No saved session found with ID …").
46
+ // Some wrappers colour the label (`\x1b[7mCODEX_HOME\x1b[0m`); strip ANSI first.
47
+ function parseCodexHomeFromDoctor(text) {
48
+ if (!text) return null;
49
+ const clean = String(text).replace(/\x1b\[[0-9;]*m/g, '');
50
+ let m = clean.match(/\bCODEX_HOME\s+(.+?)\s*\((?:dir|file)\)/); // table form
51
+ if (!m) m = clean.match(/\bCODEX_HOME=(.+?)\s*$/m); // banner form
52
+ if (!m) return null;
53
+ const p = m[1].trim();
54
+ return p || null;
55
+ }
56
+
57
+ // Build the [exe, args] needed to run `<cli.command> doctor` honouring
58
+ // the same shell-wrapping rules webTerminal uses. Mirrors the relevant
59
+ // bits of server.js' resolveCommand — kept local so this module doesn't
60
+ // drag a dependency on server.js.
61
+ function buildDoctorInvocation(command, shell) {
62
+ const cmd = String(command || '').replace(/^\.[\\/]/, '');
63
+ if (!cmd) return null;
64
+ if (shell === 'pwsh') {
65
+ return {
66
+ exe: 'pwsh.exe',
67
+ args: ['-NoLogo', '-NonInteractive', '-Command', `& { ${cmd} doctor }`],
68
+ };
69
+ }
70
+ if (shell === 'cmd') {
71
+ return {
72
+ exe: process.env.ComSpec || 'cmd.exe',
73
+ args: ['/d', '/s', '/c', `${cmd} doctor`],
74
+ };
75
+ }
76
+ // direct
77
+ if (path.isAbsolute(cmd)) {
78
+ const ext = path.extname(cmd).toLowerCase();
79
+ if (ext === '.cmd' || ext === '.bat') {
80
+ return { exe: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', `"${cmd}" doctor`] };
81
+ }
82
+ if (ext === '.ps1') {
83
+ return { exe: 'powershell.exe', args: ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', cmd, 'doctor'] };
84
+ }
85
+ return { exe: cmd, args: ['doctor'] };
86
+ }
87
+ // bare name on direct defer to cmd.exe so Windows resolves via PATH
88
+ return { exe: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', `${cmd} doctor`] };
89
+ }
90
+
91
+ async function probeCodexHome({ command, shell }) {
92
+ const key = cacheKey(command, shell);
93
+ if (codexHomeCache.has(key)) return codexHomeCache.get(key);
94
+ const inv = buildDoctorInvocation(command, shell);
95
+ if (!inv) { codexHomeCache.set(key, null); return null; }
96
+ const { stdout, stderr } = await execWithTimeout(inv.exe, inv.args);
97
+ // Some wrappers print their banner to stderr; doctor itself prints
98
+ // the CODEX_HOME line to stdout. Search both to be safe.
99
+ const home = parseCodexHomeFromDoctor(stdout) || parseCodexHomeFromDoctor(stderr);
100
+ codexHomeCache.set(key, home);
101
+ return home;
102
+ }
103
+
104
+ // Copy ccsm's bundled light codex syntax theme into the codex home's themes/
105
+ // dir so `-c tui.theme=ccsm-light` resolves. This theme carries light
106
+ // markup.inserted/deleted backgrounds, which at true-color level override
107
+ // codex's diff palette the only way to get a LIGHT diff on Windows, where
108
+ // codex's own background detection (default_bg()) is compiled out and always
109
+ // falls back to a dark diff. Idempotent (writes only when missing/changed).
110
+ async function ensureCodexLightTheme(home) {
111
+ if (!home) return false;
112
+ const src = path.join(__dirname, 'codexThemes', 'ccsm-light.tmTheme');
113
+ const dstDir = path.join(home, 'themes');
114
+ const dst = path.join(dstDir, 'ccsm-light.tmTheme');
115
+ try {
116
+ const content = await fs.readFile(src, 'utf8');
117
+ await fs.mkdir(dstDir, { recursive: true });
118
+ let existing = null;
119
+ try { existing = await fs.readFile(dst, 'utf8'); } catch {}
120
+ if (existing !== content) await fs.writeFile(dst, content, 'utf8');
121
+ return true;
122
+ } catch { return false; }
123
+ }
124
+
125
+ module.exports = { probeCodexHome, parseCodexHomeFromDoctor, ensureCodexLightTheme };
126
+