@bakapiano/ccsm 0.2.0 → 0.3.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/CLAUDE.md CHANGED
@@ -20,7 +20,7 @@ Then open http://localhost:7777.
20
20
  Default port `7777`, default workDir `~/ccsm-workspaces`. Config + snapshots live at `~/.ccsm/` (override with `CCSM_HOME=<path>`). All settings editable through the Config panel (`~/.ccsm/config.json` on disk). Notable knobs:
21
21
 
22
22
  - `port` (default `7777`) — preferred listen port. If taken, ccsm tries `+1..+9` then asks the OS for any free port. The startup log prints the actual URL so you always see where it ended up.
23
- - `autoOpenBrowser` (default true) — after `listen()` succeeds, ccsm spawns `cmd /c start "" <url>` to open the UI in the default browser. Disable for headless / nohup setups.
23
+ - `browserMode` (default `app`) — how to open the UI on server start. `app` finds Edge or Chrome and spawns it with `--app=<url> --user-data-dir=<DATA_DIR>/browser-profile` for a chromeless webview-style window (no tabs, no address bar). `tab` opens the default browser as a regular tab. `none` skips opening. Legacy `autoOpenBrowser: false` still maps to `none` for back-compat.
24
24
  - `claudeCommand` (default `"claude"`) — what gets `--resume`'d or freshly invoked inside the new terminal. Can be an exe (`claude`, `claude.exe`), a PowerShell alias or function (`ccp`), or any wrapper script — see `commandShell` below.
25
25
  - `terminal` — `wt` | `powershell` | `pwsh` | `cmd`. wt opens a fresh window per launch (`wt -w new` is set to defeat the "fold into existing window" setting some users have). The other three each spawn via `cmd /c start ... <shell>`.
26
26
  - `commandShell` (default `pwsh`) — only consulted when `terminal=wt`. Values `pwsh` / `powershell` wrap `claudeCommand` inside `<shell> -NoExit -NoLogo -Command "Set-Location ...; & '<cmd>' '<args>'..."` so PowerShell aliases / functions / profile-defined names (like `ccp` from `$PROFILE`) resolve. `none` runs the command directly via wt (raw `CreateProcess`) — fine if `claudeCommand` is an actual exe on PATH, broken for aliases. `pwsh` / `powershell` kinds already wrap natively so this knob doesn't affect them; `cmd` kind has no shell concept for aliases.
package/lib/config.js CHANGED
@@ -22,7 +22,10 @@ const DEFAULTS = {
22
22
  terminal: 'wt',
23
23
  commandShell: 'pwsh',
24
24
  autoFocusOnLaunch: true,
25
- autoOpenBrowser: true,
25
+ // 'app' — Edge/Chrome --app=<url> chromeless window (looks like a desktop app)
26
+ // 'tab' — open in default browser as a normal tab
27
+ // 'none' — don't open anything
28
+ browserMode: 'app',
26
29
  // Add the repos you most often need on hand. The "new session" button
27
30
  // clones any selected entries into the workspace before launching claude.
28
31
  // Example shape:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bakapiano/ccsm",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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/app.js CHANGED
@@ -165,7 +165,9 @@ function renderConfig() {
165
165
  $('#cfgClaudeCommand').value = state.config.claudeCommand || 'claude';
166
166
  $('#cfgCommandShell').value = state.config.commandShell || 'pwsh';
167
167
  $('#cfgAutoFocus').checked = state.config.autoFocusOnLaunch !== false;
168
- $('#cfgAutoOpenBrowser').checked = state.config.autoOpenBrowser !== false;
168
+ $('#cfgBrowserMode').value =
169
+ state.config.browserMode ||
170
+ (state.config.autoOpenBrowser === false ? 'none' : 'app');
169
171
  const termSel = $('#cfgTerminal');
170
172
  termSel.innerHTML = (state.terminals || []).map((t) =>
171
173
  `<option value="${escapeHtml(t.name)}" ${t.name === state.config.terminal ? 'selected' : ''}>${escapeHtml(t.name)} (${escapeHtml(t.processName)})</option>`
@@ -205,7 +207,7 @@ function readConfigFromForm() {
205
207
  terminal: $('#cfgTerminal').value || 'wt',
206
208
  commandShell: $('#cfgCommandShell').value || 'pwsh',
207
209
  autoFocusOnLaunch: $('#cfgAutoFocus').checked,
208
- autoOpenBrowser: $('#cfgAutoOpenBrowser').checked,
210
+ browserMode: $('#cfgBrowserMode').value || 'app',
209
211
  finderPrompt: $('#cfgFinderPrompt').value,
210
212
  repos,
211
213
  };
package/public/index.html CHANGED
@@ -123,9 +123,12 @@
123
123
  <input id="cfgAutoFocus" type="checkbox" />
124
124
  <span style="color: var(--text);">auto-focus newly launched window</span>
125
125
  </label>
126
- <label class="full" style="flex-direction: row; align-items: center; gap: 8px;">
127
- <input id="cfgAutoOpenBrowser" type="checkbox" />
128
- <span style="color: var(--text);">auto-open this UI in browser on server start</span>
126
+ <label>browser open mode (on server start)
127
+ <select id="cfgBrowserMode" class="select">
128
+ <option value="app">app Edge/Chrome chromeless window</option>
129
+ <option value="tab">tab — default browser, normal tab</option>
130
+ <option value="none">off — don't open anything</option>
131
+ </select>
129
132
  </label>
130
133
  <label class="full">finder prompt
131
134
  <textarea id="cfgFinderPrompt" rows="3"></textarea>
package/server.js CHANGED
@@ -342,12 +342,53 @@ function listenWithFallback(preferred) {
342
342
  });
343
343
  }
344
344
 
345
- function openInBrowser(url) {
346
- if (process.platform !== 'win32') return;
345
+ function findAppModeBrowser() {
346
+ const candidates = [
347
+ 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
348
+ 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
349
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
350
+ process.env.LOCALAPPDATA &&
351
+ path.join(process.env.LOCALAPPDATA, 'Google\\Chrome\\Application\\chrome.exe'),
352
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
353
+ ].filter(Boolean);
354
+ const fs = require('node:fs');
355
+ for (const p of candidates) {
356
+ if (fs.existsSync(p)) return p;
357
+ }
358
+ return null;
359
+ }
360
+
361
+ function openInBrowser(url, mode) {
362
+ if (process.platform !== 'win32' || mode === 'none') return;
347
363
  const { spawn } = require('node:child_process');
348
- // cmd's `start` builtin opens URL in the default browser. The empty "" is
349
- // the window-title slot for `start`, otherwise `<url>` would be eaten as
350
- // the title when it has spaces.
364
+ const fs = require('node:fs');
365
+
366
+ if (mode === 'app') {
367
+ const exe = findAppModeBrowser();
368
+ if (exe) {
369
+ // Per-ccsm profile dir so we don't get the "already running, --app
370
+ // ignored" merge behavior of Edge/Chrome when the user has a normal
371
+ // window open. Lives under DATA_DIR so it's tidied with the rest.
372
+ const profileDir = path.join(DATA_DIR, 'browser-profile');
373
+ fs.mkdirSync(profileDir, { recursive: true });
374
+ const child = spawn(
375
+ exe,
376
+ [
377
+ `--app=${url}`,
378
+ `--user-data-dir=${profileDir}`,
379
+ '--window-size=1400,1000',
380
+ '--no-first-run',
381
+ '--no-default-browser-check',
382
+ ],
383
+ { detached: true, stdio: 'ignore' }
384
+ );
385
+ child.unref();
386
+ return;
387
+ }
388
+ console.log('[ccsm] no Edge/Chrome found for app mode, falling back to default browser');
389
+ }
390
+
391
+ // mode === 'tab' (or app-mode fallback)
351
392
  const child = spawn('cmd.exe', ['/c', 'start', '', url], {
352
393
  detached: true,
353
394
  stdio: 'ignore',
@@ -364,7 +405,8 @@ function openInBrowser(url) {
364
405
  console.log(`data dir: ${DATA_DIR}`);
365
406
  console.log(`work dir: ${cfg.workDir}`);
366
407
  console.log(`terminal: ${cfg.terminal} · ${cfg.claudeCommand}${cfg.terminal === 'wt' ? ` (via ${cfg.commandShell})` : ''}`);
367
- if (cfg.autoOpenBrowser !== false) openInBrowser(url);
408
+ const mode = cfg.browserMode || (cfg.autoOpenBrowser === false ? 'none' : 'app');
409
+ openInBrowser(url, mode);
368
410
  startSnapshotLoop();
369
411
  })().catch((err) => {
370
412
  console.error('startup failed:', err);