claude-code-session-manager 0.8.6 → 0.10.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 (54) hide show
  1. package/README.md +95 -65
  2. package/dist/assets/{cssMode-DBg6nxUL.js → cssMode-DWlBzlpW.js} +1 -1
  3. package/dist/assets/{freemarker2-CyjUGY3f.js → freemarker2-Cgg83m-Z.js} +1 -1
  4. package/dist/assets/{handlebars-lhtCWqlB.js → handlebars-C4r4LOI9.js} +1 -1
  5. package/dist/assets/{html-egptHwbZ.js → html-DaxRI5sW.js} +1 -1
  6. package/dist/assets/htmlMode-Bu_8jtXo.js +1 -0
  7. package/dist/assets/{index-DjeqNwqn.js → index-C_tgFedf.js} +1115 -1081
  8. package/dist/assets/{index-DnLtSCQS.css → index-Dj3Db4OA.css} +1 -1
  9. package/dist/assets/{javascript-tZbiID3O.js → javascript-D5Ztx-Ej.js} +1 -1
  10. package/dist/assets/{jsonMode-BGtPN-L-.js → jsonMode-tfsgezVc.js} +1 -1
  11. package/dist/assets/{liquid-DvTeXhev.js → liquid-F2cD9OL0.js} +1 -1
  12. package/dist/assets/{lspLanguageFeatures-D9xoxVlV.js → lspLanguageFeatures-Bz_Eih8F.js} +2 -2
  13. package/dist/assets/{mdx-BQ3Ja4wM.js → mdx-BPlD1clX.js} +1 -1
  14. package/dist/assets/{ort-wasm-simd-threaded.asyncify-CtKKja6V.wasm → ort-wasm-simd-threaded.asyncify-DMmc6YqF.wasm} +0 -0
  15. package/dist/assets/{python-C71RWXaP.js → python-B4gUOWNI.js} +1 -1
  16. package/dist/assets/{razor-w__Mkyns.js → razor-B6pMxVp1.js} +1 -1
  17. package/dist/assets/{tsMode-DOQLQDB3.js → tsMode-C9nq6cHi.js} +1 -1
  18. package/dist/assets/{typescript-DEiub2Jt.js → typescript-Do5Vtwxu.js} +1 -1
  19. package/dist/assets/{whisperWorker-QfIS0sPF.js → whisperWorker-CcsPqZUS.js} +19 -19
  20. package/dist/assets/{xml-RXkLQscS.js → xml-C0mTbVRp.js} +1 -1
  21. package/dist/assets/{yaml-C8HIpJku.js → yaml-D3sePJfA.js} +1 -1
  22. package/dist/index.html +2 -2
  23. package/package.json +18 -10
  24. package/screenshots/.gitkeep +0 -0
  25. package/screenshots/README-screenshots.md +13 -0
  26. package/src/main/config.cjs +47 -9
  27. package/src/main/historyAggregator.cjs +10 -5
  28. package/src/main/index.cjs +85 -14
  29. package/src/main/ipcSchemas.cjs +165 -3
  30. package/src/main/lib/claudeBin.cjs +39 -0
  31. package/src/main/lib/encodeCwd.cjs +19 -0
  32. package/src/main/lib/fileTail.cjs +35 -0
  33. package/src/main/lib/insideHome.cjs +38 -0
  34. package/src/main/lib/prdFrontmatter.cjs +51 -0
  35. package/src/main/lib/sendToRenderer.cjs +21 -0
  36. package/src/main/memoryTool.cjs +203 -0
  37. package/src/main/otelSettings.cjs +2 -7
  38. package/src/main/pluginInstall.cjs +129 -0
  39. package/src/main/pty.cjs +13 -29
  40. package/src/main/queueOps.cjs +404 -0
  41. package/src/main/scheduler/prdParser.cjs +135 -0
  42. package/src/main/scheduler.cjs +291 -250
  43. package/src/main/sessionsStore.cjs +2 -6
  44. package/src/main/supervisor.cjs +3 -35
  45. package/src/main/teams.cjs +95 -0
  46. package/src/main/transcripts.cjs +5 -7
  47. package/src/main/usage.cjs +8 -0
  48. package/src/main/voiceHotkey.cjs +13 -9
  49. package/src/main/voiceSettings.cjs +2 -9
  50. package/src/main/voiceWizard.cjs +4 -11
  51. package/src/main/watchers.cjs +18 -42
  52. package/src/preload/api.d.ts +153 -1
  53. package/src/preload/index.cjs +29 -0
  54. package/dist/assets/htmlMode-tPDeHGOB.js +0 -1
@@ -1 +1 @@
1
- import{l as e}from"./index-DjeqNwqn.js";const n={comments:{blockComment:["<!--","-->"]},brackets:[["<",">"]],autoClosingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],onEnterRules:[{beforeText:new RegExp("<([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$","i"),afterText:/^<\/([_:\w][_:\w-.\d]*)\s*>$/i,action:{indentAction:e.IndentAction.IndentOutdent}},{beforeText:new RegExp("<(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$","i"),action:{indentAction:e.IndentAction.Indent}}]},o={defaultToken:"",tokenPostfix:".xml",ignoreCase:!0,qualifiedName:/(?:[\w\.\-]+:)?[\w\.\-]+/,tokenizer:{root:[[/[^<&]+/,""],{include:"@whitespace"},[/(<)(@qualifiedName)/,[{token:"delimiter"},{token:"tag",next:"@tag"}]],[/(<\/)(@qualifiedName)(\s*)(>)/,[{token:"delimiter"},{token:"tag"},"",{token:"delimiter"}]],[/(<\?)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/(<\!)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/<\!\[CDATA\[/,{token:"delimiter.cdata",next:"@cdata"}],[/&\w+;/,"string.escape"]],cdata:[[/[^\]]+/,""],[/\]\]>/,{token:"delimiter.cdata",next:"@pop"}],[/\]/,""]],tag:[[/[ \t\r\n]+/,""],[/(@qualifiedName)(\s*=\s*)("[^"]*"|'[^']*')/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">?\/]*|'[^'>?\/]*)(?=[\?\/]\>)/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">]*|'[^'>]*)/,["attribute.name","","attribute.value"]],[/@qualifiedName/,"attribute.name"],[/\?>/,{token:"delimiter",next:"@pop"}],[/(\/)(>)/,[{token:"tag"},{token:"delimiter",next:"@pop"}]],[/>/,{token:"delimiter",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,""],[/<!--/,{token:"comment",next:"@comment"}]],comment:[[/[^<\-]+/,"comment.content"],[/-->/,{token:"comment",next:"@pop"}],[/<!--/,"comment.content.invalid"],[/[<\-]/,"comment.content"]]}};export{n as conf,o as language};
1
+ import{l as e}from"./index-C_tgFedf.js";const n={comments:{blockComment:["<!--","-->"]},brackets:[["<",">"]],autoClosingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],onEnterRules:[{beforeText:new RegExp("<([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$","i"),afterText:/^<\/([_:\w][_:\w-.\d]*)\s*>$/i,action:{indentAction:e.IndentAction.IndentOutdent}},{beforeText:new RegExp("<(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$","i"),action:{indentAction:e.IndentAction.Indent}}]},o={defaultToken:"",tokenPostfix:".xml",ignoreCase:!0,qualifiedName:/(?:[\w\.\-]+:)?[\w\.\-]+/,tokenizer:{root:[[/[^<&]+/,""],{include:"@whitespace"},[/(<)(@qualifiedName)/,[{token:"delimiter"},{token:"tag",next:"@tag"}]],[/(<\/)(@qualifiedName)(\s*)(>)/,[{token:"delimiter"},{token:"tag"},"",{token:"delimiter"}]],[/(<\?)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/(<\!)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/<\!\[CDATA\[/,{token:"delimiter.cdata",next:"@cdata"}],[/&\w+;/,"string.escape"]],cdata:[[/[^\]]+/,""],[/\]\]>/,{token:"delimiter.cdata",next:"@pop"}],[/\]/,""]],tag:[[/[ \t\r\n]+/,""],[/(@qualifiedName)(\s*=\s*)("[^"]*"|'[^']*')/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">?\/]*|'[^'>?\/]*)(?=[\?\/]\>)/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">]*|'[^'>]*)/,["attribute.name","","attribute.value"]],[/@qualifiedName/,"attribute.name"],[/\?>/,{token:"delimiter",next:"@pop"}],[/(\/)(>)/,[{token:"tag"},{token:"delimiter",next:"@pop"}]],[/>/,{token:"delimiter",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,""],[/<!--/,{token:"comment",next:"@comment"}]],comment:[[/[^<\-]+/,"comment.content"],[/-->/,{token:"comment",next:"@pop"}],[/<!--/,"comment.content.invalid"],[/[<\-]/,"comment.content"]]}};export{n as conf,o as language};
@@ -1 +1 @@
1
- import{l as e}from"./index-DjeqNwqn.js";const t={comments:{lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],folding:{offSide:!0},onEnterRules:[{beforeText:/:\s*$/,action:{indentAction:e.IndentAction.Indent}}]},o={tokenPostfix:".yaml",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.square",open:"[",close:"]"}],keywords:["true","True","TRUE","false","False","FALSE","null","Null","Null","~"],numberInteger:/(?:0|[+-]?[0-9]+)/,numberFloat:/(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,numberOctal:/0o[0-7]+/,numberHex:/0x[0-9a-fA-F]+/,numberInfinity:/[+-]?\.(?:inf|Inf|INF)/,numberNaN:/\.(?:nan|Nan|NAN)/,numberDate:/\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,escapes:/\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,tokenizer:{root:[{include:"@whitespace"},{include:"@comment"},[/%[^ ]+.*$/,"meta.directive"],[/---/,"operators.directivesEnd"],[/\.{3}/,"operators.documentEnd"],[/[-?:](?= )/,"operators"],{include:"@anchor"},{include:"@tagHandle"},{include:"@flowCollections"},{include:"@blockStyle"},[/@numberInteger(?![ \t]*\S+)/,"number"],[/@numberFloat(?![ \t]*\S+)/,"number.float"],[/@numberOctal(?![ \t]*\S+)/,"number.octal"],[/@numberHex(?![ \t]*\S+)/,"number.hex"],[/@numberInfinity(?![ \t]*\S+)/,"number.infinity"],[/@numberNaN(?![ \t]*\S+)/,"number.nan"],[/@numberDate(?![ \t]*\S+)/,"number.date"],[/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/,["type","white","operators","white"]],{include:"@flowScalars"},[/.+?(?=(\s+#|$))/,{cases:{"@keywords":"keyword","@default":"string"}}]],object:[{include:"@whitespace"},{include:"@comment"},[/\}/,"@brackets","@pop"],[/,/,"delimiter.comma"],[/:(?= )/,"operators"],[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/,"type"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\},]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],array:[{include:"@whitespace"},{include:"@comment"},[/\]/,"@brackets","@pop"],[/,/,"delimiter.comma"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\],]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],multiString:[[/^( +).+$/,"string","@multiStringContinued.$1"]],multiStringContinued:[[/^( *).+$/,{cases:{"$1==$S2":"string","@default":{token:"@rematch",next:"@popall"}}}]],whitespace:[[/[ \t\r\n]+/,"white"]],comment:[[/#.*$/,"comment"]],flowCollections:[[/\[/,"@brackets","@array"],[/\{/,"@brackets","@object"]],flowScalars:[[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'[^']*'/,"string"],[/"/,"string","@doubleQuotedString"]],doubleQuotedString:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],blockStyle:[[/[>|][0-9]*[+-]?$/,"operators","@multiString"]],flowNumber:[[/@numberInteger(?=[ \t]*[,\]\}])/,"number"],[/@numberFloat(?=[ \t]*[,\]\}])/,"number.float"],[/@numberOctal(?=[ \t]*[,\]\}])/,"number.octal"],[/@numberHex(?=[ \t]*[,\]\}])/,"number.hex"],[/@numberInfinity(?=[ \t]*[,\]\}])/,"number.infinity"],[/@numberNaN(?=[ \t]*[,\]\}])/,"number.nan"],[/@numberDate(?=[ \t]*[,\]\}])/,"number.date"]],tagHandle:[[/\![^ ]*/,"tag"]],anchor:[[/[&*][^ ]+/,"namespace"]]}};export{t as conf,o as language};
1
+ import{l as e}from"./index-C_tgFedf.js";const t={comments:{lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],folding:{offSide:!0},onEnterRules:[{beforeText:/:\s*$/,action:{indentAction:e.IndentAction.Indent}}]},o={tokenPostfix:".yaml",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.square",open:"[",close:"]"}],keywords:["true","True","TRUE","false","False","FALSE","null","Null","Null","~"],numberInteger:/(?:0|[+-]?[0-9]+)/,numberFloat:/(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,numberOctal:/0o[0-7]+/,numberHex:/0x[0-9a-fA-F]+/,numberInfinity:/[+-]?\.(?:inf|Inf|INF)/,numberNaN:/\.(?:nan|Nan|NAN)/,numberDate:/\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,escapes:/\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,tokenizer:{root:[{include:"@whitespace"},{include:"@comment"},[/%[^ ]+.*$/,"meta.directive"],[/---/,"operators.directivesEnd"],[/\.{3}/,"operators.documentEnd"],[/[-?:](?= )/,"operators"],{include:"@anchor"},{include:"@tagHandle"},{include:"@flowCollections"},{include:"@blockStyle"},[/@numberInteger(?![ \t]*\S+)/,"number"],[/@numberFloat(?![ \t]*\S+)/,"number.float"],[/@numberOctal(?![ \t]*\S+)/,"number.octal"],[/@numberHex(?![ \t]*\S+)/,"number.hex"],[/@numberInfinity(?![ \t]*\S+)/,"number.infinity"],[/@numberNaN(?![ \t]*\S+)/,"number.nan"],[/@numberDate(?![ \t]*\S+)/,"number.date"],[/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/,["type","white","operators","white"]],{include:"@flowScalars"},[/.+?(?=(\s+#|$))/,{cases:{"@keywords":"keyword","@default":"string"}}]],object:[{include:"@whitespace"},{include:"@comment"},[/\}/,"@brackets","@pop"],[/,/,"delimiter.comma"],[/:(?= )/,"operators"],[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/,"type"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\},]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],array:[{include:"@whitespace"},{include:"@comment"},[/\]/,"@brackets","@pop"],[/,/,"delimiter.comma"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\],]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],multiString:[[/^( +).+$/,"string","@multiStringContinued.$1"]],multiStringContinued:[[/^( *).+$/,{cases:{"$1==$S2":"string","@default":{token:"@rematch",next:"@popall"}}}]],whitespace:[[/[ \t\r\n]+/,"white"]],comment:[[/#.*$/,"comment"]],flowCollections:[[/\[/,"@brackets","@array"],[/\{/,"@brackets","@object"]],flowScalars:[[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'[^']*'/,"string"],[/"/,"string","@doubleQuotedString"]],doubleQuotedString:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],blockStyle:[[/[>|][0-9]*[+-]?$/,"operators","@multiString"]],flowNumber:[[/@numberInteger(?=[ \t]*[,\]\}])/,"number"],[/@numberFloat(?=[ \t]*[,\]\}])/,"number.float"],[/@numberOctal(?=[ \t]*[,\]\}])/,"number.octal"],[/@numberHex(?=[ \t]*[,\]\}])/,"number.hex"],[/@numberInfinity(?=[ \t]*[,\]\}])/,"number.infinity"],[/@numberNaN(?=[ \t]*[,\]\}])/,"number.nan"],[/@numberDate(?=[ \t]*[,\]\}])/,"number.date"]],tagHandle:[[/\![^ ]*/,"tag"]],anchor:[[/[&*][^ ]+/,"namespace"]]}};export{t as conf,o as language};
package/dist/index.html CHANGED
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Claude Session Manager</title>
7
- <script type="module" crossorigin src="./assets/index-DjeqNwqn.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-DnLtSCQS.css">
7
+ <script type="module" crossorigin src="./assets/index-C_tgFedf.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-Dj3Db4OA.css">
9
9
  </head>
10
10
  <body class="bg-bg text-fg font-mono antialiased">
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-code-session-manager",
3
- "version": "0.8.6",
4
- "description": "Local cockpit for Claude Code CLI sessions — terminal + full config surface.",
3
+ "version": "0.10.0",
4
+ "description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
5
5
  "type": "module",
6
6
  "main": "src/main/index.cjs",
7
7
  "bin": {
@@ -14,13 +14,13 @@
14
14
  "dist/index.html",
15
15
  "dist/assets/",
16
16
  "dist/vad/",
17
+ "screenshots/",
17
18
  "README.md"
18
19
  ],
19
20
  "scripts": {
20
21
  "dev": "concurrently -k -n vite,electron -c magenta,cyan \"npm:dev:renderer\" \"npm:dev:electron\"",
21
22
  "dev:renderer": "vite",
22
23
  "dev:electron": "wait-on http://localhost:5173 && SM_DEV=1 electron .",
23
- "build:renderer": "vite build",
24
24
  "build": "vite build",
25
25
  "typecheck": "tsc --noEmit",
26
26
  "start": "electron .",
@@ -39,7 +39,15 @@
39
39
  "terminal",
40
40
  "electron",
41
41
  "session-manager",
42
- "cli"
42
+ "cli",
43
+ "cockpit",
44
+ "mcp",
45
+ "agents",
46
+ "hooks",
47
+ "scheduler",
48
+ "voice",
49
+ "whisper",
50
+ "prd"
43
51
  ],
44
52
  "author": "bilkobibitkov",
45
53
  "license": "MIT",
@@ -63,10 +71,9 @@
63
71
  "linux"
64
72
  ],
65
73
  "dependencies": {
66
- "@electron/rebuild": "^3.7.0",
74
+ "@electron/rebuild": "4.0.4",
67
75
  "@huggingface/transformers": "^4.1.0",
68
76
  "@monaco-editor/react": "^4.6.0",
69
- "monaco-editor": "^0.55.0",
70
77
  "@opentelemetry/api": "^1.9.0",
71
78
  "@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
72
79
  "@opentelemetry/resources": "^1.30.0",
@@ -78,9 +85,12 @@
78
85
  "@xterm/addon-web-links": "^0.11.0",
79
86
  "@xterm/xterm": "^5.5.0",
80
87
  "chokidar": "^4.0.1",
81
- "electron": "^33.2.0",
88
+ "electron": "42.1.0",
89
+ "framer-motion": "^12.38.0",
90
+ "monaco-editor": "0.55.1",
82
91
  "node-pty": "^1.2.0-beta.12",
83
92
  "onnxruntime-web": "^1.24.3",
93
+ "recharts": "^2.15.4",
84
94
  "zod": "^3.23.8",
85
95
  "zustand": "^5.0.0"
86
96
  },
@@ -92,15 +102,13 @@
92
102
  "@vitejs/plugin-react": "^4.3.3",
93
103
  "autoprefixer": "^10.4.20",
94
104
  "concurrently": "^9.1.0",
95
- "framer-motion": "^12.38.0",
96
105
  "postcss": "^8.4.49",
97
106
  "react": "^18.3.1",
98
107
  "react-dom": "^18.3.1",
99
- "recharts": "^2.15.4",
100
108
  "tailwindcss": "^3.4.15",
101
109
  "typescript": "^5.6.3",
102
110
  "vite": "^6.0.1",
103
- "vitest": "^2.1.9",
111
+ "vitest": "4.1.6",
104
112
  "wait-on": "^8.0.1"
105
113
  }
106
114
  }
File without changes
@@ -0,0 +1,13 @@
1
+ # Screenshots needed
2
+
3
+ The repo README references these six screenshots. Each one is a TODO — the
4
+ images do not exist yet. PNG, target 1500×950, under 200 KB each (pngquant).
5
+
6
+ 1. **`overview-cockpit.png`** — TODO: capture this view. Overview tab full window. Captures AppStatusBar pills at the top, CockpitStrip, the two-by-five instrument grid, TeamsCard (if enabled), system row, and the quick-actions footer.
7
+ 2. **`agent-view.png`** — TODO: capture this view. Agent-View tab with the animated workshop scene visible: subagent dock, plan whiteboard, todo board, and the SchedulerDock with at least one mini-bot active.
8
+ 3. **`command-palette.png`** — TODO: capture this view. CommandPalette open over any tab, ideally with the scheduler section expanded to show several scheduler commands (Run now / Force tick / Lint queue).
9
+ 4. **`scheduler.png`** — TODO: capture this view. Plans tab → PRDs sub-view. PRD list with multi-select checkboxes on the left, structured editor in the centre, queue-health findings panel visible.
10
+ 5. **`voice.png`** — TODO: capture this view. Voice subsystem — either the MicWizard first-run dialog with device picker and sample utterance UI, or the in-tab voice settings panel with a live VAD trace.
11
+ 6. **`hooks-events.png`** — TODO: capture this view. Hooks tab with the sidebar listing all 29 events and one event hovered to show the inline description tooltip from `hookEventDocs.ts`.
12
+
13
+ Total image budget across all six: roughly 1 MB so the npm tarball does not bloat.
@@ -20,6 +20,8 @@ const fsp = require('node:fs/promises');
20
20
  const path = require('node:path');
21
21
  const os = require('node:os');
22
22
  const chokidar = require('chokidar');
23
+ const logs = require('./logs.cjs');
24
+ const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
23
25
 
24
26
  /** Map<absPath, {watcher, refCount}> — one chokidar watcher per path. */
25
27
  const watchers = new Map();
@@ -157,16 +159,29 @@ async function readText(abs) {
157
159
 
158
160
  /**
159
161
  * Atomic write — write to <path>.tmp-<pid>-<ts> then rename. Creates parent
160
- * directories if missing.
162
+ * directories if missing. Cleans up the tmp file on rename failure.
163
+ *
164
+ * opts.mode: optional POSIX permission bits, applied to the tmp file before
165
+ * rename. Used by voiceSettings/otelSettings for 0o600 credential storage.
161
166
  */
162
- async function writeTextAtomic(abs, text) {
167
+ async function writeTextAtomic(abs, text, opts = {}) {
163
168
  const real = validatePath(expandHome(abs));
164
169
  validateWrite(real);
165
170
  const dir = path.dirname(real);
166
171
  await fsp.mkdir(dir, { recursive: true });
167
172
  const tmp = `${real}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
168
- await fsp.writeFile(tmp, text, 'utf8');
169
- await fsp.rename(tmp, real);
173
+ try {
174
+ await fsp.writeFile(tmp, text, opts.mode ? { encoding: 'utf8', mode: opts.mode } : 'utf8');
175
+ if (opts.mode) {
176
+ // chmod explicitly because some platforms ignore the mode arg on
177
+ // writeFile when the file pre-exists.
178
+ try { await fsp.chmod(tmp, opts.mode); } catch { /* */ }
179
+ }
180
+ await fsp.rename(tmp, real);
181
+ } catch (e) {
182
+ try { await fsp.unlink(tmp); } catch { /* tmp never created or already gone */ }
183
+ throw e;
184
+ }
170
185
  const stat = await fsp.stat(real);
171
186
  return { ok: true, mtimeMs: stat.mtimeMs };
172
187
  }
@@ -176,6 +191,28 @@ async function writeJson(abs, data) {
176
191
  return writeTextAtomic(abs, pretty);
177
192
  }
178
193
 
194
+ /**
195
+ * Sync variant — only for child-exit handlers and similar callbacks where
196
+ * an event-loop yield would deadlock the caller. Use writeJson/writeTextAtomic
197
+ * for everything else.
198
+ */
199
+ function writeJsonSync(abs, data) {
200
+ const real = validatePath(expandHome(abs));
201
+ validateWrite(real);
202
+ const dir = path.dirname(real);
203
+ fs.mkdirSync(dir, { recursive: true });
204
+ const tmp = `${real}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
205
+ try {
206
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
207
+ fs.renameSync(tmp, real);
208
+ } catch (e) {
209
+ try { fs.unlinkSync(tmp); } catch { /* tmp never created or already gone */ }
210
+ throw e;
211
+ }
212
+ const stat = fs.statSync(real);
213
+ return { ok: true, mtimeMs: stat.mtimeMs };
214
+ }
215
+
179
216
  async function listDir(abs, { filesOnly = false, dirsOnly = false, includeHidden = false } = {}) {
180
217
  abs = validatePath(expandHome(abs));
181
218
  try {
@@ -257,9 +294,7 @@ function watch(paths) {
257
294
  } catch {
258
295
  /* file may have been deleted */
259
296
  }
260
- if (window && !window.isDestroyed()) {
261
- window.webContents.send('config:changed', { path: key, mtimeMs, kind });
262
- }
297
+ sendIfAlive(window, 'config:changed', { path: key, mtimeMs, kind });
263
298
  };
264
299
  w.on('add', emit('add'));
265
300
  w.on('change', emit('change'));
@@ -299,8 +334,8 @@ function registerConfigHandlers() {
299
334
  ipcMain.handle('config:write-text', v(s.configWriteText, ({ path: p, text }) => writeTextAtomic(p, text)));
300
335
  ipcMain.handle('config:list-dir', v(s.configListDir, ({ path: p, opts }) => listDir(p, opts || {})));
301
336
  ipcMain.handle('config:exists', v(s.configPath, ({ path: p }) => exists(p)));
302
- ipcMain.on('config:watch', (_e, { paths }) => { try { s.configWatch.parse(paths); watch(paths); } catch { /* ignore */ } });
303
- ipcMain.on('config:unwatch', (_e, { paths }) => { try { s.configWatch.parse(paths); unwatch(paths); } catch { /* ignore */ } });
337
+ ipcMain.on('config:watch', (_e, { paths }) => { try { s.configWatch.parse(paths); watch(paths); } catch (e) { logs.writeLine({ level: 'warn', scope: 'config', message: 'config:watch schema reject', meta: { error: e?.message } }); } });
338
+ ipcMain.on('config:unwatch', (_e, { paths }) => { try { s.configWatch.parse(paths); unwatch(paths); } catch (e) { logs.writeLine({ level: 'warn', scope: 'config', message: 'config:unwatch schema reject', meta: { error: e?.message } }); } });
304
339
  }
305
340
 
306
341
  module.exports = {
@@ -311,8 +346,11 @@ module.exports = {
311
346
  readJson,
312
347
  readText,
313
348
  writeJson,
349
+ writeJsonSync,
314
350
  writeTextAtomic,
315
351
  listDir,
316
352
  exists,
317
353
  addAllowedRoot,
354
+ validatePath,
355
+ validateWrite,
318
356
  };
@@ -4,6 +4,7 @@ const { ipcMain } = require('electron');
4
4
  const fsp = require('node:fs/promises');
5
5
  const path = require('node:path');
6
6
  const os = require('node:os');
7
+ const { schemas } = require('./ipcSchemas.cjs');
7
8
 
8
9
  const PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
9
10
  const SLOW_THRESHOLD_MS = 2_000;
@@ -105,14 +106,18 @@ async function parseJSONL(filePath, stat) {
105
106
  }
106
107
 
107
108
  function registerHistoryAggregatorHandlers() {
108
- ipcMain.handle('history:aggregate', async (_e, req) => {
109
+ ipcMain.handle('history:aggregate', async (_e, rawReq) => {
110
+ // Wire the historyAggregate schema (previously defined but never used).
111
+ // safeParse so a malformed payload still falls through to defaults
112
+ // (today − 30d) rather than throwing — matches the current "best-effort"
113
+ // semantics expected by the History tab.
114
+ const parsed = schemas.historyAggregate.safeParse(rawReq);
115
+ const req = parsed.success ? (parsed.data ?? {}) : {};
109
116
  const t0 = Date.now();
110
117
  const today = new Date().toISOString().slice(0, 10);
111
- let effectiveTo = (req?.toDate && /^\d{4}-\d{2}-\d{2}$/.test(req.toDate)) ? req.toDate : today;
118
+ let effectiveTo = req?.toDate ? req.toDate : today;
112
119
  if (effectiveTo > today) effectiveTo = today;
113
- const effectiveFrom = (req?.fromDate && /^\d{4}-\d{2}-\d{2}$/.test(req.fromDate))
114
- ? req.fromDate
115
- : subtractDays(today, 30);
120
+ const effectiveFrom = req?.fromDate ? req.fromDate : subtractDays(today, 30);
116
121
 
117
122
  const buckets = new Map();
118
123
  let partial = false;
@@ -4,7 +4,7 @@ const path = require('node:path');
4
4
  const fs = require('node:fs');
5
5
  const fsp = require('node:fs/promises');
6
6
  const os = require('node:os');
7
- const { schemas } = require('./ipcSchemas.cjs');
7
+ const { schemas, validated } = require('./ipcSchemas.cjs');
8
8
  const { cleanChildEnv } = require('./lib/cleanEnv.cjs');
9
9
  const { manager: ptyManager, registerPtyHandlers } = require('./pty.cjs');
10
10
  const configMgr = require('./config.cjs');
@@ -17,9 +17,13 @@ const voiceWizard = require('./voiceWizard.cjs');
17
17
  const scheduler = require('./scheduler.cjs');
18
18
  const supervisor = require('./supervisor.cjs');
19
19
  const watchers = require('./watchers.cjs');
20
+ const teams = require('./teams.cjs');
21
+ const queueOps = require('./queueOps.cjs');
22
+ const pluginInstall = require('./pluginInstall.cjs');
20
23
  const otel = require('./otel.cjs');
21
24
  const otelSettings = require('./otelSettings.cjs');
22
25
  const { registerHistoryAggregatorHandlers } = require('./historyAggregator.cjs');
26
+ const memoryTool = require('./memoryTool.cjs');
23
27
 
24
28
  let mainWindow = null;
25
29
  let rebooting = false;
@@ -110,6 +114,7 @@ async function rebootApp() {
110
114
  });
111
115
  scheduler.attachWindow(mainWindow);
112
116
  watchers.attachWindow(mainWindow);
117
+ pluginInstall.attachWindow(mainWindow);
113
118
  rebooting = false;
114
119
  return;
115
120
  }
@@ -190,6 +195,10 @@ ipcMain.handle('app:home-dir', () => os.homedir());
190
195
 
191
196
  ipcMain.handle('app:cwd', () => process.cwd());
192
197
 
198
+ // E2E plumbing: tests set SM_E2E=1 to suppress the voice wizard auto-trigger.
199
+ // The renderer reads this once on mount.
200
+ ipcMain.handle('app:is-e2e', () => process.env.SM_E2E === '1');
201
+
193
202
  ipcMain.handle('app:engage-rules-path', () => process.env.SESSION_MANAGER_ENGAGE_RULES || null);
194
203
 
195
204
  ipcMain.handle('app:pick-directory', async () => {
@@ -211,10 +220,18 @@ ipcMain.on('app:reboot-app', () => rebootApp());
211
220
  // string. Timeout is enforced via SIGKILL on a timer because spawn's built-in
212
221
  // `timeout` option only sends SIGTERM, which a wedged shell may ignore.
213
222
  ipcMain.handle('app:test-fire-hook', async (_e, payload) => {
214
- const command = typeof payload?.command === 'string' ? payload.command : '';
215
- const env = payload && typeof payload.env === 'object' && payload.env !== null ? payload.env : null;
216
- const stdin = typeof payload?.payload === 'string' ? payload.payload : '';
217
- const requested = Number(payload?.timeoutMs);
223
+ // Zod-validate up front so a malformed renderer payload can never reach the
224
+ // shell. We `safeParse` (not throw) because the existing return shape is
225
+ // `{ exitCode, stdout, stderr, durationMs }` callers (Hooks.tsx) don't
226
+ // wrap the call in try/catch.
227
+ const parsed = schemas.appTestFireHook.safeParse(payload);
228
+ if (!parsed.success) {
229
+ return { exitCode: -1, stdout: '', stderr: `invalid payload: ${parsed.error.message}`, durationMs: 0 };
230
+ }
231
+ const command = parsed.data.command;
232
+ const env = parsed.data.env ?? null;
233
+ const stdin = typeof parsed.data.payload === 'string' ? parsed.data.payload : '';
234
+ const requested = parsed.data.timeoutMs;
218
235
  const timeoutMs = Number.isFinite(requested) && requested > 0
219
236
  ? Math.min(requested, 30_000)
220
237
  : 5_000;
@@ -322,8 +339,11 @@ ipcMain.handle('app:test-fire-hook', async (_e, payload) => {
322
339
  // render `—` without branching on error shape. 1s ceiling keeps a wedged git
323
340
  // (network filesystem, hung index lock) from blocking the renderer.
324
341
  ipcMain.handle('app:git-branch', async (_e, payload) => {
325
- const cwd = payload && typeof payload.cwd === 'string' ? payload.cwd : null;
326
- if (!cwd) return null;
342
+ // safeParse (not throw) so a malformed call resolves to null matches the
343
+ // existing semantics where StatusBar renders `—` on any failure.
344
+ const parsed = schemas.appGitBranch.safeParse(payload);
345
+ if (!parsed.success) return null;
346
+ const { cwd } = parsed.data;
327
347
  return await new Promise((resolve) => {
328
348
  execFile('git', ['branch', '--show-current'], { cwd, timeout: 1000, windowsHide: true }, (err, stdout) => {
329
349
  if (err) { resolve(null); return; }
@@ -333,6 +353,44 @@ ipcMain.handle('app:git-branch', async (_e, payload) => {
333
353
  });
334
354
  });
335
355
 
356
+ // Containment check used by the open-in-{editor,finder,terminal} handlers.
357
+ // Bare `startsWith(home)` matches `/home/bilkoEVIL` when home=`/home/bilko` —
358
+ // the classic prefix-trap. Resolve real paths (follow symlinks) and require
359
+ // either exact equality or a separator boundary. Returns null on success or
360
+ // an error string on failure. ENOENT (cwd doesn't exist) is a soft fail —
361
+ // downstream `spawn`/`shell.openPath` will surface a more precise error.
362
+ function checkInsideHome(cwd) {
363
+ if (typeof cwd !== 'string' || !cwd) return 'cwd outside home';
364
+ const home = os.homedir();
365
+ let realCwd;
366
+ let realHome;
367
+ try {
368
+ realHome = fs.realpathSync(home);
369
+ } catch {
370
+ return 'home directory unresolved';
371
+ }
372
+ try {
373
+ realCwd = fs.realpathSync(cwd);
374
+ } catch (err) {
375
+ if (err && err.code === 'ENOENT') {
376
+ // Fall through to the existing error path: the resolved-but-nonexistent
377
+ // path still has to be home-contained to avoid information disclosure
378
+ // via stat-probes (`/etc/secrets/...` → editor errors that reveal
379
+ // existence).
380
+ const resolved = path.resolve(cwd);
381
+ if (resolved !== realHome && !resolved.startsWith(realHome + path.sep)) {
382
+ return 'cwd outside home';
383
+ }
384
+ return null;
385
+ }
386
+ return 'cwd outside home';
387
+ }
388
+ if (realCwd !== realHome && !realCwd.startsWith(realHome + path.sep)) {
389
+ return 'cwd outside home';
390
+ }
391
+ return null;
392
+ }
393
+
336
394
  // Returns the resolved path of a command, or null if not found on PATH.
337
395
  function findCommand(name) {
338
396
  try {
@@ -348,8 +406,8 @@ function findCommand(name) {
348
406
 
349
407
  ipcMain.handle('app:open-in-editor', async (_e, payload) => {
350
408
  const { cwd, editor } = schemas.openInEditor.parse(payload);
351
- const home = os.homedir();
352
- if (!path.resolve(cwd).startsWith(home)) throw new Error('cwd outside home');
409
+ const err = checkInsideHome(cwd);
410
+ if (err) throw new Error(err);
353
411
  const candidates = (editor && editor !== 'auto')
354
412
  ? [editor]
355
413
  : [process.env.VISUAL, process.env.EDITOR, 'code', 'cursor', 'subl', 'nano'].filter(Boolean);
@@ -364,16 +422,16 @@ ipcMain.handle('app:open-in-editor', async (_e, payload) => {
364
422
 
365
423
  ipcMain.handle('app:open-in-finder', async (_e, payload) => {
366
424
  const { cwd } = schemas.openInFinder.parse(payload);
367
- const home = os.homedir();
368
- if (!path.resolve(cwd).startsWith(home)) throw new Error('cwd outside home');
425
+ const err = checkInsideHome(cwd);
426
+ if (err) throw new Error(err);
369
427
  await shell.openPath(cwd);
370
428
  return { ok: true };
371
429
  });
372
430
 
373
431
  ipcMain.handle('app:open-in-terminal', async (_e, payload) => {
374
432
  const { cwd } = schemas.openInTerminal.parse(payload);
375
- const home = os.homedir();
376
- if (!path.resolve(cwd).startsWith(home)) throw new Error('cwd outside home');
433
+ const err = checkInsideHome(cwd);
434
+ if (err) throw new Error(err);
377
435
  if (process.platform === 'linux') {
378
436
  const terms = ['gnome-terminal', 'konsole', 'xfce4-terminal', 'xterm'];
379
437
  for (const t of terms) {
@@ -412,7 +470,11 @@ voiceHotkey.registerHotkeyHandlers();
412
470
  voiceWizard.registerWizardHandlers();
413
471
  scheduler.registerScheduleHandlers();
414
472
  watchers.registerWatcherHandlers();
473
+ teams.registerTeamsHandlers();
474
+ queueOps.registerQueueOpsHandlers();
415
475
  registerHistoryAggregatorHandlers();
476
+ pluginInstall.registerPluginInstallHandlers();
477
+ memoryTool.registerMemoryHandlers();
416
478
 
417
479
  // OTEL telemetry export (opt-in via ~/.config/session-manager/otel.json).
418
480
  ipcMain.handle('otel:get-config', async () => otelSettings.load());
@@ -497,15 +559,23 @@ app.whenReady().then(async () => {
497
559
  });
498
560
 
499
561
  // Inject Content-Security-Policy for all renderer responses.
562
+ // frame-src / frame-ancestors locked to 'none' — the app is a top-level
563
+ // Electron BrowserWindow; iframes/embedding have no legitimate use.
500
564
  const CSP = [
501
565
  "default-src 'self'",
502
566
  "script-src 'self' 'wasm-unsafe-eval'",
503
567
  "style-src 'self' 'unsafe-inline'",
504
568
  "img-src 'self' data: blob:",
505
569
  "font-src 'self' data:",
506
- "connect-src 'self' https://api.anthropic.com",
570
+ // schemastore.org is used by Monaco for JSON schema validation
571
+ // (settings.json, keybindings.json — see App.tsx::installMonacoSchemas).
572
+ // The json.schemastore.org URL redirects to www.schemastore.org, so both
573
+ // hosts must be in the allowlist or CSP blocks the redirect.
574
+ "connect-src 'self' https://api.anthropic.com https://registry.npmjs.org https://json.schemastore.org https://www.schemastore.org",
507
575
  "media-src 'self' blob:",
508
576
  "worker-src 'self' blob:",
577
+ "frame-src 'none'",
578
+ "frame-ancestors 'none'",
509
579
  ].join('; ') + ';';
510
580
  session.defaultSession.webRequest.onHeadersReceived((details, cb) => {
511
581
  cb({
@@ -595,6 +665,7 @@ app.whenReady().then(async () => {
595
665
  });
596
666
  scheduler.attachWindow(mainWindow);
597
667
  watchers.attachWindow(mainWindow);
668
+ pluginInstall.attachWindow(mainWindow);
598
669
  scheduler.init().catch((e) => {
599
670
  logs.writeLine({ scope: 'scheduler', level: 'error', message: 'init failed', meta: { error: e?.message } });
600
671
  });