claude-code-session-manager 0.2.7 → 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.
Files changed (32) hide show
  1. package/dist/assets/{cssMode-BSA8IXdh.js → cssMode-CmcpKapf.js} +1 -1
  2. package/dist/assets/{editor.main-DNyTQ8C2.js → editor.main-QdhRqSRj.js} +3 -3
  3. package/dist/assets/{freemarker2-Dic49d2Z.js → freemarker2-MB42wuNH.js} +1 -1
  4. package/dist/assets/{handlebars-B0ttxNtX.js → handlebars-BNqpiTGG.js} +1 -1
  5. package/dist/assets/{html-BglndHCa.js → html-Uip3YcGr.js} +1 -1
  6. package/dist/assets/{htmlMode-DQDBWc2B.js → htmlMode-DYyTepoa.js} +1 -1
  7. package/dist/assets/index-BzbwWnyF.css +32 -0
  8. package/dist/assets/index-DIBKIDmx.js +2971 -0
  9. package/dist/assets/{javascript-mHLCLV2P.js → javascript-DrEGK9I4.js} +1 -1
  10. package/dist/assets/{jsonMode-BjtyoJTt.js → jsonMode-CSTtGACB.js} +1 -1
  11. package/dist/assets/{liquid-BOVepZ_L.js → liquid-RBrA4NW9.js} +1 -1
  12. package/dist/assets/{lspLanguageFeatures-CsKny4JJ.js → lspLanguageFeatures-BduwC_yH.js} +1 -1
  13. package/dist/assets/{mdx-BTgcnA78.js → mdx-DkPoTolY.js} +1 -1
  14. package/dist/assets/{python-ZXDfZDk7.js → python-CVO-ynpa.js} +1 -1
  15. package/dist/assets/{razor-CnSo5CZS.js → razor-M76CcRjZ.js} +1 -1
  16. package/dist/assets/{tsMode-BggS4HL2.js → tsMode-D8OXqf9n.js} +1 -1
  17. package/dist/assets/{typescript-B4jtvSCe.js → typescript-CBXprweT.js} +1 -1
  18. package/dist/assets/{whisperWorker-BhltUYOx.js → whisperWorker-HvcbMQn6.js} +17 -17
  19. package/dist/assets/{xml-CoM1qQrw.js → xml-EEhcqUb-.js} +1 -1
  20. package/dist/assets/{yaml-CSgZykpA.js → yaml-B8BKq6X4.js} +1 -1
  21. package/dist/index.html +2 -2
  22. package/package.json +1 -1
  23. package/src/main/index.cjs +50 -1
  24. package/src/main/lib/voice-hotkey-log.cjs +60 -0
  25. package/src/main/logs.cjs +82 -0
  26. package/src/main/voiceHotkey.cjs +346 -0
  27. package/src/main/voiceSettings.cjs +337 -0
  28. package/src/main/voiceWizard.cjs +54 -0
  29. package/src/preload/api.d.ts +86 -0
  30. package/src/preload/index.cjs +33 -0
  31. package/dist/assets/index-BYHTuGIu.css +0 -32
  32. package/dist/assets/index-CBPfy0j1.js +0 -2971
@@ -0,0 +1,337 @@
1
+ /**
2
+ * VoiceSettings — persists voice hotkey configuration to disk.
3
+ *
4
+ * Storage: ~/.config/session-manager/voice.json
5
+ * Shape: {
6
+ * accelerator: string, mode: 'hold'|'toggle', global: boolean, schemaVersion: 1,
7
+ * // F5 device picker (optional, additive):
8
+ * device?: { selectedDeviceId: string|null, selectedLabel: string|null, schemaVersion: 1 },
9
+ * // F7 first-run wizard (optional, additive):
10
+ * wizard?: { completedSchema: number|null, completedAt: string|null }
11
+ * }
12
+ *
13
+ * Mirrors the atomic write pattern from sessionsStore.cjs (tmp file + rename).
14
+ *
15
+ * F1 v1: schema migrations are out of scope; we just stamp `schemaVersion: 1`
16
+ * on every write so a future loader can branch. See PRD F1 v2 §Open Questions.
17
+ *
18
+ * F5/F7 invariant: writes preserve unknown top-level keys so the hotkey-config
19
+ * writer doesn't clobber the device/wizard subtrees (and vice versa).
20
+ * Last-writer-wins within a single subtree is acceptable per F5 PRD v2 §Persistence.
21
+ */
22
+
23
+ const fs = require('node:fs');
24
+ const fsp = require('node:fs/promises');
25
+ const path = require('node:path');
26
+ const os = require('node:os');
27
+
28
+ const SCHEMA_VERSION = 1;
29
+ const DEVICE_SCHEMA_VERSION = 1;
30
+ // F7: bumping this constant invalidates all completed wizards globally.
31
+ // Trade-off accepted per F7 PRD v2 §Schema-version bump policy.
32
+ const WIZARD_SCHEMA = 1;
33
+ // F8: turn-detector subtree. Bumping invalidates persisted state and falls
34
+ // back to defaults (mode='off', enabled=false) on next load.
35
+ const TURN_DETECTOR_SCHEMA = 1;
36
+
37
+ function defaultsForPlatform() {
38
+ // Cmd+Option+V is unbound by default on macOS (PRD F1 v2). Linux/Windows
39
+ // share Ctrl+Shift+Space — also unbound on default GNOME 45 / KDE 6 / Win.
40
+ if (process.platform === 'darwin') {
41
+ return { accelerator: 'Cmd+Option+V', mode: 'hold', global: false, schemaVersion: SCHEMA_VERSION };
42
+ }
43
+ return { accelerator: 'Ctrl+Shift+Space', mode: 'hold', global: false, schemaVersion: SCHEMA_VERSION };
44
+ }
45
+
46
+ function storePath() {
47
+ return path.join(os.homedir(), '.config', 'session-manager', 'voice.json');
48
+ }
49
+
50
+ // PRD F1 v2 §Chord schema. Anchored, requires ≥1 modifier so a bare
51
+ // alphanumeric can't bind a hotkey that fires while typing in a text box.
52
+ // Modifier order matches Electron's accelerator grammar; key part is letter,
53
+ // digit, F1-F24, or one of a small whitelist.
54
+ const ACCELERATOR_RE = /^(CommandOrControl|CmdOrCtrl|Cmd|Command|Ctrl|Control|Alt|Option|Shift|Super|Meta)(\+(CommandOrControl|CmdOrCtrl|Cmd|Command|Ctrl|Control|Alt|Option|Shift|Super|Meta))*\+([A-Z]|[0-9]|F([1-9]|1[0-9]|2[0-4])|Space|Tab|Enter|Backspace|Delete|Escape|Esc)$/;
55
+
56
+ function isValidConfig(cfg) {
57
+ if (!cfg || typeof cfg !== 'object') return false;
58
+ if (typeof cfg.accelerator !== 'string' || cfg.accelerator.length === 0) return false;
59
+ if (!ACCELERATOR_RE.test(cfg.accelerator)) return false;
60
+ if (cfg.mode !== 'hold' && cfg.mode !== 'toggle') return false;
61
+ if (typeof cfg.global !== 'boolean') return false;
62
+ // PRD §"Hold-mode-global removed": globalShortcut has no keyup so hold
63
+ // can't be implemented faithfully. Reject the combo at write time.
64
+ if (cfg.global === true && cfg.mode === 'hold') return false;
65
+ return true;
66
+ }
67
+
68
+ function normalize(cfg) {
69
+ return {
70
+ accelerator: String(cfg.accelerator),
71
+ mode: cfg.mode === 'toggle' ? 'toggle' : 'hold',
72
+ global: !!cfg.global,
73
+ schemaVersion: SCHEMA_VERSION,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Read the raw on-disk JSON object (or null if absent / unparseable). Used
79
+ * internally so writers can preserve unknown subtrees on partial updates.
80
+ */
81
+ async function readRaw() {
82
+ const p = storePath();
83
+ try {
84
+ const raw = await fsp.readFile(p, 'utf8');
85
+ const data = JSON.parse(raw);
86
+ return (data && typeof data === 'object') ? data : null;
87
+ } catch (e) {
88
+ if (e.code !== 'ENOENT') {
89
+ console.warn('[voiceSettings] readRaw failed:', e.message);
90
+ }
91
+ return null;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Atomic full-file write of a merged object. Preserves any keys not touched
97
+ * by the caller. Complexity: O(k) over top-level keys; k is tiny.
98
+ *
99
+ * Serialized through `writeQueue` so concurrent F1/F5/F7 saves cannot
100
+ * read-then-merge-then-write atop each other and silently drop a patch.
101
+ * The queue is per-process; cross-process coordination is not in scope.
102
+ */
103
+ let writeQueue = Promise.resolve();
104
+ async function writeMerged(patch) {
105
+ const run = async () => {
106
+ const p = storePath();
107
+ await fsp.mkdir(path.dirname(p), { recursive: true }).catch(() => {});
108
+ const existing = (await readRaw()) || {};
109
+ const next = { ...existing, ...patch };
110
+ const body = JSON.stringify(next, null, 2) + '\n';
111
+ const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
112
+ await fsp.writeFile(tmp, body, 'utf8', { mode: 0o600 });
113
+ // chmod tmp explicitly because some platforms ignore the mode arg on
114
+ // writeFile when the file pre-exists. Then rename for atomicity.
115
+ try { await fsp.chmod(tmp, 0o600); } catch { /* */ }
116
+ await fsp.rename(tmp, p);
117
+ return { ok: true };
118
+ };
119
+ // Tail-promise pattern: each call awaits the previous, so writes are
120
+ // ordered. Errors do not poison the queue.
121
+ const next = writeQueue.then(run, run);
122
+ writeQueue = next.catch(() => {});
123
+ return next;
124
+ }
125
+
126
+ /** Read voice config from disk; create with defaults if absent. */
127
+ async function load() {
128
+ const data = await readRaw();
129
+ if (data === null) {
130
+ const def = defaultsForPlatform();
131
+ await save(def);
132
+ return def;
133
+ }
134
+ if (!isValidConfig(data)) {
135
+ // Treat malformed file as "create defaults" — we never lose user data
136
+ // because we don't overwrite their accelerator silently; we just don't
137
+ // honor a broken config. The user can hand-edit voice.json in v1.
138
+ console.warn('[voiceSettings] invalid config on disk, using defaults');
139
+ const def = defaultsForPlatform();
140
+ await save(def);
141
+ return def;
142
+ }
143
+ return normalize(data);
144
+ }
145
+
146
+ /**
147
+ * Atomic write of the hotkey config keys, preserving any other subtrees
148
+ * (notably F5 `device`). Tmp file + rename per sessionsStore.cjs:46-48.
149
+ */
150
+ async function save(cfg) {
151
+ const n = normalize(cfg);
152
+ return writeMerged({
153
+ accelerator: n.accelerator,
154
+ mode: n.mode,
155
+ global: n.global,
156
+ schemaVersion: n.schemaVersion,
157
+ });
158
+ }
159
+
160
+ // ──────────────────────────────────────────── F5 device-pref helpers
161
+ // Device prefs share `voice.json` to keep one canonical settings file. They
162
+ // live under the `device` subtree so the F1 normalizer doesn't strip them.
163
+
164
+ const DEFAULT_DEVICE = Object.freeze({
165
+ selectedDeviceId: null,
166
+ selectedLabel: null,
167
+ schemaVersion: DEVICE_SCHEMA_VERSION,
168
+ });
169
+
170
+ function isValidDevicePref(d) {
171
+ if (!d || typeof d !== 'object') return false;
172
+ const id = d.selectedDeviceId;
173
+ const lbl = d.selectedLabel;
174
+ if (id !== null && typeof id !== 'string') return false;
175
+ if (lbl !== null && typeof lbl !== 'string') return false;
176
+ return true;
177
+ }
178
+
179
+ function normalizeDevice(d) {
180
+ return {
181
+ selectedDeviceId: typeof d.selectedDeviceId === 'string' && d.selectedDeviceId ? d.selectedDeviceId : null,
182
+ selectedLabel: typeof d.selectedLabel === 'string' && d.selectedLabel ? d.selectedLabel : null,
183
+ schemaVersion: DEVICE_SCHEMA_VERSION,
184
+ };
185
+ }
186
+
187
+ /** Read F5 device pref. Defaults are returned (and persisted) on absence. */
188
+ async function loadDevice() {
189
+ const raw = await readRaw();
190
+ const sub = raw && typeof raw === 'object' ? raw.device : null;
191
+ if (!isValidDevicePref(sub)) {
192
+ // Don't auto-write on load — empty/missing device is the legitimate
193
+ // fresh-install state. The first setDevicePref will materialize it.
194
+ return { ...DEFAULT_DEVICE };
195
+ }
196
+ return normalizeDevice(sub);
197
+ }
198
+
199
+ /** Atomic write of just the device subtree, preserving other keys. */
200
+ async function saveDevice(d) {
201
+ if (!isValidDevicePref(d)) {
202
+ throw new Error('Invalid device pref shape');
203
+ }
204
+ return writeMerged({ device: normalizeDevice(d) });
205
+ }
206
+
207
+ // ──────────────────────────────────────────── F7 wizard-state helpers
208
+ // Wizard state lives under the `wizard` subtree of voice.json. Mirrors the F5
209
+ // device pattern: additive, preserves other subtrees on write, returns a
210
+ // safe default when absent so the wizard arms on first launch.
211
+
212
+ const DEFAULT_WIZARD = Object.freeze({
213
+ completedSchema: null,
214
+ completedAt: null,
215
+ });
216
+
217
+ function isValidWizardState(w) {
218
+ if (!w || typeof w !== 'object') return false;
219
+ const cs = w.completedSchema;
220
+ const ca = w.completedAt;
221
+ if (cs !== null && typeof cs !== 'number') return false;
222
+ if (ca !== null && typeof ca !== 'string') return false;
223
+ return true;
224
+ }
225
+
226
+ function normalizeWizard(w) {
227
+ return {
228
+ completedSchema: typeof w.completedSchema === 'number' ? w.completedSchema : null,
229
+ completedAt: typeof w.completedAt === 'string' && w.completedAt ? w.completedAt : null,
230
+ };
231
+ }
232
+
233
+ /**
234
+ * Read the F7 wizard subtree. Returns the default (un-completed) shape when
235
+ * the subtree is missing or malformed — the wizard arms in that case. We do
236
+ * NOT auto-write a default on load; the first markComplete materializes it.
237
+ */
238
+ async function loadWizard() {
239
+ const raw = await readRaw();
240
+ const sub = raw && typeof raw === 'object' ? raw.wizard : null;
241
+ if (!isValidWizardState(sub)) {
242
+ return { ...DEFAULT_WIZARD };
243
+ }
244
+ return normalizeWizard(sub);
245
+ }
246
+
247
+ /** Atomic write of just the wizard subtree, preserving other keys. */
248
+ async function saveWizard(state) {
249
+ if (!isValidWizardState(state)) {
250
+ throw new Error('Invalid wizard state shape');
251
+ }
252
+ return writeMerged({ wizard: normalizeWizard(state) });
253
+ }
254
+
255
+ // ──────────────────────────────────────────── F8 turn-detector helpers
256
+ // Lives under the `turnDetector` subtree of voice.json. MVP-only fields;
257
+ // the actual smart-turn-v3 ONNX model is NOT loaded in v1 — see F8 PRD v2
258
+ // and the follow-up TODO in turnDetectorWorker.ts. Mirrors F5 device pattern.
259
+
260
+ const DEFAULT_TURN_DETECTOR = Object.freeze({
261
+ enabled: false,
262
+ mode: 'off', // 'audio' | 'text' | 'off'
263
+ dictationMode: false,
264
+ schemaVersion: TURN_DETECTOR_SCHEMA,
265
+ });
266
+
267
+ const TURN_DETECTOR_MODES = new Set(['audio', 'text', 'off']);
268
+
269
+ function isValidTurnDetectorState(t) {
270
+ if (!t || typeof t !== 'object') return false;
271
+ if (typeof t.enabled !== 'boolean') return false;
272
+ if (typeof t.mode !== 'string' || !TURN_DETECTOR_MODES.has(t.mode)) return false;
273
+ if (typeof t.dictationMode !== 'boolean') return false;
274
+ return true;
275
+ }
276
+
277
+ function normalizeTurnDetector(t) {
278
+ const mode = TURN_DETECTOR_MODES.has(t.mode) ? t.mode : 'off';
279
+ // Cross-field invariant: `enabled` and `mode === 'off'` are inconsistent.
280
+ // We normalize so callers can be sloppy: if mode is 'off' then enabled=false.
281
+ const enabled = mode === 'off' ? false : !!t.enabled;
282
+ return {
283
+ enabled,
284
+ mode,
285
+ dictationMode: !!t.dictationMode,
286
+ schemaVersion: TURN_DETECTOR_SCHEMA,
287
+ };
288
+ }
289
+
290
+ /**
291
+ * Read the F8 turn-detector subtree. Returns DEFAULT (`mode: 'off'`) when the
292
+ * subtree is missing or malformed — F8 MVP is opt-in, so a missing config
293
+ * yields the safe pure-VAD path. We do NOT auto-write a default on load.
294
+ */
295
+ async function loadTurnDetector() {
296
+ const raw = await readRaw();
297
+ const sub = raw && typeof raw === 'object' ? raw.turnDetector : null;
298
+ if (!isValidTurnDetectorState(sub)) {
299
+ return { ...DEFAULT_TURN_DETECTOR };
300
+ }
301
+ return normalizeTurnDetector(sub);
302
+ }
303
+
304
+ /** Atomic write of just the turnDetector subtree, preserving other keys. */
305
+ async function saveTurnDetector(state) {
306
+ if (!isValidTurnDetectorState(state)) {
307
+ throw new Error('Invalid turn-detector state shape');
308
+ }
309
+ return writeMerged({ turnDetector: normalizeTurnDetector(state) });
310
+ }
311
+
312
+ module.exports = {
313
+ load,
314
+ save,
315
+ storePath,
316
+ defaultsForPlatform,
317
+ isValidConfig,
318
+ SCHEMA_VERSION,
319
+ // F5
320
+ loadDevice,
321
+ saveDevice,
322
+ isValidDevicePref,
323
+ DEFAULT_DEVICE,
324
+ DEVICE_SCHEMA_VERSION,
325
+ // F7
326
+ loadWizard,
327
+ saveWizard,
328
+ isValidWizardState,
329
+ DEFAULT_WIZARD,
330
+ WIZARD_SCHEMA,
331
+ // F8
332
+ loadTurnDetector,
333
+ saveTurnDetector,
334
+ isValidTurnDetectorState,
335
+ DEFAULT_TURN_DETECTOR,
336
+ TURN_DETECTOR_SCHEMA,
337
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * voiceWizard — F7 first-run mic-check wizard, main-process side.
3
+ *
4
+ * Owns three IPC channels:
5
+ * - voice:wizard-state → returns persisted state + current schema constant
6
+ * - voice:wizard-complete → stamps completedAt + completedSchema to voice.json
7
+ * - app:is-e2e → exposes process.env.SM_E2E === '1' to the renderer
8
+ *
9
+ * No window state held here; persistence is delegated to voiceSettings.cjs
10
+ * (additive `wizard` subtree on the same file as F1/F5).
11
+ */
12
+
13
+ const { ipcMain } = require('electron');
14
+ const voiceSettings = require('./voiceSettings.cjs');
15
+
16
+ function registerWizardHandlers() {
17
+ ipcMain.handle('voice:wizard-state', async () => {
18
+ const w = await voiceSettings.loadWizard();
19
+ return {
20
+ completedSchema: w.completedSchema,
21
+ completedAt: w.completedAt,
22
+ currentSchema: voiceSettings.WIZARD_SCHEMA,
23
+ };
24
+ });
25
+
26
+ ipcMain.handle('voice:wizard-complete', async () => {
27
+ const next = {
28
+ completedSchema: voiceSettings.WIZARD_SCHEMA,
29
+ completedAt: new Date().toISOString(),
30
+ };
31
+ await voiceSettings.saveWizard(next);
32
+ return { ok: true, ...next, currentSchema: voiceSettings.WIZARD_SCHEMA };
33
+ });
34
+
35
+ // E2E plumbing: tests set SM_E2E=1 to suppress the wizard auto-trigger.
36
+ // The renderer reads this once on mount.
37
+ ipcMain.handle('app:is-e2e', () => process.env.SM_E2E === '1');
38
+
39
+ // F8 — turn-detector settings (additive subtree on voice.json).
40
+ // MVP: persistence only; no model loaded in v1 (see PRD §Loading & inference).
41
+ ipcMain.handle('voice:get-turn-detector', async () => {
42
+ return await voiceSettings.loadTurnDetector();
43
+ });
44
+
45
+ ipcMain.handle('voice:set-turn-detector', async (_e, state) => {
46
+ if (!voiceSettings.isValidTurnDetectorState(state)) {
47
+ throw new Error('Invalid turn-detector state payload');
48
+ }
49
+ await voiceSettings.saveTurnDetector(state);
50
+ return { ok: true, state: await voiceSettings.loadTurnDetector() };
51
+ });
52
+ }
53
+
54
+ module.exports = { registerWizardHandlers };
@@ -119,6 +119,66 @@ export interface BillingFetchResult {
119
119
  error?: string;
120
120
  }
121
121
 
122
+ export interface VoiceHotkeyConfig {
123
+ accelerator: string;
124
+ mode: 'hold' | 'toggle';
125
+ global: boolean;
126
+ schemaVersion: 1;
127
+ }
128
+
129
+ export interface VoiceHotkeyEvent {
130
+ phase: 'down' | 'up';
131
+ source: 'window' | 'global';
132
+ }
133
+
134
+ export interface VoiceSetHotkeyResult {
135
+ ok: boolean;
136
+ config: VoiceHotkeyConfig;
137
+ }
138
+
139
+ /** F5 — persisted audio-input device preference. */
140
+ export interface VoiceDevicePref {
141
+ selectedDeviceId: string | null;
142
+ selectedLabel: string | null;
143
+ schemaVersion: 1;
144
+ }
145
+
146
+ /** F7 — first-run mic-check wizard state. */
147
+ export interface VoiceWizardState {
148
+ /** Schema version stamped at the time the user completed the wizard, or null. */
149
+ completedSchema: number | null;
150
+ /** ISO-8601 completion timestamp, or null if never completed. */
151
+ completedAt: string | null;
152
+ /** Hardcoded current schema constant; bumping invalidates all completed wizards. */
153
+ currentSchema: number;
154
+ }
155
+
156
+ export interface VoiceMarkWizardCompleteResult {
157
+ ok: boolean;
158
+ completedSchema: number;
159
+ completedAt: string;
160
+ currentSchema: number;
161
+ }
162
+
163
+ /**
164
+ * F8 — semantic turn-detection settings. MVP shape only; the actual
165
+ * smart-turn-v3 ONNX model is not loaded in v1. Persisted at
166
+ * ~/.config/session-manager/voice.json under `turnDetector`. See PRD F8 v2.
167
+ */
168
+ export interface VoiceTurnDetectorState {
169
+ enabled: boolean;
170
+ /** 'audio' = smart-turn-v3 (planned); 'text' = rejected on license; 'off' = pure-VAD (default). */
171
+ mode: 'audio' | 'text' | 'off';
172
+ /** When true, bypass the model and use pure-VAD endpointing (out-of-distribution code). */
173
+ dictationMode: boolean;
174
+ schemaVersion: 1;
175
+ }
176
+
177
+ export interface VoiceSetTurnDetectorResult {
178
+ ok: boolean;
179
+ state: VoiceTurnDetectorState;
180
+ }
181
+
122
182
  export interface SessionManagerAPI {
123
183
  app: {
124
184
  version: () => Promise<string>;
@@ -127,6 +187,8 @@ export interface SessionManagerAPI {
127
187
  engageRulesPath: () => Promise<string | null>;
128
188
  pickDirectory: () => Promise<string | null>;
129
189
  rebootApp: () => void;
190
+ /** F7 — true under SM_E2E=1; renderer uses this to suppress wizard auto-trigger. */
191
+ isE2E: () => Promise<boolean>;
130
192
  onNewSession: (handler: () => void) => () => void;
131
193
  onRebootSession: (handler: () => void) => () => void;
132
194
  };
@@ -152,6 +214,10 @@ export interface SessionManagerAPI {
152
214
  billing: {
153
215
  fetch: () => Promise<BillingFetchResult>;
154
216
  };
217
+ logs: {
218
+ write: (scope: string, level: 'debug' | 'info' | 'warn' | 'error', message: string, meta?: unknown) => void;
219
+ dir: () => Promise<string>;
220
+ };
155
221
  config: {
156
222
  readJson: (path: string) => Promise<ReadJsonResult>;
157
223
  readText: (path: string) => Promise<ReadTextResult>;
@@ -163,6 +229,26 @@ export interface SessionManagerAPI {
163
229
  unwatch: (paths: string[]) => void;
164
230
  onChanged: (handler: (info: ConfigChangedEvent) => void) => () => void;
165
231
  };
232
+ voice: {
233
+ onHotkey: (handler: (event: VoiceHotkeyEvent) => void) => () => void;
234
+ onHotkeyConfigChanged: (handler: (cfg: VoiceHotkeyConfig) => void) => () => void;
235
+ getHotkeyConfig: () => Promise<VoiceHotkeyConfig>;
236
+ setHotkeyConfig: (cfg: VoiceHotkeyConfig) => Promise<VoiceSetHotkeyResult>;
237
+ getHotkeyConfigPath: () => Promise<string>;
238
+ setRecording: (recording: boolean) => void;
239
+ /** F5: read persisted audio-input device preference. */
240
+ getDevicePref: () => Promise<VoiceDevicePref>;
241
+ /** F5: persist audio-input device preference. */
242
+ setDevicePref: (pref: { selectedDeviceId: string | null; selectedLabel: string | null }) => Promise<{ ok: boolean }>;
243
+ /** F7: read first-run wizard state + current schema constant. */
244
+ getWizardState: () => Promise<VoiceWizardState>;
245
+ /** F7: stamp wizard as complete with the current schema. */
246
+ markWizardComplete: () => Promise<VoiceMarkWizardCompleteResult>;
247
+ /** F8 — read persisted turn-detector settings. */
248
+ getTurnDetector: () => Promise<VoiceTurnDetectorState>;
249
+ /** F8 — persist turn-detector settings. */
250
+ setTurnDetector: (state: VoiceTurnDetectorState) => Promise<VoiceSetTurnDetectorResult>;
251
+ };
166
252
  }
167
253
 
168
254
  declare global {
@@ -8,6 +8,8 @@ contextBridge.exposeInMainWorld('api', {
8
8
  engageRulesPath: () => ipcRenderer.invoke('app:engage-rules-path'),
9
9
  pickDirectory: () => ipcRenderer.invoke('app:pick-directory'),
10
10
  rebootApp: () => ipcRenderer.send('app:reboot-app'),
11
+ // F7: lets the renderer suppress the wizard auto-trigger under SM_E2E=1.
12
+ isE2E: () => ipcRenderer.invoke('app:is-e2e'),
11
13
  onNewSession: (handler) => {
12
14
  const listener = () => handler();
13
15
  ipcRenderer.on('app:new-session', listener);
@@ -57,6 +59,11 @@ contextBridge.exposeInMainWorld('api', {
57
59
  billing: {
58
60
  fetch: () => ipcRenderer.invoke('billing:fetch'),
59
61
  },
62
+ logs: {
63
+ write: (scope, level, message, meta) =>
64
+ ipcRenderer.send('log:write', { scope, level, message, meta }),
65
+ dir: () => ipcRenderer.invoke('log:dir'),
66
+ },
60
67
  config: {
61
68
  readJson: (path) => ipcRenderer.invoke('config:read-json', { path }),
62
69
  readText: (path) => ipcRenderer.invoke('config:read-text', { path }),
@@ -72,4 +79,30 @@ contextBridge.exposeInMainWorld('api', {
72
79
  return () => ipcRenderer.removeListener('config:changed', listener);
73
80
  },
74
81
  },
82
+ voice: {
83
+ onHotkey: (handler) => {
84
+ const listener = (_e, payload) => handler(payload);
85
+ ipcRenderer.on('voice:hotkey', listener);
86
+ return () => ipcRenderer.removeListener('voice:hotkey', listener);
87
+ },
88
+ onHotkeyConfigChanged: (handler) => {
89
+ const listener = (_e, cfg) => handler(cfg);
90
+ ipcRenderer.on('voice:hotkey-changed', listener);
91
+ return () => ipcRenderer.removeListener('voice:hotkey-changed', listener);
92
+ },
93
+ getHotkeyConfig: () => ipcRenderer.invoke('voice:get-hotkey-config'),
94
+ setHotkeyConfig: (cfg) => ipcRenderer.invoke('voice:set-hotkey', cfg),
95
+ getHotkeyConfigPath: () => ipcRenderer.invoke('voice:get-hotkey-config-path'),
96
+ setRecording: (recording) => ipcRenderer.send('voice:set-recording', !!recording),
97
+ // F5 device picker prefs (~/.config/session-manager/voice.json `device` key).
98
+ getDevicePref: () => ipcRenderer.invoke('voice:get-device-pref'),
99
+ setDevicePref: (pref) => ipcRenderer.invoke('voice:set-device-pref', pref),
100
+ // F7 first-run wizard state (~/.config/session-manager/voice.json `wizard` key).
101
+ getWizardState: () => ipcRenderer.invoke('voice:wizard-state'),
102
+ markWizardComplete: () => ipcRenderer.invoke('voice:wizard-complete'),
103
+ // F8 turn-detector settings (~/.config/session-manager/voice.json `turnDetector` key).
104
+ // MVP: settings persistence + kill switch only; no model is actually loaded in v1.
105
+ getTurnDetector: () => ipcRenderer.invoke('voice:get-turn-detector'),
106
+ setTurnDetector: (state) => ipcRenderer.invoke('voice:set-turn-detector', state),
107
+ },
75
108
  });
@@ -1,32 +0,0 @@
1
- /**
2
- * Copyright (c) 2014 The xterm.js authors. All rights reserved.
3
- * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
4
- * https://github.com/chjj/term.js
5
- * @license MIT
6
- *
7
- * Permission is hereby granted, free of charge, to any person obtaining a copy
8
- * of this software and associated documentation files (the "Software"), to deal
9
- * in the Software without restriction, including without limitation the rights
10
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- * copies of the Software, and to permit persons to whom the Software is
12
- * furnished to do so, subject to the following conditions:
13
- *
14
- * The above copyright notice and this permission notice shall be included in
15
- * all copies or substantial portions of the Software.
16
- *
17
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- * THE SOFTWARE.
24
- *
25
- * Originally forked from (with the author's permission):
26
- * Fabrice Bellard's javascript vt100 for jslinux:
27
- * http://bellard.org/jslinux/
28
- * Copyright (c) 2011 Fabrice Bellard
29
- * The original design remains. The terminal itself
30
- * has been extended to include xterm CSI codes, among
31
- * other features.
32
- */.xterm{cursor:text;position:relative;-moz-user-select:none;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::-moz-selection{color:transparent}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;-moz-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{-webkit-text-decoration:double underline;text-decoration:double underline}.xterm-underline-3{-webkit-text-decoration:wavy underline;text-decoration:wavy underline}.xterm-underline-4{-webkit-text-decoration:dotted underline;text-decoration:dotted underline}.xterm-underline-5{-webkit-text-decoration:dashed underline;text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{-webkit-text-decoration:overline double underline;text-decoration:overline double underline}.xterm-overline.xterm-underline-3{-webkit-text-decoration:overline wavy underline;text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{-webkit-text-decoration:overline dotted underline;text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{-webkit-text-decoration:overline dashed underline;text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,SFMono-Regular,Menlo,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.-bottom-\[5px\]{bottom:-5px}.-bottom-\[7px\]{bottom:-7px}.-top-20{top:-5rem}.-top-28{top:-7rem}.-top-48{top:-12rem}.bottom-0{bottom:0}.bottom-24{bottom:6rem}.bottom-3{bottom:.75rem}.bottom-\[88px\]{bottom:88px}.left-0{left:0}.left-1\/2{left:50%}.left-4{left:1rem}.left-\[72\%\]{left:72%}.right-0{right:0}.right-4{right:1rem}.top-0{top:0}.top-0\.5{top:.125rem}.top-10{top:2.5rem}.top-4{top:1rem}.z-10{z-index:10}.mx-2{margin-left:.5rem;margin-right:.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-2{height:.5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-9{height:2.25rem}.h-full{height:100%}.h-px{height:1px}.min-h-0{min-height:0px}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-32{width:8rem}.w-4{width:1rem}.w-48{width:12rem}.w-52{width:13rem}.w-56{width:14rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-full{width:100%}.min-w-0{min-width:0px}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl{max-width:56rem}.max-w-md{max-width:28rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0\.5{--tw-translate-x: .125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-3\.5{--tw-translate-x: .875rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-45{--tw-rotate: 45deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-grab{cursor:grab}.cursor-grabbing{cursor:grabbing}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-wait{cursor:wait}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-y-1{row-gap:.25rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-line>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(36 42 51 / var(--tw-divide-opacity, 1))}.divide-line\/60>:not([hidden])~:not([hidden]){border-color:#242a3399}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-xl{border-radius:.75rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-accent{--tw-border-opacity: 1;border-color:rgb(217 119 87 / var(--tw-border-opacity, 1))}.border-accent\/30{border-color:#d977574d}.border-accent\/40{border-color:#d9775766}.border-accent\/50{border-color:#d9775780}.border-blue-900\/40{border-color:#1e3a8a66}.border-line{--tw-border-opacity: 1;border-color:rgb(36 42 51 / var(--tw-border-opacity, 1))}.border-line\/50{border-color:#242a3380}.border-line\/60{border-color:#242a3399}.border-purple-900\/40{border-color:#581c8766}.border-red-900\/40{border-color:#7f1d1d66}.border-transparent{border-color:transparent}.border-yellow-600\/50{border-color:#ca8a0480}.border-yellow-600\/60{border-color:#ca8a0499}.border-yellow-900\/40{border-color:#713f1266}.bg-accent{--tw-bg-opacity: 1;background-color:rgb(217 119 87 / var(--tw-bg-opacity, 1))}.bg-accent\/15{background-color:#d9775726}.bg-bg{--tw-bg-opacity: 1;background-color:rgb(11 13 16 / var(--tw-bg-opacity, 1))}.bg-bg-elev{--tw-bg-opacity: 1;background-color:rgb(18 21 26 / var(--tw-bg-opacity, 1))}.bg-bg-elev\/40{background-color:#12151a66}.bg-bg-elev\/80{background-color:#12151acc}.bg-bg-hi{--tw-bg-opacity: 1;background-color:rgb(26 31 39 / var(--tw-bg-opacity, 1))}.bg-bg-hi\/30{background-color:#1a1f274d}.bg-blue-950\/30{background-color:#1725544d}.bg-emerald-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity, 1))}.bg-fg{--tw-bg-opacity: 1;background-color:rgb(230 232 236 / var(--tw-bg-opacity, 1))}.bg-fg-dim{--tw-bg-opacity: 1;background-color:rgb(138 147 160 / var(--tw-bg-opacity, 1))}.bg-fg-faint{--tw-bg-opacity: 1;background-color:rgb(84 92 104 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-line\/60{background-color:#242a3399}.bg-purple-950\/30{background-color:#3b07644d}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.bg-red-950\/20{background-color:#450a0a33}.bg-red-950\/30{background-color:#450a0a4d}.bg-yellow-400{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.bg-yellow-950\/20{background-color:#42200633}.bg-yellow-950\/30{background-color:#4220064d}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.from-bg{--tw-gradient-from: #0b0d10 var(--tw-gradient-from-position);--tw-gradient-to: rgb(11 13 16 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-bg{--tw-gradient-to: rgb(11 13 16 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #0b0d10 var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-bg-elev{--tw-gradient-to: #12151a var(--tw-gradient-to-position)}.p-1\.5{padding:.375rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.pb-1{padding-bottom:.25rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:JetBrains Mono,ui-monospace,SFMono-Regular,Menlo,monospace}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-normal{font-weight:400}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-relaxed{line-height:1.625}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-accent{--tw-text-opacity: 1;color:rgb(217 119 87 / var(--tw-text-opacity, 1))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-bg{--tw-text-opacity: 1;color:rgb(11 13 16 / var(--tw-text-opacity, 1))}.text-blue-400\/80{color:#60a5facc}.text-fg{--tw-text-opacity: 1;color:rgb(230 232 236 / var(--tw-text-opacity, 1))}.text-fg-dim{--tw-text-opacity: 1;color:rgb(138 147 160 / var(--tw-text-opacity, 1))}.text-fg-faint{--tw-text-opacity: 1;color:rgb(84 92 104 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-purple-400\/80{color:#c084fccc}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-400\/80{color:#f87171cc}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.text-yellow-500\/70{color:#eab308b3}.text-yellow-500\/80{color:#eab308cc}.text-yellow-500\/90{color:#eab308e6}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline-offset-2{text-underline-offset:2px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.placeholder-fg-faint::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(84 92 104 / var(--tw-placeholder-opacity, 1))}.placeholder-fg-faint::placeholder{--tw-placeholder-opacity: 1;color:rgb(84 92 104 / var(--tw-placeholder-opacity, 1))}.accent-accent{accent-color:#d97757}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-red-400{--tw-ring-opacity: 1;--tw-ring-color: rgb(248 113 113 / var(--tw-ring-opacity, 1))}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-2xl{--tw-drop-shadow: drop-shadow(0 25px 25px rgb(0 0 0 / .15));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert: invert(100%);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.\!filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\]{transition-property:width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}html,body,#root{height:100%;margin:0;overflow:hidden}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:#0b0d10}::-webkit-scrollbar-thumb{background:#242a33;border-radius:5px}::-webkit-scrollbar-thumb:hover{background:#2f3742}.hover\:border-fg-faint:hover{--tw-border-opacity: 1;border-color:rgb(84 92 104 / var(--tw-border-opacity, 1))}.hover\:bg-accent:hover{--tw-bg-opacity: 1;background-color:rgb(217 119 87 / var(--tw-bg-opacity, 1))}.hover\:bg-accent\/25:hover{background-color:#d9775740}.hover\:bg-bg-elev:hover{--tw-bg-opacity: 1;background-color:rgb(18 21 26 / var(--tw-bg-opacity, 1))}.hover\:bg-bg-elev\/20:hover{background-color:#12151a33}.hover\:bg-bg-elev\/50:hover{background-color:#12151a80}.hover\:bg-bg-hi:hover{--tw-bg-opacity: 1;background-color:rgb(26 31 39 / var(--tw-bg-opacity, 1))}.hover\:bg-bg-hi\/50:hover{background-color:#1a1f2780}.hover\:bg-transparent:hover{background-color:transparent}.hover\:bg-yellow-600:hover{--tw-bg-opacity: 1;background-color:rgb(202 138 4 / var(--tw-bg-opacity, 1))}.hover\:text-bg:hover{--tw-text-opacity: 1;color:rgb(11 13 16 / var(--tw-text-opacity, 1))}.hover\:text-fg:hover{--tw-text-opacity: 1;color:rgb(230 232 236 / var(--tw-text-opacity, 1))}.hover\:text-fg-dim:hover{--tw-text-opacity: 1;color:rgb(138 147 160 / var(--tw-text-opacity, 1))}.hover\:text-red-400:hover{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.group:hover .group-hover\:opacity-100{opacity:1}