@join-pantheon/cli 0.33.0-rc.1 → 0.33.0-rc.2
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/cli.js +53 -43
- package/dist/client.js +16 -0
- package/dist/machine.js +136 -0
- package/dist/uninstall.js +1 -1
- package/dist/win-host.js +30 -6
- package/helper/macos/prebuilt/manifest.json +1 -1
- package/installer/install.ps1 +4 -1
- package/installer/install.sh +15 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -914,7 +914,8 @@ ${B("control")}
|
|
|
914
914
|
(auto-detects claude / codex / cursor / copilot / kiro on this machine)
|
|
915
915
|
pantheon spend woken runs on this machine, and what pantheon cannot see
|
|
916
916
|
pantheon leave remove what pantheon put on this machine
|
|
917
|
-
pantheon uninstall leave, then remove Pantheon itself: its own Node, the pantheon command, the PATH lines
|
|
917
|
+
pantheon uninstall leave, then remove Pantheon itself: its own Node, the pantheon command, the PATH lines,
|
|
918
|
+
and all it keeps in ~/.pantheon (files of yours there stay)
|
|
918
919
|
pantheon panic emergency stop: ends the run in flight and the worker, and switches off wake rights in every team you are in
|
|
919
920
|
pantheon doctor [--share] check this machine end to end; --share prints it without names or paths, to paste to us
|
|
920
921
|
pantheon network [--proxy <url> | --direct | --clear] how this machine reaches the relay; save a proxy for every Pantheon process here
|
|
@@ -1038,7 +1039,7 @@ async function teamStanding(all, m) {
|
|
|
1038
1039
|
return { others: null, founder: false };
|
|
1039
1040
|
}
|
|
1040
1041
|
finally {
|
|
1041
|
-
c.
|
|
1042
|
+
c.dispose();
|
|
1042
1043
|
}
|
|
1043
1044
|
}
|
|
1044
1045
|
function slipDistance(a, b) {
|
|
@@ -1080,7 +1081,7 @@ async function main() {
|
|
|
1080
1081
|
await installNetwork();
|
|
1081
1082
|
}
|
|
1082
1083
|
catch { }
|
|
1083
|
-
if (!QUIET.has(cmd)) {
|
|
1084
|
+
if (!QUIET.has(cmd) && cmd !== "uninstall") {
|
|
1084
1085
|
try {
|
|
1085
1086
|
console.error(D(updateBanner(VERSION)));
|
|
1086
1087
|
}
|
|
@@ -2507,7 +2508,8 @@ async function main() {
|
|
|
2507
2508
|
if (uninstalling && !argv.includes("--yes")) {
|
|
2508
2509
|
const teams = plan.teams.length && !keepIdentity ? `leaves ${plan.teams.map((t) => `"${t.name}"`).join(", ")} (this machine's key goes), ` : "";
|
|
2509
2510
|
const what = `this removes Pantheon from this machine: it ${teams}uninstalls its services, hooks and agent entries` +
|
|
2510
|
-
(install?.present ? `, and deletes Pantheon's own Node in ${install.runtime}, the pantheon command and the PATH lines the installer added
|
|
2511
|
+
(install?.present ? `, and deletes Pantheon's own Node in ${install.runtime}, the pantheon command and the PATH lines the installer added` : "") +
|
|
2512
|
+
`. Everything Pantheon keeps in ${CONFIG_DIR} goes${keepIdentity ? " (but your identity, --keep-identity)" : ""}; files of your own there stay.`;
|
|
2511
2513
|
if (!process.stdin.isTTY)
|
|
2512
2514
|
die(`${what}\n If you mean it: pantheon uninstall --yes`);
|
|
2513
2515
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -2557,52 +2559,56 @@ async function main() {
|
|
|
2557
2559
|
left.push(...u.left);
|
|
2558
2560
|
}
|
|
2559
2561
|
const { unlinkSync, existsSync: exists, rmSync, readdirSync } = await import("node:fs");
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
}
|
|
2564
|
-
|
|
2562
|
+
const spare = [];
|
|
2563
|
+
const hasConfig = !!all && exists(CONFIG_PATH);
|
|
2564
|
+
if (hasConfig && keepIdentity) {
|
|
2565
|
+
left.push(`your identity and ${plan.teams.length} team membership${plan.teams.length === 1 ? "" : "s"}, ${CONFIG_PATH} (--keep-identity)`);
|
|
2566
|
+
}
|
|
2567
|
+
else if (!keepIdentity && (hasConfig || uninstalling)) {
|
|
2568
|
+
if (hasConfig) {
|
|
2565
2569
|
unlinkSync(CONFIG_PATH);
|
|
2566
2570
|
removed.push(`${CONFIG_PATH} — your key and ${plan.teams.length ? `the token${plan.teams.length === 1 ? "" : "s"} for ${plan.teams.map((t) => `"${t.name}"`).join(", ")}` : "account"}; rejoining means a new invite`);
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2571
|
+
}
|
|
2572
|
+
const runHomes = join(CONFIG_PATH, "..", "run-homes");
|
|
2573
|
+
const { restorePendingQuarantines, pendingQuarantines } = await import("./agents.js");
|
|
2574
|
+
const put = restorePendingQuarantines(runHomes);
|
|
2575
|
+
if (put.length)
|
|
2576
|
+
removed.push(`nothing of your repos' — what interrupted runs had held aside: ${put.join("; ")}`);
|
|
2577
|
+
const still = pendingQuarantines(runHomes);
|
|
2578
|
+
const keptAside = (dir) => { try {
|
|
2579
|
+
return readdirSync(join(dir, "quarantine-kept")).length > 0;
|
|
2580
|
+
}
|
|
2581
|
+
catch {
|
|
2582
|
+
return false;
|
|
2583
|
+
} };
|
|
2584
|
+
let homes = [];
|
|
2585
|
+
try {
|
|
2586
|
+
homes = readdirSync(runHomes);
|
|
2587
|
+
}
|
|
2588
|
+
catch { }
|
|
2589
|
+
const keep = new Set([...still.map((x) => x.runHome), ...homes.map((h) => join(runHomes, h)).filter(keptAside)]);
|
|
2590
|
+
for (const h of homes) {
|
|
2591
|
+
const dir = join(runHomes, h);
|
|
2592
|
+
if (keep.has(dir))
|
|
2593
|
+
continue;
|
|
2580
2594
|
try {
|
|
2581
|
-
|
|
2595
|
+
rmSync(dir, { recursive: true, force: true });
|
|
2582
2596
|
}
|
|
2583
2597
|
catch { }
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
continue;
|
|
2589
|
-
try {
|
|
2590
|
-
rmSync(dir, { recursive: true, force: true });
|
|
2591
|
-
}
|
|
2592
|
-
catch { }
|
|
2593
|
-
}
|
|
2594
|
-
if (!keep.size) {
|
|
2595
|
-
try {
|
|
2596
|
-
rmSync(runHomes, { recursive: true, force: true });
|
|
2597
|
-
}
|
|
2598
|
-
catch { }
|
|
2598
|
+
}
|
|
2599
|
+
if (!keep.size) {
|
|
2600
|
+
try {
|
|
2601
|
+
rmSync(runHomes, { recursive: true, force: true });
|
|
2599
2602
|
}
|
|
2600
|
-
|
|
2601
|
-
left.push(`${x.runHome} — it still holds ${x.paths.join(", ")} from ${x.worktree}, which could not be put back safely; compare and move them back by hand, then delete the folder`);
|
|
2602
|
-
for (const dir of keep)
|
|
2603
|
-
if (keptAside(dir) && !still.some((x) => x.runHome === dir))
|
|
2604
|
-
left.push(`${join(dir, "quarantine-kept")} — agent config an interrupted run held, set aside because the repo had moved on; check it, then delete the folder`);
|
|
2603
|
+
catch { }
|
|
2605
2604
|
}
|
|
2605
|
+
else
|
|
2606
|
+
spare.push(runHomes);
|
|
2607
|
+
for (const x of still)
|
|
2608
|
+
left.push(`${x.runHome} — it still holds ${x.paths.join(", ")} from ${x.worktree}, which could not be put back safely; compare and move them back by hand, then delete the folder`);
|
|
2609
|
+
for (const dir of keep)
|
|
2610
|
+
if (keptAside(dir) && !still.some((x) => x.runHome === dir))
|
|
2611
|
+
left.push(`${join(dir, "quarantine-kept")} — agent config an interrupted run held, set aside because the repo had moved on; check it, then delete the folder`);
|
|
2606
2612
|
}
|
|
2607
2613
|
{
|
|
2608
2614
|
const { removePantheonWorktrees, removeHomeLeftovers } = await import("./machine.js");
|
|
@@ -2626,6 +2632,10 @@ async function main() {
|
|
|
2626
2632
|
}
|
|
2627
2633
|
if (!runningManaged)
|
|
2628
2634
|
left.push(`this pantheon itself (${process.argv[1] ?? "the one you ran"}), which npm installed — remove it with: ${NPM_UNINSTALL}`);
|
|
2635
|
+
const { sweepPantheonHome } = await import("./machine.js");
|
|
2636
|
+
const swept = sweepPantheonHome(CONFIG_DIR, { keepIdentity, spare });
|
|
2637
|
+
removed.push(...swept.removed);
|
|
2638
|
+
left.push(...swept.left);
|
|
2629
2639
|
}
|
|
2630
2640
|
console.error(`\n${B("Removed")}`);
|
|
2631
2641
|
for (const r of removed)
|
package/dist/client.js
CHANGED
|
@@ -21,6 +21,7 @@ export class PantheonClient {
|
|
|
21
21
|
seen = new Map();
|
|
22
22
|
backoff = 500;
|
|
23
23
|
closed = false;
|
|
24
|
+
disposed = false;
|
|
24
25
|
everConnected = false;
|
|
25
26
|
localOverride = null;
|
|
26
27
|
usingLoopback = false;
|
|
@@ -170,6 +171,11 @@ export class PantheonClient {
|
|
|
170
171
|
}
|
|
171
172
|
attempt(address) {
|
|
172
173
|
return new Promise((resolve, reject) => {
|
|
174
|
+
if (this.disposed) {
|
|
175
|
+
this.closed = true;
|
|
176
|
+
reject(new Error("this client was disposed"));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
173
179
|
let settled = false;
|
|
174
180
|
let heard = false;
|
|
175
181
|
const fail = reject;
|
|
@@ -195,6 +201,8 @@ export class PantheonClient {
|
|
|
195
201
|
this.watchdog.unref?.();
|
|
196
202
|
ws.on("open", () => { });
|
|
197
203
|
ws.on("message", (raw) => {
|
|
204
|
+
if (this.disposed)
|
|
205
|
+
return;
|
|
198
206
|
this.lastInbound = Date.now();
|
|
199
207
|
let msg;
|
|
200
208
|
try {
|
|
@@ -438,6 +446,14 @@ export class PantheonClient {
|
|
|
438
446
|
this.waiters = [];
|
|
439
447
|
this.ws?.close(4000, "handover");
|
|
440
448
|
}
|
|
449
|
+
dispose() {
|
|
450
|
+
this.disposed = true;
|
|
451
|
+
this.close();
|
|
452
|
+
if (this.watchdog) {
|
|
453
|
+
clearInterval(this.watchdog);
|
|
454
|
+
this.watchdog = null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
441
457
|
close() {
|
|
442
458
|
this.closed = true;
|
|
443
459
|
for (const w of this.waiters) {
|
package/dist/machine.js
CHANGED
|
@@ -705,6 +705,142 @@ export function removeHomeLeftovers(home, keep, opts = {}) {
|
|
|
705
705
|
}
|
|
706
706
|
return { removed, left };
|
|
707
707
|
}
|
|
708
|
+
export const PANTHEON_HOME_ENTRIES = [
|
|
709
|
+
{ name: "config.json", what: "your key and team tokens", identity: true },
|
|
710
|
+
{ name: "config.json.bak", what: "the previous config.json", identity: true },
|
|
711
|
+
{ name: "keys.json", what: "the teammates' keys this machine pinned", identity: true },
|
|
712
|
+
{ name: "nonces.json", what: "the signed requests this machine has seen", identity: true },
|
|
713
|
+
{ name: "continued.json", what: "what this machine already handled", identity: true },
|
|
714
|
+
{ name: "cursors.json", what: "where each agent session last read", identity: true },
|
|
715
|
+
{ name: "worker-cursor.json", what: "where the worker last read", identity: true },
|
|
716
|
+
{ name: "profile.json", what: "your profile", identity: true },
|
|
717
|
+
{ name: "worker-policy.json", what: "the worker's wake policy", identity: true },
|
|
718
|
+
{ name: "spend.json", what: "the spend ledger", identity: true },
|
|
719
|
+
{ name: "personas", what: "the worker's personas", dir: true, identity: true },
|
|
720
|
+
{ name: "worker-state.json", what: "the worker's last state" },
|
|
721
|
+
{ name: "worker-run.json", what: "the worker's record of a run in flight" },
|
|
722
|
+
{ name: "run-slot.json", what: "the worker's run slot" },
|
|
723
|
+
{ name: "worker.pid", what: "the worker's pid file" },
|
|
724
|
+
{ name: /^notifier-state(-.+)?\.json$/, sample: "notifier-state-team%2Fone.json", what: "the alerts' state" },
|
|
725
|
+
{ name: "update-check.json", what: "the update check's cache" },
|
|
726
|
+
{ name: "uri-key", what: "the key pantheon:// links are signed with" },
|
|
727
|
+
{ name: "board.json", what: "the board's state" },
|
|
728
|
+
{ name: "hook-intent.json", what: "the hook intent" },
|
|
729
|
+
{ name: "hook-guard.json", what: "the hook guard" },
|
|
730
|
+
{ name: /^(worker|relay|notifier|menubar)\.log(\.1)?$/, sample: "worker.log", what: "service logs an older version wrote here" },
|
|
731
|
+
{ name: /^\.write-check\..+$/, sample: ".write-check.1234.5678", what: "a leftover write probe" },
|
|
732
|
+
{ name: "bin", what: "the menu bar's and alerts' files", dir: true },
|
|
733
|
+
{ name: "logs", what: "the service logs", dir: true },
|
|
734
|
+
{ name: "host", what: "the Windows host's queue, state and markers", dir: true },
|
|
735
|
+
{ name: "mcp", what: "the agent windows' server state", dir: true },
|
|
736
|
+
{ name: "run-homes", what: "the woken runs' homes", dir: true },
|
|
737
|
+
{ name: "worktrees", what: "the worker's worktrees", dir: true },
|
|
738
|
+
{ name: "no-hooks", what: "the empty hooks folder woken runs' git uses", dir: true },
|
|
739
|
+
{ name: "lfs-hooks", what: "the LFS-only hooks folder woken runs' git uses", dir: true },
|
|
740
|
+
{ name: "runtime", what: "Pantheon's own Node", dir: true, kept: "managed" },
|
|
741
|
+
{ name: "data", what: "the relay's ledger", dir: true, kept: "it is your team's history and may be its only copy; delete it yourself once no relay runs from it" },
|
|
742
|
+
{ name: /^(\.DS_Store|Thumbs\.db|desktop\.ini)$/i, sample: ".DS_Store", what: "the file browser's folder cache" },
|
|
743
|
+
];
|
|
744
|
+
const LOCK_OR_TMP = /^(.+?)(\.lock|(\.[0-9a-f]+)+\.tmp)$/i;
|
|
745
|
+
export function homeEntryFor(name) {
|
|
746
|
+
const direct = (n) => PANTHEON_HOME_ENTRIES.find((e) => typeof e.name === "string" ? e.name === n : e.name.test(n)) ?? null;
|
|
747
|
+
const e = direct(name);
|
|
748
|
+
if (e)
|
|
749
|
+
return e;
|
|
750
|
+
const m = LOCK_OR_TMP.exec(name);
|
|
751
|
+
const base = m ? direct(m[1]) : null;
|
|
752
|
+
return base && !base.dir ? base : null;
|
|
753
|
+
}
|
|
754
|
+
const pause = (ms) => { try {
|
|
755
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
756
|
+
}
|
|
757
|
+
catch { } };
|
|
758
|
+
const realSweepFs = {
|
|
759
|
+
list: (d) => readdirSync(d),
|
|
760
|
+
exists: (p) => existsSync(p),
|
|
761
|
+
remove: (p) => rmSync(p, { recursive: true, force: true }),
|
|
762
|
+
removeDir: (d) => rmdirSync(d),
|
|
763
|
+
};
|
|
764
|
+
export function sweepPantheonHome(home, o = {}) {
|
|
765
|
+
const fs = o.fs ?? realSweepFs;
|
|
766
|
+
const tries = o.tries ?? 5, waitMs = o.waitMs ?? 200;
|
|
767
|
+
const os = o.os ?? platform();
|
|
768
|
+
const removed = [], left = [];
|
|
769
|
+
if (!fs.exists(home))
|
|
770
|
+
return { removed, left, gone: true };
|
|
771
|
+
const spare = new Set((o.spare ?? []).map((p) => resolve(p)));
|
|
772
|
+
const attempt = (p, rm) => {
|
|
773
|
+
let why = "";
|
|
774
|
+
for (let i = 0; i < tries; i++) {
|
|
775
|
+
try {
|
|
776
|
+
rm(p);
|
|
777
|
+
}
|
|
778
|
+
catch (e) {
|
|
779
|
+
why = String(e.code ?? e.message.split("\n")[0]);
|
|
780
|
+
}
|
|
781
|
+
if (!fs.exists(p))
|
|
782
|
+
return null;
|
|
783
|
+
if (i < tries - 1)
|
|
784
|
+
pause(waitMs * (i + 1));
|
|
785
|
+
}
|
|
786
|
+
return why || "still there";
|
|
787
|
+
};
|
|
788
|
+
const lockedHint = os === "win32" ? "; a Pantheon process (an agent window, the menu bar) may still have it open — close it, then delete it" : "; delete it yourself";
|
|
789
|
+
const gone = [], foreign = [], keptForIdentity = [];
|
|
790
|
+
let names = [];
|
|
791
|
+
try {
|
|
792
|
+
names = fs.list(home);
|
|
793
|
+
}
|
|
794
|
+
catch (e) {
|
|
795
|
+
return { removed, left: [`${home} — could not be read (${e.message.split("\n")[0]}), so nothing in it was removed`], gone: false };
|
|
796
|
+
}
|
|
797
|
+
for (const name of names.sort()) {
|
|
798
|
+
const p = join(home, name);
|
|
799
|
+
const e = homeEntryFor(name);
|
|
800
|
+
if (!e) {
|
|
801
|
+
foreign.push(name);
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
if (spare.has(resolve(p)))
|
|
805
|
+
continue;
|
|
806
|
+
if (e.kept === "managed")
|
|
807
|
+
continue;
|
|
808
|
+
if (e.kept) {
|
|
809
|
+
left.push(`${e.what}, ${p} — kept: ${e.kept}`);
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (o.keepIdentity && e.identity) {
|
|
813
|
+
keptForIdentity.push(name);
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const why = attempt(p, fs.remove);
|
|
817
|
+
if (why === null)
|
|
818
|
+
gone.push(name);
|
|
819
|
+
else
|
|
820
|
+
left.push(`${e.what}, ${p} — could not remove (${why})${lockedHint}`);
|
|
821
|
+
}
|
|
822
|
+
if (gone.length)
|
|
823
|
+
removed.push(`Pantheon's own files in ${home}: ${gone.join(", ")}`);
|
|
824
|
+
if (keptForIdentity.length)
|
|
825
|
+
left.push(`what goes with your identity in ${home}: ${keptForIdentity.join(", ")} (--keep-identity)`);
|
|
826
|
+
let rest = [];
|
|
827
|
+
try {
|
|
828
|
+
rest = fs.list(home);
|
|
829
|
+
}
|
|
830
|
+
catch { }
|
|
831
|
+
if (!rest.length) {
|
|
832
|
+
const why = attempt(home, fs.removeDir);
|
|
833
|
+
if (why === null) {
|
|
834
|
+
removed.push(`${home} itself, now empty`);
|
|
835
|
+
return { removed, left, gone: true };
|
|
836
|
+
}
|
|
837
|
+
left.push(`${home} — empty, but could not remove it (${why})${lockedHint}`);
|
|
838
|
+
return { removed, left, gone: false };
|
|
839
|
+
}
|
|
840
|
+
if (foreign.length)
|
|
841
|
+
left.push(`${home}, for what is not Pantheon's: ${foreign.join(", ")} — Pantheon never writes ${foreign.length === 1 ? "it" : "these"}, so ${foreign.length === 1 ? "it was" : "they were"} not touched`);
|
|
842
|
+
return { removed, left, gone: false };
|
|
843
|
+
}
|
|
708
844
|
export const accountStillExists = (email) => `your account${email ? ` ${email}` : ""} still exists — this machine is signed out. Sign back in with: pantheon login${email ? ` --email ${email}` : ""} (pantheon signup would fail: the email is taken). To delete the account itself: pantheon account delete, before leaving`;
|
|
709
845
|
const LOG_KINDS = ["worker", "relay", "notifier", "menubar"];
|
|
710
846
|
export function uninstallMachine(opts, io) {
|
package/dist/uninstall.js
CHANGED
|
@@ -5,7 +5,7 @@ import { posix, win32 } from "node:path";
|
|
|
5
5
|
import { expectedLauncher } from "./managed.js";
|
|
6
6
|
export const BEGIN_MARK = "# >>> pantheon >>>";
|
|
7
7
|
export const END_MARK = "# <<< pantheon <<<";
|
|
8
|
-
export const PROFILES = [".profile", ".bash_profile", ".bashrc", ".zshrc", ".zprofile"];
|
|
8
|
+
export const PROFILES = [".profile", ".bash_profile", ".bash_login", ".bashrc", ".zshrc", ".zprofile"];
|
|
9
9
|
export const FISH_CONF = ".config/fish/conf.d/pantheon.fish";
|
|
10
10
|
export function withoutPathBlock(text) {
|
|
11
11
|
const lines = text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
package/dist/win-host.js
CHANGED
|
@@ -633,10 +633,12 @@ export function realHostIo(o = {}) {
|
|
|
633
633
|
exit: (code) => process.exit(code),
|
|
634
634
|
};
|
|
635
635
|
}
|
|
636
|
-
export
|
|
636
|
+
export const HOST_LOCK_STARTUP_GRACE_MS = 2 * 60_000;
|
|
637
|
+
export function takeHostLock(home = CONFIG_DIR, pid = process.pid, now = Date.now, alive = pidAlive) {
|
|
637
638
|
const f = hostFiles(home);
|
|
638
639
|
mkdirSync(f.dir, { recursive: true });
|
|
639
|
-
|
|
640
|
+
let lastHolder = -1;
|
|
641
|
+
for (let i = 0; i < 4; i++) {
|
|
640
642
|
try {
|
|
641
643
|
const fd = openSync(f.lock, "wx");
|
|
642
644
|
writeSync(fd, String(pid));
|
|
@@ -646,13 +648,35 @@ export function takeHostLock(home = CONFIG_DIR, pid = process.pid) {
|
|
|
646
648
|
catch (e) {
|
|
647
649
|
if (e.code !== "EEXIST")
|
|
648
650
|
throw e;
|
|
649
|
-
|
|
650
|
-
|
|
651
|
+
}
|
|
652
|
+
const held = Number(readFileSafe(f.lock));
|
|
653
|
+
let age = 0;
|
|
654
|
+
try {
|
|
655
|
+
age = now() - statSync(f.lock).mtimeMs;
|
|
656
|
+
}
|
|
657
|
+
catch {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (Number.isInteger(held) && held > 0) {
|
|
661
|
+
lastHolder = held;
|
|
662
|
+
if (held === pid)
|
|
663
|
+
return null;
|
|
664
|
+
if (alive(held) && (hostAlive(readHostState(home)) || age < HOST_LOCK_STARTUP_GRACE_MS))
|
|
651
665
|
return held;
|
|
652
|
-
rmSync(f.lock, { force: true });
|
|
653
666
|
}
|
|
667
|
+
else if (age < 10_000) {
|
|
668
|
+
return lastHolder;
|
|
669
|
+
}
|
|
670
|
+
const gone = `${f.lock}.stale-${pid}-${i}`;
|
|
671
|
+
try {
|
|
672
|
+
renameSync(f.lock, gone);
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
rmSync(gone, { force: true });
|
|
654
678
|
}
|
|
655
|
-
return
|
|
679
|
+
return lastHolder;
|
|
656
680
|
}
|
|
657
681
|
const readFileSafe = (p) => { try {
|
|
658
682
|
return readFileSync(p, "utf8").trim();
|
package/installer/install.ps1
CHANGED
|
@@ -622,7 +622,10 @@ function Install-Pantheon {
|
|
|
622
622
|
# Start-Process -NoNewWindow, so pantheon gets this console itself: a real
|
|
623
623
|
# terminal for the sign-in questions, not a pipe PowerShell reads from.
|
|
624
624
|
$State.step = 'starting pantheon'
|
|
625
|
-
|
|
625
|
+
# With the same --use-system-ca as npm and pantheon.cmd: this first run is the
|
|
626
|
+
# one that signs in, and behind a TLS-inspecting proxy the relay's
|
|
627
|
+
# certificate chains only to the company root in the Windows store.
|
|
628
|
+
$line = ConvertTo-ArgLine (@($ca) + @($entry) + $Arguments)
|
|
626
629
|
$noConsole = $false
|
|
627
630
|
try { $noConsole = [Console]::IsInputRedirected } catch { $noConsole = $false }
|
|
628
631
|
if (-not $noConsole) {
|
package/installer/install.sh
CHANGED
|
@@ -508,6 +508,17 @@ add_block() {
|
|
|
508
508
|
note "the pantheon block in $f has a marker line without its pair, so $f was left as it is; put $BIN_DIR at the front of PATH there yourself"
|
|
509
509
|
return 0
|
|
510
510
|
fi
|
|
511
|
+
# One of each, but the end before the begin (edited by hand): the rewrite
|
|
512
|
+
# below would skip from the begin marker to the end of the file and drop
|
|
513
|
+
# everything after it, so the file is not touched either.
|
|
514
|
+
if [ "${begins:-0}" -eq 1 ]; then
|
|
515
|
+
bline=$(grep -nxF "$BEGIN_MARK" "$f" | head -n 1 | cut -d: -f1)
|
|
516
|
+
eline=$(grep -nxF "$END_MARK" "$f" | head -n 1 | cut -d: -f1)
|
|
517
|
+
if [ "${bline:-0}" -gt "${eline:-0}" ]; then
|
|
518
|
+
note "the pantheon block in $f has its end marker before its begin marker, so $f was left as it is; put $BIN_DIR at the front of PATH there yourself"
|
|
519
|
+
return 0
|
|
520
|
+
fi
|
|
521
|
+
fi
|
|
511
522
|
if [ "${begins:-0}" -eq 1 ]; then
|
|
512
523
|
awk -v b="$BEGIN_MARK" -v e="$END_MARK" '$0 == b { keep = 1 } keep { print } $0 == e { keep = 0 }' "$f" >"$TMP_DIR/old"
|
|
513
524
|
same "$TMP_DIR/old" "$TMP_DIR/block" && return 0
|
|
@@ -531,10 +542,13 @@ add_block() {
|
|
|
531
542
|
}
|
|
532
543
|
|
|
533
544
|
login_shell=$(basename "${SHELL:-sh}")
|
|
534
|
-
# ~/.profile: read by sh and dash logins, by bash when it has no .bash_profile,
|
|
545
|
+
# ~/.profile: read by sh and dash logins, by bash when it has no .bash_profile or .bash_login,
|
|
535
546
|
# and by the `sh -lc` Pantheon itself uses to find programs.
|
|
536
547
|
add_block "$HOME/.profile"
|
|
537
548
|
[ -f "$HOME/.bash_profile" ] && add_block "$HOME/.bash_profile"
|
|
549
|
+
# bash logins read the first of .bash_profile, .bash_login and .profile: with
|
|
550
|
+
# no .bash_profile, an existing .bash_login is the one read, not .profile.
|
|
551
|
+
[ -f "$HOME/.bash_login" ] && add_block "$HOME/.bash_login"
|
|
538
552
|
{ [ -f "$HOME/.bashrc" ] || [ "$login_shell" = bash ]; } && add_block "$HOME/.bashrc"
|
|
539
553
|
{ [ -f "$HOME/.zshrc" ] || [ "$login_shell" = zsh ]; } && add_block "$HOME/.zshrc"
|
|
540
554
|
[ -f "$HOME/.zprofile" ] && add_block "$HOME/.zprofile"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.33.0-rc.
|
|
3
|
+
"version": "0.33.0-rc.2",
|
|
4
4
|
"description": "Let your coding agent collaborate directly with your teammates' agents — ask, agree an interface, and deliver, without a human relaying messages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|