claude-code-session-manager 0.15.0 → 0.17.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.
Files changed (24) hide show
  1. package/dist/assets/{TiptapBody-D0tfDVZb.js → TiptapBody-CQUVYNDX.js} +1 -1
  2. package/dist/assets/{cssMode-C3tZkaJ9.js → cssMode-8ncTkitH.js} +1 -1
  3. package/dist/assets/{freemarker2-DEh8tC5X.js → freemarker2-DiWr5tWQ.js} +1 -1
  4. package/dist/assets/{handlebars-D_6KmsOK.js → handlebars-BdG5bj_X.js} +1 -1
  5. package/dist/assets/{html-DF1KVjzv.js → html-BjT3cHU8.js} +1 -1
  6. package/dist/assets/{htmlMode-DEakPokt.js → htmlMode-B6lopeCZ.js} +1 -1
  7. package/dist/assets/{index-D-kX3T0V.css → index-CSotRGGc.css} +1 -1
  8. package/dist/assets/{index-Zg61GP50.js → index-DCaakMzv.js} +1388 -1323
  9. package/dist/assets/{javascript-Du7a359D.js → javascript-BCMuo5_4.js} +1 -1
  10. package/dist/assets/{jsonMode-W3BJwbUD.js → jsonMode-1KRBrXXK.js} +1 -1
  11. package/dist/assets/{liquid-DDj1fqca.js → liquid-ls0rkUL4.js} +1 -1
  12. package/dist/assets/{lspLanguageFeatures-uyEbiR-d.js → lspLanguageFeatures-BA-sGQJB.js} +1 -1
  13. package/dist/assets/{mdx-DUqSETvC.js → mdx-T7BxS13S.js} +1 -1
  14. package/dist/assets/{python-D7S2lUAn.js → python-CYW9xWwh.js} +1 -1
  15. package/dist/assets/{razor-19nfZNaZ.js → razor-D06Ehp7u.js} +1 -1
  16. package/dist/assets/{tsMode-CQke5zpL.js → tsMode-yKkshrv7.js} +1 -1
  17. package/dist/assets/{typescript-D4ge0PUF.js → typescript-Dlkgf9PI.js} +1 -1
  18. package/dist/assets/{xml-D42QDS7q.js → xml-BSp0bNTm.js} +1 -1
  19. package/dist/assets/{yaml-CEiz9NyU.js → yaml-C8041Bt_.js} +1 -1
  20. package/dist/index.html +2 -2
  21. package/package.json +3 -1
  22. package/src/main/files.cjs +35 -4
  23. package/src/main/index.cjs +84 -4
  24. package/src/preload/api.d.ts +1 -1
@@ -1,4 +1,4 @@
1
- const { app, BrowserWindow, ipcMain, dialog, Menu, session, systemPreferences, globalShortcut, shell, clipboard } = require('electron');
1
+ const { app, BrowserWindow, ipcMain, dialog, Menu, session, systemPreferences, globalShortcut, shell, clipboard, powerSaveBlocker, protocol } = require('electron');
2
2
  const { spawn, execFile, execFileSync } = require('node:child_process');
3
3
  const path = require('node:path');
4
4
  const fs = require('node:fs');
@@ -35,11 +35,14 @@ const searchIpc = require('./search.cjs');
35
35
  const repoAnalyzer = require('./repoAnalyzer.cjs');
36
36
  const hivesIpc = require('./hives.cjs');
37
37
  const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
38
- const { checkInsideHome } = require('./lib/insideHome.cjs');
38
+ const { checkInsideHome, assertInsideHome } = require('./lib/insideHome.cjs');
39
39
  const { openInEditor, openFileInEditor, openInFinder, openInTerminal } = require('./lib/openExternalApp.cjs');
40
40
 
41
41
  let mainWindow = null;
42
42
  let rebooting = false;
43
+ // powerSaveBlocker handle — keeps the system from suspending while the app runs
44
+ // so the scheduler's polling and jobs aren't frozen. -1 = not held.
45
+ let powerBlockerId = -1;
43
46
 
44
47
  // Boot diagnostics — populated at app.whenReady so the renderer can poll their
45
48
  // state via IPC and surface toasts on the failure paths. The first-paint
@@ -239,6 +242,10 @@ function createWindow() {
239
242
  contextIsolation: true,
240
243
  nodeIntegration: false,
241
244
  sandbox: true,
245
+ // Enables Chromium's built-in PDF viewer so the Editor's PdfPane iframe
246
+ // renders .pdf over smfile:// instead of triggering a download. The only
247
+ // Pepper "plugin" in modern Electron is the PDF viewer (Flash is gone).
248
+ plugins: true,
242
249
  },
243
250
  });
244
251
 
@@ -594,6 +601,31 @@ if (process.env.SM_E2E === '1') {
594
601
  try { app.disableHardwareAcceleration(); } catch { /* */ }
595
602
  }
596
603
 
604
+ // smfile:// — privileged scheme that serves home-scoped files to the in-app
605
+ // Editor's HTML preview iframe. Loading the user's HTML via a custom scheme
606
+ // (rather than srcdoc) gives the iframe document its OWN origin + empty CSP, so
607
+ // the page's own visualization scripts run — while the iframe is still
608
+ // sandboxed without allow-same-origin (opaque origin), keeping it walled off
609
+ // from the host app, its IPC bridge, and the user's filesystem. Must be
610
+ // registered before app.whenReady() (Electron rejects late scheme privileges).
611
+ protocol.registerSchemesAsPrivileged([
612
+ { scheme: 'smfile', privileges: { standard: true, secure: true, supportFetchAPI: true, stream: true } },
613
+ ]);
614
+
615
+ // Common content types for previewed assets. Anything unknown is served as
616
+ // octet-stream so the browser sniffs / downloads rather than mis-rendering.
617
+ const SMFILE_MIME = {
618
+ '.html': 'text/html', '.htm': 'text/html',
619
+ '.css': 'text/css', '.js': 'text/javascript', '.mjs': 'text/javascript',
620
+ '.json': 'application/json', '.svg': 'image/svg+xml',
621
+ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
622
+ '.gif': 'image/gif', '.webp': 'image/webp', '.ico': 'image/x-icon',
623
+ '.avif': 'image/avif', '.bmp': 'image/bmp',
624
+ '.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf',
625
+ '.txt': 'text/plain', '.csv': 'text/csv', '.xml': 'application/xml',
626
+ '.pdf': 'application/pdf',
627
+ };
628
+
597
629
  // Single-instance lock (PRD F1 v2 §requestSingleInstanceLock).
598
630
  // In dev mode we skip the lock so two-dev-instance workflows still work.
599
631
  // E2E tests also skip so playwright.electron.launch can run multiple specs
@@ -685,7 +717,7 @@ app.whenReady().then(async () => {
685
717
  // — adds ~3 packages and ~2MB to the renderer build; the network fetch
686
718
  // happens once per cold launch and is cached by Electron's HTTP cache.
687
719
  "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
688
- "img-src 'self' data: blob:",
720
+ "img-src 'self' data: blob: smfile:",
689
721
  "font-src 'self' data: https://fonts.gstatic.com",
690
722
  // schemastore.org is used by Monaco for JSON schema validation
691
723
  // (settings.json, keybindings.json — see App.tsx::installMonacoSchemas).
@@ -694,10 +726,18 @@ app.whenReady().then(async () => {
694
726
  "connect-src 'self' https://api.anthropic.com https://registry.npmjs.org https://json.schemastore.org https://www.schemastore.org",
695
727
  "media-src 'self' blob:",
696
728
  "worker-src 'self' blob:",
697
- "frame-src 'none'",
729
+ // smfile: powers the Editor's sandboxed HTML preview iframe. The iframe is
730
+ // sandboxed WITHOUT allow-same-origin, so its document has an opaque origin
731
+ // and cannot reach the host even though it may run its own scripts.
732
+ "frame-src smfile:",
698
733
  "frame-ancestors 'none'",
699
734
  ].join('; ') + ';';
700
735
  session.defaultSession.webRequest.onHeadersReceived((details, cb) => {
736
+ // smfile:// serves the Editor's HTML-preview documents. They must NOT
737
+ // inherit the app CSP — `frame-ancestors 'none'` would forbid framing them,
738
+ // and `script-src 'self'` would block the page's own visualization scripts.
739
+ // Isolation comes from the iframe sandbox (opaque origin), not from CSP.
740
+ if (details.url.startsWith('smfile:')) { cb({}); return; }
701
741
  cb({
702
742
  responseHeaders: {
703
743
  ...details.responseHeaders,
@@ -706,6 +746,29 @@ app.whenReady().then(async () => {
706
746
  });
707
747
  });
708
748
 
749
+ // smfile:// handler — serves a single home-scoped file for the Editor's HTML
750
+ // preview iframe (and its relative assets: ./chart.js, ./data.json, images).
751
+ // URL shape: smfile://local/<absolute-path>. The pathname IS the absolute
752
+ // path; assertInsideHome enforces containment + rejects symlink escapes. The
753
+ // sandboxed iframe's relative requests resolve against this same scheme, so
754
+ // co-located assets load while everything stays inside home.
755
+ protocol.handle('smfile', async (request) => {
756
+ try {
757
+ const url = new URL(request.url);
758
+ const abs = decodeURIComponent(url.pathname);
759
+ const { realPath } = assertInsideHome(abs); // throws if outside home
760
+ const st = await fs.promises.stat(realPath);
761
+ if (st.isDirectory()) return new Response('Not a file', { status: 404 });
762
+ if (st.size > 25 * 1024 * 1024) return new Response('Too large', { status: 413 });
763
+ const buf = await fs.promises.readFile(realPath);
764
+ const ext = path.extname(realPath).toLowerCase();
765
+ const type = SMFILE_MIME[ext] || 'application/octet-stream';
766
+ return new Response(buf, { headers: { 'content-type': type } });
767
+ } catch (err) {
768
+ return new Response(`smfile error: ${err && err.message}`, { status: 404 });
769
+ }
770
+ });
771
+
709
772
  // Grant microphone / media permissions only for trusted origins.
710
773
  const MEDIA_PERMS = new Set(['media', 'audioCapture', 'microphone']);
711
774
  const isTrustedOrigin = (url) =>
@@ -792,6 +855,19 @@ app.whenReady().then(async () => {
792
855
  logs.writeLine({ scope: 'scheduler', level: 'error', message: 'init failed', meta: { error: e?.message } });
793
856
  });
794
857
 
858
+ // Keep the machine awake while the app is open. The scheduler polls billing
859
+ // usage every 2 min and runs `claude -p` jobs that must survive an idle
860
+ // laptop — a system suspend (GNOME/Pop!_OS idle or lid timeout) would freeze
861
+ // both. `prevent-app-suspension` stops suspend but still lets the display
862
+ // dim/sleep, so battery impact is limited to keeping the CPU resumable.
863
+ // On Linux this routes through the org.freedesktop.login1 inhibitor.
864
+ try {
865
+ powerBlockerId = powerSaveBlocker.start('prevent-app-suspension');
866
+ logs.writeLine({ scope: 'main', level: 'info', message: 'powerSaveBlocker started', meta: { id: powerBlockerId } });
867
+ } catch (e) {
868
+ logs.writeLine({ scope: 'main', level: 'warn', message: 'powerSaveBlocker failed', meta: { error: e?.message } });
869
+ }
870
+
795
871
  // OTEL: load persisted config and start the exporter only if `enabled`.
796
872
  // Failures are non-fatal — the app must keep working without telemetry.
797
873
  otelSettings.load()
@@ -810,6 +886,10 @@ app.on('will-quit', () => {
810
886
  // PRD F1 v2 §IPC plumbing: must unregisterAll on will-quit.
811
887
  try { globalShortcut.unregisterAll(); } catch { /* */ }
812
888
  voiceHotkey.disposeOnQuit();
889
+ if (powerBlockerId !== -1) {
890
+ try { powerSaveBlocker.stop(powerBlockerId); } catch { /* */ }
891
+ powerBlockerId = -1;
892
+ }
813
893
  });
814
894
 
815
895
  app.on('window-all-closed', () => {
@@ -467,7 +467,7 @@ export interface FileEntry {
467
467
  }
468
468
 
469
469
  export interface FilesListResult { ok: boolean; entries: FileEntry[]; error: string | null }
470
- export interface FilesReadResult { ok: boolean; text: string; error: string | null; size: number }
470
+ export interface FilesReadResult { ok: boolean; text: string; error: string | null; size: number; binary?: boolean; mime?: string }
471
471
  export interface FilesWriteResult { ok: boolean; error: string | null }
472
472
  export interface FilesCreateResult { ok: boolean; path?: string; error: string | null }
473
473
  export interface FilesRenameResult { ok: boolean; newPath?: string; error: string | null }