@lifeaitools/clauth 1.1.0 → 1.3.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/index.js CHANGED
@@ -532,40 +532,93 @@ program
532
532
  });
533
533
 
534
534
  // ──────────────────────────────────────────────
535
- // clauth tunnel setup
535
+ // clauth tunnel start|stop|status
536
+ // (setup moved to in-browser wizard at http://127.0.0.1:52437)
536
537
  // ──────────────────────────────────────────────
537
538
  const tunnelCmd = program.command("tunnel").description("Manage Cloudflare tunnel for claude.ai web integration");
538
539
 
539
540
  tunnelCmd
540
541
  .command("setup")
541
- .description("Interactive wizard configure cloudflared named tunnel")
542
+ .description("Open the tunnel setup wizard in your browser")
542
543
  .action(async () => {
543
- const { actionTunnelSetup } = await import("./commands/tunnel.js");
544
- await actionTunnelSetup();
544
+ console.log(chalk.cyan("\n Tunnel setup is now handled in the browser.\n"));
545
+ console.log(chalk.white(" 1. Start the daemon: clauth serve start"));
546
+ console.log(chalk.white(" 2. Open: http://127.0.0.1:52437"));
547
+ console.log(chalk.white(" 3. Unlock the vault and click \"Setup Tunnel\"\n"));
545
548
  });
546
549
 
547
550
  tunnelCmd
548
551
  .command("start")
549
552
  .description("Tell daemon to start the tunnel")
550
553
  .action(async () => {
551
- const { actionTunnelStart } = await import("./commands/tunnel.js");
552
- await actionTunnelStart();
554
+ try {
555
+ const r = await fetch("http://127.0.0.1:52437/tunnel/start", {
556
+ method: "POST",
557
+ headers: { "Content-Type": "application/json" },
558
+ signal: AbortSignal.timeout(5000),
559
+ });
560
+ const data = await r.json().catch(() => ({}));
561
+ if (!r.ok) {
562
+ console.error(` ✗ ${data.error || r.statusText}`);
563
+ if (r.status === 401) console.error(" Unlock the daemon first: http://127.0.0.1:52437");
564
+ process.exit(1);
565
+ }
566
+ console.log(` ✓ ${data.message || "Tunnel starting — check status with: clauth tunnel status"}`);
567
+ } catch (e) {
568
+ console.error(" ✗ Daemon not running. Start it with: clauth serve");
569
+ process.exit(1);
570
+ }
553
571
  });
554
572
 
555
573
  tunnelCmd
556
574
  .command("stop")
557
575
  .description("Tell daemon to stop the tunnel")
558
576
  .action(async () => {
559
- const { actionTunnelStop } = await import("./commands/tunnel.js");
560
- await actionTunnelStop();
577
+ try {
578
+ const r = await fetch("http://127.0.0.1:52437/tunnel/stop", {
579
+ method: "POST",
580
+ headers: { "Content-Type": "application/json" },
581
+ signal: AbortSignal.timeout(5000),
582
+ });
583
+ const data = await r.json().catch(() => ({}));
584
+ if (!r.ok) {
585
+ console.error(` ✗ ${data.error || r.statusText}`);
586
+ process.exit(1);
587
+ }
588
+ console.log(" ✓ Tunnel stopped.");
589
+ } catch (e) {
590
+ console.error(" ✗ Daemon not running.");
591
+ process.exit(1);
592
+ }
561
593
  });
562
594
 
563
595
  tunnelCmd
564
596
  .command("status")
565
597
  .description("Show current tunnel status")
566
598
  .action(async () => {
567
- const { actionTunnelStatus } = await import("./commands/tunnel.js");
568
- await actionTunnelStatus();
599
+ try {
600
+ const r = await fetch("http://127.0.0.1:52437/tunnel", {
601
+ signal: AbortSignal.timeout(5000),
602
+ });
603
+ const data = await r.json().catch(() => ({}));
604
+ const icons = {
605
+ live: "✓", starting: "◌", not_configured: "⚠",
606
+ not_started: "○", error: "✗", missing_cloudflared: "✗",
607
+ };
608
+ const labels = {
609
+ live: `Live — ${data.url || ""}`,
610
+ starting: "Starting...",
611
+ not_configured: "Not configured — open http://127.0.0.1:52437 and click Setup Tunnel",
612
+ not_started: "Not started — run: clauth tunnel start",
613
+ error: `Error${data.error ? ": " + data.error : ""} — check cloudflared config`,
614
+ missing_cloudflared: "cloudflared not installed — https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/",
615
+ };
616
+ const status = data.status || "unknown";
617
+ console.log(`\n ${icons[status] || "?"} Tunnel: ${labels[status] || status}\n`);
618
+ } catch (e) {
619
+ console.error(" ✗ Daemon not running. Start it with: clauth serve");
620
+ process.exit(1);
621
+ }
569
622
  });
570
623
 
571
624
  // ──────────────────────────────────────────────
@@ -591,6 +644,7 @@ program
591
644
  .option("-p, --pw <password>", "clauth password (optional — omit to start locked, unlock in browser)")
592
645
  .option("--services <list>", "Comma-separated service whitelist (default: all)")
593
646
  .option("--tunnel <hostname>", "Fixed tunnel hostname (e.g. clauth.prtrust.fund) — uses named Cloudflare Tunnel instead of random URL")
647
+ .option("--staged", "Start on staging port (52438) for blue-green verification before make-live")
594
648
  .option("--action <action>", "Internal: action override for daemon child")
595
649
  .addHelpText("after", `
596
650
  Actions:
@@ -601,8 +655,9 @@ Actions:
601
655
  foreground Run in foreground (Ctrl+C to stop) — default if no action given
602
656
  mcp Run as MCP stdio server for Claude Code (JSON-RPC over stdin/stdout)
603
657
  install Store password securely + register auto-start service (cross-platform)
604
- Windows: DPAPI + Scheduled Task | macOS: Keychain + LaunchAgent | Linux: libsecret/openssl + systemd
658
+ Windows: DPAPI + HKCU\\Run | macOS: Keychain + LaunchAgent | Linux: libsecret/openssl + systemd
605
659
  uninstall Remove auto-start service + delete stored password
660
+ upgrade Blue-green upgrade: start new version on staging port, verify, then make live
606
661
 
607
662
  MCP SSE (built into start/foreground):
608
663
  The HTTP daemon also serves MCP SSE transport at GET /sse + POST /message.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,210 +0,0 @@
1
- // cli/commands/tunnel.js
2
- // clauth tunnel setup — configures Cloudflare named tunnel for claude.ai web integration
3
-
4
- import { execSync, spawnSync } from "child_process";
5
- import os from "os";
6
- import path from "path";
7
- import fs from "fs";
8
- import readline from "readline";
9
-
10
- function ask(question) {
11
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
12
- return new Promise(resolve => rl.question(question, ans => { rl.close(); resolve(ans.trim()); }));
13
- }
14
-
15
- async function actionTunnelSetup() {
16
- console.log("\n clauth tunnel setup\n");
17
- console.log(" This wizard configures a Cloudflare named tunnel so claude.ai web");
18
- console.log(" can connect to your local clauth daemon without exposing any ports.\n");
19
-
20
- // Step 1: Check cloudflared is installed
21
- try {
22
- execSync("cloudflared --version", { stdio: "ignore" });
23
- console.log(" ✓ cloudflared detected\n");
24
- } catch {
25
- console.log(" ✗ cloudflared not found.\n");
26
- console.log(" Install it first:");
27
- console.log(" Windows: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/");
28
- console.log(" Or via winget: winget install Cloudflare.cloudflared\n");
29
- process.exit(1);
30
- }
31
-
32
- // Step 2: Authenticate with Cloudflare
33
- console.log(" Step 1/4 — Authenticate with Cloudflare");
34
- console.log(" This will open your browser to log in.\n");
35
- const doAuth = await ask(" Proceed? (y/n): ");
36
- if (doAuth.toLowerCase() !== "y") { console.log(" Aborted."); process.exit(0); }
37
-
38
- try {
39
- spawnSync("cloudflared", ["tunnel", "login"], { stdio: "inherit" });
40
- console.log(" ✓ Authenticated\n");
41
- } catch (e) {
42
- console.error(" ✗ Login failed:", e.message);
43
- process.exit(1);
44
- }
45
-
46
- // Step 3: Create tunnel
47
- console.log(" Step 2/4 — Create named tunnel");
48
- const tunnelName = await ask(" Tunnel name (default: clauth): ") || "clauth";
49
-
50
- let tunnelId;
51
- try {
52
- const result = execSync(`cloudflared tunnel create ${tunnelName}`, { encoding: "utf8" });
53
- const match = result.match(/Created tunnel (.+) with id ([a-f0-9-]+)/i);
54
- if (match) tunnelId = match[2];
55
- console.log(` ✓ Tunnel created: ${tunnelName} (${tunnelId || "see above"})\n`);
56
- } catch (e) {
57
- // Tunnel may already exist
58
- console.log(" (Tunnel may already exist — continuing)\n");
59
- try {
60
- const listResult = execSync(`cloudflared tunnel list`, { encoding: "utf8" });
61
- const lines = listResult.split("\n");
62
- for (const line of lines) {
63
- if (line.toLowerCase().includes(tunnelName.toLowerCase())) {
64
- const parts = line.trim().split(/\s+/);
65
- if (parts[0] && parts[0].includes("-")) tunnelId = parts[0];
66
- }
67
- }
68
- } catch {
69
- // ignore list errors
70
- }
71
- }
72
-
73
- // Step 4: Configure hostname
74
- console.log(" Step 3/4 — Set public hostname");
75
- const hostname = await ask(" Public hostname (e.g. clauth.yourdomain.com): ");
76
- if (!hostname) { console.log(" Hostname required."); process.exit(1); }
77
-
78
- // Write config.yml
79
- const cfDir = path.join(os.homedir(), ".cloudflared");
80
- if (!fs.existsSync(cfDir)) fs.mkdirSync(cfDir, { recursive: true });
81
-
82
- const configPath = path.join(cfDir, "config.yml");
83
- const credFile = tunnelId ? path.join(cfDir, `${tunnelId}.json`) : `<tunnel-id>.json`;
84
- const config = `tunnel: ${tunnelId || tunnelName}
85
- credentials-file: ${credFile}
86
- ingress:
87
- - hostname: ${hostname}
88
- service: http://127.0.0.1:52437
89
- - service: http_status:404
90
- `;
91
- fs.writeFileSync(configPath, config, "utf8");
92
- console.log(` ✓ Config written: ${configPath}\n`);
93
-
94
- // Route DNS
95
- console.log(" Step 4/4 — Configure DNS");
96
- try {
97
- execSync(`cloudflared tunnel route dns ${tunnelName} ${hostname}`, { stdio: "inherit" });
98
- console.log(` ✓ DNS configured: ${hostname}\n`);
99
- } catch {
100
- console.log(` ⚠ DNS route failed — add manually in Cloudflare dashboard:`);
101
- console.log(` CNAME ${hostname} → ${tunnelId}.cfargotunnel.com\n`);
102
- }
103
-
104
- // Save hostname to clauth config via api module
105
- try {
106
- const apiMod = await import("../api.js").catch(() => null);
107
- if (apiMod) {
108
- const sbUrl = (apiMod.getBaseUrl?.() || "").replace("/functions/v1/auth-vault", "");
109
- const sbKey = apiMod.getAnonKey?.();
110
- if (sbUrl && sbKey) {
111
- await fetch(
112
- `${sbUrl}/rest/v1/clauth_config`,
113
- {
114
- method: "POST",
115
- headers: {
116
- apikey: sbKey,
117
- Authorization: `Bearer ${sbKey}`,
118
- "Content-Type": "application/json",
119
- Prefer: "resolution=merge-duplicates",
120
- },
121
- body: JSON.stringify({ key: "tunnel_hostname", value: hostname }),
122
- }
123
- );
124
- console.log(` ✓ Hostname saved to clauth config: ${hostname}`);
125
- }
126
- }
127
- } catch {
128
- console.log(` ⚠ Could not save to DB. Hostname is set in config.yml — 'clauth serve' will pick it up.`);
129
- }
130
-
131
- console.log("\n Setup complete!");
132
- console.log(` Restart the daemon: clauth serve restart`);
133
- console.log(` Or use: npm run worker:restart (from clauth directory)\n`);
134
- }
135
-
136
- // clauth tunnel start — tells daemon to start the tunnel
137
- async function actionTunnelStart() {
138
- try {
139
- const r = await fetch("http://127.0.0.1:52437/tunnel/start", {
140
- method: "POST",
141
- headers: { "Content-Type": "application/json" },
142
- signal: AbortSignal.timeout(5000),
143
- });
144
- const data = await r.json().catch(() => ({}));
145
- if (!r.ok) {
146
- console.error(` ✗ ${data.error || r.statusText}`);
147
- if (r.status === 401) console.error(" Unlock the daemon first: http://127.0.0.1:52437");
148
- process.exit(1);
149
- }
150
- console.log(` ✓ ${data.message || "Tunnel starting — check status with: clauth tunnel status"}`);
151
- } catch (e) {
152
- console.error(" ✗ Daemon not running. Start it with: clauth serve");
153
- process.exit(1);
154
- }
155
- }
156
-
157
- // clauth tunnel stop — tells daemon to stop the tunnel
158
- async function actionTunnelStop() {
159
- try {
160
- const r = await fetch("http://127.0.0.1:52437/tunnel/stop", {
161
- method: "POST",
162
- headers: { "Content-Type": "application/json" },
163
- signal: AbortSignal.timeout(5000),
164
- });
165
- const data = await r.json().catch(() => ({}));
166
- if (!r.ok) {
167
- console.error(` ✗ ${data.error || r.statusText}`);
168
- process.exit(1);
169
- }
170
- console.log(" ✓ Tunnel stopped.");
171
- } catch (e) {
172
- console.error(" ✗ Daemon not running.");
173
- process.exit(1);
174
- }
175
- }
176
-
177
- // clauth tunnel status — shows current tunnel status from daemon
178
- async function actionTunnelStatus() {
179
- try {
180
- const r = await fetch("http://127.0.0.1:52437/tunnel", {
181
- signal: AbortSignal.timeout(5000),
182
- });
183
- const data = await r.json().catch(() => ({}));
184
-
185
- const icons = {
186
- live: "✓",
187
- starting: "◌",
188
- not_configured: "⚠",
189
- not_started: "○",
190
- error: "✗",
191
- missing_cloudflared: "✗",
192
- };
193
- const labels = {
194
- live: `Live — ${data.url || ""}`,
195
- starting: "Starting...",
196
- not_configured: "Not configured — run: clauth tunnel setup",
197
- not_started: "Not started — run: clauth tunnel start",
198
- error: `Error${data.error ? ": " + data.error : ""} — check cloudflared config`,
199
- missing_cloudflared: "cloudflared not installed — https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/",
200
- };
201
-
202
- const status = data.status || "unknown";
203
- console.log(`\n ${icons[status] || "?"} Tunnel: ${labels[status] || status}\n`);
204
- } catch (e) {
205
- console.error(" ✗ Daemon not running. Start it with: clauth serve");
206
- process.exit(1);
207
- }
208
- }
209
-
210
- export { actionTunnelSetup, actionTunnelStart, actionTunnelStop, actionTunnelStatus };