@lifeaitools/clauth 1.18.0 → 1.19.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/cli/commands/serve.js +110 -8
- package/package.json +1 -1
package/cli/commands/serve.js
CHANGED
|
@@ -3337,6 +3337,80 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3337
3337
|
});
|
|
3338
3338
|
}
|
|
3339
3339
|
|
|
3340
|
+
// Self-heal: mint a connector run-token from the Cloudflare API using the
|
|
3341
|
+
// vault's `cloudflare` token, so `cloudflared tunnel run --token` can start
|
|
3342
|
+
// the tunnel with ZERO local files (~/.cloudflared/config.yml + creds JSON).
|
|
3343
|
+
// This survives a home-dir wipe — clauth has the keys, so it rebuilds the
|
|
3344
|
+
// connector itself. Returns null on any failure so startTunnel() can fall
|
|
3345
|
+
// back to the legacy file-based `tunnel run`.
|
|
3346
|
+
async function getTunnelRunToken() {
|
|
3347
|
+
try {
|
|
3348
|
+
if (!password) return null;
|
|
3349
|
+
const sbUrl = (api.getBaseUrl() || "").replace("/functions/v1/auth-vault", "");
|
|
3350
|
+
const sbKey = api.getAnonKey();
|
|
3351
|
+
|
|
3352
|
+
// Resolve tunnelId: clauth_config.tunnel_id, else config.yml `tunnel:` line
|
|
3353
|
+
let tid = null;
|
|
3354
|
+
if (sbUrl && sbKey) {
|
|
3355
|
+
try {
|
|
3356
|
+
const r = await fetch(`${sbUrl}/rest/v1/clauth_config?key=eq.tunnel_id&select=value`,
|
|
3357
|
+
{ headers: { apikey: sbKey, Authorization: `Bearer ${sbKey}` }, signal: AbortSignal.timeout(5000) });
|
|
3358
|
+
if (r.ok) {
|
|
3359
|
+
const rows = await r.json();
|
|
3360
|
+
if (rows.length && rows[0].value && rows[0].value !== "null") {
|
|
3361
|
+
tid = typeof rows[0].value === "string" ? JSON.parse(rows[0].value) : rows[0].value;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
} catch {}
|
|
3365
|
+
}
|
|
3366
|
+
if (!tid) {
|
|
3367
|
+
try {
|
|
3368
|
+
const yml = fs.readFileSync(path.join(os.homedir(), ".cloudflared", "config.yml"), "utf8");
|
|
3369
|
+
const m = yml.match(/^\s*tunnel:\s*(\S+)/m);
|
|
3370
|
+
if (m) tid = m[1];
|
|
3371
|
+
} catch {}
|
|
3372
|
+
}
|
|
3373
|
+
if (!tid) return null;
|
|
3374
|
+
|
|
3375
|
+
// CF API token from the vault
|
|
3376
|
+
const { token, timestamp } = deriveToken(password, machineHash);
|
|
3377
|
+
const cr = await api.retrieve(password, machineHash, token, timestamp, "cloudflare");
|
|
3378
|
+
const cfToken = cr?.value;
|
|
3379
|
+
if (!cfToken) return null;
|
|
3380
|
+
|
|
3381
|
+
// accountId: clauth_config.cf_account_id, else first account on the token
|
|
3382
|
+
let accountId = null;
|
|
3383
|
+
if (sbUrl && sbKey) {
|
|
3384
|
+
try {
|
|
3385
|
+
const r = await fetch(`${sbUrl}/rest/v1/clauth_config?key=eq.cf_account_id&select=value`,
|
|
3386
|
+
{ headers: { apikey: sbKey, Authorization: `Bearer ${sbKey}` }, signal: AbortSignal.timeout(5000) });
|
|
3387
|
+
if (r.ok) {
|
|
3388
|
+
const rows = await r.json();
|
|
3389
|
+
if (rows.length && rows[0].value && rows[0].value !== "null") {
|
|
3390
|
+
accountId = typeof rows[0].value === "string" ? JSON.parse(rows[0].value) : rows[0].value;
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
} catch {}
|
|
3394
|
+
}
|
|
3395
|
+
if (!accountId) {
|
|
3396
|
+
const ar = await fetch("https://api.cloudflare.com/client/v4/accounts",
|
|
3397
|
+
{ headers: { Authorization: `Bearer ${cfToken}` }, signal: AbortSignal.timeout(8000) });
|
|
3398
|
+
const ad = await ar.json();
|
|
3399
|
+
accountId = ad?.result?.[0]?.id || null;
|
|
3400
|
+
}
|
|
3401
|
+
if (!accountId) return null;
|
|
3402
|
+
|
|
3403
|
+
// Mint the connector run-token
|
|
3404
|
+
const tr = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/cfd_tunnel/${tid}/token`,
|
|
3405
|
+
{ headers: { Authorization: `Bearer ${cfToken}` }, signal: AbortSignal.timeout(8000) });
|
|
3406
|
+
const td = await tr.json();
|
|
3407
|
+
if (td?.success && td.result) return td.result;
|
|
3408
|
+
return null;
|
|
3409
|
+
} catch {
|
|
3410
|
+
return null;
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3340
3414
|
async function startTunnel() {
|
|
3341
3415
|
if (tunnelProc) {
|
|
3342
3416
|
// Already running — ensure status reflects reality (caller may have reset it to "starting")
|
|
@@ -3387,9 +3461,15 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3387
3461
|
// Named tunnel (fixed subdomain) or quick tunnel (random URL)
|
|
3388
3462
|
let args;
|
|
3389
3463
|
if (tunnelHostname) {
|
|
3390
|
-
//
|
|
3391
|
-
//
|
|
3392
|
-
|
|
3464
|
+
// Self-heal first: mint a connector token from the CF API (clauth has
|
|
3465
|
+
// the keys) and run `--token` — no local config.yml/creds needed.
|
|
3466
|
+
// Fall back to the legacy file-based `tunnel run` only if minting fails.
|
|
3467
|
+
const runToken = await getTunnelRunToken();
|
|
3468
|
+
if (runToken) {
|
|
3469
|
+
args = ["tunnel", "run", "--token", runToken];
|
|
3470
|
+
} else {
|
|
3471
|
+
args = ["tunnel", "run"];
|
|
3472
|
+
}
|
|
3393
3473
|
tunnelUrl = `https://${tunnelHostname}`;
|
|
3394
3474
|
} else {
|
|
3395
3475
|
// Quick tunnel — random URL each session
|
|
@@ -4657,6 +4737,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
4657
4737
|
detached: true,
|
|
4658
4738
|
stdio: ["ignore", out, out],
|
|
4659
4739
|
env: { ...process.env, __CLAUTH_DAEMON: "1" },
|
|
4740
|
+
windowsHide: true,
|
|
4660
4741
|
});
|
|
4661
4742
|
child.unref();
|
|
4662
4743
|
stopTunnel();
|
|
@@ -5412,6 +5493,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
5412
5493
|
detached: true,
|
|
5413
5494
|
stdio: ["ignore", out, out],
|
|
5414
5495
|
env: childEnv,
|
|
5496
|
+
windowsHide: true,
|
|
5415
5497
|
});
|
|
5416
5498
|
child.unref();
|
|
5417
5499
|
|
|
@@ -6218,6 +6300,16 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
6218
6300
|
body: JSON.stringify({ key: "tunnel_hostname", value: JSON.stringify(hostname) }),
|
|
6219
6301
|
});
|
|
6220
6302
|
|
|
6303
|
+
// Persist tunnel_id too — required for token-based self-heal after a
|
|
6304
|
+
// daemon restart or a ~/.cloudflared wipe (getTunnelRunToken reads it).
|
|
6305
|
+
if (tunnelId) {
|
|
6306
|
+
await fetch(`${sbUrl}/rest/v1/clauth_config`, {
|
|
6307
|
+
method: "POST",
|
|
6308
|
+
headers: { apikey: sbKey, Authorization: `Bearer ${sbKey}`, "Content-Type": "application/json", Prefer: "resolution=merge-duplicates" },
|
|
6309
|
+
body: JSON.stringify({ key: "tunnel_id", value: JSON.stringify(tunnelId) }),
|
|
6310
|
+
});
|
|
6311
|
+
}
|
|
6312
|
+
|
|
6221
6313
|
tunnelHostname = hostname;
|
|
6222
6314
|
tunnelUrl = `https://${hostname}`;
|
|
6223
6315
|
|
|
@@ -6341,7 +6433,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
6341
6433
|
const sendEvt = (data) => res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
6342
6434
|
try {
|
|
6343
6435
|
const { spawn } = await import("child_process");
|
|
6344
|
-
const proc = spawn("cloudflared", ["tunnel", "login"], { stdio: ["ignore","pipe","pipe"] });
|
|
6436
|
+
const proc = spawn("cloudflared", ["tunnel", "login"], { stdio: ["ignore","pipe","pipe"], windowsHide: true });
|
|
6345
6437
|
proc.stdout.on("data", d => d.toString().split("\n").forEach(l => l.trim() && sendEvt({ line: l })));
|
|
6346
6438
|
proc.stderr.on("data", d => d.toString().split("\n").forEach(l => l.trim() && sendEvt({ line: l })));
|
|
6347
6439
|
proc.on("close", code => { sendEvt({ done: true, code }); res.end(); });
|
|
@@ -6375,7 +6467,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
6375
6467
|
sendEvt({ line: `Creating tunnel "${name}"…`, step: 1 });
|
|
6376
6468
|
let tunnelId = null;
|
|
6377
6469
|
await new Promise((resolve, reject) => {
|
|
6378
|
-
const proc = spawn("cloudflared", ["tunnel", "create", name], { stdio: ["ignore","pipe","pipe"] });
|
|
6470
|
+
const proc = spawn("cloudflared", ["tunnel", "create", name], { stdio: ["ignore","pipe","pipe"], windowsHide: true });
|
|
6379
6471
|
let output = "";
|
|
6380
6472
|
proc.stdout.on("data", d => { const s = d.toString(); output += s; s.split("\n").forEach(l => l.trim() && sendEvt({ line: l, step: 1 })); });
|
|
6381
6473
|
proc.stderr.on("data", d => { const s = d.toString(); output += s; s.split("\n").forEach(l => l.trim() && sendEvt({ line: l, step: 1 })); });
|
|
@@ -6395,7 +6487,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
6395
6487
|
// Step 2: route DNS
|
|
6396
6488
|
sendEvt({ line: `Routing DNS: ${hostname}…`, step: 2 });
|
|
6397
6489
|
await new Promise((resolve) => {
|
|
6398
|
-
const proc = spawn("cloudflared", ["tunnel", "route", "dns", name, hostname], { stdio: ["ignore","pipe","pipe"] });
|
|
6490
|
+
const proc = spawn("cloudflared", ["tunnel", "route", "dns", name, hostname], { stdio: ["ignore","pipe","pipe"], windowsHide: true });
|
|
6399
6491
|
proc.stdout.on("data", d => d.toString().split("\n").forEach(l => l.trim() && sendEvt({ line: l, step: 2 })));
|
|
6400
6492
|
proc.stderr.on("data", d => d.toString().split("\n").forEach(l => l.trim() && sendEvt({ line: l, step: 2 })));
|
|
6401
6493
|
proc.on("close", () => resolve());
|
|
@@ -6877,6 +6969,7 @@ async function actionStart(opts) {
|
|
|
6877
6969
|
detached: true,
|
|
6878
6970
|
stdio: ["ignore", out, out],
|
|
6879
6971
|
env: { ...process.env, __CLAUTH_DAEMON: "1", ...(isStaged ? { __CLAUTH_STAGED: "1" } : {}) },
|
|
6972
|
+
windowsHide: true,
|
|
6880
6973
|
});
|
|
6881
6974
|
child.unref();
|
|
6882
6975
|
|
|
@@ -7101,11 +7194,18 @@ function getAgentPool() {
|
|
|
7101
7194
|
defaultModel: process.env.CLAUTH_AGENT_MODEL || "claude-haiku-4-5",
|
|
7102
7195
|
});
|
|
7103
7196
|
_agentPool.startReaper();
|
|
7197
|
+
console.log(`[agent-pool] init: binary=${_agentPool.binary ? 'found' : 'MISSING'} warmEnabled=${_agentPool.warmEnabled} available=${_agentPool.available()} poolSize=${_agentPool.poolSize} warmSize=${_agentPool.warmSize}`);
|
|
7104
7198
|
// WP-P2: boot persistent re-targetable warm workers in the background so the
|
|
7105
7199
|
// first real call_agent dispatch hits a hot worker (sub-boot latency). Never
|
|
7106
7200
|
// blocks daemon startup; if warm isn't ready yet, dispatch falls back to cold.
|
|
7107
7201
|
if (_agentPool.warmEnabled && _agentPool.available()) {
|
|
7108
|
-
_agentPool.prime().
|
|
7202
|
+
_agentPool.prime().then((r) => {
|
|
7203
|
+
console.log(`[agent-pool] prime complete: mode=${r.mode} warmed=${r.warmed} workers=${r.workers ?? 'n/a'}`);
|
|
7204
|
+
}).catch((e) => {
|
|
7205
|
+
console.error(`[agent-pool] prime failed: ${e.message}`);
|
|
7206
|
+
});
|
|
7207
|
+
} else {
|
|
7208
|
+
console.log(`[agent-pool] prime skipped: warmEnabled=${_agentPool.warmEnabled} available=${_agentPool.available()}`);
|
|
7109
7209
|
}
|
|
7110
7210
|
return _agentPool;
|
|
7111
7211
|
}
|
|
@@ -7945,7 +8045,7 @@ function sendTerminalMessage(session_id, message) {
|
|
|
7945
8045
|
if (!terminalSessions.has(session_id)) return;
|
|
7946
8046
|
const s = terminalSessions.get(session_id);
|
|
7947
8047
|
const response = result.ok
|
|
7948
|
-
? (result.
|
|
8048
|
+
? (result.package || '').trim() || '(no output)'
|
|
7949
8049
|
: `(warm dispatch error: ${result.error || 'unknown'})`;
|
|
7950
8050
|
// Still update rolling context for terminal_recv compatibility
|
|
7951
8051
|
const turn = `\n\nUser: ${message}\nAssistant: ${response}`;
|
|
@@ -7990,6 +8090,7 @@ function sendTerminalMessage(session_id, message) {
|
|
|
7990
8090
|
env: process.env,
|
|
7991
8091
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
7992
8092
|
shell: true,
|
|
8093
|
+
windowsHide: true,
|
|
7993
8094
|
});
|
|
7994
8095
|
session.activeProc = proc;
|
|
7995
8096
|
|
|
@@ -8205,6 +8306,7 @@ function launchVisibleChitchatTerminal(session_id, cwd = CHITCHAT_FALLBACK_CWD)
|
|
|
8205
8306
|
cwd,
|
|
8206
8307
|
detached: true,
|
|
8207
8308
|
stdio: "ignore",
|
|
8309
|
+
windowsHide: true,
|
|
8208
8310
|
});
|
|
8209
8311
|
child.on("error", (err) => {
|
|
8210
8312
|
console.warn(`[ClaudeAItoCLI] visible terminal process error: ${err.message}`);
|