@join-pantheon/cli 0.32.1 → 0.33.0-rc.1
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/accounts.js +1 -1
- package/dist/board.js +1417 -0
- package/dist/claude-config.js +189 -0
- package/dist/cli.js +298 -58
- package/dist/command-state.js +2 -1
- package/dist/dbus.js +92 -13
- package/dist/doctor.js +36 -5
- package/dist/folder-guard.js +89 -0
- package/dist/git-safe.js +3 -3
- package/dist/host-env.js +47 -1
- package/dist/install.js +76 -10
- package/dist/installer.js +2 -1
- package/dist/ledger.js +2 -2
- package/dist/machine.js +38 -3
- package/dist/managed.js +27 -1
- package/dist/notice-linux.js +501 -0
- package/dist/notice-windows.js +323 -0
- package/dist/notifier.js +12 -3
- package/dist/quickstart.js +28 -11
- package/dist/relay.js +64 -9
- package/dist/service.js +455 -29
- package/dist/setup-guide.js +22 -5
- package/dist/store.js +18 -2
- package/dist/upgrade.js +249 -4
- package/dist/uri-handler.js +335 -0
- package/dist/win-host.js +969 -0
- package/dist/worker-policy.js +3 -0
- package/dist/worker.js +11 -11
- package/helper/macos/prebuilt/manifest.json +2 -2
- package/helper/macos/prebuilt/pantheon-hud +0 -0
- package/helper/windows/pantheon-host.cs +170 -0
- package/helper/windows/pantheon-notice.ps1 +127 -0
- package/helper/windows/pantheon-tray.ps1 +731 -0
- package/helper/windows/pantheon.ico +0 -0
- package/installer/install.ps1 +234 -18
- package/installer/install.sh +21 -0
- package/package.json +2 -2
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { chmodSync, closeSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, writeSync } from "node:fs";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { homedir, platform } from "node:os";
|
|
4
|
+
import { dirname, join, win32 } from "node:path";
|
|
5
|
+
import { eolOf, stripBom, withEol } from "./text-file.js";
|
|
6
|
+
export const claudeJsonFile = (home = homedir()) => join(home, ".claude.json");
|
|
7
|
+
export function claudeHomeConflict(env = process.env, os = platform(), home = homedir()) {
|
|
8
|
+
if (os !== "win32" || !env.HOME)
|
|
9
|
+
return null;
|
|
10
|
+
if (win32.resolve(env.HOME).toLowerCase() === win32.resolve(home).toLowerCase())
|
|
11
|
+
return null;
|
|
12
|
+
return `HOME (${env.HOME}) is not your Windows profile (${home}), whose ~/.claude.json Claude Code reads — it was left as it is`;
|
|
13
|
+
}
|
|
14
|
+
export const claudeUserEntryShape = (entry) => ({ type: "stdio", command: entry.command, args: [...entry.args], env: {} });
|
|
15
|
+
const STALE_MS = 10_000;
|
|
16
|
+
const WAIT_MS = 15_000;
|
|
17
|
+
const sleepSync = (ms) => { try {
|
|
18
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
19
|
+
}
|
|
20
|
+
catch { } };
|
|
21
|
+
export function withClaudeJsonLock(file, fn, waitMs = WAIT_MS) {
|
|
22
|
+
const lock = `${file}.lock`;
|
|
23
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
24
|
+
const deadline = Date.now() + waitMs;
|
|
25
|
+
for (let first = true;; first = false) {
|
|
26
|
+
if (!first) {
|
|
27
|
+
if (Date.now() > deadline)
|
|
28
|
+
throw new Error(`${file} is being saved by Claude Code right now (${lock}) — try again in a moment`);
|
|
29
|
+
sleepSync(10 + Math.floor(Math.random() * 20));
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
mkdirSync(lock);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
const code = e.code;
|
|
37
|
+
if (code !== "EEXIST" && !(process.platform === "win32" && (code === "EPERM" || code === "EACCES")))
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
let st;
|
|
41
|
+
try {
|
|
42
|
+
st = lstatSync(lock);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (Math.abs(Date.now() - st.mtimeMs) > STALE_MS) {
|
|
48
|
+
try {
|
|
49
|
+
if (st.isDirectory())
|
|
50
|
+
rmdirSync(lock);
|
|
51
|
+
else
|
|
52
|
+
rmSync(lock, { force: true });
|
|
53
|
+
}
|
|
54
|
+
catch { }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return fn();
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
try {
|
|
62
|
+
rmdirSync(lock);
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function readText(path) {
|
|
68
|
+
let st;
|
|
69
|
+
try {
|
|
70
|
+
st = statSync(path);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (!st.isFile())
|
|
76
|
+
throw new Error(`${path} is not a plain file`);
|
|
77
|
+
return readFileSync(path, "utf8");
|
|
78
|
+
}
|
|
79
|
+
function indentOf(text) {
|
|
80
|
+
const m = /^\{\r?\n([ \t]+)"/.exec(stripBom(text));
|
|
81
|
+
return m ? (m[1].includes("\t") ? "\t" : m[1].length) : 2;
|
|
82
|
+
}
|
|
83
|
+
export function updateClaudeJson(file, change) {
|
|
84
|
+
let target = file;
|
|
85
|
+
try {
|
|
86
|
+
if (lstatSync(file).isSymbolicLink())
|
|
87
|
+
target = realpathSync(file);
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
return withClaudeJsonLock(file, () => {
|
|
91
|
+
for (let attempt = 0;; attempt++) {
|
|
92
|
+
const was = readText(target);
|
|
93
|
+
let doc = {};
|
|
94
|
+
if (was !== null && stripBom(was).trim()) {
|
|
95
|
+
let parsed;
|
|
96
|
+
try {
|
|
97
|
+
parsed = JSON.parse(stripBom(was));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
throw new Error(`${file} is not valid JSON — Claude Code could not read it either; open Claude Code once (it repairs it), then run pantheon install --user`);
|
|
101
|
+
}
|
|
102
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
103
|
+
throw new Error(`${file} does not hold a JSON object — left as it is`);
|
|
104
|
+
doc = parsed;
|
|
105
|
+
}
|
|
106
|
+
const { said, write } = change(doc);
|
|
107
|
+
if (!write)
|
|
108
|
+
return said;
|
|
109
|
+
let next = JSON.stringify(doc, null, was === null ? 2 : indentOf(was)) + "\n";
|
|
110
|
+
if (was !== null)
|
|
111
|
+
next = (was.charCodeAt(0) === 0xfeff ? "" : "") + withEol(next, eolOf(was));
|
|
112
|
+
const tmp = `${target}.pantheon.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
113
|
+
let mode = 0o600;
|
|
114
|
+
try {
|
|
115
|
+
mode = statSync(target).mode & 0o777;
|
|
116
|
+
}
|
|
117
|
+
catch { }
|
|
118
|
+
try {
|
|
119
|
+
const fd = openSync(tmp, "wx", mode);
|
|
120
|
+
try {
|
|
121
|
+
writeSync(fd, next);
|
|
122
|
+
fsyncSync(fd);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
closeSync(fd);
|
|
126
|
+
}
|
|
127
|
+
if (process.platform !== "win32")
|
|
128
|
+
chmodSync(tmp, mode);
|
|
129
|
+
if (readText(target) !== was) {
|
|
130
|
+
rmSync(tmp, { force: true });
|
|
131
|
+
if (attempt < 5)
|
|
132
|
+
continue;
|
|
133
|
+
throw new Error(`${file} kept changing while it was being written — try again in a moment`);
|
|
134
|
+
}
|
|
135
|
+
renameWithRetry(tmp, target);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
try {
|
|
139
|
+
rmSync(tmp, { force: true });
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
throw e;
|
|
143
|
+
}
|
|
144
|
+
return said;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function renameWithRetry(from, to) {
|
|
149
|
+
for (let i = 0;; i++) {
|
|
150
|
+
try {
|
|
151
|
+
renameSync(from, to);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
if (i >= 20 || !["EPERM", "EACCES", "EBUSY"].includes(e.code ?? ""))
|
|
156
|
+
throw e;
|
|
157
|
+
sleepSync(10);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export function mergeClaudeUserEntry(file, entry, isOurs) {
|
|
162
|
+
return updateClaudeJson(file, (doc) => {
|
|
163
|
+
const had = doc["mcpServers"];
|
|
164
|
+
const servers = had && typeof had === "object" && !Array.isArray(had) ? had : {};
|
|
165
|
+
const want = claudeUserEntryShape(entry);
|
|
166
|
+
const cur = servers["pantheon"];
|
|
167
|
+
if (cur !== undefined && JSON.stringify(cur) === JSON.stringify(want))
|
|
168
|
+
return { said: "unchanged", write: false };
|
|
169
|
+
if (cur !== undefined && !isOurs(cur))
|
|
170
|
+
return { said: "not ours", write: false };
|
|
171
|
+
servers["pantheon"] = want;
|
|
172
|
+
doc["mcpServers"] = servers;
|
|
173
|
+
return { said: cur === undefined ? "created" : "updated", write: true };
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
export function removeClaudeUserEntry(file, isOurs) {
|
|
177
|
+
if (readText(file) === null)
|
|
178
|
+
return "absent";
|
|
179
|
+
return updateClaudeJson(file, (doc) => {
|
|
180
|
+
const servers = doc["mcpServers"];
|
|
181
|
+
if (!servers || typeof servers !== "object" || !("pantheon" in servers))
|
|
182
|
+
return { said: "absent", write: false };
|
|
183
|
+
if (!isOurs(servers["pantheon"]))
|
|
184
|
+
return { said: "not ours", write: false };
|
|
185
|
+
delete servers["pantheon"];
|
|
186
|
+
return { said: "removed", write: true };
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=claude-config.js.map
|