@phenx-inc/ctlsurf 0.3.6 → 0.3.8
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 +276 -56
- package/out/headless/index.mjs.map +3 -3
- package/out/main/index.js +152 -94
- package/out/preload/index.js +3 -0
- package/out/renderer/assets/{cssMode-DQW-brNd.js → cssMode-CYoo4t9f.js} +3 -3
- package/out/renderer/assets/{freemarker2-DxgOckH2.js → freemarker2--UQnPZsn.js} +1 -1
- package/out/renderer/assets/{handlebars-BX1Wpk_3.js → handlebars-DVDrmX0C.js} +1 -1
- package/out/renderer/assets/{html-t-KXioI0.js → html-D1-cXoLy.js} +1 -1
- package/out/renderer/assets/{htmlMode-Dya7iUjr.js → htmlMode-f5nBuprq.js} +3 -3
- package/out/renderer/assets/{index-D6JBcQ20.css → index-65hyKM_8.css} +16 -0
- package/out/renderer/assets/{index-DNqZidnO.js → index-D23nru43.js} +64 -23
- package/out/renderer/assets/{javascript-DZzW2adn.js → javascript-CcarFzBL.js} +2 -2
- package/out/renderer/assets/{jsonMode-D_Wv7XH8.js → jsonMode-BvF-xK9U.js} +3 -3
- package/out/renderer/assets/{liquid-BJAHAm2T.js → liquid-CHLtUKl2.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-BgMd-KJk.js → lspLanguageFeatures-B9aNeatS.js} +1 -1
- package/out/renderer/assets/{mdx-B6Zod3ry.js → mdx-HGDrkifZ.js} +1 -1
- package/out/renderer/assets/{python-Cgt13-KH.js → python-B_dPzjJ6.js} +1 -1
- package/out/renderer/assets/{razor-BcwFJGYS.js → razor-CHheM4ot.js} +1 -1
- package/out/renderer/assets/{tsMode-BTjzM6fl.js → tsMode-CdC3i1gG.js} +1 -1
- package/out/renderer/assets/{typescript-DZYDQEUb.js → typescript-BX6guVRK.js} +1 -1
- package/out/renderer/assets/{xml-CloiUoIW.js → xml-CpS-pOPE.js} +1 -1
- package/out/renderer/assets/{yaml-CdKdpE-z.js → yaml-Du0AjOHW.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/bridge.ts +9 -3
- package/src/main/headless.ts +35 -2
- package/src/main/index.ts +10 -39
- package/src/main/orchestrator.ts +29 -1
- package/src/main/timeTracker.ts +74 -45
- package/src/main/tui.ts +20 -8
- package/src/main/updateCheck.ts +40 -0
- package/src/preload/index.ts +6 -0
- package/src/renderer/App.tsx +2 -0
- package/src/renderer/components/AgentPicker.tsx +38 -3
- package/src/renderer/styles.css +16 -0
package/out/headless/index.mjs
CHANGED
|
@@ -5466,6 +5466,88 @@ var require_electron = __commonJS({
|
|
|
5466
5466
|
}
|
|
5467
5467
|
});
|
|
5468
5468
|
|
|
5469
|
+
// package.json
|
|
5470
|
+
var require_package = __commonJS({
|
|
5471
|
+
"package.json"(exports, module) {
|
|
5472
|
+
module.exports = {
|
|
5473
|
+
name: "@phenx-inc/ctlsurf",
|
|
5474
|
+
version: "0.3.8",
|
|
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
|
+
main: "out/main/index.js",
|
|
5477
|
+
bin: {
|
|
5478
|
+
ctlsurf: "./bin/ctlsurf-worker.js",
|
|
5479
|
+
"ctlsurf-worker": "./bin/ctlsurf-worker.js"
|
|
5480
|
+
},
|
|
5481
|
+
files: [
|
|
5482
|
+
"bin/",
|
|
5483
|
+
"out/",
|
|
5484
|
+
"resources/",
|
|
5485
|
+
"scripts/",
|
|
5486
|
+
"src/",
|
|
5487
|
+
"package.json",
|
|
5488
|
+
"electron-vite.config.ts",
|
|
5489
|
+
"tsconfig*.json"
|
|
5490
|
+
],
|
|
5491
|
+
scripts: {
|
|
5492
|
+
dev: "electron-vite dev",
|
|
5493
|
+
"dev:terminal": "esbuild src/main/headless.ts --bundle --platform=node --target=node18 --format=esm --outfile=out/headless/index.mjs --external:node-pty --external:ws --sourcemap && node out/headless/index.mjs",
|
|
5494
|
+
build: "electron-vite build && npm run build:headless",
|
|
5495
|
+
"build:headless": "esbuild src/main/headless.ts --bundle --platform=node --target=node18 --format=esm --outfile=out/headless/index.mjs --external:node-pty --external:ws --sourcemap",
|
|
5496
|
+
prepublishOnly: "npm run build",
|
|
5497
|
+
preview: "electron-vite preview",
|
|
5498
|
+
package: "electron-builder",
|
|
5499
|
+
postinstall: "node-gyp rebuild --directory=node_modules/node-pty 2>/dev/null || true"
|
|
5500
|
+
},
|
|
5501
|
+
keywords: [
|
|
5502
|
+
"ctlsurf",
|
|
5503
|
+
"terminal",
|
|
5504
|
+
"tui",
|
|
5505
|
+
"coding-agent",
|
|
5506
|
+
"claude-code",
|
|
5507
|
+
"codex",
|
|
5508
|
+
"electron",
|
|
5509
|
+
"node-pty",
|
|
5510
|
+
"xterm"
|
|
5511
|
+
],
|
|
5512
|
+
license: "MIT",
|
|
5513
|
+
engines: {
|
|
5514
|
+
node: ">=18"
|
|
5515
|
+
},
|
|
5516
|
+
dependencies: {
|
|
5517
|
+
"@monaco-editor/react": "^4.7.0",
|
|
5518
|
+
"@xterm/addon-fit": "^0.10.0",
|
|
5519
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
5520
|
+
"@xterm/addon-web-links": "^0.11.0",
|
|
5521
|
+
"@xterm/headless": "^6.0.0",
|
|
5522
|
+
"@xterm/xterm": "^5.5.0",
|
|
5523
|
+
"electron-store": "^10.0.0",
|
|
5524
|
+
esbuild: "^0.27.4",
|
|
5525
|
+
"monaco-editor": "^0.55.1",
|
|
5526
|
+
"node-pty": "^1.0.0",
|
|
5527
|
+
ws: "^8.20.0"
|
|
5528
|
+
},
|
|
5529
|
+
devDependencies: {
|
|
5530
|
+
"@electron/rebuild": "^4.0.3",
|
|
5531
|
+
"@types/node": "^22.15.0",
|
|
5532
|
+
"@types/react": "^19.1.0",
|
|
5533
|
+
"@types/react-dom": "^19.1.0",
|
|
5534
|
+
"@types/ws": "^8.18.1",
|
|
5535
|
+
"@vitejs/plugin-react": "^4.5.2",
|
|
5536
|
+
electron: "^35.0.0",
|
|
5537
|
+
"electron-builder": "^25.1.8",
|
|
5538
|
+
"electron-vite": "^3.1.0",
|
|
5539
|
+
react: "^19.1.0",
|
|
5540
|
+
"react-dom": "^19.1.0",
|
|
5541
|
+
typescript: "^5.8.3"
|
|
5542
|
+
},
|
|
5543
|
+
optionalDependencies: {
|
|
5544
|
+
bufferutil: "^4.1.0",
|
|
5545
|
+
"utf-8-validate": "^6.0.6"
|
|
5546
|
+
}
|
|
5547
|
+
};
|
|
5548
|
+
}
|
|
5549
|
+
});
|
|
5550
|
+
|
|
5469
5551
|
// src/main/orchestrator.ts
|
|
5470
5552
|
import path from "path";
|
|
5471
5553
|
import fs from "fs";
|
|
@@ -5682,6 +5764,7 @@ var import_addon_serialize = __toESM(require_addon_serialize());
|
|
|
5682
5764
|
var ConversationBridge = class {
|
|
5683
5765
|
wsClient = null;
|
|
5684
5766
|
sessionActive = false;
|
|
5767
|
+
loggingEnabled = false;
|
|
5685
5768
|
inputBuffer = "";
|
|
5686
5769
|
outputBuffer = "";
|
|
5687
5770
|
outputFlushTimer = null;
|
|
@@ -5691,7 +5774,11 @@ var ConversationBridge = class {
|
|
|
5691
5774
|
setWsClient(ws) {
|
|
5692
5775
|
this.wsClient = ws;
|
|
5693
5776
|
}
|
|
5777
|
+
setLoggingEnabled(enabled) {
|
|
5778
|
+
this.loggingEnabled = enabled;
|
|
5779
|
+
}
|
|
5694
5780
|
startSession() {
|
|
5781
|
+
if (!this.loggingEnabled) return;
|
|
5695
5782
|
this.clearOutputTimers();
|
|
5696
5783
|
this.outputBuffer = "";
|
|
5697
5784
|
this.inputBuffer = "";
|
|
@@ -5700,13 +5787,13 @@ var ConversationBridge = class {
|
|
|
5700
5787
|
console.log("[bridge] Session started");
|
|
5701
5788
|
}
|
|
5702
5789
|
feedOutput(data) {
|
|
5703
|
-
if (!this.sessionActive) return;
|
|
5790
|
+
if (!this.sessionActive || !this.loggingEnabled) return;
|
|
5704
5791
|
this.outputBuffer += data;
|
|
5705
5792
|
this.terminalCapture.write(data);
|
|
5706
5793
|
this.scheduleOutputFlush();
|
|
5707
5794
|
}
|
|
5708
5795
|
feedInput(data) {
|
|
5709
|
-
if (!this.sessionActive) return;
|
|
5796
|
+
if (!this.sessionActive || !this.loggingEnabled) return;
|
|
5710
5797
|
this.inputBuffer += data;
|
|
5711
5798
|
if (data.includes("\r") || data.includes("\n")) {
|
|
5712
5799
|
const cleaned = cleanInput(this.inputBuffer);
|
|
@@ -5728,7 +5815,7 @@ var ConversationBridge = class {
|
|
|
5728
5815
|
this.sendEntry("terminal_output", cleaned);
|
|
5729
5816
|
}
|
|
5730
5817
|
sendEntry(type, content) {
|
|
5731
|
-
if (!this.wsClient) return;
|
|
5818
|
+
if (!this.wsClient || !this.loggingEnabled) return;
|
|
5732
5819
|
this.wsClient.sendChatLog({
|
|
5733
5820
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5734
5821
|
type,
|
|
@@ -6271,55 +6358,68 @@ var TimeTracker = class {
|
|
|
6271
6358
|
if (this.sessions.has(tabId)) {
|
|
6272
6359
|
await this.endSession(tabId);
|
|
6273
6360
|
}
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6361
|
+
const startedAt = Date.now();
|
|
6362
|
+
const state = {
|
|
6363
|
+
blockId: null,
|
|
6364
|
+
rowId: null,
|
|
6365
|
+
sessionUuid: randomUUID(),
|
|
6366
|
+
cwd,
|
|
6367
|
+
agentName,
|
|
6368
|
+
idleTimeoutMin,
|
|
6369
|
+
startedAt,
|
|
6370
|
+
lastActivity: startedAt,
|
|
6371
|
+
activeMs: 0,
|
|
6372
|
+
idleTimeoutMs: Math.max(1, idleTimeoutMin) * 60 * 1e3,
|
|
6373
|
+
firstCheckpointTimer: null,
|
|
6374
|
+
checkpointTimer: null,
|
|
6375
|
+
ended: false
|
|
6376
|
+
};
|
|
6377
|
+
this.sessions.set(tabId, state);
|
|
6378
|
+
await this.tryResolve(tabId);
|
|
6379
|
+
state.firstCheckpointTimer = setTimeout(() => {
|
|
6380
|
+
void this.checkpoint(tabId);
|
|
6381
|
+
const live = this.sessions.get(tabId);
|
|
6382
|
+
if (live && !live.ended) {
|
|
6383
|
+
live.checkpointTimer = setInterval(() => {
|
|
6384
|
+
void this.checkpoint(tabId);
|
|
6385
|
+
}, CHECKPOINT_INTERVAL_MS);
|
|
6279
6386
|
}
|
|
6280
|
-
|
|
6281
|
-
|
|
6387
|
+
}, FIRST_CHECKPOINT_DELAY_MS);
|
|
6388
|
+
const pending = !state.blockId || !state.rowId;
|
|
6389
|
+
log2(`Started tracking tab=${tabId} agent="${agentName}" cwd=${cwd}${pending ? " (pending datastore \u2014 will retry on each checkpoint)" : ""}`);
|
|
6390
|
+
}
|
|
6391
|
+
/** Attempts to locate (or create) the datastore + add the session row.
|
|
6392
|
+
* Returns true once the session is resolved (blockId + rowId set).
|
|
6393
|
+
* Safe to call repeatedly: re-running while pending will keep retrying;
|
|
6394
|
+
* once resolved it's a no-op. */
|
|
6395
|
+
async tryResolve(tabId) {
|
|
6396
|
+
const s = this.sessions.get(tabId);
|
|
6397
|
+
if (!s) return false;
|
|
6398
|
+
if (s.blockId && s.rowId) return true;
|
|
6399
|
+
try {
|
|
6400
|
+
const blockId = await this.ensureDatastore(s.cwd);
|
|
6401
|
+
if (!blockId) return false;
|
|
6282
6402
|
const row = await this.api.addRow(blockId, {
|
|
6283
|
-
Started: formatStarted(startedAt),
|
|
6284
|
-
"Active Time":
|
|
6285
|
-
"Last Updated": new Date(
|
|
6286
|
-
Agent: agentName,
|
|
6403
|
+
Started: formatStarted(s.startedAt),
|
|
6404
|
+
"Active Time": Math.round(s.activeMs / 6e4),
|
|
6405
|
+
"Last Updated": (/* @__PURE__ */ new Date()).toISOString(),
|
|
6406
|
+
Agent: s.agentName,
|
|
6287
6407
|
Worker: os2.hostname(),
|
|
6288
|
-
Session: sessionUuid,
|
|
6408
|
+
Session: s.sessionUuid,
|
|
6289
6409
|
Notes: ""
|
|
6290
6410
|
});
|
|
6291
6411
|
const rowId = row?.id;
|
|
6292
6412
|
if (!rowId) {
|
|
6293
|
-
log2(
|
|
6294
|
-
return;
|
|
6413
|
+
log2(`addRow returned no id for tab=${tabId}; will retry on next checkpoint`);
|
|
6414
|
+
return false;
|
|
6295
6415
|
}
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
agentName,
|
|
6301
|
-
idleTimeoutMin,
|
|
6302
|
-
startedAt,
|
|
6303
|
-
lastActivity: startedAt,
|
|
6304
|
-
activeMs: 0,
|
|
6305
|
-
idleTimeoutMs: Math.max(1, idleTimeoutMin) * 60 * 1e3,
|
|
6306
|
-
firstCheckpointTimer: null,
|
|
6307
|
-
checkpointTimer: null,
|
|
6308
|
-
ended: false
|
|
6309
|
-
};
|
|
6310
|
-
state.firstCheckpointTimer = setTimeout(() => {
|
|
6311
|
-
void this.checkpoint(tabId);
|
|
6312
|
-
const live = this.sessions.get(tabId);
|
|
6313
|
-
if (live && !live.ended) {
|
|
6314
|
-
live.checkpointTimer = setInterval(() => {
|
|
6315
|
-
void this.checkpoint(tabId);
|
|
6316
|
-
}, CHECKPOINT_INTERVAL_MS);
|
|
6317
|
-
}
|
|
6318
|
-
}, FIRST_CHECKPOINT_DELAY_MS);
|
|
6319
|
-
this.sessions.set(tabId, state);
|
|
6320
|
-
log2(`Started tracking tab=${tabId} agent="${agentName}" cwd=${cwd}`);
|
|
6416
|
+
s.blockId = blockId;
|
|
6417
|
+
s.rowId = rowId;
|
|
6418
|
+
log2(`Resolved datastore for tab=${tabId} (cwd=${s.cwd})`);
|
|
6419
|
+
return true;
|
|
6321
6420
|
} catch (err) {
|
|
6322
|
-
log2(`
|
|
6421
|
+
log2(`tryResolve failed for tab=${tabId}: ${err?.message || err}`);
|
|
6422
|
+
return false;
|
|
6323
6423
|
}
|
|
6324
6424
|
}
|
|
6325
6425
|
isTracking(tabId) {
|
|
@@ -6359,14 +6459,21 @@ var TimeTracker = class {
|
|
|
6359
6459
|
async endSession(tabId) {
|
|
6360
6460
|
const s = this.sessions.get(tabId);
|
|
6361
6461
|
if (!s || s.ended) return;
|
|
6362
|
-
s.ended = true;
|
|
6363
6462
|
if (s.firstCheckpointTimer) clearTimeout(s.firstCheckpointTimer);
|
|
6364
6463
|
if (s.checkpointTimer) clearInterval(s.checkpointTimer);
|
|
6365
6464
|
try {
|
|
6366
|
-
|
|
6465
|
+
if (!s.blockId || !s.rowId) {
|
|
6466
|
+
await this.tryResolve(tabId);
|
|
6467
|
+
}
|
|
6468
|
+
if (s.blockId && s.rowId) {
|
|
6469
|
+
await this.writeRow(s, Date.now());
|
|
6470
|
+
} else {
|
|
6471
|
+
log2(`endSession for tab=${tabId}: never resolved datastore; ${Math.round(s.activeMs / 6e4)}min not recorded`);
|
|
6472
|
+
}
|
|
6367
6473
|
} catch (err) {
|
|
6368
6474
|
log2(`endSession write failed: ${err?.message || err}`);
|
|
6369
6475
|
}
|
|
6476
|
+
s.ended = true;
|
|
6370
6477
|
this.sessions.delete(tabId);
|
|
6371
6478
|
}
|
|
6372
6479
|
async endAll() {
|
|
@@ -6376,13 +6483,16 @@ var TimeTracker = class {
|
|
|
6376
6483
|
async checkpoint(tabId) {
|
|
6377
6484
|
const s = this.sessions.get(tabId);
|
|
6378
6485
|
if (!s || s.ended) return;
|
|
6486
|
+
if (!s.blockId || !s.rowId) {
|
|
6487
|
+
if (!await this.tryResolve(tabId)) return;
|
|
6488
|
+
}
|
|
6379
6489
|
try {
|
|
6380
6490
|
await this.writeRow(s, Date.now());
|
|
6381
6491
|
} catch (err) {
|
|
6382
6492
|
log2(`checkpoint failed: ${err?.message || err}; retrying in 2s`);
|
|
6383
6493
|
setTimeout(() => {
|
|
6384
6494
|
const live = this.sessions.get(tabId);
|
|
6385
|
-
if (!live || live.ended) return;
|
|
6495
|
+
if (!live || live.ended || !live.blockId || !live.rowId) return;
|
|
6386
6496
|
this.writeRow(live, Date.now()).catch((err2) => {
|
|
6387
6497
|
log2(`checkpoint retry failed: ${err2?.message || err2}`);
|
|
6388
6498
|
});
|
|
@@ -6390,6 +6500,7 @@ var TimeTracker = class {
|
|
|
6390
6500
|
}
|
|
6391
6501
|
}
|
|
6392
6502
|
async writeRow(s, _endTimeMs) {
|
|
6503
|
+
if (!s.blockId || !s.rowId) return;
|
|
6393
6504
|
const activeMin = Math.round(s.activeMs / 6e4);
|
|
6394
6505
|
await this.api.updateRow(s.blockId, s.rowId, {
|
|
6395
6506
|
"Active Time": activeMin,
|
|
@@ -6523,7 +6634,8 @@ var Orchestrator = class {
|
|
|
6523
6634
|
currentCwd = null;
|
|
6524
6635
|
settings = {
|
|
6525
6636
|
activeProfile: "production",
|
|
6526
|
-
profiles: { ...DEFAULT_PROFILES }
|
|
6637
|
+
profiles: { ...DEFAULT_PROFILES },
|
|
6638
|
+
logChat: false
|
|
6527
6639
|
};
|
|
6528
6640
|
constructor(settingsDir, events) {
|
|
6529
6641
|
this.settingsDir = settingsDir;
|
|
@@ -6558,6 +6670,21 @@ var Orchestrator = class {
|
|
|
6558
6670
|
}
|
|
6559
6671
|
});
|
|
6560
6672
|
this.bridge.setWsClient(this.workerWs);
|
|
6673
|
+
this.bridge.setLoggingEnabled(!!this.settings.logChat);
|
|
6674
|
+
}
|
|
6675
|
+
// ─── Chat logging ───────────────────────────────
|
|
6676
|
+
get logChatEnabled() {
|
|
6677
|
+
return !!this.settings.logChat;
|
|
6678
|
+
}
|
|
6679
|
+
setLogChat(enabled) {
|
|
6680
|
+
this.settings.logChat = enabled;
|
|
6681
|
+
this.saveSettings();
|
|
6682
|
+
this.bridge.setLoggingEnabled(enabled);
|
|
6683
|
+
if (!enabled) {
|
|
6684
|
+
this.bridge.endSession();
|
|
6685
|
+
} else if (this.activeTabId) {
|
|
6686
|
+
this.bridge.startSession();
|
|
6687
|
+
}
|
|
6561
6688
|
}
|
|
6562
6689
|
// ─── Settings ───────────────────────────────────
|
|
6563
6690
|
getActiveProfile() {
|
|
@@ -6605,7 +6732,8 @@ var Orchestrator = class {
|
|
|
6605
6732
|
baseUrl: raw.ctlsurfBaseUrl || "https://app.ctlsurf.com",
|
|
6606
6733
|
dataspacePageId: raw.ctlsurfDataspacePageId || ""
|
|
6607
6734
|
}
|
|
6608
|
-
}
|
|
6735
|
+
},
|
|
6736
|
+
logChat: !!raw.logChat
|
|
6609
6737
|
};
|
|
6610
6738
|
this.saveSettings();
|
|
6611
6739
|
log3("[settings] Migrated legacy settings to profiles");
|
|
@@ -6614,15 +6742,20 @@ var Orchestrator = class {
|
|
|
6614
6742
|
if (!this.settings.profiles.production) {
|
|
6615
6743
|
this.settings.profiles.production = { ...DEFAULT_PROFILES.production };
|
|
6616
6744
|
}
|
|
6745
|
+
if (this.settings.logChat === void 0) {
|
|
6746
|
+
this.settings.logChat = false;
|
|
6747
|
+
}
|
|
6617
6748
|
}
|
|
6618
6749
|
}
|
|
6619
6750
|
} catch {
|
|
6620
6751
|
this.settings = {
|
|
6621
6752
|
activeProfile: "production",
|
|
6622
|
-
profiles: { ...DEFAULT_PROFILES }
|
|
6753
|
+
profiles: { ...DEFAULT_PROFILES },
|
|
6754
|
+
logChat: false
|
|
6623
6755
|
};
|
|
6624
6756
|
}
|
|
6625
6757
|
this.applyProfile(this.getActiveProfile());
|
|
6758
|
+
this.bridge.setLoggingEnabled(!!this.settings.logChat);
|
|
6626
6759
|
}
|
|
6627
6760
|
saveSettings() {
|
|
6628
6761
|
const settingsPath = path.join(this.settingsDir, "settings.json");
|
|
@@ -6752,7 +6885,9 @@ var Orchestrator = class {
|
|
|
6752
6885
|
const t = this.tabs.get(tabId);
|
|
6753
6886
|
if (t?.termStreamTimer) clearTimeout(t.termStreamTimer);
|
|
6754
6887
|
});
|
|
6755
|
-
this.
|
|
6888
|
+
if (this.settings.logChat) {
|
|
6889
|
+
this.bridge.startSession();
|
|
6890
|
+
}
|
|
6756
6891
|
const profile = this.getActiveProfile();
|
|
6757
6892
|
const shouldTrack = opts?.trackTime !== void 0 ? opts.trackTime : profile.trackTime !== false;
|
|
6758
6893
|
if (shouldTrack) {
|
|
@@ -7010,8 +7145,9 @@ var Tui = class {
|
|
|
7010
7145
|
return new Promise((resolve) => {
|
|
7011
7146
|
let selected = 0;
|
|
7012
7147
|
let trackTime = options.initialTrackTime;
|
|
7148
|
+
let logChat = options.initialLogChat;
|
|
7013
7149
|
const modalWidth = 44;
|
|
7014
|
-
const modalHeight = agents.length + 4 +
|
|
7150
|
+
const modalHeight = agents.length + 4 + 3;
|
|
7015
7151
|
const startCol = Math.max(1, Math.floor((this.cols - modalWidth) / 2));
|
|
7016
7152
|
const startRow = Math.max(1, Math.floor((this.rows - modalHeight) / 2));
|
|
7017
7153
|
this.write(`${CSI}?1049h`);
|
|
@@ -7055,9 +7191,16 @@ var Tui = class {
|
|
|
7055
7191
|
const trackContentLen = 2 + 3 + 1 + "Track time".length;
|
|
7056
7192
|
const trackPad = " ".repeat(Math.max(0, modalWidth - 2 - trackContentLen));
|
|
7057
7193
|
this.write(`${CSI}${trackRow};${startCol}H${BG_MODAL}${FG_DIM}\u2502${RESET}${BG_MODAL}${trackContent}${trackPad}${FG_DIM}\u2502${RESET}`);
|
|
7058
|
-
const
|
|
7194
|
+
const logRow = trackRow + 1;
|
|
7195
|
+
const logCheckbox = logChat ? `${FG_GREEN}[\u2713]${RESET}${BG_MODAL}` : `${FG_DIM}[ ]${RESET}${BG_MODAL}`;
|
|
7196
|
+
const logLabelFg = logChat ? FG_WHITE : FG_DIM;
|
|
7197
|
+
const logContent = ` ${logCheckbox} ${logLabelFg}Log chat${RESET}${BG_MODAL}`;
|
|
7198
|
+
const logContentLen = 2 + 3 + 1 + "Log chat".length;
|
|
7199
|
+
const logPad = " ".repeat(Math.max(0, modalWidth - 2 - logContentLen));
|
|
7200
|
+
this.write(`${CSI}${logRow};${startCol}H${BG_MODAL}${FG_DIM}\u2502${RESET}${BG_MODAL}${logContent}${logPad}${FG_DIM}\u2502${RESET}`);
|
|
7201
|
+
const botRow = logRow + 1;
|
|
7059
7202
|
this.write(`${CSI}${botRow};${startCol}H${BG_MODAL}${FG_DIM}${botBorder}${RESET}`);
|
|
7060
|
-
const hint = "\u2191\u2193
|
|
7203
|
+
const hint = "\u2191\u2193 nav \xB7 Enter \xB7 t track \xB7 l log \xB7 q quit";
|
|
7061
7204
|
const hintCol = Math.max(1, Math.floor((this.cols - hint.length) / 2));
|
|
7062
7205
|
this.write(`${CSI}${botRow + 2};${hintCol}H${FG_DIM}${hint}${RESET}`);
|
|
7063
7206
|
};
|
|
@@ -7074,12 +7217,15 @@ var Tui = class {
|
|
|
7074
7217
|
} else if (key === "\x1B[B" || key === "j") {
|
|
7075
7218
|
selected = (selected + 1) % agents.length;
|
|
7076
7219
|
drawModal();
|
|
7077
|
-
} else if (key === "t" || key === "T"
|
|
7220
|
+
} else if (key === "t" || key === "T") {
|
|
7078
7221
|
trackTime = !trackTime;
|
|
7079
7222
|
drawModal();
|
|
7223
|
+
} else if (key === "l" || key === "L") {
|
|
7224
|
+
logChat = !logChat;
|
|
7225
|
+
drawModal();
|
|
7080
7226
|
} else if (key === "\r" || key === "\n") {
|
|
7081
7227
|
cleanup();
|
|
7082
|
-
resolve({ agentIdx: selected, trackTime });
|
|
7228
|
+
resolve({ agentIdx: selected, trackTime, logChat });
|
|
7083
7229
|
} else if (key === "q" || key === "\x1B" || key === "") {
|
|
7084
7230
|
cleanup();
|
|
7085
7231
|
this.write(`${CSI}?25h`);
|
|
@@ -7146,6 +7292,50 @@ var Tui = class {
|
|
|
7146
7292
|
}
|
|
7147
7293
|
};
|
|
7148
7294
|
|
|
7295
|
+
// src/main/updateCheck.ts
|
|
7296
|
+
import https from "https";
|
|
7297
|
+
var NPM_PACKAGE = "@phenx-inc/ctlsurf";
|
|
7298
|
+
function compareSemver(a, b) {
|
|
7299
|
+
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
7300
|
+
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
7301
|
+
for (let i = 0; i < 3; i++) {
|
|
7302
|
+
const ai = pa[i] || 0;
|
|
7303
|
+
const bi = pb[i] || 0;
|
|
7304
|
+
if (ai !== bi) return ai - bi;
|
|
7305
|
+
}
|
|
7306
|
+
return 0;
|
|
7307
|
+
}
|
|
7308
|
+
function fetchLatestNpmVersion(timeoutMs = 8e3) {
|
|
7309
|
+
return new Promise((resolve) => {
|
|
7310
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE)}/latest`;
|
|
7311
|
+
const req = https.get(url, { headers: { "Accept": "application/json" } }, (res) => {
|
|
7312
|
+
if (res.statusCode !== 200) {
|
|
7313
|
+
res.resume();
|
|
7314
|
+
resolve(null);
|
|
7315
|
+
return;
|
|
7316
|
+
}
|
|
7317
|
+
let body = "";
|
|
7318
|
+
res.setEncoding("utf8");
|
|
7319
|
+
res.on("data", (chunk) => {
|
|
7320
|
+
body += chunk;
|
|
7321
|
+
});
|
|
7322
|
+
res.on("end", () => {
|
|
7323
|
+
try {
|
|
7324
|
+
const json = JSON.parse(body);
|
|
7325
|
+
resolve(typeof json?.version === "string" ? json.version : null);
|
|
7326
|
+
} catch {
|
|
7327
|
+
resolve(null);
|
|
7328
|
+
}
|
|
7329
|
+
});
|
|
7330
|
+
});
|
|
7331
|
+
req.on("error", () => resolve(null));
|
|
7332
|
+
req.setTimeout(timeoutMs, () => {
|
|
7333
|
+
req.destroy();
|
|
7334
|
+
resolve(null);
|
|
7335
|
+
});
|
|
7336
|
+
});
|
|
7337
|
+
}
|
|
7338
|
+
|
|
7149
7339
|
// src/main/headless.ts
|
|
7150
7340
|
process.stdout?.on?.("error", () => {
|
|
7151
7341
|
});
|
|
@@ -7158,6 +7348,14 @@ process.on("uncaughtException", (err) => {
|
|
|
7158
7348
|
} catch {
|
|
7159
7349
|
}
|
|
7160
7350
|
});
|
|
7351
|
+
function getCurrentVersion() {
|
|
7352
|
+
try {
|
|
7353
|
+
const pkg = require_package();
|
|
7354
|
+
return typeof pkg?.version === "string" ? pkg.version : "0.0.0";
|
|
7355
|
+
} catch {
|
|
7356
|
+
return "0.0.0";
|
|
7357
|
+
}
|
|
7358
|
+
}
|
|
7161
7359
|
function parseArgs(argv) {
|
|
7162
7360
|
const args = {
|
|
7163
7361
|
agent: null,
|
|
@@ -7198,9 +7396,27 @@ function parseArgs(argv) {
|
|
|
7198
7396
|
}
|
|
7199
7397
|
return args;
|
|
7200
7398
|
}
|
|
7399
|
+
async function checkVersionAndNotify() {
|
|
7400
|
+
const current = getCurrentVersion();
|
|
7401
|
+
const latest = await fetchLatestNpmVersion(3e3);
|
|
7402
|
+
if (!latest) return;
|
|
7403
|
+
if (compareSemver(latest, current) <= 0) return;
|
|
7404
|
+
const Y = "\x1B[33m";
|
|
7405
|
+
const G = "\x1B[32m";
|
|
7406
|
+
const D = "\x1B[90m";
|
|
7407
|
+
const R = "\x1B[0m";
|
|
7408
|
+
process.stdout.write(
|
|
7409
|
+
`
|
|
7410
|
+
${Y}A new version of ctlsurf is available${R} ${D}(${current} \u2192 ${latest})${R}
|
|
7411
|
+
Update: ${G}npm i -g ${NPM_PACKAGE}${R}
|
|
7412
|
+
|
|
7413
|
+
`
|
|
7414
|
+
);
|
|
7415
|
+
}
|
|
7201
7416
|
async function main() {
|
|
7202
7417
|
const args = parseArgs(process.argv.slice(2));
|
|
7203
7418
|
const settingsDir = getSettingsDir(false);
|
|
7419
|
+
await checkVersionAndNotify();
|
|
7204
7420
|
const tui = new Tui();
|
|
7205
7421
|
const agents = getBuiltinAgents();
|
|
7206
7422
|
const orchestrator = new Orchestrator(settingsDir, {
|
|
@@ -7241,9 +7457,13 @@ async function main() {
|
|
|
7241
7457
|
};
|
|
7242
7458
|
} else {
|
|
7243
7459
|
const initialTrackTime = orchestrator.getActiveProfile().trackTime !== false;
|
|
7244
|
-
const
|
|
7460
|
+
const initialLogChat = orchestrator.logChatEnabled;
|
|
7461
|
+
const picked = await tui.showAgentPicker(agents, { initialTrackTime, initialLogChat });
|
|
7245
7462
|
agent = agents[picked.agentIdx];
|
|
7246
7463
|
trackTimeOverride = picked.trackTime;
|
|
7464
|
+
if (picked.logChat !== orchestrator.logChatEnabled) {
|
|
7465
|
+
orchestrator.setLogChat(picked.logChat);
|
|
7466
|
+
}
|
|
7247
7467
|
}
|
|
7248
7468
|
tui.update({
|
|
7249
7469
|
agentName: agent.name,
|