@phenx-inc/ctlsurf 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/headless/index.mjs +169 -10
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +175 -9
- package/out/preload/index.js +10 -0
- package/out/renderer/assets/{cssMode-eTXVdAkZ.js → cssMode-BdYp0fp9.js} +3 -3
- package/out/renderer/assets/{freemarker2-B5BKaiK4.js → freemarker2-Cya-9zr-.js} +1 -1
- package/out/renderer/assets/{handlebars-BIdLd2wU.js → handlebars-EJs9W5Ub.js} +1 -1
- package/out/renderer/assets/{html-BXL4cnLS.js → html-Ex4qy2lh.js} +1 -1
- package/out/renderer/assets/{htmlMode-46N3XG2c.js → htmlMode-CCm0aJ6C.js} +3 -3
- package/out/renderer/assets/{index-dRvutfbl.js → index-Cj7K9-Ba.js} +823 -137
- package/out/renderer/assets/{index-Cf-RsxoC.css → index-nvsNYP5c.css} +229 -0
- package/out/renderer/assets/{javascript-n_iZZzDX.js → javascript-B2niCouI.js} +2 -2
- package/out/renderer/assets/{jsonMode-DXDczSNu.js → jsonMode-BgCUHaFo.js} +3 -3
- package/out/renderer/assets/{liquid-B1QweUh7.js → liquid-DOQmSo4G.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-DqzMqkRk.js → lspLanguageFeatures-8SIcRAMh.js} +1 -1
- package/out/renderer/assets/{mdx-BCv8lm5e.js → mdx-DuKdGNKE.js} +1 -1
- package/out/renderer/assets/{python-BLNzYwDv.js → python-BUa4XFG-.js} +1 -1
- package/out/renderer/assets/{razor-CvAww8bG.js → razor-BUw8jho0.js} +1 -1
- package/out/renderer/assets/{transformers.web-DtSCnG36.js → transformers.web-_PuZoIho.js} +824 -19
- package/out/renderer/assets/{tsMode-C7m6Kr5E.js → tsMode-8FLRnobw.js} +1 -1
- package/out/renderer/assets/{typescript-DhPw4VVg.js → typescript-rdwAjCi4.js} +1 -1
- package/out/renderer/assets/{xml-B0WLFJ2U.js → xml-HOzT59Qg.js} +1 -1
- package/out/renderer/assets/{yaml-BWyn9Wd7.js → yaml-B81JfLsW.js} +1 -1
- package/out/renderer/index.html +64 -2
- package/out/repro-toggle.mjs +305 -0
- package/package.json +1 -1
- package/src/main/index.ts +9 -0
- package/src/main/orchestrator.ts +51 -10
- package/src/main/transcripts.ts +134 -0
- package/src/preload/index.ts +14 -0
- package/src/renderer/App.tsx +30 -11
- package/src/renderer/components/FloatingMic.tsx +48 -0
- package/src/renderer/components/SpeakControls.tsx +235 -0
- package/src/renderer/components/VoiceInput.tsx +183 -140
- package/src/renderer/index.html +62 -0
- package/src/renderer/lib/localWhisper.ts +94 -6
- package/src/renderer/lib/speech.ts +317 -0
- package/src/renderer/styles.css +229 -0
package/out/main/index.js
CHANGED
|
@@ -6282,6 +6282,138 @@ function capLength(str) {
|
|
|
6282
6282
|
if (str.length <= MAX_ENTRY_CHARS) return str;
|
|
6283
6283
|
return str.slice(0, MAX_ENTRY_CHARS) + `… [truncated, ${str.length} total chars]`;
|
|
6284
6284
|
}
|
|
6285
|
+
function readLatestAssistantReply(opts) {
|
|
6286
|
+
try {
|
|
6287
|
+
const cwd = stripTrailingSep(opts.cwd);
|
|
6288
|
+
if (opts.agentId === "claude") {
|
|
6289
|
+
const file = mostRecentClaudeFile(cwd, opts.claudeProjectsDir);
|
|
6290
|
+
if (!file) return null;
|
|
6291
|
+
const text = lastClaudeAssistant(file, cwd);
|
|
6292
|
+
return text ? capLength(text) : null;
|
|
6293
|
+
}
|
|
6294
|
+
const codexRoot = opts.codexSessionsDir || path.join(os.homedir(), ".codex", "sessions");
|
|
6295
|
+
for (const file of recentCodexRollouts(codexRoot)) {
|
|
6296
|
+
const text = lastCodexAgentMessage(file, cwd);
|
|
6297
|
+
if (text) return capLength(text);
|
|
6298
|
+
}
|
|
6299
|
+
return null;
|
|
6300
|
+
} catch {
|
|
6301
|
+
return null;
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
function readLines(file) {
|
|
6305
|
+
try {
|
|
6306
|
+
return fs.readFileSync(file, "utf-8").split("\n");
|
|
6307
|
+
} catch {
|
|
6308
|
+
return [];
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6311
|
+
function mostRecentClaudeFile(cwd, root) {
|
|
6312
|
+
const projectsRoot = root || path.join(os.homedir(), ".claude", "projects");
|
|
6313
|
+
const slug = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
6314
|
+
const dir = path.join(projectsRoot, slug);
|
|
6315
|
+
let names;
|
|
6316
|
+
try {
|
|
6317
|
+
names = fs.readdirSync(dir);
|
|
6318
|
+
} catch {
|
|
6319
|
+
return null;
|
|
6320
|
+
}
|
|
6321
|
+
let best = null;
|
|
6322
|
+
let bestMs = -Infinity;
|
|
6323
|
+
for (const name of names) {
|
|
6324
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
6325
|
+
const fp = path.join(dir, name);
|
|
6326
|
+
try {
|
|
6327
|
+
const ms = fs.statSync(fp).mtimeMs;
|
|
6328
|
+
if (ms > bestMs) {
|
|
6329
|
+
bestMs = ms;
|
|
6330
|
+
best = fp;
|
|
6331
|
+
}
|
|
6332
|
+
} catch {
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
return best;
|
|
6336
|
+
}
|
|
6337
|
+
function recentCodexRollouts(root) {
|
|
6338
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
6339
|
+
const now = Date.now();
|
|
6340
|
+
for (const ms of [now, now - 24 * 60 * 60 * 1e3]) {
|
|
6341
|
+
const d = new Date(ms);
|
|
6342
|
+
const yyyy = String(d.getFullYear());
|
|
6343
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
6344
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
6345
|
+
dirs.add(path.join(root, yyyy, mm, dd));
|
|
6346
|
+
}
|
|
6347
|
+
const files = [];
|
|
6348
|
+
for (const dir of dirs) {
|
|
6349
|
+
let names;
|
|
6350
|
+
try {
|
|
6351
|
+
names = fs.readdirSync(dir);
|
|
6352
|
+
} catch {
|
|
6353
|
+
continue;
|
|
6354
|
+
}
|
|
6355
|
+
for (const name of names) {
|
|
6356
|
+
if (!name.startsWith("rollout-") || !name.endsWith(".jsonl")) continue;
|
|
6357
|
+
const fp = path.join(dir, name);
|
|
6358
|
+
try {
|
|
6359
|
+
files.push({ fp, ms: fs.statSync(fp).mtimeMs });
|
|
6360
|
+
} catch {
|
|
6361
|
+
}
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
return files.sort((a, b2) => b2.ms - a.ms).map((f) => f.fp);
|
|
6365
|
+
}
|
|
6366
|
+
function lastClaudeAssistant(file, cwd) {
|
|
6367
|
+
const lines = readLines(file);
|
|
6368
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6369
|
+
const trimmed = lines[i].trim();
|
|
6370
|
+
if (!trimmed) continue;
|
|
6371
|
+
let obj;
|
|
6372
|
+
try {
|
|
6373
|
+
obj = JSON.parse(trimmed);
|
|
6374
|
+
} catch {
|
|
6375
|
+
continue;
|
|
6376
|
+
}
|
|
6377
|
+
if (!obj || typeof obj !== "object") continue;
|
|
6378
|
+
if (obj.isMeta || obj.isSidechain || obj.type !== "assistant") continue;
|
|
6379
|
+
if (typeof obj.cwd === "string" && stripTrailingSep(obj.cwd) !== cwd) continue;
|
|
6380
|
+
const text = extractClaudeText(obj.message?.content, false);
|
|
6381
|
+
if (text) return text;
|
|
6382
|
+
}
|
|
6383
|
+
return null;
|
|
6384
|
+
}
|
|
6385
|
+
function lastCodexAgentMessage(file, cwd) {
|
|
6386
|
+
const lines = readLines(file);
|
|
6387
|
+
for (const line of lines) {
|
|
6388
|
+
const trimmed = line.trim();
|
|
6389
|
+
if (!trimmed) continue;
|
|
6390
|
+
let obj;
|
|
6391
|
+
try {
|
|
6392
|
+
obj = JSON.parse(trimmed);
|
|
6393
|
+
} catch {
|
|
6394
|
+
continue;
|
|
6395
|
+
}
|
|
6396
|
+
if (obj?.type === "session_meta") {
|
|
6397
|
+
const metaCwd = obj.payload?.cwd;
|
|
6398
|
+
if (typeof metaCwd === "string" && stripTrailingSep(metaCwd) !== cwd) return null;
|
|
6399
|
+
break;
|
|
6400
|
+
}
|
|
6401
|
+
}
|
|
6402
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6403
|
+
const trimmed = lines[i].trim();
|
|
6404
|
+
if (!trimmed) continue;
|
|
6405
|
+
let obj;
|
|
6406
|
+
try {
|
|
6407
|
+
obj = JSON.parse(trimmed);
|
|
6408
|
+
} catch {
|
|
6409
|
+
continue;
|
|
6410
|
+
}
|
|
6411
|
+
if (obj?.type !== "event_msg" || obj.payload?.type !== "agent_message") continue;
|
|
6412
|
+
const text = typeof obj.payload.message === "string" ? obj.payload.message.trim() : "";
|
|
6413
|
+
if (text && !isCodexNoise(text)) return text;
|
|
6414
|
+
}
|
|
6415
|
+
return null;
|
|
6416
|
+
}
|
|
6285
6417
|
var bufferUtil = { exports: {} };
|
|
6286
6418
|
var constants;
|
|
6287
6419
|
var hasRequiredConstants;
|
|
@@ -11008,31 +11140,58 @@ class Orchestrator {
|
|
|
11008
11140
|
get logChatEnabled() {
|
|
11009
11141
|
return !!this.settings.logChat;
|
|
11010
11142
|
}
|
|
11143
|
+
get speakRepliesEnabled() {
|
|
11144
|
+
return !!this.settings.speakReplies;
|
|
11145
|
+
}
|
|
11146
|
+
// Latest assistant reply from a tab's transcript, for on-demand "read aloud".
|
|
11147
|
+
// Defaults to the active tab. Returns null when the tab's agent has no native
|
|
11148
|
+
// transcript (only Claude Code / Codex do) or nothing has been said yet. Reads
|
|
11149
|
+
// the transcript directly, so it works regardless of the speakReplies setting.
|
|
11150
|
+
getLastReply(tabId) {
|
|
11151
|
+
const id = tabId || this.activeTabId;
|
|
11152
|
+
if (!id) return null;
|
|
11153
|
+
const tab = this.tabs.get(id);
|
|
11154
|
+
if (!tab || !supportsTranscriptLogging(tab.agent.id)) return null;
|
|
11155
|
+
return readLatestAssistantReply({ agentId: tab.agent.id, cwd: tab.cwd });
|
|
11156
|
+
}
|
|
11011
11157
|
setLogChat(enabled) {
|
|
11012
11158
|
this.settings.logChat = enabled;
|
|
11013
11159
|
this.saveSettings();
|
|
11014
11160
|
this.bridge.setLoggingEnabled(enabled);
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
|
|
11020
|
-
|
|
11161
|
+
this.restartActiveChatLogging();
|
|
11162
|
+
}
|
|
11163
|
+
setSpeakReplies(enabled) {
|
|
11164
|
+
this.settings.speakReplies = enabled;
|
|
11165
|
+
this.saveSettings();
|
|
11166
|
+
this.restartActiveChatLogging();
|
|
11167
|
+
}
|
|
11168
|
+
// Re-evaluate the transcript tailer for the active tab against the current
|
|
11169
|
+
// logChat / speakReplies flags. Both setters funnel through here.
|
|
11170
|
+
restartActiveChatLogging() {
|
|
11171
|
+
this.stopChatLogging();
|
|
11172
|
+
if (!this.activeTabId) return;
|
|
11173
|
+
const tab = this.tabs.get(this.activeTabId);
|
|
11174
|
+
if (tab) this.startChatLogging(tab.agent, tab.cwd);
|
|
11021
11175
|
}
|
|
11022
11176
|
// Agents with native session transcripts (Claude Code, Codex) get exact
|
|
11023
11177
|
// chat text tailed from their JSONL logs; everything else falls back to
|
|
11024
11178
|
// the terminal screen-scraper bridge.
|
|
11025
11179
|
startChatLogging(agent, cwd) {
|
|
11026
11180
|
this.stopChatLogging();
|
|
11027
|
-
if (!this.settings.logChat) return;
|
|
11181
|
+
if (!this.settings.logChat && !this.settings.speakReplies) return;
|
|
11028
11182
|
if (supportsTranscriptLogging(agent.id)) {
|
|
11029
11183
|
this.transcriptTailer = new TranscriptTailer({
|
|
11030
11184
|
agentId: agent.id,
|
|
11031
11185
|
cwd,
|
|
11032
|
-
sink: (entry) =>
|
|
11186
|
+
sink: (entry) => {
|
|
11187
|
+
if (this.settings.logChat) this.workerWs.sendChatLog(entry);
|
|
11188
|
+
if (this.settings.speakReplies && entry.type === "terminal_output") {
|
|
11189
|
+
this.events.onAgentMessage?.(entry.content);
|
|
11190
|
+
}
|
|
11191
|
+
}
|
|
11033
11192
|
});
|
|
11034
11193
|
this.transcriptTailer.start();
|
|
11035
|
-
} else {
|
|
11194
|
+
} else if (this.settings.logChat) {
|
|
11036
11195
|
this.bridge.startSession();
|
|
11037
11196
|
}
|
|
11038
11197
|
}
|
|
@@ -11536,6 +11695,7 @@ const orchestrator = new Orchestrator(
|
|
|
11536
11695
|
onWorkerMessage: (message) => mainWindow?.webContents.send("worker:message", message),
|
|
11537
11696
|
onWorkerRegistered: (data) => mainWindow?.webContents.send("worker:registered", data),
|
|
11538
11697
|
onProjectChanged: (name) => mainWindow?.webContents.send("app:projectChanged", name),
|
|
11698
|
+
onAgentMessage: (text) => mainWindow?.webContents.send("agent:message", text),
|
|
11539
11699
|
onCwdChanged: () => {
|
|
11540
11700
|
mainWindow?.webContents.send("app:cwdChanged");
|
|
11541
11701
|
updateProjectBadge(orchestrator.cwd);
|
|
@@ -11771,6 +11931,12 @@ electron.ipcMain.handle("logchat:set", (_event, enabled) => {
|
|
|
11771
11931
|
orchestrator.setLogChat(!!enabled);
|
|
11772
11932
|
return { enabled: orchestrator.logChatEnabled };
|
|
11773
11933
|
});
|
|
11934
|
+
electron.ipcMain.handle("speak:get", () => ({ enabled: orchestrator.speakRepliesEnabled }));
|
|
11935
|
+
electron.ipcMain.handle("speak:set", (_event, enabled) => {
|
|
11936
|
+
orchestrator.setSpeakReplies(!!enabled);
|
|
11937
|
+
return { enabled: orchestrator.speakRepliesEnabled };
|
|
11938
|
+
});
|
|
11939
|
+
electron.ipcMain.handle("agent:getLastReply", () => orchestrator.getLastReply());
|
|
11774
11940
|
electron.ipcMain.handle("tracking:get", () => ({ active: orchestrator.isActiveTabTracking() }));
|
|
11775
11941
|
electron.ipcMain.handle("tracking:set", async (_event, enabled) => {
|
|
11776
11942
|
await orchestrator.setActiveTabTracking(enabled);
|
package/out/preload/index.js
CHANGED
|
@@ -61,6 +61,16 @@ const api = {
|
|
|
61
61
|
// Chat logging (global)
|
|
62
62
|
getLogChat: () => electron.ipcRenderer.invoke("logchat:get"),
|
|
63
63
|
setLogChat: (enabled) => electron.ipcRenderer.invoke("logchat:set", enabled),
|
|
64
|
+
// Speak agent replies (global, Electron-only)
|
|
65
|
+
getSpeakReplies: () => electron.ipcRenderer.invoke("speak:get"),
|
|
66
|
+
setSpeakReplies: (enabled) => electron.ipcRenderer.invoke("speak:set", enabled),
|
|
67
|
+
onAgentMessage: (callback) => {
|
|
68
|
+
const listener = (_event, text) => callback(text);
|
|
69
|
+
electron.ipcRenderer.on("agent:message", listener);
|
|
70
|
+
return () => electron.ipcRenderer.removeListener("agent:message", listener);
|
|
71
|
+
},
|
|
72
|
+
// Latest assistant reply from the active tab's transcript (null if none).
|
|
73
|
+
getLastReply: () => electron.ipcRenderer.invoke("agent:getLastReply"),
|
|
64
74
|
// Filesystem
|
|
65
75
|
readDir: (dirPath) => electron.ipcRenderer.invoke("fs:readDir", dirPath),
|
|
66
76
|
readFile: (filePath) => electron.ipcRenderer.invoke("fs:readFile", filePath),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as createWebWorker, l as languages } from "./index-
|
|
2
|
-
import { C as CompletionAdapter, H as HoverAdapter, D as DocumentHighlightAdapter, a as DefinitionAdapter, R as ReferenceAdapter, b as DocumentSymbolAdapter, c as RenameAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, e as DiagnosticsAdapter, S as SelectionRangeAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider } from "./lspLanguageFeatures-
|
|
3
|
-
import { h, i, j, t, k } from "./lspLanguageFeatures-
|
|
1
|
+
import { c as createWebWorker, l as languages } from "./index-Cj7K9-Ba.js";
|
|
2
|
+
import { C as CompletionAdapter, H as HoverAdapter, D as DocumentHighlightAdapter, a as DefinitionAdapter, R as ReferenceAdapter, b as DocumentSymbolAdapter, c as RenameAdapter, d as DocumentColorAdapter, F as FoldingRangeAdapter, e as DiagnosticsAdapter, S as SelectionRangeAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider } from "./lspLanguageFeatures-8SIcRAMh.js";
|
|
3
|
+
import { h, i, j, t, k } from "./lspLanguageFeatures-8SIcRAMh.js";
|
|
4
4
|
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
|
|
5
5
|
class WorkerManager {
|
|
6
6
|
constructor(defaults) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as createWebWorker, l as languages } from "./index-
|
|
2
|
-
import { H as HoverAdapter, D as DocumentHighlightAdapter, h as DocumentLinkAdapter, F as FoldingRangeAdapter, b as DocumentSymbolAdapter, S as SelectionRangeAdapter, c as RenameAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter } from "./lspLanguageFeatures-
|
|
3
|
-
import { a, e, d, R, i, j, t, k } from "./lspLanguageFeatures-
|
|
1
|
+
import { c as createWebWorker, l as languages } from "./index-Cj7K9-Ba.js";
|
|
2
|
+
import { H as HoverAdapter, D as DocumentHighlightAdapter, h as DocumentLinkAdapter, F as FoldingRangeAdapter, b as DocumentSymbolAdapter, S as SelectionRangeAdapter, c as RenameAdapter, f as DocumentFormattingEditProvider, g as DocumentRangeFormattingEditProvider, C as CompletionAdapter } from "./lspLanguageFeatures-8SIcRAMh.js";
|
|
3
|
+
import { a, e, d, R, i, j, t, k } from "./lspLanguageFeatures-8SIcRAMh.js";
|
|
4
4
|
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1e3;
|
|
5
5
|
class WorkerManager {
|
|
6
6
|
constructor(defaults) {
|