camstack 1.2.212 → 1.2.214
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/CHANGELOG.md +14 -0
- package/README.md +6 -0
- package/dist/chunk-37A556YF.js +822 -0
- package/dist/{chunk-MLWXP2SI.js → chunk-4Y3CBGOY.js} +972 -255
- package/dist/chunk-BYNP7S4W.js +56 -0
- package/dist/chunk-JPGJK5DC.js +75 -0
- package/dist/cli.js +127 -70
- package/dist/doctor-command-S7Q2U6HO.js +227 -0
- package/dist/{launcher-EACOVUIT.js → launcher-JAOMC5YY.js} +4417 -1279
- package/dist/{server-2GOCBDEY.js → server-TZCZTLBW.js} +1076 -1372
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/http/hub-http.ts
|
|
4
|
+
import * as http from "http";
|
|
5
|
+
import * as https from "https";
|
|
6
|
+
import { Buffer } from "buffer";
|
|
7
|
+
import { createHubAgent, explainTlsFailure, resolveHubTls } from "@camstack/develop";
|
|
8
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
9
|
+
function hubRequest(url, opts = {}) {
|
|
10
|
+
const parsed = new URL(url);
|
|
11
|
+
const isHttps = parsed.protocol === "https:";
|
|
12
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const requestOptions = {
|
|
15
|
+
protocol: parsed.protocol,
|
|
16
|
+
hostname: parsed.hostname,
|
|
17
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
18
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
19
|
+
method: opts.method ?? "GET",
|
|
20
|
+
headers: { ...opts.headers },
|
|
21
|
+
timeout: timeoutMs,
|
|
22
|
+
...isHttps ? { agent: createHubAgent(resolveHubTls(parsed.origin, opts.env, opts.sessionDir)) } : {}
|
|
23
|
+
};
|
|
24
|
+
const onResponse = (res) => {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
27
|
+
res.on("end", () => {
|
|
28
|
+
const buffer = Buffer.concat(chunks);
|
|
29
|
+
const status = res.statusCode ?? 0;
|
|
30
|
+
resolve({
|
|
31
|
+
ok: status >= 200 && status < 300,
|
|
32
|
+
status,
|
|
33
|
+
statusText: res.statusMessage ?? "",
|
|
34
|
+
headers: res.headers,
|
|
35
|
+
text: () => Promise.resolve(buffer.toString("utf8")),
|
|
36
|
+
json: () => Promise.resolve().then(() => JSON.parse(buffer.toString("utf8")))
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
res.on("error", reject);
|
|
40
|
+
};
|
|
41
|
+
const req = isHttps ? https.request(requestOptions, onResponse) : http.request(requestOptions, onResponse);
|
|
42
|
+
req.on("error", (err) => {
|
|
43
|
+
const explained = isHttps ? explainTlsFailure(`${parsed.protocol}//${parsed.host}`, err) : null;
|
|
44
|
+
reject(explained === null ? err : new Error(explained, { cause: err }));
|
|
45
|
+
});
|
|
46
|
+
req.on("timeout", () => {
|
|
47
|
+
req.destroy(new Error(`Hub request to ${parsed.host} timed out after ${timeoutMs}ms`));
|
|
48
|
+
});
|
|
49
|
+
if (opts.body !== void 0) req.write(opts.body);
|
|
50
|
+
req.end();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
hubRequest
|
|
56
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/discovery/hub-finder.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import {
|
|
7
|
+
defaultSessionDir,
|
|
8
|
+
isSessionFile,
|
|
9
|
+
resolveHubUrl,
|
|
10
|
+
validateHubAddress
|
|
11
|
+
} from "@camstack/develop";
|
|
12
|
+
import {
|
|
13
|
+
discoverHubs
|
|
14
|
+
} from "@camstack/develop/hub-discovery";
|
|
15
|
+
function validateManualHubUrl(value) {
|
|
16
|
+
return validateHubAddress(value);
|
|
17
|
+
}
|
|
18
|
+
function knownHubCandidates(deps) {
|
|
19
|
+
const dir = deps.sessionDir ?? defaultSessionDir();
|
|
20
|
+
const env = deps.env ?? process.env;
|
|
21
|
+
const out = [];
|
|
22
|
+
let files = [];
|
|
23
|
+
try {
|
|
24
|
+
files = fs.readdirSync(dir);
|
|
25
|
+
} catch {
|
|
26
|
+
files = [];
|
|
27
|
+
}
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
if (file.endsWith(".json")) {
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(dir, file), "utf8"));
|
|
32
|
+
if (isSessionFile(parsed)) {
|
|
33
|
+
out.push({
|
|
34
|
+
url: parsed.server,
|
|
35
|
+
source: `saved session ${parsed.username}`
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const fromEnv = resolveHubUrl({ env, session: null });
|
|
43
|
+
if (fromEnv) {
|
|
44
|
+
const name = (env["CAMSTACK_HUB_URL"] ?? "").trim() === "" ? "CAMSTACK_SERVER" : "CAMSTACK_HUB_URL";
|
|
45
|
+
out.push({ url: fromEnv.url, source: name });
|
|
46
|
+
}
|
|
47
|
+
if (deps.workspaceRoot !== void 0) {
|
|
48
|
+
const scratch = resolveHubUrl({
|
|
49
|
+
env: {},
|
|
50
|
+
workspaceRoot: deps.workspaceRoot,
|
|
51
|
+
session: null
|
|
52
|
+
});
|
|
53
|
+
if (scratch) out.push({ url: scratch.url, source: "scratchpad/.hub-url" });
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
async function findHubs(deps = {}) {
|
|
58
|
+
return discoverHubs({
|
|
59
|
+
known: knownHubCandidates(deps),
|
|
60
|
+
...deps.browse ? { browse: deps.browse } : {},
|
|
61
|
+
...deps.probe ? { probe: deps.probe } : {},
|
|
62
|
+
...deps.mdnsTimeoutMs !== void 0 ? { mdnsTimeoutMs: deps.mdnsTimeoutMs } : {},
|
|
63
|
+
...deps.onProblem ? { onProblem: deps.onProblem } : {}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function describeHub(hub) {
|
|
67
|
+
const identity = [hub.nodeId, hub.version === null ? null : `v${hub.version}`].filter((p) => p !== null).join(" ");
|
|
68
|
+
return `${hub.url}${identity === "" ? "" : ` ${identity}`} (${hub.sources.join(", ")})`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
validateManualHubUrl,
|
|
73
|
+
findHubs,
|
|
74
|
+
describeHub
|
|
75
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
hubRequest
|
|
4
|
+
} from "./chunk-BYNP7S4W.js";
|
|
5
|
+
import {
|
|
6
|
+
FFMPEG_PATH_ENV
|
|
7
|
+
} from "./chunk-4Y3CBGOY.js";
|
|
8
|
+
import {
|
|
4
9
|
describeHub,
|
|
5
10
|
findHubs,
|
|
6
11
|
validateManualHubUrl
|
|
7
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JPGJK5DC.js";
|
|
8
13
|
import "./chunk-LMMQX4CK.js";
|
|
9
14
|
|
|
10
15
|
// src/cli.ts
|
|
@@ -41,7 +46,7 @@ async function runServe(args) {
|
|
|
41
46
|
...typeof values.data === "string" ? { data: values.data } : {}
|
|
42
47
|
};
|
|
43
48
|
Object.assign(process.env, buildServeEnv(opts));
|
|
44
|
-
await import("./launcher-
|
|
49
|
+
await import("./launcher-JAOMC5YY.js");
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
// src/commands/agent.ts
|
|
@@ -86,7 +91,7 @@ async function runAgent(args) {
|
|
|
86
91
|
...typeof values.port === "string" ? { port: values.port } : {}
|
|
87
92
|
};
|
|
88
93
|
Object.assign(process.env, buildAgentEnv(opts));
|
|
89
|
-
await import("./launcher-
|
|
94
|
+
await import("./launcher-JAOMC5YY.js");
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
// src/commands/setup.ts
|
|
@@ -140,56 +145,6 @@ import { execSync } from "child_process";
|
|
|
140
145
|
import { randomBytes } from "crypto";
|
|
141
146
|
import * as fs2 from "fs";
|
|
142
147
|
import * as path2 from "path";
|
|
143
|
-
|
|
144
|
-
// src/http/hub-http.ts
|
|
145
|
-
import * as http from "http";
|
|
146
|
-
import * as https from "https";
|
|
147
|
-
import { Buffer as Buffer2 } from "buffer";
|
|
148
|
-
var insecureHubAgent = new https.Agent({ rejectUnauthorized: false, keepAlive: false });
|
|
149
|
-
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
150
|
-
function hubRequest(url, opts = {}) {
|
|
151
|
-
const parsed = new URL(url);
|
|
152
|
-
const isHttps = parsed.protocol === "https:";
|
|
153
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
154
|
-
return new Promise((resolve6, reject) => {
|
|
155
|
-
const requestOptions = {
|
|
156
|
-
protocol: parsed.protocol,
|
|
157
|
-
hostname: parsed.hostname,
|
|
158
|
-
port: parsed.port || (isHttps ? 443 : 80),
|
|
159
|
-
path: `${parsed.pathname}${parsed.search}`,
|
|
160
|
-
method: opts.method ?? "GET",
|
|
161
|
-
headers: { ...opts.headers },
|
|
162
|
-
timeout: timeoutMs,
|
|
163
|
-
...isHttps ? { agent: insecureHubAgent } : {}
|
|
164
|
-
};
|
|
165
|
-
const onResponse = (res) => {
|
|
166
|
-
const chunks = [];
|
|
167
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
168
|
-
res.on("end", () => {
|
|
169
|
-
const buffer = Buffer2.concat(chunks);
|
|
170
|
-
const status = res.statusCode ?? 0;
|
|
171
|
-
resolve6({
|
|
172
|
-
ok: status >= 200 && status < 300,
|
|
173
|
-
status,
|
|
174
|
-
statusText: res.statusMessage ?? "",
|
|
175
|
-
headers: res.headers,
|
|
176
|
-
text: () => Promise.resolve(buffer.toString("utf8")),
|
|
177
|
-
json: () => Promise.resolve().then(() => JSON.parse(buffer.toString("utf8")))
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
res.on("error", reject);
|
|
181
|
-
};
|
|
182
|
-
const req = isHttps ? https.request(requestOptions, onResponse) : http.request(requestOptions, onResponse);
|
|
183
|
-
req.on("error", reject);
|
|
184
|
-
req.on("timeout", () => {
|
|
185
|
-
req.destroy(new Error(`Hub request to ${parsed.host} timed out after ${timeoutMs}ms`));
|
|
186
|
-
});
|
|
187
|
-
if (opts.body !== void 0) req.write(opts.body);
|
|
188
|
-
req.end();
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// src/commands/deploy.ts
|
|
193
148
|
import { withAuthHint } from "@camstack/develop";
|
|
194
149
|
function isRecord(value) {
|
|
195
150
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -1585,11 +1540,80 @@ async function whoamiCommand(opts) {
|
|
|
1585
1540
|
}
|
|
1586
1541
|
}
|
|
1587
1542
|
|
|
1543
|
+
// src/commands/login-token-file.ts
|
|
1544
|
+
import * as fs5 from "fs";
|
|
1545
|
+
import {
|
|
1546
|
+
jwtClaims as jwtClaims2,
|
|
1547
|
+
jwtExpiryMs,
|
|
1548
|
+
normalizeHubAddress as normalizeHubAddress3,
|
|
1549
|
+
readTokenFile,
|
|
1550
|
+
saveSession as saveSession2
|
|
1551
|
+
} from "@camstack/develop";
|
|
1552
|
+
var GROUP_OR_OTHER_BITS = 63;
|
|
1553
|
+
function assertPrivateTokenFile(filePath, platform2 = process.platform) {
|
|
1554
|
+
let stat;
|
|
1555
|
+
try {
|
|
1556
|
+
stat = fs5.statSync(filePath);
|
|
1557
|
+
} catch (err) {
|
|
1558
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1559
|
+
throw new Error(`Cannot read token file ${filePath}: ${reason}`, { cause: err });
|
|
1560
|
+
}
|
|
1561
|
+
if (!stat.isFile()) throw new Error(`Token file ${filePath} is not a regular file.`);
|
|
1562
|
+
if (platform2 !== "win32" && (stat.mode & GROUP_OR_OTHER_BITS) !== 0) {
|
|
1563
|
+
const octal = (stat.mode & 511).toString(8);
|
|
1564
|
+
throw new Error(
|
|
1565
|
+
`Refusing token file ${filePath}: its mode is ${octal} and group/others can read it. Run \`chmod 600 ${filePath}\` (and consider the token exposed if others had access).`
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
function usernameOf(body) {
|
|
1570
|
+
if (body === null || typeof body !== "object") return void 0;
|
|
1571
|
+
const name = Reflect.get(body, "username");
|
|
1572
|
+
return typeof name === "string" && name !== "" ? name : void 0;
|
|
1573
|
+
}
|
|
1574
|
+
async function loginWithTokenFile(opts) {
|
|
1575
|
+
assertPrivateTokenFile(opts.tokenFile, opts.platform);
|
|
1576
|
+
const file = readTokenFile(opts.tokenFile);
|
|
1577
|
+
const typed = opts.server ?? file.server;
|
|
1578
|
+
if (!typed) {
|
|
1579
|
+
throw new Error("login --token-file needs the hub: pass <host> or --server <host>.");
|
|
1580
|
+
}
|
|
1581
|
+
const server = normalizeHubAddress3(typed);
|
|
1582
|
+
const res = await hubRequest(`${server}/api/auth/whoami`, {
|
|
1583
|
+
headers: { Authorization: `Bearer ${file.token}` },
|
|
1584
|
+
timeoutMs: 1e4,
|
|
1585
|
+
...opts.sessionDir ? { sessionDir: opts.sessionDir } : {}
|
|
1586
|
+
});
|
|
1587
|
+
if (res.status === 401 || res.status === 403) {
|
|
1588
|
+
throw new Error(
|
|
1589
|
+
`The hub ${server} rejected the token in ${opts.tokenFile} (revoked, expired or lacking scope).`
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
if (!res.ok) throw new Error(`The hub ${server} answered ${res.status} ${res.statusText}.`);
|
|
1593
|
+
const body = await res.json().catch(() => null);
|
|
1594
|
+
const claims = jwtClaims2(file.token);
|
|
1595
|
+
const isAdmin = claims?.["isAdmin"] === true;
|
|
1596
|
+
const username = usernameOf(body) ?? "token";
|
|
1597
|
+
const expiresAt = jwtExpiryMs(file.token);
|
|
1598
|
+
const session = {
|
|
1599
|
+
server,
|
|
1600
|
+
username,
|
|
1601
|
+
tokenId: "",
|
|
1602
|
+
token: file.token,
|
|
1603
|
+
scopes: [],
|
|
1604
|
+
createdAt: Date.now(),
|
|
1605
|
+
...isAdmin ? { isAdmin: true } : {},
|
|
1606
|
+
...expiresAt !== void 0 ? { expiresAt } : {}
|
|
1607
|
+
};
|
|
1608
|
+
const sessionFile = saveSession2(session, opts.sessionDir);
|
|
1609
|
+
return { server, username, isAdmin, sessionFile };
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1588
1612
|
// src/cli.ts
|
|
1589
1613
|
import {
|
|
1590
1614
|
loadSession as loadSession4,
|
|
1591
1615
|
MissingCredentialsError,
|
|
1592
|
-
normalizeHubAddress as
|
|
1616
|
+
normalizeHubAddress as normalizeHubAddress4,
|
|
1593
1617
|
touchSession as touchSession3,
|
|
1594
1618
|
resolveFreshCredentials as resolveFreshCredentials3
|
|
1595
1619
|
} from "@camstack/develop";
|
|
@@ -1648,7 +1672,7 @@ import { spawn } from "child_process";
|
|
|
1648
1672
|
import * as clack2 from "@clack/prompts";
|
|
1649
1673
|
|
|
1650
1674
|
// src/update-notifier.ts
|
|
1651
|
-
import * as
|
|
1675
|
+
import * as fs6 from "fs";
|
|
1652
1676
|
import * as path5 from "path";
|
|
1653
1677
|
import * as os3 from "os";
|
|
1654
1678
|
var CACHE_DIR = path5.join(os3.homedir(), ".camstack");
|
|
@@ -1658,8 +1682,8 @@ var REGISTRY_BASE = "https://registry.npmjs.org";
|
|
|
1658
1682
|
var FETCH_TIMEOUT_MS = 2500;
|
|
1659
1683
|
function readCache() {
|
|
1660
1684
|
try {
|
|
1661
|
-
if (!
|
|
1662
|
-
const parsed = JSON.parse(
|
|
1685
|
+
if (!fs6.existsSync(CACHE_FILE)) return null;
|
|
1686
|
+
const parsed = JSON.parse(fs6.readFileSync(CACHE_FILE, "utf8"));
|
|
1663
1687
|
if (!parsed || typeof parsed !== "object") return null;
|
|
1664
1688
|
const c = parsed;
|
|
1665
1689
|
if (typeof c.lastCheckAt !== "number") return null;
|
|
@@ -1674,8 +1698,8 @@ function readCache() {
|
|
|
1674
1698
|
}
|
|
1675
1699
|
function writeCache(c) {
|
|
1676
1700
|
try {
|
|
1677
|
-
if (!
|
|
1678
|
-
|
|
1701
|
+
if (!fs6.existsSync(CACHE_DIR)) fs6.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
|
|
1702
|
+
fs6.writeFileSync(CACHE_FILE, JSON.stringify(c, null, 2));
|
|
1679
1703
|
} catch {
|
|
1680
1704
|
}
|
|
1681
1705
|
}
|
|
@@ -1834,7 +1858,7 @@ async function runUpdate(args) {
|
|
|
1834
1858
|
}
|
|
1835
1859
|
|
|
1836
1860
|
// src/commands/dev.ts
|
|
1837
|
-
import * as
|
|
1861
|
+
import * as fs7 from "fs";
|
|
1838
1862
|
import * as path6 from "path";
|
|
1839
1863
|
import { parseArgs as parseArgs6 } from "util";
|
|
1840
1864
|
import * as clack3 from "@clack/prompts";
|
|
@@ -1859,14 +1883,14 @@ function resolveAddonDir(arg) {
|
|
|
1859
1883
|
throw new Error("`dev` requires a source directory, not a tarball (nothing to watch).");
|
|
1860
1884
|
}
|
|
1861
1885
|
const absolute = path6.resolve(arg);
|
|
1862
|
-
if (
|
|
1886
|
+
if (fs7.existsSync(absolute) && fs7.statSync(absolute).isDirectory()) {
|
|
1863
1887
|
return absolute;
|
|
1864
1888
|
}
|
|
1865
1889
|
for (const candidate of [
|
|
1866
1890
|
path6.resolve("packages", `addon-${arg}`),
|
|
1867
1891
|
path6.resolve("packages", arg)
|
|
1868
1892
|
]) {
|
|
1869
|
-
if (
|
|
1893
|
+
if (fs7.existsSync(candidate) && fs7.statSync(candidate).isDirectory()) {
|
|
1870
1894
|
return candidate;
|
|
1871
1895
|
}
|
|
1872
1896
|
}
|
|
@@ -1876,10 +1900,10 @@ function resolveAddonDir(arg) {
|
|
|
1876
1900
|
}
|
|
1877
1901
|
function watchRecursive(target, onEvent) {
|
|
1878
1902
|
try {
|
|
1879
|
-
const w =
|
|
1903
|
+
const w = fs7.watch(target, { recursive: true }, onEvent);
|
|
1880
1904
|
return { close: () => w.close(), recursive: true };
|
|
1881
1905
|
} catch {
|
|
1882
|
-
const w =
|
|
1906
|
+
const w = fs7.watch(target, onEvent);
|
|
1883
1907
|
return { close: () => w.close(), recursive: false };
|
|
1884
1908
|
}
|
|
1885
1909
|
}
|
|
@@ -1948,8 +1972,8 @@ async function runDev(args) {
|
|
|
1948
1972
|
token: (await resolveFreshCredentials(credentialInputs)).token
|
|
1949
1973
|
});
|
|
1950
1974
|
const watchOverride = typeof values["watch-path"] === "string" ? values["watch-path"] : void 0;
|
|
1951
|
-
const watchTarget = watchOverride ? path6.resolve(addonDir, watchOverride) :
|
|
1952
|
-
if (!
|
|
1975
|
+
const watchTarget = watchOverride ? path6.resolve(addonDir, watchOverride) : fs7.existsSync(path6.join(addonDir, DEFAULT_WATCH_SUBDIR)) ? path6.join(addonDir, DEFAULT_WATCH_SUBDIR) : addonDir;
|
|
1976
|
+
if (!fs7.existsSync(watchTarget)) {
|
|
1953
1977
|
throw new Error(`Watch path does not exist: ${watchTarget}`);
|
|
1954
1978
|
}
|
|
1955
1979
|
const debounceMs = typeof values.debounce === "string" ? parseInt(values.debounce, 10) : DEFAULT_DEBOUNCE_MS;
|
|
@@ -2528,6 +2552,7 @@ function parseToolsets(raw) {
|
|
|
2528
2552
|
function mcpHelp() {
|
|
2529
2553
|
return [
|
|
2530
2554
|
"Usage: camstack mcp [options]",
|
|
2555
|
+
" camstack mcp doctor",
|
|
2531
2556
|
"",
|
|
2532
2557
|
"Runs the CamStack MCP server on stdio. Register it with your assistant:",
|
|
2533
2558
|
" claude mcp add camstack -- camstack mcp",
|
|
@@ -2538,12 +2563,23 @@ function mcpHelp() {
|
|
|
2538
2563
|
" -s, --server <host> Hub to use instead of the cached login session",
|
|
2539
2564
|
" --token-file <p> Read an explicit token from a file",
|
|
2540
2565
|
"",
|
|
2566
|
+
"Diagnose: `camstack mcp doctor` checks, read-only, Node >= 24, the hub, its pinned CA,",
|
|
2567
|
+
"the session or token file, the token scope (admin or not) and the CLI version, each",
|
|
2568
|
+
"ok / fail / unknown with a fix hint. It never prints a token.",
|
|
2569
|
+
"No TTY (CI, a service): `camstack login <host> --token-file <path>` with a chmod 600 file.",
|
|
2570
|
+
"MCP clients such as OpenCode keep the old process: restart them after `camstack mcp` updates.",
|
|
2571
|
+
"",
|
|
2541
2572
|
"Auth is the CLI's own: run `camstack login <host>` once in a terminal.",
|
|
2542
2573
|
"The MCP never asks for or accepts a password. Every write is two-step:",
|
|
2543
2574
|
"a preview with a one-shot token, then the same call with that token."
|
|
2544
2575
|
].join("\n");
|
|
2545
2576
|
}
|
|
2546
2577
|
async function runMcp(args, cliVersion) {
|
|
2578
|
+
if (args[0] === "doctor") {
|
|
2579
|
+
const { runDoctorCommand } = await import("./doctor-command-S7Q2U6HO.js");
|
|
2580
|
+
process.exitCode = await runDoctorCommand(args.slice(1), cliVersion);
|
|
2581
|
+
return;
|
|
2582
|
+
}
|
|
2547
2583
|
const parsed = parseArgs9({
|
|
2548
2584
|
args: [...args],
|
|
2549
2585
|
options: {
|
|
@@ -2561,7 +2597,7 @@ async function runMcp(args, cliVersion) {
|
|
|
2561
2597
|
process.exitCode = 1;
|
|
2562
2598
|
return;
|
|
2563
2599
|
}
|
|
2564
|
-
const { runPublicMcp } = await import("./server-
|
|
2600
|
+
const { runPublicMcp } = await import("./server-TZCZTLBW.js");
|
|
2565
2601
|
await runPublicMcp({
|
|
2566
2602
|
cliVersion,
|
|
2567
2603
|
toolsets: selection.toolsets,
|
|
@@ -2660,6 +2696,10 @@ function buildCommands() {
|
|
|
2660
2696
|
" https://hub.local, [fe80::1]:4443. Port defaults to 4443, scheme is",
|
|
2661
2697
|
" https (http:// is refused). Example: camstack login 192.0.2.20",
|
|
2662
2698
|
"",
|
|
2699
|
+
" --token-file <path> No TTY / automation: read a token from a file readable only by you",
|
|
2700
|
+
" (chmod 600; a looser file is refused), check it against the hub and",
|
|
2701
|
+
" cache the session. A token is never taken from argv or the environment.",
|
|
2702
|
+
"",
|
|
2663
2703
|
"Default flow (no flags): finds the hub from what the LAN announces over mDNS",
|
|
2664
2704
|
" plus the hubs this machine knows (saved sessions, $CAMSTACK_HUB_URL),",
|
|
2665
2705
|
" then prompts for user + password. Found hubs show a picker (the first, the default, is",
|
|
@@ -2902,6 +2942,7 @@ async function runLogin(args) {
|
|
|
2902
2942
|
username: { type: "string", short: "u" },
|
|
2903
2943
|
password: { type: "string", short: "p" },
|
|
2904
2944
|
"token-name": { type: "string" },
|
|
2945
|
+
"token-file": { type: "string" },
|
|
2905
2946
|
// Removed (D895), still parsed so an old script does not abort: each one
|
|
2906
2947
|
// present prints a one-line warning and the login goes on.
|
|
2907
2948
|
...LEGACY_LOGIN_FLAG_OPTIONS,
|
|
@@ -2926,6 +2967,22 @@ async function runLogin(args) {
|
|
|
2926
2967
|
console.warn("--namespace is ignored: hubs are found over mDNS, not the mesh channel.");
|
|
2927
2968
|
}
|
|
2928
2969
|
for (const warning of legacyLoginFlagWarnings(parsed.values)) console.warn(warning);
|
|
2970
|
+
const tokenFile = stringOpt(parsed.values, "token-file");
|
|
2971
|
+
if (tokenFile !== void 0) {
|
|
2972
|
+
try {
|
|
2973
|
+
const done = await loginWithTokenFile({
|
|
2974
|
+
tokenFile,
|
|
2975
|
+
...explicitServer ? { server: explicitServer } : {}
|
|
2976
|
+
});
|
|
2977
|
+
console.log(
|
|
2978
|
+
`[camstack] Logged in to ${done.server} as ${done.username} (${done.isAdmin ? "admin" : "not admin"}). Session cached at ${done.sessionFile}`
|
|
2979
|
+
);
|
|
2980
|
+
} catch (err) {
|
|
2981
|
+
console.error(`[camstack] ${err instanceof Error ? err.message : String(err)}`);
|
|
2982
|
+
process.exitCode = 1;
|
|
2983
|
+
}
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2929
2986
|
await loginCommand({
|
|
2930
2987
|
...explicitServer ? { server: explicitServer } : {},
|
|
2931
2988
|
...optionalString(parsed.values, "username"),
|
|
@@ -2945,7 +3002,7 @@ async function runLogout(args) {
|
|
|
2945
3002
|
console.log(commandHelp("logout"));
|
|
2946
3003
|
return;
|
|
2947
3004
|
}
|
|
2948
|
-
const server =
|
|
3005
|
+
const server = normalizeHubAddress4(
|
|
2949
3006
|
stringOpt(parsed.values, "server") ?? process.env.CAMSTACK_SERVER ?? DEFAULT_SERVER_URL
|
|
2950
3007
|
);
|
|
2951
3008
|
await logoutCommand({ server });
|
|
@@ -2963,7 +3020,7 @@ async function runWhoami(args) {
|
|
|
2963
3020
|
return;
|
|
2964
3021
|
}
|
|
2965
3022
|
const server = stringOpt(parsed.values, "server");
|
|
2966
|
-
await whoamiCommand(server ? { server:
|
|
3023
|
+
await whoamiCommand(server ? { server: normalizeHubAddress4(server) } : {});
|
|
2967
3024
|
}
|
|
2968
3025
|
async function resolveClosureDeployOpts(values) {
|
|
2969
3026
|
const nodeId = stringOpt(values, "node");
|