@lifeaitools/clauth 0.7.0 → 0.7.3

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.
@@ -166,6 +166,20 @@ function dashboardHtml(port, whitelist) {
166
166
  .btn-mcp-setup{background:#1e293b;color:#94a3b8;border:1px solid #334155;padding:6px 12px;font-size:.8rem;border-radius:6px;cursor:pointer;font-weight:500;transition:all .15s}
167
167
  .btn-mcp-setup:hover{border-color:#60a5fa;color:#60a5fa}
168
168
  .btn-mcp-setup:disabled{opacity:.4;cursor:not-allowed}
169
+ .btn-add{background:#1a2e1a;color:#4ade80;border:1px solid #166534;padding:7px 16px;font-size:.85rem;border-radius:7px;cursor:pointer;font-weight:500;transition:all .15s}
170
+ .btn-add:hover{background:#1a3d22;border-color:#4ade80}
171
+ .add-panel{display:none;background:#1a1f2e;border:1px solid #334155;border-radius:8px;padding:1.25rem;margin-bottom:1.5rem}
172
+ .add-panel h3{font-size:.9rem;font-weight:600;color:#f8fafc;margin-bottom:1rem}
173
+ .add-row{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;margin-bottom:.75rem}
174
+ .add-field{display:flex;flex-direction:column;gap:4px}
175
+ .add-field label{font-size:.75rem;color:#64748b}
176
+ .add-input{background:#0f172a;border:1px solid #334155;border-radius:6px;color:#e2e8f0;font-family:'Courier New',monospace;font-size:.88rem;padding:7px 12px;outline:none;width:200px;transition:border-color .2s}
177
+ .add-input:focus{border-color:#3b82f6}
178
+ .add-select{background:#0f172a;border:1px solid #334155;border-radius:6px;color:#e2e8f0;font-size:.88rem;padding:7px 12px;outline:none;width:200px;transition:border-color .2s;cursor:pointer}
179
+ .add-select:focus{border-color:#3b82f6}
180
+ .add-foot{display:flex;gap:8px;align-items:center}
181
+ .add-msg{font-size:.82rem}
182
+ .add-msg.ok{color:#4ade80} .add-msg.fail{color:#f87171}
169
183
  .footer{margin-top:2rem;font-size:.75rem;color:#475569;text-align:center}
170
184
  .oauth-fields{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}
171
185
  .oauth-field{display:flex;flex-direction:column;gap:3px}
@@ -204,11 +218,33 @@ function dashboardHtml(port, whitelist) {
204
218
  </div>
205
219
  <div class="toolbar">
206
220
  <button class="btn-refresh" onclick="loadServices()">↻ Refresh</button>
221
+ <button class="btn-add" onclick="toggleAddService()">+ Add Service</button>
207
222
  <button class="btn-check" id="check-btn" onclick="checkAll()">⬤ Check All</button>
208
223
  <button class="btn-lock" onclick="lockVault()">🔒 Lock</button>
209
224
  <button class="btn-cancel" style="margin-left:auto" onclick="toggleChangePw()">Change Password</button>
210
225
  </div>
211
226
 
227
+ <div class="add-panel" id="add-panel">
228
+ <h3>Add New Service</h3>
229
+ <div class="add-row">
230
+ <div class="add-field">
231
+ <label>Service name</label>
232
+ <input class="add-input" id="add-name" type="text" placeholder="e.g. coolify-admin" autocomplete="off" spellcheck="false">
233
+ </div>
234
+ <div class="add-field">
235
+ <label>Key type</label>
236
+ <select class="add-select" id="add-type">
237
+ <option value="">Loading…</option>
238
+ </select>
239
+ </div>
240
+ </div>
241
+ <div class="add-foot">
242
+ <button class="btn-chpw-save" onclick="addService()">Create Service</button>
243
+ <button class="btn-cancel" onclick="toggleAddService()">Cancel</button>
244
+ <span class="add-msg" id="add-msg"></span>
245
+ </div>
246
+ </div>
247
+
212
248
  <div class="chpw-panel" id="chpw-panel">
213
249
  <h3>Change Master Password</h3>
214
250
  <div class="chpw-row">
@@ -722,11 +758,78 @@ async function changePassword() {
722
758
  }
723
759
  }
724
760
 
725
- // Enter key on lock screen
761
+ // ── Add Service ──────────────────────────────
762
+ const TYPE_LABELS = {
763
+ token: "token (API key)",
764
+ secret: "secret (password, secret)",
765
+ keypair: "keypair (user:key pair)",
766
+ connstring: "connstring (connection string)",
767
+ oauth: "oauth (OAuth credentials)",
768
+ };
769
+
770
+ async function toggleAddService() {
771
+ const panel = document.getElementById("add-panel");
772
+ const open = panel.style.display === "block";
773
+ panel.style.display = open ? "none" : "block";
774
+ if (!open) {
775
+ document.getElementById("add-name").value = "";
776
+ document.getElementById("add-msg").textContent = "";
777
+ // Fetch available key types from server
778
+ const sel = document.getElementById("add-type");
779
+ sel.innerHTML = '<option value="">Loading…</option>';
780
+ try {
781
+ const r = await fetch(BASE + "/meta").then(r => r.json());
782
+ const types = r.key_types || ["token", "secret", "keypair", "connstring", "oauth"];
783
+ sel.innerHTML = types.map(t =>
784
+ \`<option value="\${t}">\${TYPE_LABELS[t] || t}</option>\`
785
+ ).join("");
786
+ } catch {
787
+ sel.innerHTML = ["token","secret","keypair","connstring","oauth"].map(t =>
788
+ \`<option value="\${t}">\${TYPE_LABELS[t] || t}</option>\`
789
+ ).join("");
790
+ }
791
+ document.getElementById("add-name").focus();
792
+ }
793
+ }
794
+
795
+ async function addService() {
796
+ const name = document.getElementById("add-name").value.trim().toLowerCase();
797
+ const type = document.getElementById("add-type").value;
798
+ const msg = document.getElementById("add-msg");
799
+
800
+ if (!name) { msg.className = "add-msg fail"; msg.textContent = "Service name is required."; return; }
801
+ if (!/^[a-z0-9][a-z0-9_-]*$/.test(name)) { msg.className = "add-msg fail"; msg.textContent = "Lowercase letters, numbers, hyphens, underscores only."; return; }
802
+
803
+ msg.className = "add-msg"; msg.textContent = "Creating…";
804
+ try {
805
+ const r = await fetch(BASE + "/add-service", {
806
+ method: "POST",
807
+ headers: { "Content-Type": "application/json" },
808
+ body: JSON.stringify({ name, key_type: type, label: name })
809
+ }).then(r => r.json());
810
+
811
+ if (r.locked) { showLockScreen(); return; }
812
+ if (r.error) throw new Error(r.error);
813
+
814
+ msg.className = "add-msg ok"; msg.textContent = "✓ " + name + " created";
815
+ setTimeout(() => {
816
+ document.getElementById("add-panel").style.display = "none";
817
+ msg.textContent = "";
818
+ loadServices();
819
+ }, 1200);
820
+ } catch (e) {
821
+ msg.className = "add-msg fail"; msg.textContent = "✗ " + e.message;
822
+ }
823
+ }
824
+
825
+ // Enter key on lock screen and add-service panel
726
826
  document.addEventListener("DOMContentLoaded", () => {
727
827
  document.getElementById("lock-input").addEventListener("keydown", e => {
728
828
  if (e.key === "Enter") unlock();
729
829
  });
830
+ document.getElementById("add-name").addEventListener("keydown", e => {
831
+ if (e.key === "Enter") addService();
832
+ });
730
833
  });
731
834
 
732
835
  // ── Tunnel management ───────────────────────
@@ -1292,7 +1395,9 @@ function createServer(initPassword, whitelist, port, tunnelHostname = null) {
1292
1395
  res.writeHead(401, { "Content-Type": "application/json", ...CORS });
1293
1396
  return res.end(JSON.stringify({ error: "invalid_token" }));
1294
1397
  }
1295
- // Token valid — fall through to MCP handling below
1398
+ // Token valid — mark as remote caller (claude.ai via tunnel)
1399
+ req._clauthRemote = true;
1400
+ // fall through to MCP handling below
1296
1401
  }
1297
1402
 
1298
1403
  // ── MCP Streamable HTTP transport (2025-03-26 spec) ──
@@ -1333,6 +1438,7 @@ function createServer(initPassword, whitelist, port, tunnelHostname = null) {
1333
1438
  if (rpcMethod === "tools/call") {
1334
1439
  const { name, arguments: args } = body.params || {};
1335
1440
  const vault = sseVault();
1441
+ vault.remote = !!req._clauthRemote;
1336
1442
  try {
1337
1443
  const result = await handleMcpTool(vault, name, args || {});
1338
1444
  password = vault.password;
@@ -1535,6 +1641,31 @@ function createServer(initPassword, whitelist, port, tunnelHostname = null) {
1535
1641
  return false;
1536
1642
  }
1537
1643
 
1644
+ // GET /meta — return valid key_types from DB check constraint
1645
+ if (method === "GET" && reqPath === "/meta") {
1646
+ if (lockedGuard(res)) return;
1647
+ try {
1648
+ const baseUrl = api.getBaseUrl().replace("/functions/v1/auth-vault", "");
1649
+ const anonKey = api.getAnonKey();
1650
+ const sql = `SELECT check_clause FROM information_schema.check_constraints WHERE constraint_name = 'clauth_services_key_type_check'`;
1651
+ const r = await fetch(`${baseUrl}/rest/v1/rpc/`, {
1652
+ method: "POST",
1653
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${anonKey}`, "apikey": anonKey }
1654
+ }).catch(() => null);
1655
+ // Fallback: parse from a direct SQL query via postgrest isn't possible without an RPC,
1656
+ // so we query the status endpoint which returns services with their key_types
1657
+ const { token, timestamp } = deriveToken(password, machineHash);
1658
+ const statusResult = await api.status(password, machineHash, token, timestamp);
1659
+ const existingTypes = [...new Set((statusResult.services || []).map(s => s.key_type).filter(Boolean))];
1660
+ // Merge with known types (in case no service of that type exists yet)
1661
+ const knownTypes = ["token", "secret", "keypair", "connstring", "oauth"];
1662
+ const allTypes = [...new Set([...knownTypes, ...existingTypes])];
1663
+ return ok(res, { key_types: allTypes });
1664
+ } catch (err) {
1665
+ return ok(res, { key_types: ["token", "secret", "keypair", "connstring", "oauth"] });
1666
+ }
1667
+ }
1668
+
1538
1669
  // GET /status
1539
1670
  if (method === "GET" && reqPath === "/status") {
1540
1671
  if (lockedGuard(res)) return;
@@ -1783,6 +1914,38 @@ function createServer(initPassword, whitelist, port, tunnelHostname = null) {
1783
1914
  }
1784
1915
  }
1785
1916
 
1917
+ // POST /add-service — register a new service in the vault
1918
+ if (method === "POST" && reqPath === "/add-service") {
1919
+ if (lockedGuard(res)) return;
1920
+
1921
+ let body;
1922
+ try { body = await readBody(req); } catch {
1923
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
1924
+ return res.end(JSON.stringify({ error: "Invalid JSON body" }));
1925
+ }
1926
+
1927
+ const { name, label, key_type, description } = body;
1928
+ if (!name || typeof name !== "string" || !name.trim()) {
1929
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
1930
+ return res.end(JSON.stringify({ error: "name is required" }));
1931
+ }
1932
+ const validTypes = ["token", "keypair", "connstring", "oauth", "secret"];
1933
+ const type = (key_type || "token").toLowerCase();
1934
+ if (!validTypes.includes(type)) {
1935
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
1936
+ return res.end(JSON.stringify({ error: `key_type must be one of: ${validTypes.join(", ")}` }));
1937
+ }
1938
+
1939
+ try {
1940
+ const { token, timestamp } = deriveToken(password, machineHash);
1941
+ const result = await api.addService(password, machineHash, token, timestamp, name.trim().toLowerCase(), label || name.trim(), type, description || "");
1942
+ if (result.error) return strike(res, 502, result.error);
1943
+ return ok(res, { ok: true, service: name.trim().toLowerCase() });
1944
+ } catch (err) {
1945
+ return strike(res, 502, err.message);
1946
+ }
1947
+ }
1948
+
1786
1949
  // Unknown route — don't count browser/MCP noise as auth failures
1787
1950
  // Don't count browser noise, MCP discovery probes, or OAuth probes as auth failures
1788
1951
  const isBenign = reqPath.startsWith("/.well-known/") || [
@@ -2286,7 +2449,14 @@ async function handleMcpTool(vault, name, args) {
2286
2449
  };
2287
2450
  }
2288
2451
 
2289
- // Default: file
2452
+ // Remote caller (claude.ai via tunnel) — can't read local temp files
2453
+ // Return value inline in MCP response (safe: stays in AI context, not a shell transcript)
2454
+ if (vault.remote) {
2455
+ const envVar = ENV_MAP[service] || service.toUpperCase().replace(/-/g, "_");
2456
+ return mcpResult(`${envVar}=${value}`);
2457
+ }
2458
+
2459
+ // Default: file (local callers)
2290
2460
  const envVar = ENV_MAP[service] || service.toUpperCase().replace(/-/g, "_");
2291
2461
  const filePath = writeTempSecret(service, value);
2292
2462
  return mcpResult(`${service} → ${filePath} (auto-deletes in 30s)\nEnv var: ${envVar}\nUsage: export ${envVar}=$(cat ${filePath.replace(/\\/g, "/")})`);
@@ -2322,6 +2492,14 @@ async function handleMcpTool(vault, name, args) {
2322
2492
 
2323
2493
  if (!lines.length) return mcpError(`No services retrieved:\n${errors.join("\n")}`);
2324
2494
 
2495
+ // Remote caller — return env vars inline
2496
+ if (vault.remote) {
2497
+ let msg = lines.join("\n");
2498
+ if (errors.length) msg += `\n\nErrors:\n${errors.join("\n")}`;
2499
+ return mcpResult(msg);
2500
+ }
2501
+
2502
+ // Local caller — write temp env file
2325
2503
  const envFilePath = path.join(os.tmpdir(), ".clauth-env");
2326
2504
  fs.writeFileSync(envFilePath, lines.join("\n") + "\n", { mode: 0o600 });
2327
2505
  setTimeout(() => { try { fs.unlinkSync(envFilePath); } catch {} }, 30_000);
@@ -2497,6 +2675,98 @@ async function actionMcp(opts) {
2497
2675
  createMcpServer(password, whitelist);
2498
2676
  }
2499
2677
 
2678
+ // ── DPAPI auto-start install / uninstall (Windows only) ──────
2679
+ const AUTOSTART_DIR = path.join(os.homedir(), "AppData", "Roaming", "clauth");
2680
+ const BOOT_KEY_PATH = path.join(AUTOSTART_DIR, "boot.key");
2681
+ const PS_SCRIPT_PATH = path.join(AUTOSTART_DIR, "autostart.ps1");
2682
+ const TASK_NAME = "ClauthAutostart";
2683
+
2684
+ async function actionInstall(opts) {
2685
+ if (os.platform() !== "win32") {
2686
+ console.log(chalk.red("\n serve install is only supported on Windows\n"));
2687
+ process.exit(1);
2688
+ }
2689
+
2690
+ const { default: inquirer } = await import("inquirer");
2691
+ const { pw } = await inquirer.prompt([{
2692
+ type: "password", name: "pw",
2693
+ message: "Enter clauth password to store for auto-start:", mask: "*"
2694
+ }]);
2695
+
2696
+ fs.mkdirSync(AUTOSTART_DIR, { recursive: true });
2697
+
2698
+ // Encrypt password with Windows DPAPI (CurrentUser scope — machine+user bound)
2699
+ const spinner = ora("Encrypting password with Windows DPAPI...").start();
2700
+ let encrypted;
2701
+ try {
2702
+ const { execSync } = await import("child_process");
2703
+ const pwEscaped = pw.replace(/'/g, "''");
2704
+ const psExpr = `[Convert]::ToBase64String([Security.Cryptography.ProtectedData]::Protect([Text.Encoding]::UTF8.GetBytes('${pwEscaped}'),$null,'CurrentUser'))`;
2705
+ encrypted = execSync(`powershell -NoProfile -Command "${psExpr}"`, { encoding: "utf8" }).trim();
2706
+ fs.writeFileSync(BOOT_KEY_PATH, encrypted, "utf8");
2707
+ spinner.succeed(chalk.green("Password encrypted → boot.key"));
2708
+ } catch (err) {
2709
+ spinner.fail(chalk.red(`DPAPI encryption failed: ${err.message}`));
2710
+ process.exit(1);
2711
+ }
2712
+
2713
+ // Write PowerShell autostart script — decrypts boot.key and pipes to clauth serve start
2714
+ const cliEntry = path.resolve(__dirname, "../index.js").replace(/\\/g, "\\\\");
2715
+ const nodeExe = process.execPath.replace(/\\/g, "\\\\");
2716
+ const bootKey = BOOT_KEY_PATH.replace(/\\/g, "\\\\");
2717
+ const psScript = [
2718
+ "# clauth autostart — generated by clauth serve install",
2719
+ `$enc = (Get-Content '${bootKey}' -Raw).Trim()`,
2720
+ `$pw = [Text.Encoding]::UTF8.GetString([Security.Cryptography.ProtectedData]::Unprotect([Convert]::FromBase64String($enc),$null,'CurrentUser'))`,
2721
+ `Start-Process '${nodeExe}' -ArgumentList "'${cliEntry}' serve start -p $pw" -WindowStyle Hidden`,
2722
+ ].join("\n");
2723
+ fs.writeFileSync(PS_SCRIPT_PATH, psScript, "utf8");
2724
+
2725
+ // Register Windows Scheduled Task — triggers on user logon
2726
+ const spinner2 = ora("Registering Windows Scheduled Task...").start();
2727
+ try {
2728
+ const { execSync } = await import("child_process");
2729
+ const psScriptEsc = PS_SCRIPT_PATH.replace(/\\/g, "\\\\");
2730
+ const args = `-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "${psScriptEsc}"`;
2731
+ execSync(
2732
+ `schtasks /create /f /tn "${TASK_NAME}" /sc onlogon /tr "powershell.exe ${args}"`,
2733
+ { encoding: "utf8", stdio: "pipe" }
2734
+ );
2735
+ spinner2.succeed(chalk.green(`Scheduled Task "${TASK_NAME}" registered`));
2736
+ } catch (err) {
2737
+ spinner2.fail(chalk.yellow(`Scheduled task failed (non-fatal): ${err.message}`));
2738
+ console.log(chalk.gray(" You can still start manually: clauth serve start"));
2739
+ }
2740
+
2741
+ console.log(chalk.cyan("\n Auto-start installed:\n"));
2742
+ console.log(chalk.gray(` boot.key: ${BOOT_KEY_PATH}`));
2743
+ console.log(chalk.gray(` script: ${PS_SCRIPT_PATH}`));
2744
+ console.log(chalk.gray(` task: ${TASK_NAME}\n`));
2745
+ console.log(chalk.green(" Daemon will auto-start on next Windows login.\n"));
2746
+ }
2747
+
2748
+ async function actionUninstall() {
2749
+ if (os.platform() !== "win32") {
2750
+ console.log(chalk.red("\n serve uninstall is only supported on Windows\n"));
2751
+ process.exit(1);
2752
+ }
2753
+ const { execSync } = await import("child_process");
2754
+
2755
+ // Remove scheduled task
2756
+ try {
2757
+ execSync(`schtasks /delete /f /tn "${TASK_NAME}"`, { encoding: "utf8", stdio: "pipe" });
2758
+ console.log(chalk.green(` Removed Scheduled Task: ${TASK_NAME}`));
2759
+ } catch { console.log(chalk.gray(` Task not found (already removed): ${TASK_NAME}`)); }
2760
+
2761
+ // Remove boot.key and autostart script
2762
+ for (const f of [BOOT_KEY_PATH, PS_SCRIPT_PATH]) {
2763
+ try { fs.unlinkSync(f); console.log(chalk.green(` Deleted: ${f}`)); }
2764
+ catch { console.log(chalk.gray(` Not found (skipped): ${f}`)); }
2765
+ }
2766
+
2767
+ console.log(chalk.cyan("\n Auto-start uninstalled.\n"));
2768
+ }
2769
+
2500
2770
  // ── Export ────────────────────────────────────────────────────
2501
2771
  export async function runServe(opts) {
2502
2772
  const action = opts.action || "foreground";
@@ -2508,9 +2778,11 @@ export async function runServe(opts) {
2508
2778
  case "ping": return actionPing();
2509
2779
  case "foreground": return actionForeground(opts);
2510
2780
  case "mcp": return actionMcp(opts);
2781
+ case "install": return actionInstall(opts);
2782
+ case "uninstall": return actionUninstall();
2511
2783
  default:
2512
2784
  console.log(chalk.red(`\n Unknown serve action: ${action}`));
2513
- console.log(chalk.gray(" Actions: start | stop | restart | ping | foreground | mcp\n"));
2785
+ console.log(chalk.gray(" Actions: start | stop | restart | ping | foreground | mcp | install | uninstall\n"));
2514
2786
  process.exit(1);
2515
2787
  }
2516
2788
  }
@@ -15,16 +15,18 @@ function getMachineId() {
15
15
 
16
16
  try {
17
17
  if (platform === "win32") {
18
- // Primary: BIOS UUID via WMIC
19
- const uuid = execSync("wmic csproduct get uuid /format:value", {
20
- encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"]
21
- }).match(/UUID=([A-F0-9-]+)/i)?.[1]?.trim();
18
+ // Primary: BIOS UUID via PowerShell (wmic removed in Win11 26xxx+)
19
+ const psPath = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
20
+ const uuid = execSync(
21
+ `${psPath} -NoProfile -Command "(Get-CimInstance Win32_ComputerSystemProduct).UUID"`,
22
+ { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }
23
+ ).trim();
22
24
 
23
- // Secondary: Windows MachineGuid from registry
25
+ // Secondary: Windows MachineGuid from registry (via PowerShell for Win11 compat)
24
26
  const machineGuid = execSync(
25
- "reg query HKLM\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid",
27
+ `${psPath} -NoProfile -Command "(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Cryptography').MachineGuid"`,
26
28
  { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }
27
- ).match(/MachineGuid\s+REG_SZ\s+([a-f0-9-]+)/i)?.[1]?.trim();
29
+ ).trim();
28
30
 
29
31
  if (!uuid || !machineGuid) throw new Error("Could not read Windows machine IDs");
30
32
  return { primary: uuid, secondary: machineGuid, platform: "win32" };
package/cli/index.js CHANGED
@@ -464,7 +464,7 @@ program.addHelpText("beforeAll", chalk.cyan(`
464
464
  // ──────────────────────────────────────────────
465
465
  program
466
466
  .command("serve [action]")
467
- .description("Manage localhost HTTP vault daemon (start|stop|restart|ping)")
467
+ .description("Manage localhost HTTP vault daemon (start|stop|restart|ping|install|uninstall)")
468
468
  .option("--port <n>", "Port (default: 52437)")
469
469
  .option("-p, --pw <password>", "clauth password (optional — omit to start locked, unlock in browser)")
470
470
  .option("--services <list>", "Comma-separated service whitelist (default: all)")
@@ -478,6 +478,8 @@ Actions:
478
478
  ping Check if the daemon is running
479
479
  foreground Run in foreground (Ctrl+C to stop) — default if no action given
480
480
  mcp Run as MCP stdio server for Claude Code (JSON-RPC over stdin/stdout)
481
+ install (Windows) Encrypt password with DPAPI + create Scheduled Task for auto-start on login
482
+ uninstall (Windows) Remove Scheduled Task + delete boot.key
481
483
 
482
484
  MCP SSE (built into start/foreground):
483
485
  The HTTP daemon also serves MCP SSE transport at GET /sse + POST /message.
@@ -492,6 +494,8 @@ Examples:
492
494
  clauth serve start --services github,vercel
493
495
  clauth serve mcp Start MCP server for Claude Code
494
496
  clauth serve mcp -p mypass Start MCP server pre-unlocked
497
+ clauth serve install (Windows) Set up auto-start on login via DPAPI
498
+ clauth serve uninstall (Windows) Remove auto-start
495
499
  `)
496
500
  .action(async (action, opts) => {
497
501
  const resolvedAction = opts.action || action || "foreground";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "0.7.0",
3
+ "version": "0.7.3",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {