claude-code-session-manager 0.15.0 → 0.16.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/dist/assets/{TiptapBody-D0tfDVZb.js → TiptapBody-CFh7PFkf.js} +1 -1
- package/dist/assets/{cssMode-C3tZkaJ9.js → cssMode-Cll4QpTW.js} +1 -1
- package/dist/assets/{freemarker2-DEh8tC5X.js → freemarker2-lo-fLEl5.js} +1 -1
- package/dist/assets/{handlebars-D_6KmsOK.js → handlebars-CMA5gb0k.js} +1 -1
- package/dist/assets/{html-DF1KVjzv.js → html-BQ2X1VHF.js} +1 -1
- package/dist/assets/{htmlMode-DEakPokt.js → htmlMode-Da8nU8Ys.js} +1 -1
- package/dist/assets/{index-D-kX3T0V.css → index-6uZy0Pbe.css} +1 -1
- package/dist/assets/{index-Zg61GP50.js → index-Bs_D2jQM.js} +1392 -1329
- package/dist/assets/{javascript-Du7a359D.js → javascript-BfjktvBd.js} +1 -1
- package/dist/assets/{jsonMode-W3BJwbUD.js → jsonMode-Duc74W4E.js} +1 -1
- package/dist/assets/{liquid-DDj1fqca.js → liquid-CONgoaDI.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-uyEbiR-d.js → lspLanguageFeatures-DdHwUVu5.js} +1 -1
- package/dist/assets/{mdx-DUqSETvC.js → mdx-DIQgvOto.js} +1 -1
- package/dist/assets/{python-D7S2lUAn.js → python-BcGmrT3q.js} +1 -1
- package/dist/assets/{razor-19nfZNaZ.js → razor-B2RyqYc0.js} +1 -1
- package/dist/assets/{tsMode-CQke5zpL.js → tsMode-CRnbNLOY.js} +1 -1
- package/dist/assets/{typescript-D4ge0PUF.js → typescript-CeTpx2hJ.js} +1 -1
- package/dist/assets/{xml-D42QDS7q.js → xml-DIN_EFpG.js} +1 -1
- package/dist/assets/{yaml-CEiz9NyU.js → yaml-DpCxufnr.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/main/index.cjs +78 -4
package/src/main/index.cjs
CHANGED
|
@@ -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
|
|
@@ -594,6 +597,29 @@ if (process.env.SM_E2E === '1') {
|
|
|
594
597
|
try { app.disableHardwareAcceleration(); } catch { /* */ }
|
|
595
598
|
}
|
|
596
599
|
|
|
600
|
+
// smfile:// — privileged scheme that serves home-scoped files to the in-app
|
|
601
|
+
// Editor's HTML preview iframe. Loading the user's HTML via a custom scheme
|
|
602
|
+
// (rather than srcdoc) gives the iframe document its OWN origin + empty CSP, so
|
|
603
|
+
// the page's own visualization scripts run — while the iframe is still
|
|
604
|
+
// sandboxed without allow-same-origin (opaque origin), keeping it walled off
|
|
605
|
+
// from the host app, its IPC bridge, and the user's filesystem. Must be
|
|
606
|
+
// registered before app.whenReady() (Electron rejects late scheme privileges).
|
|
607
|
+
protocol.registerSchemesAsPrivileged([
|
|
608
|
+
{ scheme: 'smfile', privileges: { standard: true, secure: true, supportFetchAPI: true, stream: true } },
|
|
609
|
+
]);
|
|
610
|
+
|
|
611
|
+
// Common content types for previewed assets. Anything unknown is served as
|
|
612
|
+
// octet-stream so the browser sniffs / downloads rather than mis-rendering.
|
|
613
|
+
const SMFILE_MIME = {
|
|
614
|
+
'.html': 'text/html', '.htm': 'text/html',
|
|
615
|
+
'.css': 'text/css', '.js': 'text/javascript', '.mjs': 'text/javascript',
|
|
616
|
+
'.json': 'application/json', '.svg': 'image/svg+xml',
|
|
617
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
618
|
+
'.gif': 'image/gif', '.webp': 'image/webp', '.ico': 'image/x-icon',
|
|
619
|
+
'.woff': 'font/woff', '.woff2': 'font/woff2', '.ttf': 'font/ttf',
|
|
620
|
+
'.txt': 'text/plain', '.csv': 'text/csv', '.xml': 'application/xml',
|
|
621
|
+
};
|
|
622
|
+
|
|
597
623
|
// Single-instance lock (PRD F1 v2 §requestSingleInstanceLock).
|
|
598
624
|
// In dev mode we skip the lock so two-dev-instance workflows still work.
|
|
599
625
|
// E2E tests also skip so playwright.electron.launch can run multiple specs
|
|
@@ -685,7 +711,7 @@ app.whenReady().then(async () => {
|
|
|
685
711
|
// — adds ~3 packages and ~2MB to the renderer build; the network fetch
|
|
686
712
|
// happens once per cold launch and is cached by Electron's HTTP cache.
|
|
687
713
|
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
|
|
688
|
-
"img-src 'self' data: blob:",
|
|
714
|
+
"img-src 'self' data: blob: smfile:",
|
|
689
715
|
"font-src 'self' data: https://fonts.gstatic.com",
|
|
690
716
|
// schemastore.org is used by Monaco for JSON schema validation
|
|
691
717
|
// (settings.json, keybindings.json — see App.tsx::installMonacoSchemas).
|
|
@@ -694,10 +720,18 @@ app.whenReady().then(async () => {
|
|
|
694
720
|
"connect-src 'self' https://api.anthropic.com https://registry.npmjs.org https://json.schemastore.org https://www.schemastore.org",
|
|
695
721
|
"media-src 'self' blob:",
|
|
696
722
|
"worker-src 'self' blob:",
|
|
697
|
-
|
|
723
|
+
// smfile: powers the Editor's sandboxed HTML preview iframe. The iframe is
|
|
724
|
+
// sandboxed WITHOUT allow-same-origin, so its document has an opaque origin
|
|
725
|
+
// and cannot reach the host even though it may run its own scripts.
|
|
726
|
+
"frame-src smfile:",
|
|
698
727
|
"frame-ancestors 'none'",
|
|
699
728
|
].join('; ') + ';';
|
|
700
729
|
session.defaultSession.webRequest.onHeadersReceived((details, cb) => {
|
|
730
|
+
// smfile:// serves the Editor's HTML-preview documents. They must NOT
|
|
731
|
+
// inherit the app CSP — `frame-ancestors 'none'` would forbid framing them,
|
|
732
|
+
// and `script-src 'self'` would block the page's own visualization scripts.
|
|
733
|
+
// Isolation comes from the iframe sandbox (opaque origin), not from CSP.
|
|
734
|
+
if (details.url.startsWith('smfile:')) { cb({}); return; }
|
|
701
735
|
cb({
|
|
702
736
|
responseHeaders: {
|
|
703
737
|
...details.responseHeaders,
|
|
@@ -706,6 +740,29 @@ app.whenReady().then(async () => {
|
|
|
706
740
|
});
|
|
707
741
|
});
|
|
708
742
|
|
|
743
|
+
// smfile:// handler — serves a single home-scoped file for the Editor's HTML
|
|
744
|
+
// preview iframe (and its relative assets: ./chart.js, ./data.json, images).
|
|
745
|
+
// URL shape: smfile://local/<absolute-path>. The pathname IS the absolute
|
|
746
|
+
// path; assertInsideHome enforces containment + rejects symlink escapes. The
|
|
747
|
+
// sandboxed iframe's relative requests resolve against this same scheme, so
|
|
748
|
+
// co-located assets load while everything stays inside home.
|
|
749
|
+
protocol.handle('smfile', async (request) => {
|
|
750
|
+
try {
|
|
751
|
+
const url = new URL(request.url);
|
|
752
|
+
const abs = decodeURIComponent(url.pathname);
|
|
753
|
+
const { realPath } = assertInsideHome(abs); // throws if outside home
|
|
754
|
+
const st = await fs.promises.stat(realPath);
|
|
755
|
+
if (st.isDirectory()) return new Response('Not a file', { status: 404 });
|
|
756
|
+
if (st.size > 25 * 1024 * 1024) return new Response('Too large', { status: 413 });
|
|
757
|
+
const buf = await fs.promises.readFile(realPath);
|
|
758
|
+
const ext = path.extname(realPath).toLowerCase();
|
|
759
|
+
const type = SMFILE_MIME[ext] || 'application/octet-stream';
|
|
760
|
+
return new Response(buf, { headers: { 'content-type': type } });
|
|
761
|
+
} catch (err) {
|
|
762
|
+
return new Response(`smfile error: ${err && err.message}`, { status: 404 });
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
|
|
709
766
|
// Grant microphone / media permissions only for trusted origins.
|
|
710
767
|
const MEDIA_PERMS = new Set(['media', 'audioCapture', 'microphone']);
|
|
711
768
|
const isTrustedOrigin = (url) =>
|
|
@@ -792,6 +849,19 @@ app.whenReady().then(async () => {
|
|
|
792
849
|
logs.writeLine({ scope: 'scheduler', level: 'error', message: 'init failed', meta: { error: e?.message } });
|
|
793
850
|
});
|
|
794
851
|
|
|
852
|
+
// Keep the machine awake while the app is open. The scheduler polls billing
|
|
853
|
+
// usage every 2 min and runs `claude -p` jobs that must survive an idle
|
|
854
|
+
// laptop — a system suspend (GNOME/Pop!_OS idle or lid timeout) would freeze
|
|
855
|
+
// both. `prevent-app-suspension` stops suspend but still lets the display
|
|
856
|
+
// dim/sleep, so battery impact is limited to keeping the CPU resumable.
|
|
857
|
+
// On Linux this routes through the org.freedesktop.login1 inhibitor.
|
|
858
|
+
try {
|
|
859
|
+
powerBlockerId = powerSaveBlocker.start('prevent-app-suspension');
|
|
860
|
+
logs.writeLine({ scope: 'main', level: 'info', message: 'powerSaveBlocker started', meta: { id: powerBlockerId } });
|
|
861
|
+
} catch (e) {
|
|
862
|
+
logs.writeLine({ scope: 'main', level: 'warn', message: 'powerSaveBlocker failed', meta: { error: e?.message } });
|
|
863
|
+
}
|
|
864
|
+
|
|
795
865
|
// OTEL: load persisted config and start the exporter only if `enabled`.
|
|
796
866
|
// Failures are non-fatal — the app must keep working without telemetry.
|
|
797
867
|
otelSettings.load()
|
|
@@ -810,6 +880,10 @@ app.on('will-quit', () => {
|
|
|
810
880
|
// PRD F1 v2 §IPC plumbing: must unregisterAll on will-quit.
|
|
811
881
|
try { globalShortcut.unregisterAll(); } catch { /* */ }
|
|
812
882
|
voiceHotkey.disposeOnQuit();
|
|
883
|
+
if (powerBlockerId !== -1) {
|
|
884
|
+
try { powerSaveBlocker.stop(powerBlockerId); } catch { /* */ }
|
|
885
|
+
powerBlockerId = -1;
|
|
886
|
+
}
|
|
813
887
|
});
|
|
814
888
|
|
|
815
889
|
app.on('window-all-closed', () => {
|