orchestrating 0.1.23 → 0.1.26
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/orch +202 -3
- package/package.json +1 -1
package/bin/orch
CHANGED
|
@@ -178,6 +178,200 @@ if (firstArg === "logout") {
|
|
|
178
178
|
process.exit(0);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
if (firstArg === "daemon") {
|
|
182
|
+
await handleDaemon();
|
|
183
|
+
// handleDaemon runs forever (or exits on fatal error)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// --- Daemon mode ---
|
|
187
|
+
async function handleDaemon() {
|
|
188
|
+
const DIM = "\x1b[2m";
|
|
189
|
+
const GREEN = "\x1b[32m";
|
|
190
|
+
const RED = "\x1b[31m";
|
|
191
|
+
const RESET = "\x1b[0m";
|
|
192
|
+
const PREFIX = "[daemon]";
|
|
193
|
+
|
|
194
|
+
let token = getAuthToken();
|
|
195
|
+
if (!token) {
|
|
196
|
+
console.error(`${RED}No credentials found. Run 'orch login' to authenticate.${RESET}`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
if (isTokenExpired()) {
|
|
200
|
+
const refreshed = await refreshAuthToken();
|
|
201
|
+
if (refreshed) {
|
|
202
|
+
token = refreshed;
|
|
203
|
+
} else {
|
|
204
|
+
console.error(`${RED}Session expired. Run 'orch login' to re-authenticate.${RESET}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const hostname = os.hostname().replace(/\.(lan|local|home|internal)$/i, "");
|
|
210
|
+
const cliVersion = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf-8")).version;
|
|
211
|
+
const serverUrl = process.env.ORC_URL || process.env.CAST_URL || "wss://api.orchestrat.ing/ws";
|
|
212
|
+
const orchPath = fileURLToPath(import.meta.url);
|
|
213
|
+
|
|
214
|
+
const PING_INTERVAL_MS = 30_000;
|
|
215
|
+
const PONG_TIMEOUT_MS = 10_000;
|
|
216
|
+
let ws = null;
|
|
217
|
+
let pingTimer = null;
|
|
218
|
+
let pongTimer = null;
|
|
219
|
+
let reconnectTimer = null;
|
|
220
|
+
|
|
221
|
+
process.stderr.write(`${GREEN}${PREFIX} Listening for remote sessions as "${hostname}"${RESET}\n`);
|
|
222
|
+
process.stderr.write(`${DIM}${PREFIX} Tip: Use 'nohup orch daemon &' to run in background${RESET}\n`);
|
|
223
|
+
|
|
224
|
+
let reconnecting = false;
|
|
225
|
+
|
|
226
|
+
function scheduleReconnect(delaySec) {
|
|
227
|
+
if (reconnecting) return;
|
|
228
|
+
reconnecting = true;
|
|
229
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
230
|
+
reconnectTimer = setTimeout(() => {
|
|
231
|
+
reconnecting = false;
|
|
232
|
+
connect();
|
|
233
|
+
}, delaySec * 1000);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function connect() {
|
|
237
|
+
// Refresh token if needed before connecting
|
|
238
|
+
if (isTokenExpired()) {
|
|
239
|
+
const refreshed = await refreshAuthToken();
|
|
240
|
+
if (refreshed) {
|
|
241
|
+
token = refreshed;
|
|
242
|
+
process.stderr.write(`${DIM}${PREFIX} Token refreshed${RESET}\n`);
|
|
243
|
+
} else {
|
|
244
|
+
process.stderr.write(`${RED}${PREFIX} Token refresh failed — retrying in 10s${RESET}\n`);
|
|
245
|
+
scheduleReconnect(10);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (ws) {
|
|
251
|
+
ws.removeAllListeners();
|
|
252
|
+
ws.close();
|
|
253
|
+
}
|
|
254
|
+
const sock = new WebSocket(serverUrl);
|
|
255
|
+
ws = sock;
|
|
256
|
+
|
|
257
|
+
sock.on("open", () => {
|
|
258
|
+
sock.send(JSON.stringify({
|
|
259
|
+
type: "register_daemon",
|
|
260
|
+
token,
|
|
261
|
+
hostname,
|
|
262
|
+
cliVersion,
|
|
263
|
+
}));
|
|
264
|
+
process.stderr.write(`${GREEN}${PREFIX} Connected${RESET}\n`);
|
|
265
|
+
|
|
266
|
+
// Keepalive pings
|
|
267
|
+
if (pingTimer) clearInterval(pingTimer);
|
|
268
|
+
if (pongTimer) clearTimeout(pongTimer);
|
|
269
|
+
pingTimer = setInterval(() => {
|
|
270
|
+
if (sock.readyState === WebSocket.OPEN) {
|
|
271
|
+
sock.ping();
|
|
272
|
+
pongTimer = setTimeout(() => {
|
|
273
|
+
sock.terminate();
|
|
274
|
+
}, PONG_TIMEOUT_MS);
|
|
275
|
+
}
|
|
276
|
+
}, PING_INTERVAL_MS);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
sock.on("pong", () => {
|
|
280
|
+
if (pongTimer) { clearTimeout(pongTimer); pongTimer = null; }
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
sock.on("message", (raw) => {
|
|
284
|
+
let msg;
|
|
285
|
+
try { msg = JSON.parse(raw.toString()); } catch { return; }
|
|
286
|
+
|
|
287
|
+
if (msg.type === "error") {
|
|
288
|
+
process.stderr.write(`${RED}${PREFIX} Server: ${msg.error}${RESET}\n`);
|
|
289
|
+
if (/unauthorized|auth|token/i.test(msg.error || "")) {
|
|
290
|
+
// Don't reconnect from here — let the close handler do it after refreshing
|
|
291
|
+
refreshAuthToken().then((refreshed) => {
|
|
292
|
+
if (refreshed) {
|
|
293
|
+
token = refreshed;
|
|
294
|
+
process.stderr.write(`${DIM}${PREFIX} Token refreshed — will reconnect${RESET}\n`);
|
|
295
|
+
} else {
|
|
296
|
+
process.stderr.write(`${RED}${PREFIX} Auth failed. Run 'orch login'.${RESET}\n`);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (msg.type === "start_session") {
|
|
305
|
+
const { requestId, command: cmd, args: cmdArgs, cwd, label: lbl, yolo } = msg;
|
|
306
|
+
const cmdStr = [cmd, ...cmdArgs].join(" ");
|
|
307
|
+
process.stderr.write(`${GREEN}${PREFIX} Spawning: ${cmdStr}${RESET}\n`);
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
const childArgs = [];
|
|
311
|
+
if (lbl) childArgs.push("-l", lbl);
|
|
312
|
+
if (yolo) childArgs.push("-y");
|
|
313
|
+
childArgs.push(cmd, ...cmdArgs);
|
|
314
|
+
|
|
315
|
+
const child = spawn(process.execPath, [orchPath, ...childArgs], {
|
|
316
|
+
stdio: "ignore",
|
|
317
|
+
detached: true,
|
|
318
|
+
cwd: cwd || process.cwd(),
|
|
319
|
+
env: { ...process.env },
|
|
320
|
+
});
|
|
321
|
+
child.unref();
|
|
322
|
+
|
|
323
|
+
if (sock.readyState === WebSocket.OPEN) {
|
|
324
|
+
sock.send(JSON.stringify({
|
|
325
|
+
type: "session_started",
|
|
326
|
+
requestId,
|
|
327
|
+
success: true,
|
|
328
|
+
}));
|
|
329
|
+
}
|
|
330
|
+
} catch (err) {
|
|
331
|
+
process.stderr.write(`${RED}${PREFIX} Spawn failed: ${err.message}${RESET}\n`);
|
|
332
|
+
if (sock.readyState === WebSocket.OPEN) {
|
|
333
|
+
sock.send(JSON.stringify({
|
|
334
|
+
type: "session_started",
|
|
335
|
+
requestId,
|
|
336
|
+
success: false,
|
|
337
|
+
error: err.message,
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
sock.on("close", () => {
|
|
345
|
+
if (ws === sock) ws = null;
|
|
346
|
+
if (pingTimer) { clearInterval(pingTimer); pingTimer = null; }
|
|
347
|
+
if (pongTimer) { clearTimeout(pongTimer); pongTimer = null; }
|
|
348
|
+
process.stderr.write(`${DIM}${PREFIX} Disconnected — reconnecting in 2s${RESET}\n`);
|
|
349
|
+
scheduleReconnect(2);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
sock.on("error", () => {
|
|
353
|
+
// Will trigger close
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
connect();
|
|
358
|
+
|
|
359
|
+
// Graceful shutdown
|
|
360
|
+
const shutdown = () => {
|
|
361
|
+
process.stderr.write(`\n${DIM}${PREFIX} Shutting down${RESET}\n`);
|
|
362
|
+
if (pingTimer) clearInterval(pingTimer);
|
|
363
|
+
if (pongTimer) clearTimeout(pongTimer);
|
|
364
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
365
|
+
if (ws) ws.close();
|
|
366
|
+
process.exit(0);
|
|
367
|
+
};
|
|
368
|
+
process.on("SIGINT", shutdown);
|
|
369
|
+
process.on("SIGTERM", shutdown);
|
|
370
|
+
|
|
371
|
+
// Keep alive forever
|
|
372
|
+
await new Promise(() => {});
|
|
373
|
+
}
|
|
374
|
+
|
|
181
375
|
// --- Structured adapters ---
|
|
182
376
|
// Commands that match an adapter key get spawned with structured JSON I/O
|
|
183
377
|
// instead of PTY wrapping. Future adapters (codex, gemini) go here.
|
|
@@ -220,6 +414,7 @@ while (i < args.length) {
|
|
|
220
414
|
console.error("Usage: orch [-l label] [-y] <command> [args...]");
|
|
221
415
|
console.error(" orch login — Authenticate with orchestrat.ing");
|
|
222
416
|
console.error(" orch logout — Clear stored credentials");
|
|
417
|
+
console.error(" orch daemon — Run background daemon for remote session launching");
|
|
223
418
|
console.error("");
|
|
224
419
|
console.error(" -l <label> Optional human-readable session label");
|
|
225
420
|
console.error(" -y, --yolo Skip all permission prompts (auto-approve everything)");
|
|
@@ -229,6 +424,7 @@ while (i < args.length) {
|
|
|
229
424
|
console.error(' orch -y claude "build a website"');
|
|
230
425
|
console.error(' orch -l "deploy fix" codex');
|
|
231
426
|
console.error(" orch bash");
|
|
427
|
+
console.error(" orch daemon");
|
|
232
428
|
console.error("");
|
|
233
429
|
console.error("Environment:");
|
|
234
430
|
console.error(" ORC_URL WebSocket server URL (default: wss://api.orchestrat.ing/ws)");
|
|
@@ -268,13 +464,15 @@ if (authToken && isTokenExpired()) {
|
|
|
268
464
|
authToken = refreshed;
|
|
269
465
|
process.stderr.write(`${DIM}[orch] Token refreshed${RESET}\n`);
|
|
270
466
|
} else {
|
|
271
|
-
process.stderr.write(
|
|
467
|
+
process.stderr.write(`${RED}Session expired. Run 'orch login' to re-authenticate.${RESET}\n`);
|
|
468
|
+
process.exit(1);
|
|
272
469
|
}
|
|
273
470
|
}
|
|
274
471
|
|
|
275
472
|
// Warn if no auth and connecting to remote server
|
|
276
473
|
if (!authToken && !serverUrl.includes("localhost") && !serverUrl.includes("127.0.0.1")) {
|
|
277
|
-
console.error(
|
|
474
|
+
console.error(`${RED}No credentials found. Run 'orch login' to authenticate.${RESET}`);
|
|
475
|
+
process.exit(1);
|
|
278
476
|
}
|
|
279
477
|
|
|
280
478
|
// --- WebSocket connection with reconnect ---
|
|
@@ -988,8 +1186,9 @@ async function connectWs() {
|
|
|
988
1186
|
authFailed = false;
|
|
989
1187
|
connectWs();
|
|
990
1188
|
} else {
|
|
991
|
-
process.stderr.write(`${RED}Run 'orch login' to authenticate.${RESET}\n`);
|
|
1189
|
+
process.stderr.write(`${RED}Session expired. Run 'orch login' to re-authenticate.${RESET}\n`);
|
|
992
1190
|
authFailed = true;
|
|
1191
|
+
process.exit(1);
|
|
993
1192
|
}
|
|
994
1193
|
});
|
|
995
1194
|
}
|