@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.
- package/cli/commands/serve.js +276 -4
- package/cli/fingerprint.js +9 -7
- package/cli/index.js +5 -1
- package/package.json +1 -1
package/cli/commands/serve.js
CHANGED
|
@@ -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
|
-
//
|
|
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 —
|
|
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
|
-
//
|
|
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
|
}
|
package/cli/fingerprint.js
CHANGED
|
@@ -15,16 +15,18 @@ function getMachineId() {
|
|
|
15
15
|
|
|
16
16
|
try {
|
|
17
17
|
if (platform === "win32") {
|
|
18
|
-
// Primary: BIOS UUID via
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
"
|
|
27
|
+
`${psPath} -NoProfile -Command "(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Cryptography').MachineGuid"`,
|
|
26
28
|
{ encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }
|
|
27
|
-
).
|
|
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";
|