@lifeaitools/clauth 1.30.2 → 1.30.4
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/.clauth-skill/SKILL.md +111 -111
- package/cli/api.classify.test.js +75 -75
- package/cli/assets/codevelop/launcher-active.cmd.template +20 -20
- package/cli/assets/codevelop/launcher-static.cmd.template +7 -7
- package/cli/assets/codevelop/windows-terminal.profiles.json +48 -48
- package/cli/assets/watchdog.ps1 +42 -42
- package/cli/commands/agent-cron.js +396 -396
- package/cli/commands/agent-pool.js +22 -6
- package/cli/commands/codevelop.js +1190 -1190
- package/cli/commands/doctor.js +302 -302
- package/cli/commands/install.js +10 -10
- package/cli/commands/invite.js +175 -175
- package/cli/commands/join.js +179 -179
- package/cli/commands/npm.js +182 -182
- package/cli/commands/scrub.js +327 -327
- package/cli/commands/scrub.test.js +115 -115
- package/cli/commands/serve.js +1070 -144
- package/cli/commands/watchdog.js +209 -209
- package/cli/conf-path.js +21 -21
- package/cli/enrollment-script.js +62 -0
- package/cli/index.js +1050 -1089
- package/cli/lib/fs-git.js +282 -282
- package/cli/recovery.js +101 -101
- package/cli/studio-debug.js +1095 -1087
- package/cli/watchdog-registry.js +209 -209
- package/cli/watchdog-registry.test.js +89 -89
- package/install.ps1 +21 -21
- package/package.json +68 -68
- package/scripts/bin/bootstrap-linux +0 -0
- package/scripts/bin/bootstrap-macos +0 -0
- package/scripts/bin/bootstrap-win.exe +0 -0
- package/supabase/migrations/001_clauth_schema.sql +12 -12
- package/supabase/migrations/003_clauth_config.sql +13 -13
- package/supabase/migrations/003_machine_enrollments.sql +39 -39
- package/cli/commands/serve/tools/fs.js +0 -1055
package/cli/commands/doctor.js
CHANGED
|
@@ -1,302 +1,302 @@
|
|
|
1
|
-
// cli/commands/doctor.js
|
|
2
|
-
// clauth doctor — check all prerequisites for a healthy installation
|
|
3
|
-
// Checks: Node version, cloudflared, network, Supabase connectivity, config, service status
|
|
4
|
-
|
|
5
|
-
import os from "os";
|
|
6
|
-
import fs from "fs";
|
|
7
|
-
import path from "path";
|
|
8
|
-
import { execSync } from "child_process";
|
|
9
|
-
import { fileURLToPath } from "url";
|
|
10
|
-
import chalk from "chalk";
|
|
11
|
-
import ora from "ora";
|
|
12
|
-
import Conf from "conf";
|
|
13
|
-
import { getConfOptions } from "../conf-path.js";
|
|
14
|
-
import { getMachineHash } from "../fingerprint.js";
|
|
15
|
-
|
|
16
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8"));
|
|
18
|
-
|
|
19
|
-
const CHECK = chalk.green("\u2713");
|
|
20
|
-
const CROSS = chalk.red("\u2717");
|
|
21
|
-
const WARN = chalk.yellow("!");
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Run a shell command and return stdout, or null on failure.
|
|
25
|
-
*/
|
|
26
|
-
function execSafe(cmd, timeoutMs = 10000) {
|
|
27
|
-
try {
|
|
28
|
-
return execSync(cmd, {
|
|
29
|
-
encoding: "utf8",
|
|
30
|
-
timeout: timeoutMs,
|
|
31
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
32
|
-
}).trim();
|
|
33
|
-
} catch {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export async function runDoctor() {
|
|
39
|
-
const results = [];
|
|
40
|
-
const platform = os.platform();
|
|
41
|
-
|
|
42
|
-
console.log(chalk.cyan(`\n clauth doctor v${pkg.version}\n`));
|
|
43
|
-
console.log(chalk.gray(` Platform: ${platform} (${os.arch()})`));
|
|
44
|
-
console.log(chalk.gray(` Node: ${process.version}`));
|
|
45
|
-
console.log();
|
|
46
|
-
|
|
47
|
-
// ── 1. Node version ──────────────────────────────────────
|
|
48
|
-
{
|
|
49
|
-
const major = parseInt(process.versions.node.split(".")[0], 10);
|
|
50
|
-
if (major >= 18) {
|
|
51
|
-
results.push(true);
|
|
52
|
-
console.log(` ${CHECK} Node.js ${process.versions.node} (>= 18 required)`);
|
|
53
|
-
} else {
|
|
54
|
-
results.push(false);
|
|
55
|
-
console.log(` ${CROSS} Node.js ${process.versions.node} — version 18+ required`);
|
|
56
|
-
console.log(chalk.gray(" Fix: Install Node.js 18+ from https://nodejs.org/"));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ── 2. cloudflared ───────────────────────────────────────
|
|
61
|
-
{
|
|
62
|
-
// Check common Windows paths if not on PATH
|
|
63
|
-
let cfVersion = execSafe("cloudflared --version");
|
|
64
|
-
if (!cfVersion && platform === "win32") {
|
|
65
|
-
const winPaths = [
|
|
66
|
-
path.join(process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)", "cloudflared", "cloudflared.exe"),
|
|
67
|
-
path.join(process.env.ProgramFiles || "C:\\Program Files", "cloudflared", "cloudflared.exe"),
|
|
68
|
-
path.join(os.homedir(), "scoop", "shims", "cloudflared.exe"),
|
|
69
|
-
"C:\\ProgramData\\chocolatey\\bin\\cloudflared.exe",
|
|
70
|
-
];
|
|
71
|
-
for (const p of winPaths) {
|
|
72
|
-
try { if (fs.statSync(p).isFile()) { cfVersion = execSafe(`"${p}" --version`); break; } } catch {}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (cfVersion) {
|
|
76
|
-
results.push(true);
|
|
77
|
-
console.log(` ${CHECK} cloudflared installed (${cfVersion.split("\n")[0]})`);
|
|
78
|
-
} else {
|
|
79
|
-
results.push(false);
|
|
80
|
-
console.log(` ${CROSS} cloudflared not found`);
|
|
81
|
-
if (platform === "win32") {
|
|
82
|
-
console.log(chalk.gray(" Fix: winget install Cloudflare.cloudflared"));
|
|
83
|
-
console.log(chalk.gray(" or: choco install cloudflared -y"));
|
|
84
|
-
} else if (platform === "darwin") {
|
|
85
|
-
console.log(chalk.gray(" Fix: brew install cloudflared"));
|
|
86
|
-
} else {
|
|
87
|
-
console.log(chalk.gray(" Fix: curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared"));
|
|
88
|
-
console.log(chalk.gray(" or: snap install cloudflared"));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ── 3. Config exists ─────────────────────────────────────
|
|
94
|
-
{
|
|
95
|
-
const config = new Conf(getConfOptions());
|
|
96
|
-
const supaUrl = config.get("supabase_url");
|
|
97
|
-
const anonKey = config.get("supabase_anon_key");
|
|
98
|
-
|
|
99
|
-
if (supaUrl && anonKey) {
|
|
100
|
-
results.push(true);
|
|
101
|
-
console.log(` ${CHECK} Config found (Supabase URL + anon key saved)`);
|
|
102
|
-
console.log(chalk.gray(` URL: ${supaUrl}`));
|
|
103
|
-
} else if (supaUrl && !anonKey) {
|
|
104
|
-
results.push(false);
|
|
105
|
-
console.log(` ${CROSS} Config incomplete — Supabase anon key missing`);
|
|
106
|
-
console.log(chalk.gray(" Fix: Run 'clauth install' to complete setup"));
|
|
107
|
-
} else {
|
|
108
|
-
results.push(false);
|
|
109
|
-
console.log(` ${CROSS} Config not found — clauth not installed`);
|
|
110
|
-
console.log(chalk.gray(" Fix: Run 'clauth install' to set up your vault"));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ── 4. Network: can reach Supabase ───────────────────────
|
|
115
|
-
{
|
|
116
|
-
const config = new Conf(getConfOptions());
|
|
117
|
-
const supaUrl = config.get("supabase_url");
|
|
118
|
-
|
|
119
|
-
if (supaUrl) {
|
|
120
|
-
try {
|
|
121
|
-
const controller = new AbortController();
|
|
122
|
-
const timeout = setTimeout(() => controller.abort(), 8000);
|
|
123
|
-
const res = await fetch(`${supaUrl}/rest/v1/`, {
|
|
124
|
-
method: "HEAD",
|
|
125
|
-
signal: controller.signal,
|
|
126
|
-
headers: { apikey: config.get("supabase_anon_key") || "" },
|
|
127
|
-
});
|
|
128
|
-
clearTimeout(timeout);
|
|
129
|
-
if (res.ok || res.status === 401 || res.status === 400) {
|
|
130
|
-
results.push(true);
|
|
131
|
-
console.log(` ${CHECK} Network — Supabase reachable (HTTP ${res.status})`);
|
|
132
|
-
} else {
|
|
133
|
-
results.push(false);
|
|
134
|
-
console.log(` ${CROSS} Network — Supabase returned HTTP ${res.status}`);
|
|
135
|
-
}
|
|
136
|
-
} catch (err) {
|
|
137
|
-
results.push(false);
|
|
138
|
-
console.log(` ${CROSS} Network — cannot reach Supabase: ${err.message}`);
|
|
139
|
-
console.log(chalk.gray(" Check your internet connection"));
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
results.push(null);
|
|
143
|
-
console.log(` ${WARN} Network — skipped (no Supabase URL configured)`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ── 5. Machine fingerprint ───────────────────────────────
|
|
148
|
-
{
|
|
149
|
-
try {
|
|
150
|
-
const hash = getMachineHash();
|
|
151
|
-
results.push(true);
|
|
152
|
-
console.log(` ${CHECK} Machine fingerprint: ${hash.slice(0, 16)}...`);
|
|
153
|
-
} catch (err) {
|
|
154
|
-
results.push(false);
|
|
155
|
-
console.log(` ${CROSS} Machine fingerprint failed: ${err.message}`);
|
|
156
|
-
console.log(chalk.gray(" Fix: Run 'clauth setup' to register this machine"));
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ── 6. Daemon status ─────────────────────────────────────
|
|
161
|
-
{
|
|
162
|
-
try {
|
|
163
|
-
const controller = new AbortController();
|
|
164
|
-
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
165
|
-
const res = await fetch("http://127.0.0.1:52437/ping", { signal: controller.signal });
|
|
166
|
-
clearTimeout(timeout);
|
|
167
|
-
if (res.ok) {
|
|
168
|
-
const data = await res.json();
|
|
169
|
-
results.push(true);
|
|
170
|
-
const locked = data.locked ? chalk.yellow(" (locked)") : chalk.green(" (unlocked)");
|
|
171
|
-
console.log(` ${CHECK} Daemon running on port ${data.port || 52437}${locked}`);
|
|
172
|
-
} else {
|
|
173
|
-
results.push(false);
|
|
174
|
-
console.log(` ${CROSS} Daemon responded with HTTP ${res.status}`);
|
|
175
|
-
}
|
|
176
|
-
} catch {
|
|
177
|
-
results.push(false);
|
|
178
|
-
console.log(` ${CROSS} Daemon not running`);
|
|
179
|
-
console.log(chalk.gray(" Fix: clauth serve start"));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ── 7. Autostart / service registration ──────────────────
|
|
184
|
-
{
|
|
185
|
-
if (platform === "win32") {
|
|
186
|
-
const taskOutput = execSafe('schtasks /query /tn "ClauthAutostart" 2>&1');
|
|
187
|
-
if (taskOutput && !taskOutput.includes("ERROR")) {
|
|
188
|
-
results.push(true);
|
|
189
|
-
console.log(` ${CHECK} Windows Scheduled Task registered (ClauthAutostart)`);
|
|
190
|
-
} else {
|
|
191
|
-
results.push(null);
|
|
192
|
-
console.log(` ${WARN} Windows Scheduled Task not registered`);
|
|
193
|
-
console.log(chalk.gray(" Optional: clauth serve install (auto-start on login)"));
|
|
194
|
-
}
|
|
195
|
-
} else if (platform === "darwin") {
|
|
196
|
-
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", "com.lifeai.clauth.plist");
|
|
197
|
-
if (fs.existsSync(plistPath)) {
|
|
198
|
-
results.push(true);
|
|
199
|
-
console.log(` ${CHECK} macOS LaunchAgent registered`);
|
|
200
|
-
} else {
|
|
201
|
-
results.push(null);
|
|
202
|
-
console.log(` ${WARN} macOS LaunchAgent not registered`);
|
|
203
|
-
console.log(chalk.gray(" Optional: clauth serve install (auto-start on login)"));
|
|
204
|
-
}
|
|
205
|
-
} else {
|
|
206
|
-
const serviceFile = path.join(os.homedir(), ".config", "systemd", "user", "clauth.service");
|
|
207
|
-
if (fs.existsSync(serviceFile)) {
|
|
208
|
-
results.push(true);
|
|
209
|
-
console.log(` ${CHECK} systemd user service registered`);
|
|
210
|
-
} else {
|
|
211
|
-
results.push(null);
|
|
212
|
-
console.log(` ${WARN} systemd user service not registered`);
|
|
213
|
-
console.log(chalk.gray(" Optional: clauth serve install (auto-start on login)"));
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ── 8. Tunnel configuration ──────────────────────────────
|
|
219
|
-
{
|
|
220
|
-
const config = new Conf(getConfOptions());
|
|
221
|
-
const tunnelHostname = config.get("tunnel_hostname");
|
|
222
|
-
if (tunnelHostname) {
|
|
223
|
-
results.push(true);
|
|
224
|
-
console.log(` ${CHECK} Tunnel configured: ${tunnelHostname}`);
|
|
225
|
-
} else {
|
|
226
|
-
results.push(null);
|
|
227
|
-
console.log(` ${WARN} Tunnel not configured (optional for remote MCP access)`);
|
|
228
|
-
console.log(chalk.gray(" Optional: clauth serve install --tunnel <hostname>"));
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ── Summary ──────────────────────────────────────────────
|
|
233
|
-
console.log();
|
|
234
|
-
const failed = results.filter(r => r === false).length;
|
|
235
|
-
const warnings = results.filter(r => r === null).length;
|
|
236
|
-
const passed = results.filter(r => r === true).length;
|
|
237
|
-
|
|
238
|
-
if (failed === 0) {
|
|
239
|
-
console.log(chalk.green(` All ${passed} checks passed.`));
|
|
240
|
-
if (warnings > 0) {
|
|
241
|
-
console.log(chalk.yellow(` ${warnings} optional recommendation${warnings > 1 ? "s" : ""}.`));
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
console.log(chalk.red(` ${failed} issue${failed > 1 ? "s" : ""} found.`));
|
|
245
|
-
if (passed > 0) console.log(chalk.green(` ${passed} check${passed > 1 ? "s" : ""} passed.`));
|
|
246
|
-
if (warnings > 0) console.log(chalk.yellow(` ${warnings} optional.`));
|
|
247
|
-
console.log(chalk.gray("\n Run the suggested fix commands above to resolve issues."));
|
|
248
|
-
}
|
|
249
|
-
console.log();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Install cloudflared on the current platform.
|
|
254
|
-
* Exported so other commands (e.g., serve install) can offer auto-install.
|
|
255
|
-
*/
|
|
256
|
-
export async function installCloudflared() {
|
|
257
|
-
const platform = os.platform();
|
|
258
|
-
const spinner = ora("Installing cloudflared...").start();
|
|
259
|
-
|
|
260
|
-
try {
|
|
261
|
-
if (platform === "win32") {
|
|
262
|
-
// Try winget first
|
|
263
|
-
try {
|
|
264
|
-
execSync(
|
|
265
|
-
"winget install Cloudflare.cloudflared --accept-source-agreements --accept-package-agreements",
|
|
266
|
-
{ stdio: "inherit", timeout: 120000 }
|
|
267
|
-
);
|
|
268
|
-
spinner.succeed(chalk.green("cloudflared installed via winget"));
|
|
269
|
-
return true;
|
|
270
|
-
} catch {
|
|
271
|
-
spinner.text = "winget failed, trying chocolatey...";
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Try chocolatey
|
|
275
|
-
try {
|
|
276
|
-
execSync("choco install cloudflared -y", { stdio: "inherit", timeout: 120000 });
|
|
277
|
-
spinner.succeed(chalk.green("cloudflared installed via chocolatey"));
|
|
278
|
-
return true;
|
|
279
|
-
} catch {
|
|
280
|
-
spinner.fail(chalk.red("Could not install cloudflared automatically"));
|
|
281
|
-
console.log(chalk.gray(" Download manually: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"));
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
} else if (platform === "darwin") {
|
|
285
|
-
execSync("brew install cloudflared", { stdio: "inherit", timeout: 120000 });
|
|
286
|
-
spinner.succeed(chalk.green("cloudflared installed via Homebrew"));
|
|
287
|
-
return true;
|
|
288
|
-
} else {
|
|
289
|
-
// Linux
|
|
290
|
-
execSync(
|
|
291
|
-
"curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared",
|
|
292
|
-
{ stdio: "inherit", timeout: 120000 }
|
|
293
|
-
);
|
|
294
|
-
spinner.succeed(chalk.green("cloudflared installed"));
|
|
295
|
-
return true;
|
|
296
|
-
}
|
|
297
|
-
} catch (err) {
|
|
298
|
-
spinner.fail(chalk.red(`cloudflared installation failed: ${err.message}`));
|
|
299
|
-
console.log(chalk.gray(" Install manually: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"));
|
|
300
|
-
return false;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
1
|
+
// cli/commands/doctor.js
|
|
2
|
+
// clauth doctor — check all prerequisites for a healthy installation
|
|
3
|
+
// Checks: Node version, cloudflared, network, Supabase connectivity, config, service status
|
|
4
|
+
|
|
5
|
+
import os from "os";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import ora from "ora";
|
|
12
|
+
import Conf from "conf";
|
|
13
|
+
import { getConfOptions } from "../conf-path.js";
|
|
14
|
+
import { getMachineHash } from "../fingerprint.js";
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8"));
|
|
18
|
+
|
|
19
|
+
const CHECK = chalk.green("\u2713");
|
|
20
|
+
const CROSS = chalk.red("\u2717");
|
|
21
|
+
const WARN = chalk.yellow("!");
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run a shell command and return stdout, or null on failure.
|
|
25
|
+
*/
|
|
26
|
+
function execSafe(cmd, timeoutMs = 10000) {
|
|
27
|
+
try {
|
|
28
|
+
return execSync(cmd, {
|
|
29
|
+
encoding: "utf8",
|
|
30
|
+
timeout: timeoutMs,
|
|
31
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
32
|
+
}).trim();
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function runDoctor() {
|
|
39
|
+
const results = [];
|
|
40
|
+
const platform = os.platform();
|
|
41
|
+
|
|
42
|
+
console.log(chalk.cyan(`\n clauth doctor v${pkg.version}\n`));
|
|
43
|
+
console.log(chalk.gray(` Platform: ${platform} (${os.arch()})`));
|
|
44
|
+
console.log(chalk.gray(` Node: ${process.version}`));
|
|
45
|
+
console.log();
|
|
46
|
+
|
|
47
|
+
// ── 1. Node version ──────────────────────────────────────
|
|
48
|
+
{
|
|
49
|
+
const major = parseInt(process.versions.node.split(".")[0], 10);
|
|
50
|
+
if (major >= 18) {
|
|
51
|
+
results.push(true);
|
|
52
|
+
console.log(` ${CHECK} Node.js ${process.versions.node} (>= 18 required)`);
|
|
53
|
+
} else {
|
|
54
|
+
results.push(false);
|
|
55
|
+
console.log(` ${CROSS} Node.js ${process.versions.node} — version 18+ required`);
|
|
56
|
+
console.log(chalk.gray(" Fix: Install Node.js 18+ from https://nodejs.org/"));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── 2. cloudflared ───────────────────────────────────────
|
|
61
|
+
{
|
|
62
|
+
// Check common Windows paths if not on PATH
|
|
63
|
+
let cfVersion = execSafe("cloudflared --version");
|
|
64
|
+
if (!cfVersion && platform === "win32") {
|
|
65
|
+
const winPaths = [
|
|
66
|
+
path.join(process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)", "cloudflared", "cloudflared.exe"),
|
|
67
|
+
path.join(process.env.ProgramFiles || "C:\\Program Files", "cloudflared", "cloudflared.exe"),
|
|
68
|
+
path.join(os.homedir(), "scoop", "shims", "cloudflared.exe"),
|
|
69
|
+
"C:\\ProgramData\\chocolatey\\bin\\cloudflared.exe",
|
|
70
|
+
];
|
|
71
|
+
for (const p of winPaths) {
|
|
72
|
+
try { if (fs.statSync(p).isFile()) { cfVersion = execSafe(`"${p}" --version`); break; } } catch {}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (cfVersion) {
|
|
76
|
+
results.push(true);
|
|
77
|
+
console.log(` ${CHECK} cloudflared installed (${cfVersion.split("\n")[0]})`);
|
|
78
|
+
} else {
|
|
79
|
+
results.push(false);
|
|
80
|
+
console.log(` ${CROSS} cloudflared not found`);
|
|
81
|
+
if (platform === "win32") {
|
|
82
|
+
console.log(chalk.gray(" Fix: winget install Cloudflare.cloudflared"));
|
|
83
|
+
console.log(chalk.gray(" or: choco install cloudflared -y"));
|
|
84
|
+
} else if (platform === "darwin") {
|
|
85
|
+
console.log(chalk.gray(" Fix: brew install cloudflared"));
|
|
86
|
+
} else {
|
|
87
|
+
console.log(chalk.gray(" Fix: curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared"));
|
|
88
|
+
console.log(chalk.gray(" or: snap install cloudflared"));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── 3. Config exists ─────────────────────────────────────
|
|
94
|
+
{
|
|
95
|
+
const config = new Conf(getConfOptions());
|
|
96
|
+
const supaUrl = config.get("supabase_url");
|
|
97
|
+
const anonKey = config.get("supabase_anon_key");
|
|
98
|
+
|
|
99
|
+
if (supaUrl && anonKey) {
|
|
100
|
+
results.push(true);
|
|
101
|
+
console.log(` ${CHECK} Config found (Supabase URL + anon key saved)`);
|
|
102
|
+
console.log(chalk.gray(` URL: ${supaUrl}`));
|
|
103
|
+
} else if (supaUrl && !anonKey) {
|
|
104
|
+
results.push(false);
|
|
105
|
+
console.log(` ${CROSS} Config incomplete — Supabase anon key missing`);
|
|
106
|
+
console.log(chalk.gray(" Fix: Run 'clauth install' to complete setup"));
|
|
107
|
+
} else {
|
|
108
|
+
results.push(false);
|
|
109
|
+
console.log(` ${CROSS} Config not found — clauth not installed`);
|
|
110
|
+
console.log(chalk.gray(" Fix: Run 'clauth install' to set up your vault"));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── 4. Network: can reach Supabase ───────────────────────
|
|
115
|
+
{
|
|
116
|
+
const config = new Conf(getConfOptions());
|
|
117
|
+
const supaUrl = config.get("supabase_url");
|
|
118
|
+
|
|
119
|
+
if (supaUrl) {
|
|
120
|
+
try {
|
|
121
|
+
const controller = new AbortController();
|
|
122
|
+
const timeout = setTimeout(() => controller.abort(), 8000);
|
|
123
|
+
const res = await fetch(`${supaUrl}/rest/v1/`, {
|
|
124
|
+
method: "HEAD",
|
|
125
|
+
signal: controller.signal,
|
|
126
|
+
headers: { apikey: config.get("supabase_anon_key") || "" },
|
|
127
|
+
});
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
if (res.ok || res.status === 401 || res.status === 400) {
|
|
130
|
+
results.push(true);
|
|
131
|
+
console.log(` ${CHECK} Network — Supabase reachable (HTTP ${res.status})`);
|
|
132
|
+
} else {
|
|
133
|
+
results.push(false);
|
|
134
|
+
console.log(` ${CROSS} Network — Supabase returned HTTP ${res.status}`);
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
results.push(false);
|
|
138
|
+
console.log(` ${CROSS} Network — cannot reach Supabase: ${err.message}`);
|
|
139
|
+
console.log(chalk.gray(" Check your internet connection"));
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
results.push(null);
|
|
143
|
+
console.log(` ${WARN} Network — skipped (no Supabase URL configured)`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── 5. Machine fingerprint ───────────────────────────────
|
|
148
|
+
{
|
|
149
|
+
try {
|
|
150
|
+
const hash = getMachineHash();
|
|
151
|
+
results.push(true);
|
|
152
|
+
console.log(` ${CHECK} Machine fingerprint: ${hash.slice(0, 16)}...`);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
results.push(false);
|
|
155
|
+
console.log(` ${CROSS} Machine fingerprint failed: ${err.message}`);
|
|
156
|
+
console.log(chalk.gray(" Fix: Run 'clauth setup' to register this machine"));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── 6. Daemon status ─────────────────────────────────────
|
|
161
|
+
{
|
|
162
|
+
try {
|
|
163
|
+
const controller = new AbortController();
|
|
164
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
165
|
+
const res = await fetch("http://127.0.0.1:52437/ping", { signal: controller.signal });
|
|
166
|
+
clearTimeout(timeout);
|
|
167
|
+
if (res.ok) {
|
|
168
|
+
const data = await res.json();
|
|
169
|
+
results.push(true);
|
|
170
|
+
const locked = data.locked ? chalk.yellow(" (locked)") : chalk.green(" (unlocked)");
|
|
171
|
+
console.log(` ${CHECK} Daemon running on port ${data.port || 52437}${locked}`);
|
|
172
|
+
} else {
|
|
173
|
+
results.push(false);
|
|
174
|
+
console.log(` ${CROSS} Daemon responded with HTTP ${res.status}`);
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
results.push(false);
|
|
178
|
+
console.log(` ${CROSS} Daemon not running`);
|
|
179
|
+
console.log(chalk.gray(" Fix: clauth serve start"));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── 7. Autostart / service registration ──────────────────
|
|
184
|
+
{
|
|
185
|
+
if (platform === "win32") {
|
|
186
|
+
const taskOutput = execSafe('schtasks /query /tn "ClauthAutostart" 2>&1');
|
|
187
|
+
if (taskOutput && !taskOutput.includes("ERROR")) {
|
|
188
|
+
results.push(true);
|
|
189
|
+
console.log(` ${CHECK} Windows Scheduled Task registered (ClauthAutostart)`);
|
|
190
|
+
} else {
|
|
191
|
+
results.push(null);
|
|
192
|
+
console.log(` ${WARN} Windows Scheduled Task not registered`);
|
|
193
|
+
console.log(chalk.gray(" Optional: clauth serve install (auto-start on login)"));
|
|
194
|
+
}
|
|
195
|
+
} else if (platform === "darwin") {
|
|
196
|
+
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", "com.lifeai.clauth.plist");
|
|
197
|
+
if (fs.existsSync(plistPath)) {
|
|
198
|
+
results.push(true);
|
|
199
|
+
console.log(` ${CHECK} macOS LaunchAgent registered`);
|
|
200
|
+
} else {
|
|
201
|
+
results.push(null);
|
|
202
|
+
console.log(` ${WARN} macOS LaunchAgent not registered`);
|
|
203
|
+
console.log(chalk.gray(" Optional: clauth serve install (auto-start on login)"));
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
const serviceFile = path.join(os.homedir(), ".config", "systemd", "user", "clauth.service");
|
|
207
|
+
if (fs.existsSync(serviceFile)) {
|
|
208
|
+
results.push(true);
|
|
209
|
+
console.log(` ${CHECK} systemd user service registered`);
|
|
210
|
+
} else {
|
|
211
|
+
results.push(null);
|
|
212
|
+
console.log(` ${WARN} systemd user service not registered`);
|
|
213
|
+
console.log(chalk.gray(" Optional: clauth serve install (auto-start on login)"));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── 8. Tunnel configuration ──────────────────────────────
|
|
219
|
+
{
|
|
220
|
+
const config = new Conf(getConfOptions());
|
|
221
|
+
const tunnelHostname = config.get("tunnel_hostname");
|
|
222
|
+
if (tunnelHostname) {
|
|
223
|
+
results.push(true);
|
|
224
|
+
console.log(` ${CHECK} Tunnel configured: ${tunnelHostname}`);
|
|
225
|
+
} else {
|
|
226
|
+
results.push(null);
|
|
227
|
+
console.log(` ${WARN} Tunnel not configured (optional for remote MCP access)`);
|
|
228
|
+
console.log(chalk.gray(" Optional: clauth serve install --tunnel <hostname>"));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── Summary ──────────────────────────────────────────────
|
|
233
|
+
console.log();
|
|
234
|
+
const failed = results.filter(r => r === false).length;
|
|
235
|
+
const warnings = results.filter(r => r === null).length;
|
|
236
|
+
const passed = results.filter(r => r === true).length;
|
|
237
|
+
|
|
238
|
+
if (failed === 0) {
|
|
239
|
+
console.log(chalk.green(` All ${passed} checks passed.`));
|
|
240
|
+
if (warnings > 0) {
|
|
241
|
+
console.log(chalk.yellow(` ${warnings} optional recommendation${warnings > 1 ? "s" : ""}.`));
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
console.log(chalk.red(` ${failed} issue${failed > 1 ? "s" : ""} found.`));
|
|
245
|
+
if (passed > 0) console.log(chalk.green(` ${passed} check${passed > 1 ? "s" : ""} passed.`));
|
|
246
|
+
if (warnings > 0) console.log(chalk.yellow(` ${warnings} optional.`));
|
|
247
|
+
console.log(chalk.gray("\n Run the suggested fix commands above to resolve issues."));
|
|
248
|
+
}
|
|
249
|
+
console.log();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Install cloudflared on the current platform.
|
|
254
|
+
* Exported so other commands (e.g., serve install) can offer auto-install.
|
|
255
|
+
*/
|
|
256
|
+
export async function installCloudflared() {
|
|
257
|
+
const platform = os.platform();
|
|
258
|
+
const spinner = ora("Installing cloudflared...").start();
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
if (platform === "win32") {
|
|
262
|
+
// Try winget first
|
|
263
|
+
try {
|
|
264
|
+
execSync(
|
|
265
|
+
"winget install Cloudflare.cloudflared --accept-source-agreements --accept-package-agreements",
|
|
266
|
+
{ stdio: "inherit", timeout: 120000 }
|
|
267
|
+
);
|
|
268
|
+
spinner.succeed(chalk.green("cloudflared installed via winget"));
|
|
269
|
+
return true;
|
|
270
|
+
} catch {
|
|
271
|
+
spinner.text = "winget failed, trying chocolatey...";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Try chocolatey
|
|
275
|
+
try {
|
|
276
|
+
execSync("choco install cloudflared -y", { stdio: "inherit", timeout: 120000 });
|
|
277
|
+
spinner.succeed(chalk.green("cloudflared installed via chocolatey"));
|
|
278
|
+
return true;
|
|
279
|
+
} catch {
|
|
280
|
+
spinner.fail(chalk.red("Could not install cloudflared automatically"));
|
|
281
|
+
console.log(chalk.gray(" Download manually: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"));
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
} else if (platform === "darwin") {
|
|
285
|
+
execSync("brew install cloudflared", { stdio: "inherit", timeout: 120000 });
|
|
286
|
+
spinner.succeed(chalk.green("cloudflared installed via Homebrew"));
|
|
287
|
+
return true;
|
|
288
|
+
} else {
|
|
289
|
+
// Linux
|
|
290
|
+
execSync(
|
|
291
|
+
"curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared",
|
|
292
|
+
{ stdio: "inherit", timeout: 120000 }
|
|
293
|
+
);
|
|
294
|
+
spinner.succeed(chalk.green("cloudflared installed"));
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
} catch (err) {
|
|
298
|
+
spinner.fail(chalk.red(`cloudflared installation failed: ${err.message}`));
|
|
299
|
+
console.log(chalk.gray(" Install manually: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"));
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
package/cli/commands/install.js
CHANGED
|
@@ -127,11 +127,11 @@ export async function runInstall(opts = {}) {
|
|
|
127
127
|
|
|
128
128
|
// Run migrations
|
|
129
129
|
const s4 = ora('Running database migrations...').start();
|
|
130
|
-
const migrations = [
|
|
131
|
-
{ name: '001_clauth_schema', file: 'supabase/migrations/001_clauth_schema.sql' },
|
|
132
|
-
{ name: '002_vault_helpers', file: 'supabase/migrations/002_vault_helpers.sql' },
|
|
133
|
-
{ name: '003_machine_enrollments', file: 'supabase/migrations/003_machine_enrollments.sql' },
|
|
134
|
-
];
|
|
130
|
+
const migrations = [
|
|
131
|
+
{ name: '001_clauth_schema', file: 'supabase/migrations/001_clauth_schema.sql' },
|
|
132
|
+
{ name: '002_vault_helpers', file: 'supabase/migrations/002_vault_helpers.sql' },
|
|
133
|
+
{ name: '003_machine_enrollments', file: 'supabase/migrations/003_machine_enrollments.sql' },
|
|
134
|
+
];
|
|
135
135
|
for (const m of migrations) {
|
|
136
136
|
const sql = readFileSync(join(ROOT, m.file), 'utf8');
|
|
137
137
|
try {
|
|
@@ -252,11 +252,11 @@ export async function runInstall(opts = {}) {
|
|
|
252
252
|
|
|
253
253
|
// ── Step 4: Run migrations ────────────────
|
|
254
254
|
const s4 = ora('Running database migrations...').start();
|
|
255
|
-
const migrations = [
|
|
256
|
-
{ name: '001_clauth_schema', file: 'supabase/migrations/001_clauth_schema.sql' },
|
|
257
|
-
{ name: '002_vault_helpers', file: 'supabase/migrations/002_vault_helpers.sql' },
|
|
258
|
-
{ name: '003_machine_enrollments', file: 'supabase/migrations/003_machine_enrollments.sql' },
|
|
259
|
-
];
|
|
255
|
+
const migrations = [
|
|
256
|
+
{ name: '001_clauth_schema', file: 'supabase/migrations/001_clauth_schema.sql' },
|
|
257
|
+
{ name: '002_vault_helpers', file: 'supabase/migrations/002_vault_helpers.sql' },
|
|
258
|
+
{ name: '003_machine_enrollments', file: 'supabase/migrations/003_machine_enrollments.sql' },
|
|
259
|
+
];
|
|
260
260
|
for (const m of migrations) {
|
|
261
261
|
const sql = readFileSync(join(ROOT, m.file), 'utf8');
|
|
262
262
|
try {
|