@phenx-inc/ctlsurf 0.3.9 → 0.3.10
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/bin/ctlsurf-worker.js +46 -43
- package/out/headless/index.mjs +28 -26
- package/out/headless/index.mjs.map +4 -4
- package/out/main/index.js +43 -47
- package/package.json +1 -1
- package/scripts/rebrand-electron.js +195 -0
- package/src/main/headless.ts +6 -0
- package/src/main/index.ts +6 -0
- package/src/main/logger.ts +10 -0
- package/src/main/orchestrator.ts +1 -4
- package/src/main/workerWs.ts +5 -8
package/bin/ctlsurf-worker.js
CHANGED
|
@@ -238,50 +238,36 @@ if (mode === 'desktop') {
|
|
|
238
238
|
try {
|
|
239
239
|
electronPath = require('electron')
|
|
240
240
|
} catch {
|
|
241
|
-
// Electron not installed
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
console.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const readline = require('readline')
|
|
250
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
251
|
-
rl.question(` Choice ${D}(1/2/3)${R}: `, (answer) => {
|
|
252
|
-
rl.close()
|
|
253
|
-
answer = answer.trim()
|
|
254
|
-
if (answer === '1') {
|
|
255
|
-
console.log(`\n${D}Installing Electron (this may take a minute)...${R}\n`)
|
|
256
|
-
try {
|
|
257
|
-
execSync('npm install electron --no-save', { cwd: ROOT, stdio: 'inherit' })
|
|
258
|
-
console.log(`\n${G}✓${R} Electron installed. Launching desktop mode...\n`)
|
|
259
|
-
try {
|
|
260
|
-
const ep = require('electron')
|
|
261
|
-
execFileSync(String(ep), [ROOT, ...args], {
|
|
262
|
-
stdio: 'inherit',
|
|
263
|
-
env: { ...process.env, CTLSURF_WORKER_CWD: process.env.CTLSURF_WORKER_CWD || process.cwd() }
|
|
264
|
-
})
|
|
265
|
-
} catch (err) {
|
|
266
|
-
process.exit(err.status || 0)
|
|
267
|
-
}
|
|
268
|
-
} catch (err) {
|
|
269
|
-
console.error(`\n${Y}!${R} Electron install failed: ${err.message}`)
|
|
270
|
-
console.error(` Try manually: cd ${ROOT} && npm install electron\n`)
|
|
271
|
-
process.exit(1)
|
|
272
|
-
}
|
|
273
|
-
} else if (answer === '2') {
|
|
274
|
-
console.log('')
|
|
275
|
-
runTerminal()
|
|
276
|
-
} else {
|
|
277
|
-
process.exit(0)
|
|
278
|
-
}
|
|
279
|
-
})
|
|
280
|
-
return
|
|
241
|
+
// Electron not installed (devDependency, or wiped by `npm update`).
|
|
242
|
+
// Auto-install silently so npm-installed users don't have to think about it.
|
|
243
|
+
if (!installElectron()) {
|
|
244
|
+
// Install failed — fall back to terminal mode
|
|
245
|
+
console.error(`\n${Y}!${R} Falling back to terminal mode.\n`)
|
|
246
|
+
runTerminal()
|
|
247
|
+
process.exit(0)
|
|
281
248
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
249
|
+
try {
|
|
250
|
+
// Bust require cache (electron's index.js reads path.txt at require time)
|
|
251
|
+
delete require.cache[require.resolve('electron')]
|
|
252
|
+
electronPath = require('electron')
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.error(`\n${Y}!${R} Electron resolution failed after install: ${err.message}\n`)
|
|
255
|
+
process.exit(1)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Rebrand Electron.app → ctlsurf.app (idempotent; sentinel file inside
|
|
260
|
+
// node_modules/electron). Re-runs automatically after npm update wipes it.
|
|
261
|
+
try {
|
|
262
|
+
const { rebrand } = require(path.join(ROOT, 'scripts', 'rebrand-electron.js'))
|
|
263
|
+
if (rebrand()) {
|
|
264
|
+
// Path may have changed — re-resolve
|
|
265
|
+
delete require.cache[require.resolve('electron')]
|
|
266
|
+
electronPath = require('electron')
|
|
267
|
+
}
|
|
268
|
+
} catch (err) {
|
|
269
|
+
// Non-fatal — app still launches, just shows as "Electron"
|
|
270
|
+
console.error(`${D}[ctlsurf] rebrand skipped: ${err.message}${R}`)
|
|
285
271
|
}
|
|
286
272
|
|
|
287
273
|
try {
|
|
@@ -296,6 +282,23 @@ if (mode === 'desktop') {
|
|
|
296
282
|
runTerminal()
|
|
297
283
|
}
|
|
298
284
|
|
|
285
|
+
function installElectron() {
|
|
286
|
+
console.log(`\n${B} ctlsurf${R} ${D}— first-run setup${R}\n`)
|
|
287
|
+
console.log(` ${D}Installing Electron (one-time, ~100 MB)...${R}`)
|
|
288
|
+
try {
|
|
289
|
+
execSync('npm install electron --no-save --silent', {
|
|
290
|
+
cwd: ROOT,
|
|
291
|
+
stdio: ['ignore', 'ignore', 'inherit']
|
|
292
|
+
})
|
|
293
|
+
console.log(` ${G}✓${R} Electron installed.\n`)
|
|
294
|
+
return true
|
|
295
|
+
} catch (err) {
|
|
296
|
+
console.error(`\n${Y}!${R} Electron install failed: ${err.message}`)
|
|
297
|
+
console.error(` Try manually: cd ${ROOT} && npm install electron\n`)
|
|
298
|
+
return false
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
299
302
|
function runTerminal() {
|
|
300
303
|
const terminalPath = path.join(ROOT, 'out/headless/index.mjs')
|
|
301
304
|
if (!fs.existsSync(terminalPath)) {
|
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.3.
|
|
5474
|
+
version: "0.3.10",
|
|
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: {
|
|
@@ -5548,6 +5548,19 @@ var require_package = __commonJS({
|
|
|
5548
5548
|
}
|
|
5549
5549
|
});
|
|
5550
5550
|
|
|
5551
|
+
// src/main/logger.ts
|
|
5552
|
+
var silent = false;
|
|
5553
|
+
function setSilent(value) {
|
|
5554
|
+
silent = value;
|
|
5555
|
+
}
|
|
5556
|
+
function log(...args) {
|
|
5557
|
+
if (silent) return;
|
|
5558
|
+
try {
|
|
5559
|
+
console.log(...args);
|
|
5560
|
+
} catch {
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
|
|
5551
5564
|
// src/main/orchestrator.ts
|
|
5552
5565
|
import path from "path";
|
|
5553
5566
|
import fs from "fs";
|
|
@@ -6051,12 +6064,6 @@ import os from "os";
|
|
|
6051
6064
|
import crypto from "crypto";
|
|
6052
6065
|
import WsModule from "ws";
|
|
6053
6066
|
var WS = typeof WebSocket !== "undefined" ? WebSocket : WsModule;
|
|
6054
|
-
function log(...args) {
|
|
6055
|
-
try {
|
|
6056
|
-
console.log(...args);
|
|
6057
|
-
} catch {
|
|
6058
|
-
}
|
|
6059
|
-
}
|
|
6060
6067
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
6061
6068
|
var RECONNECT_DELAY_MS = 5e3;
|
|
6062
6069
|
var MAX_RECONNECT_DELAY_MS = 6e4;
|
|
@@ -6220,7 +6227,7 @@ var WorkerWsClient = class {
|
|
|
6220
6227
|
case "registered": {
|
|
6221
6228
|
this.workerId = data.worker_id;
|
|
6222
6229
|
const workerStatus = data.status;
|
|
6223
|
-
|
|
6230
|
+
log(`[worker-ws] Registered as ${this.workerId}, status: ${workerStatus}`);
|
|
6224
6231
|
if (workerStatus === "pending_approval") {
|
|
6225
6232
|
this.setStatus("pending_approval");
|
|
6226
6233
|
} else {
|
|
@@ -6246,7 +6253,7 @@ var WorkerWsClient = class {
|
|
|
6246
6253
|
case "message": {
|
|
6247
6254
|
const msg = data.message;
|
|
6248
6255
|
if (msg) {
|
|
6249
|
-
|
|
6256
|
+
log(`[worker-ws] Received message: ${msg.id}`);
|
|
6250
6257
|
this.events.onMessage(msg);
|
|
6251
6258
|
}
|
|
6252
6259
|
break;
|
|
@@ -6261,7 +6268,7 @@ var WorkerWsClient = class {
|
|
|
6261
6268
|
case "heartbeat_ack":
|
|
6262
6269
|
break;
|
|
6263
6270
|
default:
|
|
6264
|
-
|
|
6271
|
+
log(`[worker-ws] Unknown message type: ${msgType}`);
|
|
6265
6272
|
}
|
|
6266
6273
|
}
|
|
6267
6274
|
send(data) {
|
|
@@ -6283,7 +6290,7 @@ var WorkerWsClient = class {
|
|
|
6283
6290
|
}
|
|
6284
6291
|
scheduleReconnect() {
|
|
6285
6292
|
if (!this.shouldReconnect) return;
|
|
6286
|
-
|
|
6293
|
+
log(`[worker-ws] Reconnecting in ${this.reconnectDelay / 1e3}s...`);
|
|
6287
6294
|
this.reconnectTimer = setTimeout(() => {
|
|
6288
6295
|
this.doConnect();
|
|
6289
6296
|
}, this.reconnectDelay);
|
|
@@ -6601,12 +6608,6 @@ var TimeTracker = class {
|
|
|
6601
6608
|
};
|
|
6602
6609
|
|
|
6603
6610
|
// src/main/orchestrator.ts
|
|
6604
|
-
function log3(...args) {
|
|
6605
|
-
try {
|
|
6606
|
-
console.log(...args);
|
|
6607
|
-
} catch {
|
|
6608
|
-
}
|
|
6609
|
-
}
|
|
6610
6611
|
var DEFAULT_IDLE_TIMEOUT_MIN = 15;
|
|
6611
6612
|
var DEFAULT_PROFILES = {
|
|
6612
6613
|
production: {
|
|
@@ -6645,11 +6646,11 @@ var Orchestrator = class {
|
|
|
6645
6646
|
this.events = events;
|
|
6646
6647
|
this.workerWs = new WorkerWsClient({
|
|
6647
6648
|
onStatusChange: (status) => {
|
|
6648
|
-
|
|
6649
|
+
log(`[worker-ws] Status: ${status}`);
|
|
6649
6650
|
events.onWorkerStatus(status);
|
|
6650
6651
|
},
|
|
6651
6652
|
onMessage: (message) => {
|
|
6652
|
-
|
|
6653
|
+
log(`[worker-ws] Incoming message: ${message.id} (${message.type})`);
|
|
6653
6654
|
events.onWorkerMessage(message);
|
|
6654
6655
|
this.workerWs.sendAck(message.id);
|
|
6655
6656
|
if (message.type === "prompt" || message.type === "task_dispatch") {
|
|
@@ -6661,7 +6662,7 @@ var Orchestrator = class {
|
|
|
6661
6662
|
}
|
|
6662
6663
|
},
|
|
6663
6664
|
onRegistered: (data) => {
|
|
6664
|
-
|
|
6665
|
+
log(`[worker-ws] Registered: worker_id=${data.worker_id}, folder_id=${data.folder_id}, status=${data.status}`);
|
|
6665
6666
|
events.onWorkerRegistered(data);
|
|
6666
6667
|
if (!data.folder_id) {
|
|
6667
6668
|
events.onWorkerStatus("no_project");
|
|
@@ -6719,7 +6720,7 @@ var Orchestrator = class {
|
|
|
6719
6720
|
const baseUrl = profile.baseUrl || process.env.CTLSURF_BASE_URL || "https://app.ctlsurf.com";
|
|
6720
6721
|
this.ctlsurfApi.setBaseUrl(baseUrl);
|
|
6721
6722
|
this.workerWs.setBaseUrl(baseUrl);
|
|
6722
|
-
|
|
6723
|
+
log(`[settings] Profile applied: ${profile.name} (${baseUrl})`);
|
|
6723
6724
|
}
|
|
6724
6725
|
loadSettings() {
|
|
6725
6726
|
try {
|
|
@@ -6744,7 +6745,7 @@ var Orchestrator = class {
|
|
|
6744
6745
|
logChat: !!raw.logChat
|
|
6745
6746
|
};
|
|
6746
6747
|
this.saveSettings();
|
|
6747
|
-
|
|
6748
|
+
log("[settings] Migrated legacy settings to profiles");
|
|
6748
6749
|
} else {
|
|
6749
6750
|
this.settings = raw;
|
|
6750
6751
|
if (!this.settings.profiles.production) {
|
|
@@ -6771,7 +6772,7 @@ var Orchestrator = class {
|
|
|
6771
6772
|
fs.mkdirSync(this.settingsDir, { recursive: true });
|
|
6772
6773
|
fs.writeFileSync(settingsPath, JSON.stringify(this.settings, null, 2));
|
|
6773
6774
|
} catch (err) {
|
|
6774
|
-
|
|
6775
|
+
log("[settings] Failed to save:", err.message);
|
|
6775
6776
|
}
|
|
6776
6777
|
}
|
|
6777
6778
|
overrideApiKey(key) {
|
|
@@ -6981,7 +6982,7 @@ var Orchestrator = class {
|
|
|
6981
6982
|
const profile = this.getActiveProfile();
|
|
6982
6983
|
const apiKey = profile.apiKey || process.env.CTLSURF_API_KEY;
|
|
6983
6984
|
if (!apiKey) {
|
|
6984
|
-
|
|
6985
|
+
log("[worker-ws] No API key, skipping WS connect");
|
|
6985
6986
|
return;
|
|
6986
6987
|
}
|
|
6987
6988
|
this.stopNoProjectPolling();
|
|
@@ -6995,7 +6996,7 @@ var Orchestrator = class {
|
|
|
6995
6996
|
if (this.noProjectPollTimer && this.noProjectPollCwd === cwd) return;
|
|
6996
6997
|
this.stopNoProjectPolling();
|
|
6997
6998
|
this.noProjectPollCwd = cwd;
|
|
6998
|
-
|
|
6999
|
+
log(`[worker-ws] Polling for project folder at ${cwd}`);
|
|
6999
7000
|
this.noProjectPollTimer = setInterval(() => {
|
|
7000
7001
|
void this.checkForProjectFolder(cwd);
|
|
7001
7002
|
}, NO_PROJECT_POLL_MS);
|
|
@@ -7016,7 +7017,7 @@ var Orchestrator = class {
|
|
|
7016
7017
|
try {
|
|
7017
7018
|
const folder = await this.ctlsurfApi.findFolderByPath(cwd);
|
|
7018
7019
|
if (folder?.id && this.currentCwd === cwd && this.currentAgent) {
|
|
7019
|
-
|
|
7020
|
+
log(`[worker-ws] Project folder appeared (${folder.id}); reconnecting`);
|
|
7020
7021
|
const agent = this.currentAgent;
|
|
7021
7022
|
this.stopNoProjectPolling();
|
|
7022
7023
|
this.workerWs.disconnect();
|
|
@@ -7393,6 +7394,7 @@ process.on("uncaughtException", (err) => {
|
|
|
7393
7394
|
} catch {
|
|
7394
7395
|
}
|
|
7395
7396
|
});
|
|
7397
|
+
setSilent(true);
|
|
7396
7398
|
function getCurrentVersion() {
|
|
7397
7399
|
try {
|
|
7398
7400
|
const pkg = require_package();
|