@phenx-inc/ctlsurf 0.13.1 → 0.13.5

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 (39) hide show
  1. package/LICENSE +22 -0
  2. package/out/headless/index.mjs +9 -23
  3. package/out/headless/index.mjs.map +3 -3
  4. package/out/main/index.js +17 -22
  5. package/out/preload/index.js +1 -0
  6. package/out/renderer/assets/{cssMode-KhYyY-BM.js → cssMode-CAEObGNt.js} +3 -3
  7. package/out/renderer/assets/{freemarker2-BFsSgcWk.js → freemarker2-D63hnnDq.js} +1 -1
  8. package/out/renderer/assets/{handlebars-Be6_aBsm.js → handlebars-UYo_IYpi.js} +1 -1
  9. package/out/renderer/assets/{html-G8mvsyfc.js → html-2dvYDi3g.js} +1 -1
  10. package/out/renderer/assets/{htmlMode-Cfd5VCjw.js → htmlMode-B6fxDDPF.js} +3 -3
  11. package/out/renderer/assets/{index-Bi_MjBqs.css → index-BEekQe3-.css} +116 -0
  12. package/out/renderer/assets/{index-MqgBcJzy.js → index-CMb4AqkZ.js} +552 -411
  13. package/out/renderer/assets/{javascript-XyDWi6dC.js → javascript-8RyFDDdk.js} +2 -2
  14. package/out/renderer/assets/{jsonMode-DZT5gBVW.js → jsonMode-CqspkWCb.js} +3 -3
  15. package/out/renderer/assets/{liquid-BuajCNJ9.js → liquid-DaEnAsYY.js} +1 -1
  16. package/out/renderer/assets/{lspLanguageFeatures-BYkg2Yc-.js → lspLanguageFeatures-Czkp5sZb.js} +1 -1
  17. package/out/renderer/assets/{mdx-D0Qx2GMS.js → mdx-Q-qSZsFg.js} +1 -1
  18. package/out/renderer/assets/{python-ChncNy6S.js → python-Sw0S0BJV.js} +1 -1
  19. package/out/renderer/assets/{razor-C6MUtfaS.js → razor-C7zQmabz.js} +1 -1
  20. package/out/renderer/assets/{tsMode-C2aMdzdU.js → tsMode-BWdOxk6v.js} +1 -1
  21. package/out/renderer/assets/{typescript-CB71AtAT.js → typescript-BrdIiKZf.js} +1 -1
  22. package/out/renderer/assets/{xml-tHS1Ec9w.js → xml-DbKLNPOq.js} +1 -1
  23. package/out/renderer/assets/{yaml-CZcp5ueF.js → yaml-ehupJBsZ.js} +1 -1
  24. package/out/renderer/index.html +2 -2
  25. package/package.json +2 -2
  26. package/src/main/bridge.ts +3 -2
  27. package/src/main/headless.ts +9 -13
  28. package/src/main/index.ts +6 -0
  29. package/src/main/pty.ts +2 -5
  30. package/src/main/ticketStore.ts +2 -1
  31. package/src/main/timeTracker.ts +4 -1
  32. package/src/main/transcripts.ts +3 -2
  33. package/src/preload/index.ts +2 -0
  34. package/src/renderer/App.tsx +1 -0
  35. package/src/renderer/components/SettingsDialog.tsx +13 -0
  36. package/src/renderer/components/SpeakControls.tsx +5 -0
  37. package/src/renderer/components/TerminalPanel.tsx +151 -30
  38. package/src/renderer/lib/speech.ts +2 -1
  39. package/src/renderer/styles.css +116 -0
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-KhYyY-BM.js","./lspLanguageFeatures-BYkg2Yc-.js","./htmlMode-Cfd5VCjw.js","./jsonMode-DZT5gBVW.js","./javascript-XyDWi6dC.js","./typescript-CB71AtAT.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-CAEObGNt.js","./lspLanguageFeatures-Czkp5sZb.js","./htmlMode-B6fxDDPF.js","./jsonMode-CqspkWCb.js","./javascript-8RyFDDdk.js","./typescript-BrdIiKZf.js"])))=>i.map(i=>d[i]);
2
2
  function getDefaultExportFromCjs(x) {
3
3
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
4
4
  }
@@ -18818,16 +18818,412 @@ function convertToEmailTable(raw) {
18818
18818
  cols: rows[0].length
18819
18819
  };
18820
18820
  }
18821
- function copySelectionAsEmailTable(terminal) {
18822
- const result = convertToEmailTable(terminal.getSelection());
18821
+ const scriptRel = /* @__PURE__ */ (function detectScriptRel() {
18822
+ const relList = typeof document !== "undefined" && document.createElement("link").relList;
18823
+ return relList && relList.supports && relList.supports("modulepreload") ? "modulepreload" : "preload";
18824
+ })();
18825
+ const assetsURL = function(dep, importerUrl) {
18826
+ return new URL(dep, importerUrl).href;
18827
+ };
18828
+ const seen = {};
18829
+ const __vitePreload = function preload(baseModule, deps, importerUrl) {
18830
+ let promise = Promise.resolve();
18831
+ if (deps && deps.length > 0) {
18832
+ let allSettled2 = function(promises) {
18833
+ return Promise.all(
18834
+ promises.map(
18835
+ (p) => Promise.resolve(p).then(
18836
+ (value) => ({ status: "fulfilled", value }),
18837
+ (reason) => ({ status: "rejected", reason })
18838
+ )
18839
+ )
18840
+ );
18841
+ };
18842
+ const links = document.getElementsByTagName("link");
18843
+ const cspNonceMeta = document.querySelector(
18844
+ "meta[property=csp-nonce]"
18845
+ );
18846
+ const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
18847
+ promise = allSettled2(
18848
+ deps.map((dep) => {
18849
+ dep = assetsURL(dep, importerUrl);
18850
+ if (dep in seen) return;
18851
+ seen[dep] = true;
18852
+ const isCss = dep.endsWith(".css");
18853
+ const cssSelector = isCss ? '[rel="stylesheet"]' : "";
18854
+ const isBaseRelative = !!importerUrl;
18855
+ if (isBaseRelative) {
18856
+ for (let i2 = links.length - 1; i2 >= 0; i2--) {
18857
+ const link22 = links[i2];
18858
+ if (link22.href === dep && (!isCss || link22.rel === "stylesheet")) {
18859
+ return;
18860
+ }
18861
+ }
18862
+ } else if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) {
18863
+ return;
18864
+ }
18865
+ const link2 = document.createElement("link");
18866
+ link2.rel = isCss ? "stylesheet" : scriptRel;
18867
+ if (!isCss) {
18868
+ link2.as = "script";
18869
+ }
18870
+ link2.crossOrigin = "";
18871
+ link2.href = dep;
18872
+ if (cspNonce) {
18873
+ link2.setAttribute("nonce", cspNonce);
18874
+ }
18875
+ document.head.appendChild(link2);
18876
+ if (isCss) {
18877
+ return new Promise((res, rej) => {
18878
+ link2.addEventListener("load", res);
18879
+ link2.addEventListener(
18880
+ "error",
18881
+ () => rej(new Error(`Unable to preload CSS for ${dep}`))
18882
+ );
18883
+ });
18884
+ }
18885
+ })
18886
+ );
18887
+ }
18888
+ function handlePreloadError(err) {
18889
+ const e = new Event("vite:preloadError", {
18890
+ cancelable: true
18891
+ });
18892
+ e.payload = err;
18893
+ window.dispatchEvent(e);
18894
+ if (!e.defaultPrevented) {
18895
+ throw err;
18896
+ }
18897
+ }
18898
+ return promise.then((res) => {
18899
+ for (const item of res || []) {
18900
+ if (item.status !== "rejected") continue;
18901
+ handlePreloadError(item.reason);
18902
+ }
18903
+ return baseModule().catch(handlePreloadError);
18904
+ });
18905
+ };
18906
+ const ENGINE_KEY = "ctlsurf.tts.engine";
18907
+ const VOICE_KEY = "ctlsurf.tts.voiceURI";
18908
+ const RATE_KEY = "ctlsurf.tts.rate";
18909
+ const MAX_SPEAK_CHARS = 1600;
18910
+ const MAX_CHUNK_CHARS = 280;
18911
+ function cleanForSpeech(input) {
18912
+ let t = input;
18913
+ t = t.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_m, body) => {
18914
+ const lines = body.replace(/\n+$/, "").split("\n").filter((l2) => l2.trim().length).length;
18915
+ return lines > 0 ? ` (code block, ${lines} ${lines === 1 ? "line" : "lines"}) ` : " (code) ";
18916
+ });
18917
+ t = t.replace(/```/g, " (code) ");
18918
+ t = t.replace(/`([^`]+)`/g, "$1");
18919
+ t = t.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
18920
+ t = t.replace(/https?:\/\/\S+/g, "link");
18921
+ t = t.replace(/(?:[\w.-]*\/){2,}([\w.-]+)/g, "$1");
18922
+ t = t.replace(/[*_#>]+/g, " ");
18923
+ t = t.replace(/^\s*[-•]\s+/gm, ", ");
18924
+ t = t.replace(/<[^>]+>/g, " ");
18925
+ t = t.replace(/\s+/g, " ").trim();
18926
+ if (t.length > MAX_SPEAK_CHARS) {
18927
+ t = t.slice(0, MAX_SPEAK_CHARS).replace(/\s+\S*$/, "") + "…";
18928
+ }
18929
+ return t;
18930
+ }
18931
+ function splitIntoChunks(text2) {
18932
+ if (text2.length <= MAX_CHUNK_CHARS) return text2 ? [text2] : [];
18933
+ const sentences = text2.match(/[^.!?]+[.!?]+|\S[^.!?]*$/g) || [text2];
18934
+ const chunks = [];
18935
+ let buf = "";
18936
+ for (const s of sentences) {
18937
+ if ((buf + s).length > MAX_CHUNK_CHARS && buf) {
18938
+ chunks.push(buf.trim());
18939
+ buf = "";
18940
+ }
18941
+ if (s.length > MAX_CHUNK_CHARS) {
18942
+ for (let i2 = 0; i2 < s.length; i2 += MAX_CHUNK_CHARS) chunks.push(s.slice(i2, i2 + MAX_CHUNK_CHARS).trim());
18943
+ } else {
18944
+ buf += s;
18945
+ }
18946
+ }
18947
+ if (buf.trim()) chunks.push(buf.trim());
18948
+ return chunks;
18949
+ }
18950
+ function getEngine() {
18951
+ try {
18952
+ return localStorage.getItem(ENGINE_KEY) === "web" ? "web" : "neural";
18953
+ } catch {
18954
+ return "neural";
18955
+ }
18956
+ }
18957
+ function setEngine(id) {
18958
+ try {
18959
+ localStorage.setItem(ENGINE_KEY, id);
18960
+ } catch {
18961
+ }
18962
+ }
18963
+ function getVoiceURI() {
18964
+ try {
18965
+ return localStorage.getItem(VOICE_KEY) || null;
18966
+ } catch {
18967
+ return null;
18968
+ }
18969
+ }
18970
+ function setVoiceURI(uri) {
18971
+ try {
18972
+ uri ? localStorage.setItem(VOICE_KEY, uri) : localStorage.removeItem(VOICE_KEY);
18973
+ } catch {
18974
+ }
18975
+ }
18976
+ function getRate() {
18977
+ try {
18978
+ const n2 = Number(localStorage.getItem(RATE_KEY));
18979
+ return Number.isFinite(n2) && n2 >= 0.5 && n2 <= 2 ? n2 : 1;
18980
+ } catch {
18981
+ return 1;
18982
+ }
18983
+ }
18984
+ function setRate(rate) {
18985
+ try {
18986
+ localStorage.setItem(RATE_KEY, String(rate));
18987
+ } catch {
18988
+ }
18989
+ }
18990
+ function listWebVoices() {
18991
+ if (typeof speechSynthesis === "undefined") return [];
18992
+ return speechSynthesis.getVoices();
18993
+ }
18994
+ const NEURAL_MODEL = "Xenova/mms-tts-eng";
18995
+ let synthPromise = null;
18996
+ function loadSynthesizer(onProgress) {
18997
+ if (!synthPromise) {
18998
+ synthPromise = (async () => {
18999
+ const { pipeline, env: env2 } = await __vitePreload(async () => {
19000
+ const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-_PuZoIho.js");
19001
+ return { pipeline: pipeline2, env: env3 };
19002
+ }, true ? [] : void 0, import.meta.url);
19003
+ env2.allowLocalModels = false;
19004
+ const common = { progress_callback: onProgress };
19005
+ const hasWebGpu = typeof navigator !== "undefined" && "gpu" in navigator;
19006
+ if (hasWebGpu) {
19007
+ try {
19008
+ const s2 = await pipeline("text-to-speech", NEURAL_MODEL, { ...common, device: "webgpu" });
19009
+ console.info("[tts] neural backend: webgpu");
19010
+ return s2;
19011
+ } catch (err) {
19012
+ console.warn("[tts] WebGPU backend failed, falling back to WASM", err);
19013
+ }
19014
+ }
19015
+ const s = await pipeline("text-to-speech", NEURAL_MODEL, common);
19016
+ console.info("[tts] neural backend: wasm");
19017
+ return s;
19018
+ })();
19019
+ synthPromise.catch(() => {
19020
+ synthPromise = null;
19021
+ });
19022
+ }
19023
+ return synthPromise;
19024
+ }
19025
+ class SpeechController {
19026
+ queue = [];
19027
+ draining = false;
19028
+ audioCtx = null;
19029
+ currentSource = null;
19030
+ generation = 0;
19031
+ // bumped by stop() to abort in-flight work
19032
+ active = false;
19033
+ onModelProgress = null;
19034
+ // Fires true while a reply is being spoken/queued, false when idle — drives
19035
+ // the visible Stop button.
19036
+ onActivityChange = null;
19037
+ onError = null;
19038
+ // Multiple UI surfaces (titlebar SpeakControls + floating read button) need
19039
+ // the speaking state at once, so beyond the single onActivityChange slot we
19040
+ // also keep a listener set.
19041
+ activityListeners = /* @__PURE__ */ new Set();
19042
+ get isActive() {
19043
+ return this.active;
19044
+ }
19045
+ // Subscribe to speaking-state changes. Fires immediately with the current
19046
+ // state so a late subscriber syncs. Returns an unsubscribe function.
19047
+ onActivity(fn) {
19048
+ this.activityListeners.add(fn);
19049
+ fn(this.active);
19050
+ return () => {
19051
+ this.activityListeners.delete(fn);
19052
+ };
19053
+ }
19054
+ // Prime/resume the AudioContext from a user gesture so neural playback isn't
19055
+ // blocked by the browser autoplay policy (the System engine is unaffected).
19056
+ unlock() {
19057
+ try {
19058
+ const ctx = this.ensureCtx();
19059
+ if (ctx.state === "suspended") void ctx.resume();
19060
+ } catch {
19061
+ }
19062
+ }
19063
+ // Start loading the neural model in the background (e.g. when the user picks
19064
+ // the neural engine) so the first utterance doesn't pay download/compile time.
19065
+ warmup() {
19066
+ if (getEngine() !== "neural") return;
19067
+ void loadSynthesizer((p) => this.onModelProgress?.(p)).then(() => this.onModelProgress?.(null)).catch(() => {
19068
+ this.onModelProgress?.(null);
19069
+ });
19070
+ }
19071
+ enqueue(rawText) {
19072
+ const text2 = cleanForSpeech(rawText);
19073
+ if (!text2) return;
19074
+ this.queue.push(...splitIntoChunks(text2));
19075
+ void this.drain();
19076
+ }
19077
+ stop() {
19078
+ this.generation++;
19079
+ this.queue = [];
19080
+ this.draining = false;
19081
+ if (typeof speechSynthesis !== "undefined") {
19082
+ try {
19083
+ speechSynthesis.cancel();
19084
+ } catch {
19085
+ }
19086
+ }
19087
+ if (this.currentSource) {
19088
+ try {
19089
+ this.currentSource.stop();
19090
+ } catch {
19091
+ }
19092
+ this.currentSource = null;
19093
+ }
19094
+ this.onModelProgress?.(null);
19095
+ this.setActive(false);
19096
+ }
19097
+ ensureCtx() {
19098
+ if (!this.audioCtx) {
19099
+ const Ctx = window.AudioContext || window.webkitAudioContext;
19100
+ this.audioCtx = new Ctx();
19101
+ }
19102
+ return this.audioCtx;
19103
+ }
19104
+ setActive(a) {
19105
+ if (this.active === a) return;
19106
+ this.active = a;
19107
+ this.onActivityChange?.(a);
19108
+ this.activityListeners.forEach((fn) => fn(a));
19109
+ }
19110
+ async drain() {
19111
+ if (this.draining) return;
19112
+ this.draining = true;
19113
+ const gen = this.generation;
19114
+ this.setActive(true);
19115
+ while (this.queue.length && gen === this.generation) {
19116
+ const chunk = this.queue.shift();
19117
+ try {
19118
+ if (getEngine() === "neural") await this.speakNeural(chunk, gen);
19119
+ else await this.speakWeb(chunk, gen);
19120
+ } catch (err) {
19121
+ console.error("[tts] speak failed", err);
19122
+ this.onModelProgress?.(null);
19123
+ const detail = err instanceof Error ? err.message : String(err);
19124
+ this.onError?.(getEngine() === "neural" ? `Neural voice failed: ${detail}` : "Speech failed");
19125
+ }
19126
+ }
19127
+ if (gen === this.generation) {
19128
+ this.draining = false;
19129
+ this.setActive(false);
19130
+ }
19131
+ }
19132
+ speakWeb(text2, gen) {
19133
+ return new Promise((resolve2) => {
19134
+ if (typeof speechSynthesis === "undefined" || gen !== this.generation) return resolve2();
19135
+ const u = new SpeechSynthesisUtterance(text2);
19136
+ u.rate = getRate();
19137
+ const wantUri = getVoiceURI();
19138
+ if (wantUri) {
19139
+ const v2 = speechSynthesis.getVoices().find((vv) => vv.voiceURI === wantUri);
19140
+ if (v2) u.voice = v2;
19141
+ }
19142
+ u.onend = () => resolve2();
19143
+ u.onerror = () => resolve2();
19144
+ speechSynthesis.speak(u);
19145
+ });
19146
+ }
19147
+ async speakNeural(text2, gen) {
19148
+ console.info("[tts] neural: loading model…");
19149
+ this.onModelProgress?.({ status: "loading" });
19150
+ const synth = await loadSynthesizer((p) => this.onModelProgress?.(p));
19151
+ if (gen !== this.generation) return;
19152
+ console.info("[tts] neural: synthesizing", JSON.stringify(text2.slice(0, 60)));
19153
+ const out = await synth(text2);
19154
+ const raw = Array.isArray(out) ? out[0] : out;
19155
+ this.onModelProgress?.(null);
19156
+ if (gen !== this.generation) return;
19157
+ if (!raw?.audio?.length) throw new Error("neural synth returned no audio");
19158
+ console.info(`[tts] neural: playing ${raw.audio.length} samples @ ${raw.sampling_rate}Hz`);
19159
+ await this.playPcm(raw.audio, raw.sampling_rate, gen);
19160
+ }
19161
+ async playPcm(pcm, sampleRate, gen) {
19162
+ if (gen !== this.generation) return;
19163
+ const ctx = this.ensureCtx();
19164
+ if (ctx.state === "suspended") {
19165
+ try {
19166
+ await ctx.resume();
19167
+ } catch {
19168
+ }
19169
+ }
19170
+ if (gen !== this.generation) return;
19171
+ return new Promise((resolve2) => {
19172
+ const buffer = ctx.createBuffer(1, pcm.length, sampleRate);
19173
+ buffer.getChannelData(0).set(pcm);
19174
+ const source = ctx.createBufferSource();
19175
+ source.buffer = buffer;
19176
+ source.connect(ctx.destination);
19177
+ source.onended = () => {
19178
+ if (this.currentSource === source) this.currentSource = null;
19179
+ resolve2();
19180
+ };
19181
+ this.currentSource = source;
19182
+ source.start();
19183
+ });
19184
+ }
19185
+ }
19186
+ const speech = new SpeechController();
19187
+ function showTerminalToast(message) {
19188
+ window.dispatchEvent(new CustomEvent("terminal-toast", { detail: { message } }));
19189
+ }
19190
+ function copyTextValue(text2) {
19191
+ if (!text2) return;
19192
+ void window.worker.copyText(text2);
19193
+ showTerminalToast("Copied to clipboard");
19194
+ }
19195
+ function convertTextToTable(text2) {
19196
+ const result = convertToEmailTable(text2);
18823
19197
  if (result) {
18824
19198
  window.worker.copyEmailTable(result.html, result.text);
19199
+ showTerminalToast(`Copied ${result.rows}×${result.cols} table to clipboard`);
19200
+ } else {
19201
+ showTerminalToast("No table found in selection");
18825
19202
  }
18826
- window.dispatchEvent(
18827
- new CustomEvent("email-table-result", {
18828
- detail: result ? { ok: true, message: `Copied ${result.rows}×${result.cols} table to clipboard` } : { ok: false, message: "No table found in selection" }
18829
- })
18830
- );
19203
+ }
19204
+ function speakTextValue(text2) {
19205
+ if (!text2.trim()) return;
19206
+ speech.unlock();
19207
+ speech.enqueue(text2);
19208
+ }
19209
+ function copySelectionPlain(terminal) {
19210
+ copyTextValue(terminal.getSelection());
19211
+ }
19212
+ function copySelectionAsEmailTable(terminal) {
19213
+ convertTextToTable(terminal.getSelection());
19214
+ }
19215
+ function speakSelection(terminal) {
19216
+ speakTextValue(terminal.getSelection());
19217
+ }
19218
+ function captureVisibleText(terminal) {
19219
+ const buf = terminal.buffer.active;
19220
+ const lines = [];
19221
+ for (let i2 = 0; i2 < terminal.rows; i2++) {
19222
+ const line = buf.getLine(buf.viewportY + i2);
19223
+ lines.push(line ? line.translateToString(true) : "");
19224
+ }
19225
+ while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
19226
+ return lines.join("\n");
18831
19227
  }
18832
19228
  function handleTerminalLink(_event, uri) {
18833
19229
  const openInPanel = (url) => window.dispatchEvent(new CustomEvent("ctlsurf-open-url", { detail: { url } }));
@@ -18910,11 +19306,23 @@ function getOrCreateTerminal(tabId, onExit) {
18910
19306
  if (e.key === "PageUp" || e.key === "PageDown" || e.key === "Home" || e.key === "End") {
18911
19307
  markGesture(tabId);
18912
19308
  }
18913
- const isCombo = (e.metaKey || e.ctrlKey) && e.shiftKey && e.code === "KeyE";
18914
- if (!isCombo) return true;
18915
- e.preventDefault();
18916
- copySelectionAsEmailTable(terminal);
18917
- return false;
19309
+ const mod = (e.metaKey || e.ctrlKey) && e.shiftKey;
19310
+ if (mod && e.code === "KeyE") {
19311
+ e.preventDefault();
19312
+ copySelectionAsEmailTable(terminal);
19313
+ return false;
19314
+ }
19315
+ if (mod && e.code === "KeyC" && terminal.hasSelection()) {
19316
+ e.preventDefault();
19317
+ copySelectionPlain(terminal);
19318
+ return false;
19319
+ }
19320
+ if (mod && e.code === "KeyS" && terminal.getSelection().trim()) {
19321
+ e.preventDefault();
19322
+ speakSelection(terminal);
19323
+ return false;
19324
+ }
19325
+ return true;
18918
19326
  });
18919
19327
  const state = {
18920
19328
  terminal,
@@ -18976,6 +19384,8 @@ function destroyTerminal(tabId) {
18976
19384
  function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
18977
19385
  const containerRef = reactExports.useRef(null);
18978
19386
  const [menu, setMenu] = reactExports.useState(null);
19387
+ const [snapOpen, setSnapOpen] = reactExports.useState(false);
19388
+ const snapTextRef = reactExports.useRef("");
18979
19389
  const [toast, setToast] = reactExports.useState(null);
18980
19390
  const [followBottom, setFollowBottom] = reactExports.useState(true);
18981
19391
  const jumpToLatest = () => {
@@ -18987,22 +19397,44 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
18987
19397
  };
18988
19398
  const handleContextMenu = (e) => {
18989
19399
  const sel = _terminals.get(tabId)?.terminal.getSelection() ?? "";
18990
- if (!convertToEmailTable(sel)) return;
19400
+ if (!sel.trim()) return;
18991
19401
  e.preventDefault();
18992
19402
  setMenu({ x: e.clientX, y: e.clientY });
18993
19403
  };
19404
+ const runCopy = () => {
19405
+ const term = _terminals.get(tabId)?.terminal;
19406
+ if (term) copySelectionPlain(term);
19407
+ setMenu(null);
19408
+ };
18994
19409
  const runConvert = () => {
18995
19410
  const term = _terminals.get(tabId)?.terminal;
18996
19411
  if (term) copySelectionAsEmailTable(term);
18997
19412
  setMenu(null);
18998
19413
  };
19414
+ const runSpeak = () => {
19415
+ const term = _terminals.get(tabId)?.terminal;
19416
+ if (term) speakSelection(term);
19417
+ setMenu(null);
19418
+ };
19419
+ const openSnapshot = () => {
19420
+ const term = _terminals.get(tabId)?.terminal;
19421
+ snapTextRef.current = term ? captureVisibleText(term) : "";
19422
+ setSnapOpen(true);
19423
+ };
19424
+ const snapAction = (kind) => {
19425
+ const domSel = window.getSelection()?.toString() ?? "";
19426
+ const text2 = domSel.trim() ? domSel : snapTextRef.current;
19427
+ if (kind === "copy") copyTextValue(text2);
19428
+ else if (kind === "table") convertTextToTable(text2);
19429
+ else speakTextValue(text2);
19430
+ };
18999
19431
  reactExports.useEffect(() => {
19000
- const onResult = (e) => {
19432
+ const onToast = (e) => {
19001
19433
  const detail = e.detail;
19002
- if (detail) setToast(detail.message);
19434
+ if (detail?.message) setToast(detail.message);
19003
19435
  };
19004
- window.addEventListener("email-table-result", onResult);
19005
- return () => window.removeEventListener("email-table-result", onResult);
19436
+ window.addEventListener("terminal-toast", onToast);
19437
+ return () => window.removeEventListener("terminal-toast", onToast);
19006
19438
  }, []);
19007
19439
  reactExports.useEffect(() => {
19008
19440
  if (!toast) return;
@@ -19126,100 +19558,75 @@ function TerminalPanel({ tabId, agent, onSpawn, onExit, isActive }) {
19126
19558
  title: "Jump to latest output",
19127
19559
  children: "↓ Latest"
19128
19560
  }
19129
- )
19561
+ ),
19562
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19563
+ "button",
19564
+ {
19565
+ type: "button",
19566
+ className: "term-util-btn",
19567
+ onMouseDown: (e) => {
19568
+ e.preventDefault();
19569
+ openSnapshot();
19570
+ },
19571
+ title: "Snapshot screen → copy / convert to table / read aloud",
19572
+ children: "⋮"
19573
+ }
19574
+ ),
19575
+ snapOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "term-snapshot", children: [
19576
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "term-snapshot-head", children: [
19577
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Select text below (or select nothing to use everything), then pick an action." }),
19578
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19579
+ "button",
19580
+ {
19581
+ type: "button",
19582
+ className: "term-snapshot-x",
19583
+ onMouseDown: (e) => {
19584
+ e.preventDefault();
19585
+ setSnapOpen(false);
19586
+ },
19587
+ title: "Close",
19588
+ children: "✕"
19589
+ }
19590
+ )
19591
+ ] }),
19592
+ /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "term-snapshot-text", children: snapTextRef.current }),
19593
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "term-snapshot-actions", children: [
19594
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
19595
+ e.preventDefault();
19596
+ snapAction("copy");
19597
+ }, children: "⧉ Copy" }),
19598
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
19599
+ e.preventDefault();
19600
+ snapAction("table");
19601
+ }, children: "▦ Convert to table" }),
19602
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
19603
+ e.preventDefault();
19604
+ snapAction("speak");
19605
+ }, children: "🔊 Read aloud" }),
19606
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "term-snapshot-done", onMouseDown: (e) => {
19607
+ e.preventDefault();
19608
+ setSnapOpen(false);
19609
+ }, children: "Close" })
19610
+ ] })
19611
+ ] })
19612
+ ] }),
19613
+ menu && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "term-context-menu", style: { position: "fixed", left: menu.x, top: menu.y }, children: [
19614
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
19615
+ e.preventDefault();
19616
+ runCopy();
19617
+ }, children: "Copy" }),
19618
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
19619
+ e.preventDefault();
19620
+ runConvert();
19621
+ }, children: "Convert to table" }),
19622
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
19623
+ e.preventDefault();
19624
+ runSpeak();
19625
+ }, children: "Read it" })
19130
19626
  ] }),
19131
- menu && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "term-context-menu", style: { position: "fixed", left: menu.x, top: menu.y }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onMouseDown: (e) => {
19132
- e.preventDefault();
19133
- runConvert();
19134
- }, children: "Copy as email table" }) }),
19135
19627
  toast && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "term-toast", children: toast })
19136
19628
  ] });
19137
19629
  }
19138
- const scriptRel = /* @__PURE__ */ (function detectScriptRel() {
19139
- const relList = typeof document !== "undefined" && document.createElement("link").relList;
19140
- return relList && relList.supports && relList.supports("modulepreload") ? "modulepreload" : "preload";
19141
- })();
19142
- const assetsURL = function(dep, importerUrl) {
19143
- return new URL(dep, importerUrl).href;
19144
- };
19145
- const seen = {};
19146
- const __vitePreload = function preload(baseModule, deps, importerUrl) {
19147
- let promise = Promise.resolve();
19148
- if (deps && deps.length > 0) {
19149
- let allSettled2 = function(promises) {
19150
- return Promise.all(
19151
- promises.map(
19152
- (p) => Promise.resolve(p).then(
19153
- (value) => ({ status: "fulfilled", value }),
19154
- (reason) => ({ status: "rejected", reason })
19155
- )
19156
- )
19157
- );
19158
- };
19159
- const links = document.getElementsByTagName("link");
19160
- const cspNonceMeta = document.querySelector(
19161
- "meta[property=csp-nonce]"
19162
- );
19163
- const cspNonce = cspNonceMeta?.nonce || cspNonceMeta?.getAttribute("nonce");
19164
- promise = allSettled2(
19165
- deps.map((dep) => {
19166
- dep = assetsURL(dep, importerUrl);
19167
- if (dep in seen) return;
19168
- seen[dep] = true;
19169
- const isCss = dep.endsWith(".css");
19170
- const cssSelector = isCss ? '[rel="stylesheet"]' : "";
19171
- const isBaseRelative = !!importerUrl;
19172
- if (isBaseRelative) {
19173
- for (let i2 = links.length - 1; i2 >= 0; i2--) {
19174
- const link22 = links[i2];
19175
- if (link22.href === dep && (!isCss || link22.rel === "stylesheet")) {
19176
- return;
19177
- }
19178
- }
19179
- } else if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) {
19180
- return;
19181
- }
19182
- const link2 = document.createElement("link");
19183
- link2.rel = isCss ? "stylesheet" : scriptRel;
19184
- if (!isCss) {
19185
- link2.as = "script";
19186
- }
19187
- link2.crossOrigin = "";
19188
- link2.href = dep;
19189
- if (cspNonce) {
19190
- link2.setAttribute("nonce", cspNonce);
19191
- }
19192
- document.head.appendChild(link2);
19193
- if (isCss) {
19194
- return new Promise((res, rej) => {
19195
- link2.addEventListener("load", res);
19196
- link2.addEventListener(
19197
- "error",
19198
- () => rej(new Error(`Unable to preload CSS for ${dep}`))
19199
- );
19200
- });
19201
- }
19202
- })
19203
- );
19204
- }
19205
- function handlePreloadError(err) {
19206
- const e = new Event("vite:preloadError", {
19207
- cancelable: true
19208
- });
19209
- e.payload = err;
19210
- window.dispatchEvent(e);
19211
- if (!e.defaultPrevented) {
19212
- throw err;
19213
- }
19214
- }
19215
- return promise.then((res) => {
19216
- for (const item of res || []) {
19217
- if (item.status !== "rejected") continue;
19218
- handlePreloadError(item.reason);
19219
- }
19220
- return baseModule().catch(handlePreloadError);
19221
- });
19222
- };
19223
19630
  const MODEL = "Xenova/whisper-base";
19224
19631
  const TARGET_SAMPLE_RATE = 16e3;
19225
19632
  const SILENCE_RMS = 8e-3;
@@ -19643,287 +20050,6 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19643
20050
  chip && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `voice-chip ${chip.kind} ${floating ? "voice-chip-floating" : ""}`, children: chip.text })
19644
20051
  ] });
19645
20052
  }
19646
- const ENGINE_KEY = "ctlsurf.tts.engine";
19647
- const VOICE_KEY = "ctlsurf.tts.voiceURI";
19648
- const RATE_KEY = "ctlsurf.tts.rate";
19649
- const MAX_SPEAK_CHARS = 1600;
19650
- const MAX_CHUNK_CHARS = 280;
19651
- function cleanForSpeech(input) {
19652
- let t = input;
19653
- t = t.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_m, body) => {
19654
- const lines = body.replace(/\n+$/, "").split("\n").filter((l2) => l2.trim().length).length;
19655
- return lines > 0 ? ` (code block, ${lines} ${lines === 1 ? "line" : "lines"}) ` : " (code) ";
19656
- });
19657
- t = t.replace(/```/g, " (code) ");
19658
- t = t.replace(/`([^`]+)`/g, "$1");
19659
- t = t.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
19660
- t = t.replace(/https?:\/\/\S+/g, "link");
19661
- t = t.replace(/(?:[\w.-]*\/){2,}([\w.-]+)/g, "$1");
19662
- t = t.replace(/[*_#>]+/g, " ");
19663
- t = t.replace(/^\s*[-•]\s+/gm, ", ");
19664
- t = t.replace(/<[^>]+>/g, " ");
19665
- t = t.replace(/\s+/g, " ").trim();
19666
- if (t.length > MAX_SPEAK_CHARS) {
19667
- t = t.slice(0, MAX_SPEAK_CHARS).replace(/\s+\S*$/, "") + "…";
19668
- }
19669
- return t;
19670
- }
19671
- function splitIntoChunks(text2) {
19672
- if (text2.length <= MAX_CHUNK_CHARS) return text2 ? [text2] : [];
19673
- const sentences = text2.match(/[^.!?]+[.!?]+|\S[^.!?]*$/g) || [text2];
19674
- const chunks = [];
19675
- let buf = "";
19676
- for (const s of sentences) {
19677
- if ((buf + s).length > MAX_CHUNK_CHARS && buf) {
19678
- chunks.push(buf.trim());
19679
- buf = "";
19680
- }
19681
- if (s.length > MAX_CHUNK_CHARS) {
19682
- for (let i2 = 0; i2 < s.length; i2 += MAX_CHUNK_CHARS) chunks.push(s.slice(i2, i2 + MAX_CHUNK_CHARS).trim());
19683
- } else {
19684
- buf += s;
19685
- }
19686
- }
19687
- if (buf.trim()) chunks.push(buf.trim());
19688
- return chunks;
19689
- }
19690
- function getEngine() {
19691
- try {
19692
- return localStorage.getItem(ENGINE_KEY) === "neural" ? "neural" : "web";
19693
- } catch {
19694
- return "web";
19695
- }
19696
- }
19697
- function setEngine(id) {
19698
- try {
19699
- localStorage.setItem(ENGINE_KEY, id);
19700
- } catch {
19701
- }
19702
- }
19703
- function getVoiceURI() {
19704
- try {
19705
- return localStorage.getItem(VOICE_KEY) || null;
19706
- } catch {
19707
- return null;
19708
- }
19709
- }
19710
- function setVoiceURI(uri) {
19711
- try {
19712
- uri ? localStorage.setItem(VOICE_KEY, uri) : localStorage.removeItem(VOICE_KEY);
19713
- } catch {
19714
- }
19715
- }
19716
- function getRate() {
19717
- try {
19718
- const n2 = Number(localStorage.getItem(RATE_KEY));
19719
- return Number.isFinite(n2) && n2 >= 0.5 && n2 <= 2 ? n2 : 1;
19720
- } catch {
19721
- return 1;
19722
- }
19723
- }
19724
- function setRate(rate) {
19725
- try {
19726
- localStorage.setItem(RATE_KEY, String(rate));
19727
- } catch {
19728
- }
19729
- }
19730
- function listWebVoices() {
19731
- if (typeof speechSynthesis === "undefined") return [];
19732
- return speechSynthesis.getVoices();
19733
- }
19734
- const NEURAL_MODEL = "Xenova/mms-tts-eng";
19735
- let synthPromise = null;
19736
- function loadSynthesizer(onProgress) {
19737
- if (!synthPromise) {
19738
- synthPromise = (async () => {
19739
- const { pipeline, env: env2 } = await __vitePreload(async () => {
19740
- const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-_PuZoIho.js");
19741
- return { pipeline: pipeline2, env: env3 };
19742
- }, true ? [] : void 0, import.meta.url);
19743
- env2.allowLocalModels = false;
19744
- const common = { progress_callback: onProgress };
19745
- const hasWebGpu = typeof navigator !== "undefined" && "gpu" in navigator;
19746
- if (hasWebGpu) {
19747
- try {
19748
- const s2 = await pipeline("text-to-speech", NEURAL_MODEL, { ...common, device: "webgpu" });
19749
- console.info("[tts] neural backend: webgpu");
19750
- return s2;
19751
- } catch (err) {
19752
- console.warn("[tts] WebGPU backend failed, falling back to WASM", err);
19753
- }
19754
- }
19755
- const s = await pipeline("text-to-speech", NEURAL_MODEL, common);
19756
- console.info("[tts] neural backend: wasm");
19757
- return s;
19758
- })();
19759
- synthPromise.catch(() => {
19760
- synthPromise = null;
19761
- });
19762
- }
19763
- return synthPromise;
19764
- }
19765
- class SpeechController {
19766
- queue = [];
19767
- draining = false;
19768
- audioCtx = null;
19769
- currentSource = null;
19770
- generation = 0;
19771
- // bumped by stop() to abort in-flight work
19772
- active = false;
19773
- onModelProgress = null;
19774
- // Fires true while a reply is being spoken/queued, false when idle — drives
19775
- // the visible Stop button.
19776
- onActivityChange = null;
19777
- onError = null;
19778
- // Multiple UI surfaces (titlebar SpeakControls + floating read button) need
19779
- // the speaking state at once, so beyond the single onActivityChange slot we
19780
- // also keep a listener set.
19781
- activityListeners = /* @__PURE__ */ new Set();
19782
- get isActive() {
19783
- return this.active;
19784
- }
19785
- // Subscribe to speaking-state changes. Fires immediately with the current
19786
- // state so a late subscriber syncs. Returns an unsubscribe function.
19787
- onActivity(fn) {
19788
- this.activityListeners.add(fn);
19789
- fn(this.active);
19790
- return () => {
19791
- this.activityListeners.delete(fn);
19792
- };
19793
- }
19794
- // Prime/resume the AudioContext from a user gesture so neural playback isn't
19795
- // blocked by the browser autoplay policy (the System engine is unaffected).
19796
- unlock() {
19797
- try {
19798
- const ctx = this.ensureCtx();
19799
- if (ctx.state === "suspended") void ctx.resume();
19800
- } catch {
19801
- }
19802
- }
19803
- // Start loading the neural model in the background (e.g. when the user picks
19804
- // the neural engine) so the first utterance doesn't pay download/compile time.
19805
- warmup() {
19806
- if (getEngine() !== "neural") return;
19807
- void loadSynthesizer((p) => this.onModelProgress?.(p)).then(() => this.onModelProgress?.(null)).catch(() => {
19808
- this.onModelProgress?.(null);
19809
- });
19810
- }
19811
- enqueue(rawText) {
19812
- const text2 = cleanForSpeech(rawText);
19813
- if (!text2) return;
19814
- this.queue.push(...splitIntoChunks(text2));
19815
- void this.drain();
19816
- }
19817
- stop() {
19818
- this.generation++;
19819
- this.queue = [];
19820
- this.draining = false;
19821
- if (typeof speechSynthesis !== "undefined") {
19822
- try {
19823
- speechSynthesis.cancel();
19824
- } catch {
19825
- }
19826
- }
19827
- if (this.currentSource) {
19828
- try {
19829
- this.currentSource.stop();
19830
- } catch {
19831
- }
19832
- this.currentSource = null;
19833
- }
19834
- this.onModelProgress?.(null);
19835
- this.setActive(false);
19836
- }
19837
- ensureCtx() {
19838
- if (!this.audioCtx) {
19839
- const Ctx = window.AudioContext || window.webkitAudioContext;
19840
- this.audioCtx = new Ctx();
19841
- }
19842
- return this.audioCtx;
19843
- }
19844
- setActive(a) {
19845
- if (this.active === a) return;
19846
- this.active = a;
19847
- this.onActivityChange?.(a);
19848
- this.activityListeners.forEach((fn) => fn(a));
19849
- }
19850
- async drain() {
19851
- if (this.draining) return;
19852
- this.draining = true;
19853
- const gen = this.generation;
19854
- this.setActive(true);
19855
- while (this.queue.length && gen === this.generation) {
19856
- const chunk = this.queue.shift();
19857
- try {
19858
- if (getEngine() === "neural") await this.speakNeural(chunk, gen);
19859
- else await this.speakWeb(chunk, gen);
19860
- } catch (err) {
19861
- console.error("[tts] speak failed", err);
19862
- this.onModelProgress?.(null);
19863
- const detail = err instanceof Error ? err.message : String(err);
19864
- this.onError?.(getEngine() === "neural" ? `Neural voice failed: ${detail}` : "Speech failed");
19865
- }
19866
- }
19867
- if (gen === this.generation) {
19868
- this.draining = false;
19869
- this.setActive(false);
19870
- }
19871
- }
19872
- speakWeb(text2, gen) {
19873
- return new Promise((resolve2) => {
19874
- if (typeof speechSynthesis === "undefined" || gen !== this.generation) return resolve2();
19875
- const u = new SpeechSynthesisUtterance(text2);
19876
- u.rate = getRate();
19877
- const wantUri = getVoiceURI();
19878
- if (wantUri) {
19879
- const v2 = speechSynthesis.getVoices().find((vv) => vv.voiceURI === wantUri);
19880
- if (v2) u.voice = v2;
19881
- }
19882
- u.onend = () => resolve2();
19883
- u.onerror = () => resolve2();
19884
- speechSynthesis.speak(u);
19885
- });
19886
- }
19887
- async speakNeural(text2, gen) {
19888
- console.info("[tts] neural: loading model…");
19889
- this.onModelProgress?.({ status: "loading" });
19890
- const synth = await loadSynthesizer((p) => this.onModelProgress?.(p));
19891
- if (gen !== this.generation) return;
19892
- console.info("[tts] neural: synthesizing", JSON.stringify(text2.slice(0, 60)));
19893
- const out = await synth(text2);
19894
- const raw = Array.isArray(out) ? out[0] : out;
19895
- this.onModelProgress?.(null);
19896
- if (gen !== this.generation) return;
19897
- if (!raw?.audio?.length) throw new Error("neural synth returned no audio");
19898
- console.info(`[tts] neural: playing ${raw.audio.length} samples @ ${raw.sampling_rate}Hz`);
19899
- await this.playPcm(raw.audio, raw.sampling_rate, gen);
19900
- }
19901
- async playPcm(pcm, sampleRate, gen) {
19902
- if (gen !== this.generation) return;
19903
- const ctx = this.ensureCtx();
19904
- if (ctx.state === "suspended") {
19905
- try {
19906
- await ctx.resume();
19907
- } catch {
19908
- }
19909
- }
19910
- if (gen !== this.generation) return;
19911
- return new Promise((resolve2) => {
19912
- const buffer = ctx.createBuffer(1, pcm.length, sampleRate);
19913
- buffer.getChannelData(0).set(pcm);
19914
- const source = ctx.createBufferSource();
19915
- source.buffer = buffer;
19916
- source.connect(ctx.destination);
19917
- source.onended = () => {
19918
- if (this.currentSource === source) this.currentSource = null;
19919
- resolve2();
19920
- };
19921
- this.currentSource = source;
19922
- source.start();
19923
- });
19924
- }
19925
- }
19926
- const speech = new SpeechController();
19927
20053
  const POS_KEY = "ctlsurf.floatingMicPos";
19928
20054
  const EDGE = 20;
19929
20055
  const TOP_MIN = 46;
@@ -20102,6 +20228,9 @@ function SpeakControls({ remote = false }) {
20102
20228
  alive = false;
20103
20229
  };
20104
20230
  }, []);
20231
+ reactExports.useEffect(() => {
20232
+ speech.warmup();
20233
+ }, []);
20105
20234
  reactExports.useEffect(() => {
20106
20235
  const off = window.worker.onAgentMessage((text2) => speech.enqueue(text2));
20107
20236
  return off;
@@ -208022,7 +208151,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
208022
208151
  modeConfigurationDefault$2
208023
208152
  );
208024
208153
  function getMode$3() {
208025
- return __vitePreload(() => import("./cssMode-KhYyY-BM.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
208154
+ return __vitePreload(() => import("./cssMode-CAEObGNt.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
208026
208155
  }
208027
208156
  languages.onLanguage("less", () => {
208028
208157
  getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
@@ -208127,7 +208256,7 @@ const razorLanguageService = registerHTMLLanguageService(
208127
208256
  );
208128
208257
  const razorDefaults = razorLanguageService.defaults;
208129
208258
  function getMode$2() {
208130
- return __vitePreload(() => import("./htmlMode-Cfd5VCjw.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
208259
+ return __vitePreload(() => import("./htmlMode-B6fxDDPF.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
208131
208260
  }
208132
208261
  function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
208133
208262
  const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
@@ -208211,7 +208340,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
208211
208340
  );
208212
208341
  const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
208213
208342
  function getMode$1() {
208214
- return __vitePreload(() => import("./jsonMode-DZT5gBVW.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208343
+ return __vitePreload(() => import("./jsonMode-CqspkWCb.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208215
208344
  }
208216
208345
  languages.register({
208217
208346
  id: "json",
@@ -208457,7 +208586,7 @@ const getJavaScriptWorker = () => {
208457
208586
  return getMode().then((mode) => mode.getJavaScriptWorker());
208458
208587
  };
208459
208588
  function getMode() {
208460
- return __vitePreload(() => import("./tsMode-C2aMdzdU.js"), true ? [] : void 0, import.meta.url);
208589
+ return __vitePreload(() => import("./tsMode-BWdOxk6v.js"), true ? [] : void 0, import.meta.url);
208461
208590
  }
208462
208591
  languages.onLanguage("typescript", () => {
208463
208592
  return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
@@ -208652,49 +208781,49 @@ registerLanguage({
208652
208781
  extensions: [".ftl", ".ftlh", ".ftlx"],
208653
208782
  aliases: ["FreeMarker2", "Apache FreeMarker2"],
208654
208783
  loader: () => {
208655
- return __vitePreload(() => import("./freemarker2-BFsSgcWk.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208784
+ return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208656
208785
  }
208657
208786
  });
208658
208787
  registerLanguage({
208659
208788
  id: "freemarker2.tag-angle.interpolation-dollar",
208660
208789
  aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
208661
208790
  loader: () => {
208662
- return __vitePreload(() => import("./freemarker2-BFsSgcWk.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
208791
+ return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
208663
208792
  }
208664
208793
  });
208665
208794
  registerLanguage({
208666
208795
  id: "freemarker2.tag-bracket.interpolation-dollar",
208667
208796
  aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
208668
208797
  loader: () => {
208669
- return __vitePreload(() => import("./freemarker2-BFsSgcWk.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
208798
+ return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
208670
208799
  }
208671
208800
  });
208672
208801
  registerLanguage({
208673
208802
  id: "freemarker2.tag-angle.interpolation-bracket",
208674
208803
  aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
208675
208804
  loader: () => {
208676
- return __vitePreload(() => import("./freemarker2-BFsSgcWk.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
208805
+ return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
208677
208806
  }
208678
208807
  });
208679
208808
  registerLanguage({
208680
208809
  id: "freemarker2.tag-bracket.interpolation-bracket",
208681
208810
  aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
208682
208811
  loader: () => {
208683
- return __vitePreload(() => import("./freemarker2-BFsSgcWk.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
208812
+ return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
208684
208813
  }
208685
208814
  });
208686
208815
  registerLanguage({
208687
208816
  id: "freemarker2.tag-auto.interpolation-dollar",
208688
208817
  aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
208689
208818
  loader: () => {
208690
- return __vitePreload(() => import("./freemarker2-BFsSgcWk.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208819
+ return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208691
208820
  }
208692
208821
  });
208693
208822
  registerLanguage({
208694
208823
  id: "freemarker2.tag-auto.interpolation-bracket",
208695
208824
  aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
208696
208825
  loader: () => {
208697
- return __vitePreload(() => import("./freemarker2-BFsSgcWk.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
208826
+ return __vitePreload(() => import("./freemarker2-D63hnnDq.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
208698
208827
  }
208699
208828
  });
208700
208829
  registerLanguage({
@@ -208715,7 +208844,7 @@ registerLanguage({
208715
208844
  extensions: [".handlebars", ".hbs"],
208716
208845
  aliases: ["Handlebars", "handlebars", "hbs"],
208717
208846
  mimetypes: ["text/x-handlebars-template"],
208718
- loader: () => __vitePreload(() => import("./handlebars-Be6_aBsm.js"), true ? [] : void 0, import.meta.url)
208847
+ loader: () => __vitePreload(() => import("./handlebars-UYo_IYpi.js"), true ? [] : void 0, import.meta.url)
208719
208848
  });
208720
208849
  registerLanguage({
208721
208850
  id: "hcl",
@@ -208728,7 +208857,7 @@ registerLanguage({
208728
208857
  extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
208729
208858
  aliases: ["HTML", "htm", "html", "xhtml"],
208730
208859
  mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
208731
- loader: () => __vitePreload(() => import("./html-G8mvsyfc.js"), true ? [] : void 0, import.meta.url)
208860
+ loader: () => __vitePreload(() => import("./html-2dvYDi3g.js"), true ? [] : void 0, import.meta.url)
208732
208861
  });
208733
208862
  registerLanguage({
208734
208863
  id: "ini",
@@ -208751,7 +208880,7 @@ registerLanguage({
208751
208880
  filenames: ["jakefile"],
208752
208881
  aliases: ["JavaScript", "javascript", "js"],
208753
208882
  mimetypes: ["text/javascript"],
208754
- loader: () => __vitePreload(() => import("./javascript-XyDWi6dC.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
208883
+ loader: () => __vitePreload(() => import("./javascript-8RyFDDdk.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
208755
208884
  });
208756
208885
  registerLanguage({
208757
208886
  id: "julia",
@@ -208790,7 +208919,7 @@ registerLanguage({
208790
208919
  extensions: [".liquid", ".html.liquid"],
208791
208920
  aliases: ["Liquid", "liquid"],
208792
208921
  mimetypes: ["application/liquid"],
208793
- loader: () => __vitePreload(() => import("./liquid-BuajCNJ9.js"), true ? [] : void 0, import.meta.url)
208922
+ loader: () => __vitePreload(() => import("./liquid-DaEnAsYY.js"), true ? [] : void 0, import.meta.url)
208794
208923
  });
208795
208924
  registerLanguage({
208796
208925
  id: "m3",
@@ -208808,7 +208937,7 @@ registerLanguage({
208808
208937
  id: "mdx",
208809
208938
  extensions: [".mdx"],
208810
208939
  aliases: ["MDX", "mdx"],
208811
- loader: () => __vitePreload(() => import("./mdx-D0Qx2GMS.js"), true ? [] : void 0, import.meta.url)
208940
+ loader: () => __vitePreload(() => import("./mdx-Q-qSZsFg.js"), true ? [] : void 0, import.meta.url)
208812
208941
  });
208813
208942
  registerLanguage({
208814
208943
  id: "mips",
@@ -208907,7 +209036,7 @@ registerLanguage({
208907
209036
  extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
208908
209037
  aliases: ["Python", "py"],
208909
209038
  firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
208910
- loader: () => __vitePreload(() => import("./python-ChncNy6S.js"), true ? [] : void 0, import.meta.url)
209039
+ loader: () => __vitePreload(() => import("./python-Sw0S0BJV.js"), true ? [] : void 0, import.meta.url)
208911
209040
  });
208912
209041
  registerLanguage({
208913
209042
  id: "qsharp",
@@ -208926,7 +209055,7 @@ registerLanguage({
208926
209055
  extensions: [".cshtml"],
208927
209056
  aliases: ["Razor", "razor"],
208928
209057
  mimetypes: ["text/x-cshtml"],
208929
- loader: () => __vitePreload(() => import("./razor-C6MUtfaS.js"), true ? [] : void 0, import.meta.url)
209058
+ loader: () => __vitePreload(() => import("./razor-C7zQmabz.js"), true ? [] : void 0, import.meta.url)
208930
209059
  });
208931
209060
  registerLanguage({
208932
209061
  id: "redis",
@@ -209059,7 +209188,7 @@ registerLanguage({
209059
209188
  aliases: ["TypeScript", "ts", "typescript"],
209060
209189
  mimetypes: ["text/typescript"],
209061
209190
  loader: () => {
209062
- return __vitePreload(() => import("./typescript-CB71AtAT.js"), true ? [] : void 0, import.meta.url);
209191
+ return __vitePreload(() => import("./typescript-BrdIiKZf.js"), true ? [] : void 0, import.meta.url);
209063
209192
  }
209064
209193
  });
209065
209194
  registerLanguage({
@@ -209104,14 +209233,14 @@ registerLanguage({
209104
209233
  firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
209105
209234
  aliases: ["XML", "xml"],
209106
209235
  mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
209107
- loader: () => __vitePreload(() => import("./xml-tHS1Ec9w.js"), true ? [] : void 0, import.meta.url)
209236
+ loader: () => __vitePreload(() => import("./xml-DbKLNPOq.js"), true ? [] : void 0, import.meta.url)
209108
209237
  });
209109
209238
  registerLanguage({
209110
209239
  id: "yaml",
209111
209240
  extensions: [".yaml", ".yml"],
209112
209241
  aliases: ["YAML", "yaml", "YML", "yml"],
209113
209242
  mimetypes: ["application/x-yaml", "text/x-yaml"],
209114
- loader: () => __vitePreload(() => import("./yaml-CZcp5ueF.js"), true ? [] : void 0, import.meta.url)
209243
+ loader: () => __vitePreload(() => import("./yaml-ehupJBsZ.js"), true ? [] : void 0, import.meta.url)
209115
209244
  });
209116
209245
  var __defProp = Object.defineProperty;
209117
209246
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -213403,6 +213532,7 @@ function SettingsDialog({ open, onClose }) {
213403
213532
  const [trackTime, setTrackTime] = reactExports.useState(false);
213404
213533
  const [idleTimeoutMin, setIdleTimeoutMin] = reactExports.useState(15);
213405
213534
  const [saved, setSaved] = reactExports.useState(false);
213535
+ const [version, setVersion] = reactExports.useState("");
213406
213536
  const loadProfiles = async () => {
213407
213537
  const data = await window.worker.listProfiles();
213408
213538
  setProfileList(data);
@@ -213413,6 +213543,8 @@ function SettingsDialog({ open, onClose }) {
213413
213543
  loadProfiles();
213414
213544
  setEditingId(null);
213415
213545
  setSaved(false);
213546
+ window.worker.getVersion().then(setVersion).catch(() => {
213547
+ });
213416
213548
  }
213417
213549
  }, [open]);
213418
213550
  const startEditing = (profile) => {
@@ -213583,7 +213715,16 @@ function SettingsDialog({ open, onClose }) {
213583
213715
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "btn-secondary", onClick: onClose, children: "Close" })
213584
213716
  ] })
213585
213717
  ] })
213586
- )
213718
+ ),
213719
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "settings-section settings-about", children: [
213720
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { children: "About" }),
213721
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "about-name", children: [
213722
+ "ctlsurf",
213723
+ version ? ` · v${version}` : ""
213724
+ ] }),
213725
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "about-copyright", children: "© 2026 Phenx, Inc. All rights reserved." }),
213726
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "settings-hint", children: "Proprietary and confidential. No license to use, copy, modify, or distribute is granted except under a separate written agreement with Phenx, Inc." })
213727
+ ] })
213587
213728
  ] }) });
213588
213729
  }
213589
213730
  const STATUS_OPTIONS = [