@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/commands/serve.js +1213 -41
- package/cli/index.js +66 -11
- package/package.json +1 -1
- package/cli/commands/tunnel.js +0 -210
package/cli/index.js
CHANGED
|
@@ -532,40 +532,93 @@ program
|
|
|
532
532
|
});
|
|
533
533
|
|
|
534
534
|
// ──────────────────────────────────────────────
|
|
535
|
-
// clauth tunnel
|
|
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("
|
|
542
|
+
.description("Open the tunnel setup wizard in your browser")
|
|
542
543
|
.action(async () => {
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
552
|
-
|
|
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
|
-
|
|
560
|
-
|
|
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
|
-
|
|
568
|
-
|
|
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 +
|
|
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
package/cli/commands/tunnel.js
DELETED
|
@@ -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 };
|