@phenx-inc/ctlsurf 0.8.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 +144 -1
- package/out/headless/index.mjs.map +2 -2
- package/out/main/index.js +144 -0
- package/out/preload/index.js +2 -0
- package/out/renderer/assets/{cssMode-BQN8v2ok.js → cssMode-BdYp0fp9.js} +3 -3
- package/out/renderer/assets/{freemarker2-DbxGYYVp.js → freemarker2-Cya-9zr-.js} +1 -1
- package/out/renderer/assets/{handlebars-3auU1CAd.js → handlebars-EJs9W5Ub.js} +1 -1
- package/out/renderer/assets/{html-D8xFiRmI.js → html-Ex4qy2lh.js} +1 -1
- package/out/renderer/assets/{htmlMode-M3MApZ4n.js → htmlMode-CCm0aJ6C.js} +3 -3
- package/out/renderer/assets/{index---H6cxNl.js → index-Cj7K9-Ba.js} +265 -242
- package/out/renderer/assets/{index-B-iM7dFC.css → index-nvsNYP5c.css} +34 -0
- package/out/renderer/assets/{javascript-BO_ViZM5.js → javascript-B2niCouI.js} +2 -2
- package/out/renderer/assets/{jsonMode-CKp2zvZu.js → jsonMode-BgCUHaFo.js} +3 -3
- package/out/renderer/assets/{liquid-C1eHcrht.js → liquid-DOQmSo4G.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-CHWJx_Tl.js → lspLanguageFeatures-8SIcRAMh.js} +1 -1
- package/out/renderer/assets/{mdx-Qqdtk7fL.js → mdx-DuKdGNKE.js} +1 -1
- package/out/renderer/assets/{python-DKu7rNbs.js → python-BUa4XFG-.js} +1 -1
- package/out/renderer/assets/{razor-BOMpCo6z.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-yAjlPR-D.js → tsMode-8FLRnobw.js} +1 -1
- package/out/renderer/assets/{typescript-BiJRCUcL.js → typescript-rdwAjCi4.js} +1 -1
- package/out/renderer/assets/{xml-D4PvYeQq.js → xml-HOzT59Qg.js} +1 -1
- package/out/renderer/assets/{yaml-BeHVkmnS.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 +2 -0
- package/src/main/orchestrator.ts +13 -1
- package/src/main/transcripts.ts +134 -0
- package/src/preload/index.ts +3 -0
- package/src/renderer/App.tsx +25 -11
- package/src/renderer/components/FloatingMic.tsx +48 -0
- package/src/renderer/components/VoiceInput.tsx +36 -149
- package/src/renderer/index.html +62 -0
- package/src/renderer/lib/localWhisper.ts +46 -2
- package/src/renderer/lib/speech.ts +18 -0
- package/src/renderer/styles.css +34 -0
package/out/headless/index.mjs
CHANGED
|
@@ -5471,7 +5471,7 @@ var require_package = __commonJS({
|
|
|
5471
5471
|
"package.json"(exports, module) {
|
|
5472
5472
|
module.exports = {
|
|
5473
5473
|
name: "@phenx-inc/ctlsurf",
|
|
5474
|
-
version: "0.
|
|
5474
|
+
version: "0.9.0",
|
|
5475
5475
|
description: "Agent-agnostic terminal and desktop app for ctlsurf \u2014 run Claude Code, Codex, or any coding agent with live session logging and remote control",
|
|
5476
5476
|
main: "out/main/index.js",
|
|
5477
5477
|
bin: {
|
|
@@ -6337,6 +6337,138 @@ function capLength(str) {
|
|
|
6337
6337
|
if (str.length <= MAX_ENTRY_CHARS) return str;
|
|
6338
6338
|
return str.slice(0, MAX_ENTRY_CHARS) + `\u2026 [truncated, ${str.length} total chars]`;
|
|
6339
6339
|
}
|
|
6340
|
+
function readLatestAssistantReply(opts) {
|
|
6341
|
+
try {
|
|
6342
|
+
const cwd = stripTrailingSep(opts.cwd);
|
|
6343
|
+
if (opts.agentId === "claude") {
|
|
6344
|
+
const file = mostRecentClaudeFile(cwd, opts.claudeProjectsDir);
|
|
6345
|
+
if (!file) return null;
|
|
6346
|
+
const text = lastClaudeAssistant(file, cwd);
|
|
6347
|
+
return text ? capLength(text) : null;
|
|
6348
|
+
}
|
|
6349
|
+
const codexRoot = opts.codexSessionsDir || path.join(os.homedir(), ".codex", "sessions");
|
|
6350
|
+
for (const file of recentCodexRollouts(codexRoot)) {
|
|
6351
|
+
const text = lastCodexAgentMessage(file, cwd);
|
|
6352
|
+
if (text) return capLength(text);
|
|
6353
|
+
}
|
|
6354
|
+
return null;
|
|
6355
|
+
} catch {
|
|
6356
|
+
return null;
|
|
6357
|
+
}
|
|
6358
|
+
}
|
|
6359
|
+
function readLines(file) {
|
|
6360
|
+
try {
|
|
6361
|
+
return fs.readFileSync(file, "utf-8").split("\n");
|
|
6362
|
+
} catch {
|
|
6363
|
+
return [];
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
function mostRecentClaudeFile(cwd, root) {
|
|
6367
|
+
const projectsRoot = root || path.join(os.homedir(), ".claude", "projects");
|
|
6368
|
+
const slug = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
6369
|
+
const dir = path.join(projectsRoot, slug);
|
|
6370
|
+
let names;
|
|
6371
|
+
try {
|
|
6372
|
+
names = fs.readdirSync(dir);
|
|
6373
|
+
} catch {
|
|
6374
|
+
return null;
|
|
6375
|
+
}
|
|
6376
|
+
let best = null;
|
|
6377
|
+
let bestMs = -Infinity;
|
|
6378
|
+
for (const name of names) {
|
|
6379
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
6380
|
+
const fp = path.join(dir, name);
|
|
6381
|
+
try {
|
|
6382
|
+
const ms = fs.statSync(fp).mtimeMs;
|
|
6383
|
+
if (ms > bestMs) {
|
|
6384
|
+
bestMs = ms;
|
|
6385
|
+
best = fp;
|
|
6386
|
+
}
|
|
6387
|
+
} catch {
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
return best;
|
|
6391
|
+
}
|
|
6392
|
+
function recentCodexRollouts(root) {
|
|
6393
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
6394
|
+
const now = Date.now();
|
|
6395
|
+
for (const ms of [now, now - 24 * 60 * 60 * 1e3]) {
|
|
6396
|
+
const d = new Date(ms);
|
|
6397
|
+
const yyyy = String(d.getFullYear());
|
|
6398
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
6399
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
6400
|
+
dirs.add(path.join(root, yyyy, mm, dd));
|
|
6401
|
+
}
|
|
6402
|
+
const files = [];
|
|
6403
|
+
for (const dir of dirs) {
|
|
6404
|
+
let names;
|
|
6405
|
+
try {
|
|
6406
|
+
names = fs.readdirSync(dir);
|
|
6407
|
+
} catch {
|
|
6408
|
+
continue;
|
|
6409
|
+
}
|
|
6410
|
+
for (const name of names) {
|
|
6411
|
+
if (!name.startsWith("rollout-") || !name.endsWith(".jsonl")) continue;
|
|
6412
|
+
const fp = path.join(dir, name);
|
|
6413
|
+
try {
|
|
6414
|
+
files.push({ fp, ms: fs.statSync(fp).mtimeMs });
|
|
6415
|
+
} catch {
|
|
6416
|
+
}
|
|
6417
|
+
}
|
|
6418
|
+
}
|
|
6419
|
+
return files.sort((a, b) => b.ms - a.ms).map((f) => f.fp);
|
|
6420
|
+
}
|
|
6421
|
+
function lastClaudeAssistant(file, cwd) {
|
|
6422
|
+
const lines = readLines(file);
|
|
6423
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6424
|
+
const trimmed = lines[i].trim();
|
|
6425
|
+
if (!trimmed) continue;
|
|
6426
|
+
let obj;
|
|
6427
|
+
try {
|
|
6428
|
+
obj = JSON.parse(trimmed);
|
|
6429
|
+
} catch {
|
|
6430
|
+
continue;
|
|
6431
|
+
}
|
|
6432
|
+
if (!obj || typeof obj !== "object") continue;
|
|
6433
|
+
if (obj.isMeta || obj.isSidechain || obj.type !== "assistant") continue;
|
|
6434
|
+
if (typeof obj.cwd === "string" && stripTrailingSep(obj.cwd) !== cwd) continue;
|
|
6435
|
+
const text = extractClaudeText(obj.message?.content, false);
|
|
6436
|
+
if (text) return text;
|
|
6437
|
+
}
|
|
6438
|
+
return null;
|
|
6439
|
+
}
|
|
6440
|
+
function lastCodexAgentMessage(file, cwd) {
|
|
6441
|
+
const lines = readLines(file);
|
|
6442
|
+
for (const line of lines) {
|
|
6443
|
+
const trimmed = line.trim();
|
|
6444
|
+
if (!trimmed) continue;
|
|
6445
|
+
let obj;
|
|
6446
|
+
try {
|
|
6447
|
+
obj = JSON.parse(trimmed);
|
|
6448
|
+
} catch {
|
|
6449
|
+
continue;
|
|
6450
|
+
}
|
|
6451
|
+
if (obj?.type === "session_meta") {
|
|
6452
|
+
const metaCwd = obj.payload?.cwd;
|
|
6453
|
+
if (typeof metaCwd === "string" && stripTrailingSep(metaCwd) !== cwd) return null;
|
|
6454
|
+
break;
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
6458
|
+
const trimmed = lines[i].trim();
|
|
6459
|
+
if (!trimmed) continue;
|
|
6460
|
+
let obj;
|
|
6461
|
+
try {
|
|
6462
|
+
obj = JSON.parse(trimmed);
|
|
6463
|
+
} catch {
|
|
6464
|
+
continue;
|
|
6465
|
+
}
|
|
6466
|
+
if (obj?.type !== "event_msg" || obj.payload?.type !== "agent_message") continue;
|
|
6467
|
+
const text = typeof obj.payload.message === "string" ? obj.payload.message.trim() : "";
|
|
6468
|
+
if (text && !isCodexNoise(text)) return text;
|
|
6469
|
+
}
|
|
6470
|
+
return null;
|
|
6471
|
+
}
|
|
6340
6472
|
|
|
6341
6473
|
// src/main/workerWs.ts
|
|
6342
6474
|
import os2 from "os";
|
|
@@ -7182,6 +7314,17 @@ var Orchestrator = class {
|
|
|
7182
7314
|
get speakRepliesEnabled() {
|
|
7183
7315
|
return !!this.settings.speakReplies;
|
|
7184
7316
|
}
|
|
7317
|
+
// Latest assistant reply from a tab's transcript, for on-demand "read aloud".
|
|
7318
|
+
// Defaults to the active tab. Returns null when the tab's agent has no native
|
|
7319
|
+
// transcript (only Claude Code / Codex do) or nothing has been said yet. Reads
|
|
7320
|
+
// the transcript directly, so it works regardless of the speakReplies setting.
|
|
7321
|
+
getLastReply(tabId) {
|
|
7322
|
+
const id = tabId || this.activeTabId;
|
|
7323
|
+
if (!id) return null;
|
|
7324
|
+
const tab = this.tabs.get(id);
|
|
7325
|
+
if (!tab || !supportsTranscriptLogging(tab.agent.id)) return null;
|
|
7326
|
+
return readLatestAssistantReply({ agentId: tab.agent.id, cwd: tab.cwd });
|
|
7327
|
+
}
|
|
7185
7328
|
setLogChat(enabled) {
|
|
7186
7329
|
this.settings.logChat = enabled;
|
|
7187
7330
|
this.saveSettings();
|