@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.
@@ -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
- // Named tunnel: cloudflared tunnel run (uses ~/.cloudflared/config.yml)
3391
- // Config maps hostname local service, so no --url needed
3392
- args = ["tunnel", "run"];
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().catch(() => {});
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.stdout || '').trim() || '(no output)'
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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {