great-cto 2.28.0 → 2.30.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/dist/main.js +125 -14
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -20,9 +20,10 @@ import { installAllCompanions } from "./companion.js";
|
|
|
20
20
|
import { bootstrap } from "./bootstrap.js";
|
|
21
21
|
import { compileFlow } from "./flow.js";
|
|
22
22
|
import { shouldUseLlmFallback, suggestArchetypeFromLlm } from "./llm-fallback.js";
|
|
23
|
-
import { readFileSync, copyFileSync, chmodSync, existsSync as fsExistsSync } from "node:fs";
|
|
23
|
+
import { readFileSync, writeFileSync, copyFileSync, chmodSync, mkdirSync, readdirSync, unlinkSync, existsSync as fsExistsSync } from "node:fs";
|
|
24
24
|
import { dirname, join } from "node:path";
|
|
25
25
|
import { fileURLToPath } from "node:url";
|
|
26
|
+
import { homedir } from "node:os";
|
|
26
27
|
function getCliVersion() {
|
|
27
28
|
try {
|
|
28
29
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -309,23 +310,24 @@ async function runRegister(args) {
|
|
|
309
310
|
log(` → will appear in great-cto board project switcher`);
|
|
310
311
|
return 0;
|
|
311
312
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
313
|
+
// ── Board server lifecycle helpers ───────────────────────────────────────────
|
|
314
|
+
/** Absolute path to the board PID file (persists across CLI invocations). */
|
|
315
|
+
function boardPidFilePath() {
|
|
316
|
+
return join(homedir(), ".great_cto", "board.pid");
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Locate the board server.mjs — checks dev layouts then plugin cache versions
|
|
320
|
+
* in descending order, returning the first path that exists.
|
|
321
|
+
*/
|
|
322
|
+
function findBoardServerPath() {
|
|
320
323
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
321
324
|
const candidates = [
|
|
322
|
-
join(here, "..", "..", "board", "server.mjs"), //
|
|
325
|
+
join(here, "..", "..", "board", "server.mjs"), // packages/cli/dist (dev)
|
|
323
326
|
join(here, "..", "board", "server.mjs"), // alt dev layout
|
|
324
327
|
join(here, "board", "server.mjs"), // flat layout
|
|
325
328
|
];
|
|
326
|
-
// Also search plugin cache (installed via npx great-cto)
|
|
327
329
|
const pluginBase = join(homedir(), ".claude", "plugins", "cache", "local", "great_cto");
|
|
328
|
-
if (
|
|
330
|
+
if (fsExistsSync(pluginBase)) {
|
|
329
331
|
try {
|
|
330
332
|
const versions = readdirSync(pluginBase).filter(v => /^\d/.test(v)).sort().reverse();
|
|
331
333
|
for (const v of versions.slice(0, 5)) {
|
|
@@ -334,7 +336,99 @@ async function runBoard(args) {
|
|
|
334
336
|
}
|
|
335
337
|
catch { /* ignore */ }
|
|
336
338
|
}
|
|
337
|
-
|
|
339
|
+
return candidates.find(fsExistsSync);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Kill the board server recorded in the PID file.
|
|
343
|
+
* Returns true if a live process was terminated.
|
|
344
|
+
*/
|
|
345
|
+
async function killExistingBoard() {
|
|
346
|
+
const pidFile = boardPidFilePath();
|
|
347
|
+
if (!fsExistsSync(pidFile))
|
|
348
|
+
return false;
|
|
349
|
+
const raw = readFileSync(pidFile, "utf8").trim();
|
|
350
|
+
const pid = parseInt(raw, 10);
|
|
351
|
+
if (!pid || isNaN(pid)) {
|
|
352
|
+
try {
|
|
353
|
+
unlinkSync(pidFile);
|
|
354
|
+
}
|
|
355
|
+
catch { /* ignore */ }
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
process.kill(pid, "SIGTERM");
|
|
360
|
+
await new Promise(r => setTimeout(r, 400));
|
|
361
|
+
try {
|
|
362
|
+
process.kill(pid, "SIGKILL");
|
|
363
|
+
}
|
|
364
|
+
catch { /* already dead — fine */ }
|
|
365
|
+
try {
|
|
366
|
+
unlinkSync(pidFile);
|
|
367
|
+
}
|
|
368
|
+
catch { /* ignore */ }
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
// process was already gone
|
|
373
|
+
try {
|
|
374
|
+
unlinkSync(pidFile);
|
|
375
|
+
}
|
|
376
|
+
catch { /* ignore */ }
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* If the board server is running (PID file present + process alive), kill it
|
|
382
|
+
* and relaunch with the latest installed version in the background.
|
|
383
|
+
* Called by runInit() after a new plugin version is installed.
|
|
384
|
+
*/
|
|
385
|
+
async function restartBoardAfterUpgrade(port) {
|
|
386
|
+
const { spawn } = await import("node:child_process");
|
|
387
|
+
const pidFile = boardPidFilePath();
|
|
388
|
+
if (!fsExistsSync(pidFile))
|
|
389
|
+
return; // board wasn't running — nothing to do
|
|
390
|
+
const raw = readFileSync(pidFile, "utf8").trim();
|
|
391
|
+
const oldPid = parseInt(raw, 10);
|
|
392
|
+
if (!oldPid || isNaN(oldPid))
|
|
393
|
+
return;
|
|
394
|
+
// Check if the process is actually alive (signal 0 = existence check)
|
|
395
|
+
try {
|
|
396
|
+
process.kill(oldPid, 0);
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
log(` ${dim(`↺ board server (pid ${oldPid}) running with old version — restarting…`)}`);
|
|
402
|
+
await killExistingBoard();
|
|
403
|
+
const serverPath = findBoardServerPath();
|
|
404
|
+
if (!serverPath) {
|
|
405
|
+
warn("board server not found after upgrade — start it manually with: great-cto board");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
// Launch detached so it outlives this init process; --no-open because the
|
|
409
|
+
// browser tab is already open pointing at the same port.
|
|
410
|
+
const child = spawn(process.execPath, [serverPath, "--no-open"], {
|
|
411
|
+
env: { ...process.env, BOARD_PORT: String(port) },
|
|
412
|
+
stdio: "ignore",
|
|
413
|
+
detached: true,
|
|
414
|
+
});
|
|
415
|
+
child.unref();
|
|
416
|
+
try {
|
|
417
|
+
mkdirSync(join(homedir(), ".great_cto"), { recursive: true });
|
|
418
|
+
writeFileSync(pidFile, String(child.pid));
|
|
419
|
+
}
|
|
420
|
+
catch { /* ignore */ }
|
|
421
|
+
log(` ${green("✓")} board restarted → http://localhost:${port}`);
|
|
422
|
+
}
|
|
423
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
424
|
+
async function runBoard(args) {
|
|
425
|
+
const { spawn } = await import("node:child_process");
|
|
426
|
+
// Stop any existing board server (e.g. old version left over after upgrade)
|
|
427
|
+
const killed = await killExistingBoard();
|
|
428
|
+
if (killed) {
|
|
429
|
+
log(` ${dim("stopped previous board server")}`);
|
|
430
|
+
}
|
|
431
|
+
const serverPath = findBoardServerPath();
|
|
338
432
|
if (!serverPath) {
|
|
339
433
|
error("Board server not found. Try reinstalling: npx great-cto@latest");
|
|
340
434
|
return 1;
|
|
@@ -347,7 +441,19 @@ async function runBoard(args) {
|
|
|
347
441
|
stdio: "inherit",
|
|
348
442
|
detached: false,
|
|
349
443
|
});
|
|
350
|
-
|
|
444
|
+
// Write PID so future invocations (including init upgrades) can find us
|
|
445
|
+
try {
|
|
446
|
+
mkdirSync(join(homedir(), ".great_cto"), { recursive: true });
|
|
447
|
+
writeFileSync(boardPidFilePath(), String(child.pid));
|
|
448
|
+
}
|
|
449
|
+
catch { /* best-effort */ }
|
|
450
|
+
child.on("exit", code => {
|
|
451
|
+
try {
|
|
452
|
+
unlinkSync(boardPidFilePath());
|
|
453
|
+
}
|
|
454
|
+
catch { /* ignore */ }
|
|
455
|
+
process.exit(code ?? 0);
|
|
456
|
+
});
|
|
351
457
|
return 0;
|
|
352
458
|
}
|
|
353
459
|
function printHelp() {
|
|
@@ -624,6 +730,11 @@ async function runInit(args) {
|
|
|
624
730
|
log(` ${dim("version")} ${installResult.version} ${dim("already installed at")} ${installResult.pluginDir}`);
|
|
625
731
|
log(` ${dim("(use --force to reinstall)")}`);
|
|
626
732
|
}
|
|
733
|
+
else {
|
|
734
|
+
// New version installed — restart board server if it was running so it
|
|
735
|
+
// picks up the updated server.mjs immediately (no manual restart needed).
|
|
736
|
+
await restartBoardAfterUpgrade(args.boardPort);
|
|
737
|
+
}
|
|
627
738
|
// ── 4. enable in settings ────────────────────────────────
|
|
628
739
|
step(4, 5, "enabling plugin in ~/.claude/settings.json");
|
|
629
740
|
const enableResult = enableGreatCto();
|
package/package.json
CHANGED