@lifeaitools/clauth 1.30.23 → 1.30.25
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 +306 -275
- package/.clauth-skill/references/operator-guide.md +175 -148
- package/README.md +363 -315
- 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 +1962 -1962
- 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/ops-install.js +211 -0
- package/cli/commands/ops.js +69 -0
- package/cli/commands/scrub.js +327 -327
- package/cli/commands/scrub.test.js +115 -115
- package/cli/commands/serve.js +381 -98
- package/cli/commands/watchdog.js +209 -209
- package/cli/conf-path.js +21 -21
- package/cli/enrollment-script.js +82 -82
- package/cli/fingerprint.js +143 -143
- package/cli/index.js +1073 -1053
- package/cli/lib/fs-git.js +282 -282
- package/cli/ops/coolify-adapter.js +80 -0
- package/cli/ops/deployment-adapter.js +63 -0
- package/cli/ops/job-store.js +116 -0
- package/cli/ops/operation-policy.js +51 -0
- package/cli/ops/pm2-adapter.js +128 -0
- package/cli/ops/serialized-executor.js +9 -0
- package/cli/recovery.js +101 -101
- package/cli/studio-debug.js +1095 -1095
- package/cli/supervisor-registry.js +594 -589
- package/cli/supervisor-registry.test.js +397 -397
- package/cli/supervisor-ui.test.js +5 -83
- package/cli/watchdog-registry.js +237 -209
- package/cli/watchdog-registry.test.js +112 -89
- package/install.ps1 +21 -21
- package/package.json +4 -2
- 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/served-script-syntax.test.mjs +0 -54
package/cli/index.js
CHANGED
|
@@ -1,1053 +1,1073 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// cli/index.js — clauth entry point
|
|
3
|
-
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
import ora from "ora";
|
|
7
|
-
import inquirer from "inquirer";
|
|
8
|
-
import Conf from "conf";
|
|
9
|
-
import { getConfOptions } from "./conf-path.js";
|
|
10
|
-
import { getMachineHash, deriveToken, deriveSeedHash } from "./fingerprint.js";
|
|
11
|
-
import * as api from "./api.js";
|
|
12
|
-
import { writeCredentialWithRecovery } from "./recovery.js";
|
|
13
|
-
import os from "os";
|
|
14
|
-
import fs from "fs";
|
|
15
|
-
import path from "path";
|
|
16
|
-
import { writeEnrollmentScript } from "./enrollment-script.js";
|
|
17
|
-
|
|
18
|
-
const config = new Conf(getConfOptions());
|
|
19
|
-
const VERSION = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
20
|
-
|
|
21
|
-
// ============================================================
|
|
22
|
-
// Password prompt helper
|
|
23
|
-
// ============================================================
|
|
24
|
-
async function promptPassword(message = "clauth password") {
|
|
25
|
-
const { pw } = await inquirer.prompt([{
|
|
26
|
-
type: "password",
|
|
27
|
-
name: "pw",
|
|
28
|
-
message,
|
|
29
|
-
mask: "*",
|
|
30
|
-
validate: v => v.length >= 8 || "Password must be at least 8 characters"
|
|
31
|
-
}]);
|
|
32
|
-
return pw;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ============================================================
|
|
36
|
-
// Auth helper — get pw + derive token
|
|
37
|
-
// ============================================================
|
|
38
|
-
async function getAuth(pw) {
|
|
39
|
-
const password = pw || await promptPassword();
|
|
40
|
-
const machineHash = getMachineHash();
|
|
41
|
-
const { token, timestamp } = deriveToken(password, machineHash);
|
|
42
|
-
return { password, machineHash, token, timestamp };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const ADDRESS_KEY_TYPES = new Set(["connstring", "fileserver", "oauth"]);
|
|
46
|
-
const ADDRESS_FIELDS = new Set(["url", "uri", "host", "hostname", "server", "address", "base_url", "endpoint", "path", "root"]);
|
|
47
|
-
|
|
48
|
-
function normalizeSearchText(value) {
|
|
49
|
-
return String(value || "").toLowerCase();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function redactUrlish(value) {
|
|
53
|
-
const text = String(value || "").trim();
|
|
54
|
-
if (!text) return "";
|
|
55
|
-
try {
|
|
56
|
-
const url = new URL(text);
|
|
57
|
-
if (url.username) url.username = "***";
|
|
58
|
-
if (url.password) url.password = "***";
|
|
59
|
-
return url.toString();
|
|
60
|
-
} catch {
|
|
61
|
-
return text.replace(/:\/\/([^:@/\s]+):([^@/\s]+)@/g, "://***:***@");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function collectAddressHints(value, keyType) {
|
|
66
|
-
if (!ADDRESS_KEY_TYPES.has(String(keyType || "").toLowerCase())) return [];
|
|
67
|
-
const hints = new Set();
|
|
68
|
-
|
|
69
|
-
function add(candidate) {
|
|
70
|
-
if (candidate === undefined || candidate === null) return;
|
|
71
|
-
const text = redactUrlish(candidate);
|
|
72
|
-
if (text) hints.add(text);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function walk(node, fieldName = "") {
|
|
76
|
-
if (node === undefined || node === null) return;
|
|
77
|
-
if (typeof node === "string") {
|
|
78
|
-
if (fieldName && ADDRESS_FIELDS.has(fieldName.toLowerCase())) add(node);
|
|
79
|
-
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(node) || /^[A-Za-z]:[\\/]/.test(node) || node.startsWith("\\\\")) add(node);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
if (Array.isArray(node)) {
|
|
83
|
-
for (const item of node) walk(item, fieldName);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (typeof node === "object") {
|
|
87
|
-
for (const [key, child] of Object.entries(node)) walk(child, key);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
walk(JSON.parse(value));
|
|
93
|
-
} catch {
|
|
94
|
-
walk(value);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return [...hints];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async function searchServices(auth, query, opts = {}) {
|
|
101
|
-
const q = normalizeSearchText(query);
|
|
102
|
-
if (!q) throw new Error("Search query is required");
|
|
103
|
-
const result = await api.status(auth.password, auth.machineHash, auth.token, auth.timestamp, opts.project);
|
|
104
|
-
if (result.error) throw new Error(result.error);
|
|
105
|
-
|
|
106
|
-
const services = result.services || [];
|
|
107
|
-
const rows = [];
|
|
108
|
-
|
|
109
|
-
for (const s of services) {
|
|
110
|
-
const fields = {
|
|
111
|
-
name: s.name,
|
|
112
|
-
label: s.label,
|
|
113
|
-
project: s.project,
|
|
114
|
-
type: s.key_type,
|
|
115
|
-
description: s.description
|
|
116
|
-
};
|
|
117
|
-
const matched = Object.entries(fields)
|
|
118
|
-
.filter(([, value]) => normalizeSearchText(value).includes(q))
|
|
119
|
-
.map(([field]) => field);
|
|
120
|
-
|
|
121
|
-
let addressHints = [];
|
|
122
|
-
if (opts.addresses === true && ADDRESS_KEY_TYPES.has(String(s.key_type || "").toLowerCase()) && s.vault_key) {
|
|
123
|
-
const secret = await api.retrieve(auth.password, auth.machineHash, auth.token, auth.timestamp, s.name);
|
|
124
|
-
if (!secret.error) {
|
|
125
|
-
addressHints = collectAddressHints(secret.value, s.key_type);
|
|
126
|
-
if (addressHints.some(h => normalizeSearchText(h).includes(q))) matched.push("address");
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (matched.length) rows.push({ ...s, matched: [...new Set(matched)], addressHints });
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return rows;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ============================================================
|
|
137
|
-
// Program
|
|
138
|
-
// ============================================================
|
|
139
|
-
const program = new Command();
|
|
140
|
-
|
|
141
|
-
program
|
|
142
|
-
.name("clauth")
|
|
143
|
-
.version(VERSION)
|
|
144
|
-
.description(chalk.cyan("🔐 clauth") + " — Hardware-bound credential vault for LIFEAI infrastructure");
|
|
145
|
-
|
|
146
|
-
// ──────────────────────────────────────────────
|
|
147
|
-
// clauth install (Supabase provisioning + skill install + test)
|
|
148
|
-
// ──────────────────────────────────────────────
|
|
149
|
-
import { runInstall } from './commands/install.js';
|
|
150
|
-
import { runUninstall } from './commands/uninstall.js';
|
|
151
|
-
import { runScrub } from './commands/scrub.js';
|
|
152
|
-
import { runServe } from './commands/serve.js';
|
|
153
|
-
import {
|
|
154
|
-
import {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
.
|
|
160
|
-
.
|
|
161
|
-
.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.
|
|
169
|
-
.
|
|
170
|
-
.option('--
|
|
171
|
-
.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
.
|
|
191
|
-
.
|
|
192
|
-
.
|
|
193
|
-
.option("--
|
|
194
|
-
.option("--
|
|
195
|
-
.option("--
|
|
196
|
-
.option("--
|
|
197
|
-
.option("--
|
|
198
|
-
.option("--
|
|
199
|
-
.option("--
|
|
200
|
-
.option("--
|
|
201
|
-
.option("--
|
|
202
|
-
.option("--
|
|
203
|
-
.option("--
|
|
204
|
-
.option("--
|
|
205
|
-
.option("--
|
|
206
|
-
.option("--
|
|
207
|
-
.option("--
|
|
208
|
-
.option("--
|
|
209
|
-
.option("--
|
|
210
|
-
.option("--
|
|
211
|
-
.option("--
|
|
212
|
-
.option("--
|
|
213
|
-
.option("--
|
|
214
|
-
.option("--
|
|
215
|
-
.option("--
|
|
216
|
-
.option("--
|
|
217
|
-
.option("--
|
|
218
|
-
.option("--
|
|
219
|
-
.option("--
|
|
220
|
-
.option("--
|
|
221
|
-
.option("--
|
|
222
|
-
.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
.
|
|
230
|
-
.
|
|
231
|
-
.
|
|
232
|
-
.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
.
|
|
245
|
-
.
|
|
246
|
-
.option("--
|
|
247
|
-
.option("--
|
|
248
|
-
.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// ──────────────────────────────────────────────
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
.
|
|
264
|
-
.
|
|
265
|
-
.option("--
|
|
266
|
-
.option("--
|
|
267
|
-
.option("--
|
|
268
|
-
.option("--
|
|
269
|
-
.option("-
|
|
270
|
-
.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
{ type: "
|
|
318
|
-
|
|
319
|
-
{ type: "password", name: "
|
|
320
|
-
default: opts.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
console.log(chalk.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// ──────────────────────────────────────────────
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
.
|
|
350
|
-
.
|
|
351
|
-
.option("--
|
|
352
|
-
.option("--
|
|
353
|
-
.option("-
|
|
354
|
-
.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
auth.
|
|
363
|
-
auth.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
opts.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
console.log(
|
|
382
|
-
console.log("");
|
|
383
|
-
console.log(chalk.
|
|
384
|
-
console.log(
|
|
385
|
-
console.log(chalk.
|
|
386
|
-
console.log(
|
|
387
|
-
console.log(chalk.gray(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
// ──────────────────────────────────────────────
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
.
|
|
401
|
-
.
|
|
402
|
-
.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
//
|
|
443
|
-
// clauth write
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
writeCmd
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
.
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
spinner.
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
.
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
.
|
|
494
|
-
.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
spinner.
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
.
|
|
528
|
-
.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
.
|
|
542
|
-
.
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
//
|
|
555
|
-
// clauth
|
|
556
|
-
//
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
addCmd
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
.
|
|
563
|
-
.
|
|
564
|
-
.option("--
|
|
565
|
-
.option("--
|
|
566
|
-
.option("
|
|
567
|
-
.
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
answers =
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
{ type: "input", name: "
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
removeCmd
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
.
|
|
598
|
-
.
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
.
|
|
616
|
-
.
|
|
617
|
-
.
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const
|
|
622
|
-
console.log(chalk.
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
lastProject
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
.
|
|
640
|
-
.
|
|
641
|
-
.option("
|
|
642
|
-
.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
// ──────────────────────────────────────────────
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
.
|
|
675
|
-
.
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
// ──────────────────────────────────────────────
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
.
|
|
696
|
-
.
|
|
697
|
-
.
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
console.log(
|
|
708
|
-
|
|
709
|
-
console.log(
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
// ──────────────────────────────────────────────
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
.
|
|
724
|
-
.
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
// ──────────────────────────────────────────────
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
.
|
|
746
|
-
.
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
clauth scrub
|
|
751
|
-
clauth scrub
|
|
752
|
-
clauth scrub
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
// ──────────────────────────────────────────────
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
.
|
|
768
|
-
.
|
|
769
|
-
.option("--
|
|
770
|
-
.
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
.
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
// ──────────────────────────────────────────────
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
invite
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
.
|
|
794
|
-
.
|
|
795
|
-
.
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
.
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
.
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
// ──────────────────────────────────────────────
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
.
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
// ──────────────────────────────────────────────
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
.
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
console.log(chalk.
|
|
841
|
-
}
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
//
|
|
847
|
-
//
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
tunnelCmd
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
.
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
console.log(chalk.
|
|
857
|
-
console.log(chalk.white("
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
.
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
console.
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
.
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
console.
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
.
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
console.
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
// ──────────────────────────────────────────────
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
.
|
|
941
|
-
.
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
'claude',
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
// ──────────────────────────────────────────────
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
// ──────────────────────────────────────────────
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
.
|
|
1005
|
-
.
|
|
1006
|
-
.option("--
|
|
1007
|
-
.option("--
|
|
1008
|
-
.option("--
|
|
1009
|
-
.option("--
|
|
1010
|
-
.option("--
|
|
1011
|
-
.option("--
|
|
1012
|
-
.option("--
|
|
1013
|
-
.option("--
|
|
1014
|
-
.
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
clauth serve
|
|
1036
|
-
clauth serve
|
|
1037
|
-
clauth serve
|
|
1038
|
-
clauth serve
|
|
1039
|
-
clauth serve
|
|
1040
|
-
clauth serve
|
|
1041
|
-
clauth serve
|
|
1042
|
-
clauth serve
|
|
1043
|
-
|
|
1044
|
-
clauth serve
|
|
1045
|
-
|
|
1046
|
-
clauth serve
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cli/index.js — clauth entry point
|
|
3
|
+
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import inquirer from "inquirer";
|
|
8
|
+
import Conf from "conf";
|
|
9
|
+
import { getConfOptions } from "./conf-path.js";
|
|
10
|
+
import { getMachineHash, deriveToken, deriveSeedHash } from "./fingerprint.js";
|
|
11
|
+
import * as api from "./api.js";
|
|
12
|
+
import { writeCredentialWithRecovery } from "./recovery.js";
|
|
13
|
+
import os from "os";
|
|
14
|
+
import fs from "fs";
|
|
15
|
+
import path from "path";
|
|
16
|
+
import { writeEnrollmentScript } from "./enrollment-script.js";
|
|
17
|
+
|
|
18
|
+
const config = new Conf(getConfOptions());
|
|
19
|
+
const VERSION = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
20
|
+
|
|
21
|
+
// ============================================================
|
|
22
|
+
// Password prompt helper
|
|
23
|
+
// ============================================================
|
|
24
|
+
async function promptPassword(message = "clauth password") {
|
|
25
|
+
const { pw } = await inquirer.prompt([{
|
|
26
|
+
type: "password",
|
|
27
|
+
name: "pw",
|
|
28
|
+
message,
|
|
29
|
+
mask: "*",
|
|
30
|
+
validate: v => v.length >= 8 || "Password must be at least 8 characters"
|
|
31
|
+
}]);
|
|
32
|
+
return pw;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================================
|
|
36
|
+
// Auth helper — get pw + derive token
|
|
37
|
+
// ============================================================
|
|
38
|
+
async function getAuth(pw) {
|
|
39
|
+
const password = pw || await promptPassword();
|
|
40
|
+
const machineHash = getMachineHash();
|
|
41
|
+
const { token, timestamp } = deriveToken(password, machineHash);
|
|
42
|
+
return { password, machineHash, token, timestamp };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ADDRESS_KEY_TYPES = new Set(["connstring", "fileserver", "oauth"]);
|
|
46
|
+
const ADDRESS_FIELDS = new Set(["url", "uri", "host", "hostname", "server", "address", "base_url", "endpoint", "path", "root"]);
|
|
47
|
+
|
|
48
|
+
function normalizeSearchText(value) {
|
|
49
|
+
return String(value || "").toLowerCase();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function redactUrlish(value) {
|
|
53
|
+
const text = String(value || "").trim();
|
|
54
|
+
if (!text) return "";
|
|
55
|
+
try {
|
|
56
|
+
const url = new URL(text);
|
|
57
|
+
if (url.username) url.username = "***";
|
|
58
|
+
if (url.password) url.password = "***";
|
|
59
|
+
return url.toString();
|
|
60
|
+
} catch {
|
|
61
|
+
return text.replace(/:\/\/([^:@/\s]+):([^@/\s]+)@/g, "://***:***@");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function collectAddressHints(value, keyType) {
|
|
66
|
+
if (!ADDRESS_KEY_TYPES.has(String(keyType || "").toLowerCase())) return [];
|
|
67
|
+
const hints = new Set();
|
|
68
|
+
|
|
69
|
+
function add(candidate) {
|
|
70
|
+
if (candidate === undefined || candidate === null) return;
|
|
71
|
+
const text = redactUrlish(candidate);
|
|
72
|
+
if (text) hints.add(text);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function walk(node, fieldName = "") {
|
|
76
|
+
if (node === undefined || node === null) return;
|
|
77
|
+
if (typeof node === "string") {
|
|
78
|
+
if (fieldName && ADDRESS_FIELDS.has(fieldName.toLowerCase())) add(node);
|
|
79
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(node) || /^[A-Za-z]:[\\/]/.test(node) || node.startsWith("\\\\")) add(node);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(node)) {
|
|
83
|
+
for (const item of node) walk(item, fieldName);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (typeof node === "object") {
|
|
87
|
+
for (const [key, child] of Object.entries(node)) walk(child, key);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
walk(JSON.parse(value));
|
|
93
|
+
} catch {
|
|
94
|
+
walk(value);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return [...hints];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function searchServices(auth, query, opts = {}) {
|
|
101
|
+
const q = normalizeSearchText(query);
|
|
102
|
+
if (!q) throw new Error("Search query is required");
|
|
103
|
+
const result = await api.status(auth.password, auth.machineHash, auth.token, auth.timestamp, opts.project);
|
|
104
|
+
if (result.error) throw new Error(result.error);
|
|
105
|
+
|
|
106
|
+
const services = result.services || [];
|
|
107
|
+
const rows = [];
|
|
108
|
+
|
|
109
|
+
for (const s of services) {
|
|
110
|
+
const fields = {
|
|
111
|
+
name: s.name,
|
|
112
|
+
label: s.label,
|
|
113
|
+
project: s.project,
|
|
114
|
+
type: s.key_type,
|
|
115
|
+
description: s.description
|
|
116
|
+
};
|
|
117
|
+
const matched = Object.entries(fields)
|
|
118
|
+
.filter(([, value]) => normalizeSearchText(value).includes(q))
|
|
119
|
+
.map(([field]) => field);
|
|
120
|
+
|
|
121
|
+
let addressHints = [];
|
|
122
|
+
if (opts.addresses === true && ADDRESS_KEY_TYPES.has(String(s.key_type || "").toLowerCase()) && s.vault_key) {
|
|
123
|
+
const secret = await api.retrieve(auth.password, auth.machineHash, auth.token, auth.timestamp, s.name);
|
|
124
|
+
if (!secret.error) {
|
|
125
|
+
addressHints = collectAddressHints(secret.value, s.key_type);
|
|
126
|
+
if (addressHints.some(h => normalizeSearchText(h).includes(q))) matched.push("address");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (matched.length) rows.push({ ...s, matched: [...new Set(matched)], addressHints });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return rows;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================================
|
|
137
|
+
// Program
|
|
138
|
+
// ============================================================
|
|
139
|
+
const program = new Command();
|
|
140
|
+
|
|
141
|
+
program
|
|
142
|
+
.name("clauth")
|
|
143
|
+
.version(VERSION)
|
|
144
|
+
.description(chalk.cyan("🔐 clauth") + " — Hardware-bound credential vault for LIFEAI infrastructure");
|
|
145
|
+
|
|
146
|
+
// ──────────────────────────────────────────────
|
|
147
|
+
// clauth install (Supabase provisioning + skill install + test)
|
|
148
|
+
// ──────────────────────────────────────────────
|
|
149
|
+
import { runInstall } from './commands/install.js';
|
|
150
|
+
import { runUninstall } from './commands/uninstall.js';
|
|
151
|
+
import { runScrub } from './commands/scrub.js';
|
|
152
|
+
import { runServe } from './commands/serve.js';
|
|
153
|
+
import { runOps } from './commands/ops.js';
|
|
154
|
+
import { runOpsInstall } from './commands/ops-install.js';
|
|
155
|
+
import { runCodevelop } from './commands/codevelop.js';
|
|
156
|
+
import { runNpm, runPublish } from './commands/npm.js';
|
|
157
|
+
|
|
158
|
+
program
|
|
159
|
+
.command('install')
|
|
160
|
+
.description('Provision Supabase, deploy Edge Function, install Claude skill')
|
|
161
|
+
.option('--ref <ref>', 'Supabase project ref')
|
|
162
|
+
.option('--pat <pat>', 'Supabase Personal Access Token')
|
|
163
|
+
.action(async (opts) => {
|
|
164
|
+
await runInstall(opts);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
program
|
|
168
|
+
.command('uninstall')
|
|
169
|
+
.description('Full teardown — drop DB objects, Edge Function, secrets, skill, config')
|
|
170
|
+
.option('--ref <ref>', 'Supabase project ref')
|
|
171
|
+
.option('--pat <pat>', 'Supabase Personal Access Token (required)')
|
|
172
|
+
.option('--yes', 'Skip confirmation prompt')
|
|
173
|
+
.action(async (opts) => {
|
|
174
|
+
if (!opts.yes) {
|
|
175
|
+
const inquirerMod = await import('inquirer');
|
|
176
|
+
const { confirm } = await inquirerMod.default.prompt([{
|
|
177
|
+
type: 'input',
|
|
178
|
+
name: 'confirm',
|
|
179
|
+
message: chalk.red('Type "CONFIRM UNINSTALL" to proceed:'),
|
|
180
|
+
}]);
|
|
181
|
+
if (confirm !== 'CONFIRM UNINSTALL') {
|
|
182
|
+
console.log(chalk.yellow('\n Uninstall cancelled.\n'));
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
await runUninstall(opts);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
program
|
|
190
|
+
.command("codevelop")
|
|
191
|
+
.description("Install and launch Claude/Codex co-development terminal sessions")
|
|
192
|
+
.argument("[action]", "install-terminal | start | join | say | read | watch | who | ask | reply | inbox | listen | request-partner | launch-peer | check-partner | sync | help", "help")
|
|
193
|
+
.option("--repo <path>", "Repo root", "C:\\Dev\\regen-root")
|
|
194
|
+
.option("--port <port>", "Isolated clauth port", "53137")
|
|
195
|
+
.option("--base-url <url>", "Override clauth base URL")
|
|
196
|
+
.option("--name <name>", "Session name")
|
|
197
|
+
.option("--session <idOrManifestPath>", "Co-develop session id or manifest path")
|
|
198
|
+
.option("--task <task>", "Initial partner request task")
|
|
199
|
+
.option("--context <path>", "Context, plan, or architecture file the partner should read first")
|
|
200
|
+
.option("--channel <name>", "Ad hoc channel name for join/say/read/watch")
|
|
201
|
+
.option("--message <text>", "Ad hoc message for say")
|
|
202
|
+
.option("--to <peer>", "Target peer for ask/reply, or optional direct ad hoc recipient for say")
|
|
203
|
+
.option("--from <peer>", "Sender peer for ask/reply")
|
|
204
|
+
.option("--turn <turn_id>", "Turn ID for reply")
|
|
205
|
+
.option("--role <role>", "Turn role, e.g. reviewer or builder")
|
|
206
|
+
.option("--skill <skill>", "Requested skill name, e.g. rdc:review")
|
|
207
|
+
.option("--verdict <verdict>", "Reply verdict: pass, fail, or blocked")
|
|
208
|
+
.option("--summary <summary>", "Reply summary")
|
|
209
|
+
.option("--evidence <items>", "Reply evidence; separate multiple items with semicolons")
|
|
210
|
+
.option("--files-changed <items>", "Reply changed files; separate multiple items with semicolons")
|
|
211
|
+
.option("--commits <items>", "Reply commits; separate multiple items with semicolons")
|
|
212
|
+
.option("--blockers <items>", "Reply blockers; separate multiple items with semicolons")
|
|
213
|
+
.option("--next <items>", "Reply next actions; separate multiple items with semicolons")
|
|
214
|
+
.option("--wait", "For ask: wait for a matching reply")
|
|
215
|
+
.option("--once", "For listen: exit after the first message event")
|
|
216
|
+
.option("--json", "For read: print raw JSON")
|
|
217
|
+
.option("--timeout-ms <ms>", "Wait timeout in milliseconds", "300000")
|
|
218
|
+
.option("--interval-ms <ms>", "Wait polling interval in milliseconds", "2000")
|
|
219
|
+
.option("--start-isolated-clauth", "Start isolated clauth if the selected port is not running")
|
|
220
|
+
.option("--peer <peer>", "Peer name for launch-peer/check-partner/sync, or ad hoc name such as codex-1")
|
|
221
|
+
.option("--dry-run", "Print and write session manifest without opening Windows Terminal")
|
|
222
|
+
.option("--no-open", "Create session/config but do not open Windows Terminal")
|
|
223
|
+
.option("--print-only", "For launch-peer: resolve command without starting the CLI")
|
|
224
|
+
.action(async (action, opts) => {
|
|
225
|
+
await runCodevelop({ ...opts, action });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
program
|
|
229
|
+
.command("npm")
|
|
230
|
+
.description("Operate npm auth safely through the clauth npm service")
|
|
231
|
+
.argument("[action]", "whoami | tokens | set-local | sync-github-secret | rerun | help", "help")
|
|
232
|
+
.argument("[args...]", "Action arguments")
|
|
233
|
+
.option("--repo <repo>", "GitHub repo, e.g. LIFEAI/rdc-skills")
|
|
234
|
+
.action(async (action, args, opts) => {
|
|
235
|
+
await runNpm(action, { ...opts, args });
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// ──────────────────────────────────────────────
|
|
239
|
+
// clauth publish [target]
|
|
240
|
+
// Guarded npm publish for ANY package — refuses to ship code that isn't
|
|
241
|
+
// committed AND pushed to GitHub (prevents npm/repo divergence from dev builds).
|
|
242
|
+
// ──────────────────────────────────────────────
|
|
243
|
+
program
|
|
244
|
+
.command("publish [target]")
|
|
245
|
+
.description("Safely publish an npm package (default: cwd). Refuses unless committed + pushed to GitHub.")
|
|
246
|
+
.option("--dry-run", "Run all guards and pack, but do not publish")
|
|
247
|
+
.option("--access <access>", "npm access: public | restricted")
|
|
248
|
+
.option("--allow-dirty", "Override the uncommitted-changes guard (NOT recommended)")
|
|
249
|
+
.option("--allow-unpushed", "Override the not-pushed-to-remote guard (NOT recommended)")
|
|
250
|
+
.action(async (target, opts) => {
|
|
251
|
+
try {
|
|
252
|
+
await runPublish(target, opts);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.error(chalk.red(err.message));
|
|
255
|
+
process.exitCode = 1;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// ──────────────────────────────────────────────
|
|
260
|
+
// clauth setup
|
|
261
|
+
// ──────────────────────────────────────────────
|
|
262
|
+
program
|
|
263
|
+
.command("setup")
|
|
264
|
+
.description("Register this machine with the vault (run after clauth install)")
|
|
265
|
+
.option("--admin-token <token>", "Bootstrap token (from clauth install output)")
|
|
266
|
+
.option("--enrollment-code <code>", "One-time enrollment code from clauth enroll")
|
|
267
|
+
.option("--supabase-url <url>", "Vault Supabase URL, for enrolling a new computer without running clauth install")
|
|
268
|
+
.option("--anon-key <key>", "Vault Supabase anon key, for enrolling a new computer without running clauth install")
|
|
269
|
+
.option("--install-id <id>", "Logical install/owner group for admin-token setup", "default")
|
|
270
|
+
.option("--label <label>", "Human label for this machine")
|
|
271
|
+
.option("-p, --pw <password>", "Password (skip interactive prompt)")
|
|
272
|
+
.action(async (opts) => {
|
|
273
|
+
console.log(chalk.cyan("\n🔐 clauth setup\n"));
|
|
274
|
+
|
|
275
|
+
if (opts.supabaseUrl) config.set("supabase_url", opts.supabaseUrl);
|
|
276
|
+
if (opts.anonKey) config.set("supabase_anon_key", opts.anonKey);
|
|
277
|
+
|
|
278
|
+
// URL + anon key may already be saved by clauth install, or provided by
|
|
279
|
+
// an old-machine enrollment command.
|
|
280
|
+
let savedUrl = config.get("supabase_url");
|
|
281
|
+
let savedAnon = config.get("supabase_anon_key");
|
|
282
|
+
if (!savedUrl || !savedAnon) {
|
|
283
|
+
const configAnswers = await inquirer.prompt([
|
|
284
|
+
{ type: "input", name: "supabaseUrl", message: "Vault Supabase URL:", default: savedUrl || opts.supabaseUrl || "" },
|
|
285
|
+
{ type: "password", name: "anonKey", message: "Vault anon key:", mask: "*", default: savedAnon || opts.anonKey || "" },
|
|
286
|
+
]);
|
|
287
|
+
if (!configAnswers.supabaseUrl || !configAnswers.anonKey) {
|
|
288
|
+
console.log(chalk.yellow(" Supabase config not found. Run clauth install first, or provide --supabase-url and --anon-key.\n"));
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
config.set("supabase_url", configAnswers.supabaseUrl);
|
|
292
|
+
config.set("supabase_anon_key", configAnswers.anonKey);
|
|
293
|
+
savedUrl = configAnswers.supabaseUrl;
|
|
294
|
+
savedAnon = configAnswers.anonKey;
|
|
295
|
+
}
|
|
296
|
+
console.log(chalk.gray(` Project: ${savedUrl}\n`));
|
|
297
|
+
|
|
298
|
+
let answers;
|
|
299
|
+
if (opts.pw && (opts.adminToken || opts.enrollmentCode)) {
|
|
300
|
+
// Non-interactive mode — all flags provided
|
|
301
|
+
answers = {
|
|
302
|
+
label: opts.label || os.hostname(),
|
|
303
|
+
pw: opts.pw,
|
|
304
|
+
adminTk: opts.adminToken,
|
|
305
|
+
enrollmentCode: opts.enrollmentCode,
|
|
306
|
+
};
|
|
307
|
+
} else if (opts.enrollmentCode) {
|
|
308
|
+
const pw = opts.pw || await promptPassword("Set clauth password for this computer");
|
|
309
|
+
answers = {
|
|
310
|
+
label: opts.label || os.hostname(),
|
|
311
|
+
pw,
|
|
312
|
+
adminTk: opts.adminToken,
|
|
313
|
+
enrollmentCode: opts.enrollmentCode,
|
|
314
|
+
};
|
|
315
|
+
} else {
|
|
316
|
+
answers = await inquirer.prompt([
|
|
317
|
+
{ type: "input", name: "label", message: "Machine label:", default: opts.label || os.hostname() },
|
|
318
|
+
{ type: "password", name: "pw", message: "Set password:", mask: "*", default: opts.pw || "" },
|
|
319
|
+
{ type: "password", name: "enrollmentCode", message: "Enrollment code (preferred for new computer; leave blank if using bootstrap token):", mask: "*",
|
|
320
|
+
default: opts.enrollmentCode || "" },
|
|
321
|
+
{ type: "password", name: "adminTk", message: "Bootstrap token (admin fallback):", mask: "*",
|
|
322
|
+
default: opts.adminToken || "" },
|
|
323
|
+
]);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const spinner = ora("Registering machine with vault...").start();
|
|
327
|
+
try {
|
|
328
|
+
const machineHash = getMachineHash();
|
|
329
|
+
const seedHash = deriveSeedHash(machineHash, answers.pw);
|
|
330
|
+
const result = answers.enrollmentCode
|
|
331
|
+
? await api.redeemEnrollment(machineHash, seedHash, answers.label, answers.enrollmentCode)
|
|
332
|
+
: await api.registerMachine(machineHash, seedHash, answers.label, answers.adminTk, { install_id: opts.installId || "default" });
|
|
333
|
+
if (result.error) throw new Error(result.error);
|
|
334
|
+
spinner.succeed(chalk.green(`Machine registered: ${machineHash.slice(0,12)}... install_id=${result.install_id || opts.installId || "default"}`));
|
|
335
|
+
|
|
336
|
+
console.log(chalk.green("\n✓ clauth is ready.\n"));
|
|
337
|
+
console.log(chalk.cyan(" clauth test — verify connection"));
|
|
338
|
+
console.log(chalk.cyan(" clauth status — see all services\n"));
|
|
339
|
+
} catch (err) {
|
|
340
|
+
spinner.fail(chalk.red(`Setup failed: ${err.message}`));
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// ──────────────────────────────────────────────
|
|
346
|
+
// clauth enroll
|
|
347
|
+
// ──────────────────────────────────────────────
|
|
348
|
+
program
|
|
349
|
+
.command("enroll")
|
|
350
|
+
.description("Create a one-time enrollment code for adding another computer")
|
|
351
|
+
.option("--label <label>", "Suggested label for the new computer")
|
|
352
|
+
.option("--ttl-minutes <minutes>", "Enrollment lifetime, 5 to 1440 minutes", "60")
|
|
353
|
+
.option("--install-id <id>", "Override install id; default is current machine's install id")
|
|
354
|
+
.option("--target <target>", "Enrollment target: windows or linux", "windows")
|
|
355
|
+
.option("-p, --pw <password>", "Password (or will prompt)")
|
|
356
|
+
.action(async (opts) => {
|
|
357
|
+
console.log(chalk.cyan("\n🔐 clauth enroll\n"));
|
|
358
|
+
const auth = await getAuth(opts.pw);
|
|
359
|
+
const spinner = ora("Creating one-time machine enrollment...").start();
|
|
360
|
+
try {
|
|
361
|
+
const result = await api.createEnrollment(
|
|
362
|
+
auth.password,
|
|
363
|
+
auth.machineHash,
|
|
364
|
+
auth.token,
|
|
365
|
+
auth.timestamp,
|
|
366
|
+
opts.label,
|
|
367
|
+
Number(opts.ttlMinutes || 60),
|
|
368
|
+
opts.installId
|
|
369
|
+
);
|
|
370
|
+
if (result.error) throw new Error(result.error);
|
|
371
|
+
const supabaseUrl = config.get("supabase_url");
|
|
372
|
+
const anonKey = config.get("supabase_anon_key");
|
|
373
|
+
const scriptPath = writeEnrollmentScript({
|
|
374
|
+
supabaseUrl,
|
|
375
|
+
anonKey,
|
|
376
|
+
enrollmentCode: result.enrollment_code,
|
|
377
|
+
label: opts.label,
|
|
378
|
+
target: opts.target,
|
|
379
|
+
});
|
|
380
|
+
spinner.succeed(chalk.green(`Enrollment created for install_id=${result.install_id}`));
|
|
381
|
+
console.log("");
|
|
382
|
+
console.log(chalk.bold(" Enrollment code:"));
|
|
383
|
+
console.log(chalk.white(` ${result.enrollment_code}`));
|
|
384
|
+
console.log("");
|
|
385
|
+
console.log(chalk.bold(" On the new computer:"));
|
|
386
|
+
console.log(chalk.gray(` Run this one-time script: ${scriptPath}`));
|
|
387
|
+
console.log(chalk.gray(" It installs clauth, enrolls with this code, installs startup, then deletes itself."));
|
|
388
|
+
console.log("");
|
|
389
|
+
console.log(chalk.gray(` Expires: ${result.expires_at}`));
|
|
390
|
+
} catch (err) {
|
|
391
|
+
spinner.fail(chalk.red(`Enroll failed: ${err.message}`));
|
|
392
|
+
process.exitCode = 1;
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// ──────────────────────────────────────────────
|
|
397
|
+
// clauth status
|
|
398
|
+
// ──────────────────────────────────────────────
|
|
399
|
+
program
|
|
400
|
+
.command("status")
|
|
401
|
+
.description("Show all services and their state")
|
|
402
|
+
.option("-p, --pw <password>", "Password (or will prompt)")
|
|
403
|
+
.option("--project <name>", "Filter by project scope")
|
|
404
|
+
.action(async (opts) => {
|
|
405
|
+
const auth = await getAuth(opts.pw);
|
|
406
|
+
const spinner = ora("Fetching service status...").start();
|
|
407
|
+
try {
|
|
408
|
+
const result = await api.status(auth.password, auth.machineHash, auth.token, auth.timestamp, opts.project);
|
|
409
|
+
spinner.stop();
|
|
410
|
+
if (result.error) { console.log(chalk.red(`Error: ${result.error}`)); return; }
|
|
411
|
+
|
|
412
|
+
const heading = opts.project ? `clauth service status (project: ${opts.project})` : "clauth service status";
|
|
413
|
+
console.log(chalk.cyan(`\n🔐 ${heading}\n`));
|
|
414
|
+
console.log(
|
|
415
|
+
chalk.bold(
|
|
416
|
+
" " + "SERVICE".padEnd(24) + "TYPE".padEnd(12) + "PROJECT".padEnd(22) + "STATUS".padEnd(12) +
|
|
417
|
+
"KEY STORED".padEnd(12) + "LAST RETRIEVED"
|
|
418
|
+
)
|
|
419
|
+
);
|
|
420
|
+
console.log(" " + "─".repeat(90));
|
|
421
|
+
|
|
422
|
+
for (const s of result.services || []) {
|
|
423
|
+
const status = s.enabled
|
|
424
|
+
? chalk.green("ACTIVE".padEnd(12))
|
|
425
|
+
: s.vault_key
|
|
426
|
+
? chalk.yellow("SUSPENDED".padEnd(12))
|
|
427
|
+
: chalk.gray("NO KEY".padEnd(12));
|
|
428
|
+
const hasKey = s.vault_key ? chalk.green("✓".padEnd(12)) : chalk.gray("—".padEnd(12));
|
|
429
|
+
const lastGet = s.last_retrieved
|
|
430
|
+
? new Date(s.last_retrieved).toLocaleDateString()
|
|
431
|
+
: chalk.gray("never");
|
|
432
|
+
const proj = s.project ? chalk.blue(s.project.padEnd(22)) : chalk.gray("—".padEnd(22));
|
|
433
|
+
|
|
434
|
+
console.log(` ${s.name.padEnd(24)}${s.key_type.padEnd(12)}${proj}${status}${hasKey}${lastGet}`);
|
|
435
|
+
}
|
|
436
|
+
console.log();
|
|
437
|
+
} catch (err) {
|
|
438
|
+
spinner.fail(chalk.red(err.message));
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// ──────────────────────────────────────────────
|
|
443
|
+
// clauth write pw <new_password>
|
|
444
|
+
// clauth write params
|
|
445
|
+
// clauth write key <service> <value>
|
|
446
|
+
// ──────────────────────────────────────────────
|
|
447
|
+
const writeCmd = program.command("write").description("Write credentials or update auth parameters");
|
|
448
|
+
|
|
449
|
+
writeCmd
|
|
450
|
+
.command("pw [newpw]")
|
|
451
|
+
.description("Set or update clauth master password")
|
|
452
|
+
.action(async (newpw) => {
|
|
453
|
+
console.log(chalk.cyan("\n🔐 clauth write pw\n"));
|
|
454
|
+
const current = await promptPassword("Current password (to verify)");
|
|
455
|
+
const pw = newpw || (await inquirer.prompt([
|
|
456
|
+
{ type: "password", name: "p", message: "New password:", mask: "*" },
|
|
457
|
+
{ type: "password", name: "c", message: "Confirm new password:", mask: "*" }
|
|
458
|
+
]).then(a => { if (a.p !== a.c) { console.log(chalk.red("Passwords don't match")); process.exit(1); } return a.p; }));
|
|
459
|
+
|
|
460
|
+
// Re-register machine with new seed hash
|
|
461
|
+
const machineHash = getMachineHash();
|
|
462
|
+
const newSeedHash = deriveSeedHash(machineHash, pw);
|
|
463
|
+
const { token, timestamp } = deriveToken(current, machineHash);
|
|
464
|
+
const adminToken = await inquirer.prompt([{
|
|
465
|
+
type: "password", name: "t", message: "Admin bootstrap token (required for re-registration):", mask: "*"
|
|
466
|
+
}]).then(a => a.t);
|
|
467
|
+
|
|
468
|
+
const spinner = ora("Updating password and re-registering machine...").start();
|
|
469
|
+
try {
|
|
470
|
+
const result = await api.registerMachine(machineHash, newSeedHash, null, adminToken);
|
|
471
|
+
if (result.error) throw new Error(result.error);
|
|
472
|
+
spinner.succeed(chalk.green("Password updated and machine re-registered."));
|
|
473
|
+
} catch (err) {
|
|
474
|
+
spinner.fail(chalk.red(err.message));
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
writeCmd
|
|
479
|
+
.command("params")
|
|
480
|
+
.description("Re-read hardware fingerprint (use after hardware change)")
|
|
481
|
+
.action(async () => {
|
|
482
|
+
const spinner = ora("Reading hardware fingerprint...").start();
|
|
483
|
+
try {
|
|
484
|
+
const hash = getMachineHash();
|
|
485
|
+
spinner.succeed(chalk.green(`Machine hash: ${hash.slice(0,16)}...`));
|
|
486
|
+
console.log(chalk.gray("Full hash: " + hash));
|
|
487
|
+
} catch (err) {
|
|
488
|
+
spinner.fail(chalk.red(err.message));
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
writeCmd
|
|
493
|
+
.command("key <service> [value]")
|
|
494
|
+
.description("Write a credential into vault for a service")
|
|
495
|
+
.option("-p, --pw <password>", "Password")
|
|
496
|
+
.action(async (service, value, opts) => {
|
|
497
|
+
const auth = await getAuth(opts.pw);
|
|
498
|
+
let val = value;
|
|
499
|
+
if (!val) {
|
|
500
|
+
const { v } = await inquirer.prompt([{ type: "password", name: "v", message: `Value for ${service}:`, mask: "*" }]);
|
|
501
|
+
val = v;
|
|
502
|
+
}
|
|
503
|
+
const spinner = ora(`Writing key for ${service}...`).start();
|
|
504
|
+
try {
|
|
505
|
+
const { result, snapshot, normalized } = await writeCredentialWithRecovery({
|
|
506
|
+
password: auth.password,
|
|
507
|
+
machineHash: auth.machineHash,
|
|
508
|
+
service,
|
|
509
|
+
value: val,
|
|
510
|
+
});
|
|
511
|
+
if (result.error) throw new Error(result.error);
|
|
512
|
+
const details = [
|
|
513
|
+
snapshot?.ok ? "recovery snapshot written" : null,
|
|
514
|
+
normalized ? "value normalized" : null,
|
|
515
|
+
].filter(Boolean);
|
|
516
|
+
spinner.succeed(chalk.green(`Key stored in vault: auth.${service}${details.length ? ` (${details.join(", ")})` : ""}`));
|
|
517
|
+
} catch (err) {
|
|
518
|
+
spinner.fail(chalk.red(err.message));
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// ──────────────────────────────────────────────
|
|
523
|
+
// clauth enable <service|all>
|
|
524
|
+
// clauth disable <service|all>
|
|
525
|
+
// ──────────────────────────────────────────────
|
|
526
|
+
program
|
|
527
|
+
.command("enable <service>")
|
|
528
|
+
.description("Enable a service (or 'all')")
|
|
529
|
+
.option("-p, --pw <password>")
|
|
530
|
+
.action(async (service, opts) => {
|
|
531
|
+
const auth = await getAuth(opts.pw);
|
|
532
|
+
const spinner = ora(`Enabling ${service}...`).start();
|
|
533
|
+
try {
|
|
534
|
+
const result = await api.enable(auth.password, auth.machineHash, auth.token, auth.timestamp, service, true);
|
|
535
|
+
if (result.error) throw new Error(result.error);
|
|
536
|
+
spinner.succeed(chalk.green(`Enabled: ${service}`));
|
|
537
|
+
} catch (err) { spinner.fail(chalk.red(err.message)); }
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
program
|
|
541
|
+
.command("disable <service>")
|
|
542
|
+
.description("Disable a service (or 'all')")
|
|
543
|
+
.option("-p, --pw <password>")
|
|
544
|
+
.action(async (service, opts) => {
|
|
545
|
+
const auth = await getAuth(opts.pw);
|
|
546
|
+
const spinner = ora(`Disabling ${service}...`).start();
|
|
547
|
+
try {
|
|
548
|
+
const result = await api.enable(auth.password, auth.machineHash, auth.token, auth.timestamp, service, false);
|
|
549
|
+
if (result.error) throw new Error(result.error);
|
|
550
|
+
spinner.succeed(chalk.yellow(`Disabled: ${service}`));
|
|
551
|
+
} catch (err) { spinner.fail(chalk.red(err.message)); }
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// ──────────────────────────────────────────────
|
|
555
|
+
// clauth add service <name>
|
|
556
|
+
// clauth remove service <name>
|
|
557
|
+
// clauth list services
|
|
558
|
+
// ──────────────────────────────────────────────
|
|
559
|
+
const addCmd = program.command("add").description("Add resources to the registry");
|
|
560
|
+
|
|
561
|
+
addCmd
|
|
562
|
+
.command("service <name>")
|
|
563
|
+
.description("Register a new service slot")
|
|
564
|
+
.option("--type <type>", "Key type: token | keypair | connstring | oauth")
|
|
565
|
+
.option("--label <label>", "Human-readable label")
|
|
566
|
+
.option("--description <desc>", "Description")
|
|
567
|
+
.option("--project <project>", "Project scope (groups related services)")
|
|
568
|
+
.option("-p, --pw <password>")
|
|
569
|
+
.action(async (name, opts) => {
|
|
570
|
+
const auth = await getAuth(opts.pw);
|
|
571
|
+
let answers;
|
|
572
|
+
if (opts.type && opts.label) {
|
|
573
|
+
// Non-interactive — all flags provided
|
|
574
|
+
answers = { label: opts.label, key_type: opts.type, desc: opts.description || "" };
|
|
575
|
+
} else {
|
|
576
|
+
answers = await inquirer.prompt([
|
|
577
|
+
{ type: "input", name: "label", message: "Label:", default: opts.label || name },
|
|
578
|
+
{ type: "list", name: "key_type", message: "Key type:", choices: ["token","keypair","connstring","oauth"], default: opts.type || "token" },
|
|
579
|
+
{ type: "input", name: "desc", message: "Description (optional):", default: opts.description || "" }
|
|
580
|
+
]);
|
|
581
|
+
}
|
|
582
|
+
const spinner = ora(`Adding service: ${name}${opts.project ? ` (project: ${opts.project})` : ""}...`).start();
|
|
583
|
+
try {
|
|
584
|
+
const result = await api.addService(
|
|
585
|
+
auth.password, auth.machineHash, auth.token, auth.timestamp,
|
|
586
|
+
name, answers.label, answers.key_type, answers.desc, opts.project
|
|
587
|
+
);
|
|
588
|
+
if (result.error) throw new Error(result.error);
|
|
589
|
+
spinner.succeed(chalk.green(`Service added: ${name} (${answers.key_type})${opts.project ? chalk.blue(` [${opts.project}]`) : ""}`));
|
|
590
|
+
console.log(chalk.gray(` Next: clauth write key ${name}`));
|
|
591
|
+
} catch (err) { spinner.fail(chalk.red(err.message)); }
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
const removeCmd = program.command("remove").description("Remove resources from the registry");
|
|
595
|
+
|
|
596
|
+
removeCmd
|
|
597
|
+
.command("service <name>")
|
|
598
|
+
.description("Remove a service and its key from vault")
|
|
599
|
+
.option("-p, --pw <password>")
|
|
600
|
+
.action(async (name, opts) => {
|
|
601
|
+
const { confirm } = await inquirer.prompt([{
|
|
602
|
+
type: "input", name: "confirm",
|
|
603
|
+
message: chalk.red(`Type "CONFIRM REMOVE ${name.toUpperCase()}" to proceed:`)
|
|
604
|
+
}]);
|
|
605
|
+
const auth = await getAuth(opts.pw);
|
|
606
|
+
const spinner = ora(`Removing ${name}...`).start();
|
|
607
|
+
try {
|
|
608
|
+
const result = await api.removeService(auth.password, auth.machineHash, auth.token, auth.timestamp, name, confirm);
|
|
609
|
+
if (result.error) throw new Error(result.error);
|
|
610
|
+
spinner.succeed(chalk.yellow(`Removed: ${name}`));
|
|
611
|
+
} catch (err) { spinner.fail(chalk.red(err.message)); }
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
program
|
|
615
|
+
.command("list")
|
|
616
|
+
.description("List all registered services")
|
|
617
|
+
.option("-p, --pw <password>")
|
|
618
|
+
.option("--project <name>", "Filter by project scope")
|
|
619
|
+
.action(async (opts) => {
|
|
620
|
+
const auth = await getAuth(opts.pw);
|
|
621
|
+
const result = await api.status(auth.password, auth.machineHash, auth.token, auth.timestamp, opts.project);
|
|
622
|
+
if (result.error) { console.log(chalk.red(result.error)); return; }
|
|
623
|
+
const heading = opts.project ? `Registered services (project: ${opts.project})` : "Registered services";
|
|
624
|
+
console.log(chalk.cyan(`\n ${heading}:\n`));
|
|
625
|
+
let lastProject = undefined;
|
|
626
|
+
for (const s of result.services || []) {
|
|
627
|
+
const proj = s.project || null;
|
|
628
|
+
if (proj !== lastProject) {
|
|
629
|
+
if (lastProject !== undefined) console.log();
|
|
630
|
+
console.log(chalk.gray(` [${proj || "global"}]`));
|
|
631
|
+
lastProject = proj;
|
|
632
|
+
}
|
|
633
|
+
console.log(` ${chalk.bold(s.name.padEnd(24))} ${chalk.gray(s.key_type.padEnd(12))} ${chalk.gray(s.label || "")}`);
|
|
634
|
+
}
|
|
635
|
+
console.log();
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
program
|
|
639
|
+
.command("search <query>")
|
|
640
|
+
.description("Search services by name, label, project, description, or type")
|
|
641
|
+
.option("-p, --pw <password>")
|
|
642
|
+
.option("--project <name>", "Filter by project scope")
|
|
643
|
+
.option("--addresses", "Also search redacted address hints from address-bearing secrets (may retrieve multiple secrets)")
|
|
644
|
+
.action(async (query, opts) => {
|
|
645
|
+
const auth = await getAuth(opts.pw);
|
|
646
|
+
const spinner = ora("Searching services...").start();
|
|
647
|
+
try {
|
|
648
|
+
const rows = await searchServices(auth, query, { project: opts.project, addresses: opts.addresses === true });
|
|
649
|
+
spinner.stop();
|
|
650
|
+
console.log(chalk.cyan(`\n Search results for "${query}":\n`));
|
|
651
|
+
if (!rows.length) {
|
|
652
|
+
console.log(chalk.gray(" No matching services found.\n"));
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
console.log(chalk.bold(" " + "SERVICE".padEnd(24) + "TYPE".padEnd(12) + "PROJECT".padEnd(20) + "MATCHED"));
|
|
656
|
+
console.log(" " + "─".repeat(78));
|
|
657
|
+
for (const s of rows) {
|
|
658
|
+
const project = s.project || "global";
|
|
659
|
+
console.log(` ${chalk.bold(s.name.padEnd(24))}${String(s.key_type || "").padEnd(12)}${project.padEnd(20)}${s.matched.join(", ")}`);
|
|
660
|
+
if (s.label) console.log(chalk.gray(` label: ${s.label}`));
|
|
661
|
+
if (s.description) console.log(chalk.gray(` description: ${s.description}`));
|
|
662
|
+
for (const hint of s.addressHints || []) console.log(chalk.gray(` address: ${hint}`));
|
|
663
|
+
}
|
|
664
|
+
console.log();
|
|
665
|
+
} catch (err) {
|
|
666
|
+
spinner.fail(chalk.red(err.message));
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// ──────────────────────────────────────────────
|
|
671
|
+
// clauth test <service|all>
|
|
672
|
+
// ──────────────────────────────────────────────
|
|
673
|
+
program
|
|
674
|
+
.command("test [service]")
|
|
675
|
+
.description("Test HMAC handshake — no key returned")
|
|
676
|
+
.option("-p, --pw <password>")
|
|
677
|
+
.action(async (service, opts) => {
|
|
678
|
+
const auth = await getAuth(opts.pw);
|
|
679
|
+
const spinner = ora("Testing auth handshake...").start();
|
|
680
|
+
try {
|
|
681
|
+
const result = await api.test(auth.password, auth.machineHash, auth.token, auth.timestamp);
|
|
682
|
+
if (result.error) throw new Error(`${result.error}: ${result.reason}`);
|
|
683
|
+
spinner.succeed(chalk.green("PASS — HMAC validated"));
|
|
684
|
+
console.log(chalk.gray(` Machine: ${auth.machineHash.slice(0,16)}...`));
|
|
685
|
+
console.log(chalk.gray(` Window: ${new Date(result.timestamp).toISOString()}`));
|
|
686
|
+
} catch (err) {
|
|
687
|
+
spinner.fail(chalk.red("FAIL — " + err.message));
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
// ──────────────────────────────────────────────
|
|
692
|
+
// clauth get <service>
|
|
693
|
+
// ──────────────────────────────────────────────
|
|
694
|
+
program
|
|
695
|
+
.command("get <service>")
|
|
696
|
+
.description("Retrieve a key from vault")
|
|
697
|
+
.option("-p, --pw <password>")
|
|
698
|
+
.option("--json", "Output raw JSON")
|
|
699
|
+
.action(async (service, opts) => {
|
|
700
|
+
const auth = await getAuth(opts.pw);
|
|
701
|
+
const spinner = ora(`Retrieving ${service}...`).start();
|
|
702
|
+
try {
|
|
703
|
+
const result = await api.retrieve(auth.password, auth.machineHash, auth.token, auth.timestamp, service);
|
|
704
|
+
spinner.stop();
|
|
705
|
+
if (result.error) { console.log(chalk.red(`Error: ${result.error}`)); return; }
|
|
706
|
+
if (opts.json) {
|
|
707
|
+
console.log(JSON.stringify(result, null, 2));
|
|
708
|
+
} else {
|
|
709
|
+
console.log(chalk.cyan(`\n🔑 ${service} (${result.key_type})\n`));
|
|
710
|
+
const val = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
|
|
711
|
+
console.log(val);
|
|
712
|
+
console.log();
|
|
713
|
+
}
|
|
714
|
+
} catch (err) {
|
|
715
|
+
spinner.fail(chalk.red(err.message));
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
// ──────────────────────────────────────────────
|
|
720
|
+
// clauth revoke <service|all>
|
|
721
|
+
// ──────────────────────────────────────────────
|
|
722
|
+
program
|
|
723
|
+
.command("revoke <service>")
|
|
724
|
+
.description("Delete key from vault (destructive)")
|
|
725
|
+
.option("-p, --pw <password>")
|
|
726
|
+
.action(async (service, opts) => {
|
|
727
|
+
const phrase = service === "all" ? "CONFIRM REVOKE ALL" : `CONFIRM REVOKE ${service.toUpperCase()}`;
|
|
728
|
+
const { confirm } = await inquirer.prompt([{
|
|
729
|
+
type: "input", name: "confirm",
|
|
730
|
+
message: chalk.red(`Type "${phrase}" to proceed:`)
|
|
731
|
+
}]);
|
|
732
|
+
const auth = await getAuth(opts.pw);
|
|
733
|
+
const spinner = ora(`Revoking ${service}...`).start();
|
|
734
|
+
try {
|
|
735
|
+
const result = await api.revoke(auth.password, auth.machineHash, auth.token, auth.timestamp, service, confirm);
|
|
736
|
+
if (result.error) throw new Error(result.error);
|
|
737
|
+
spinner.succeed(chalk.yellow(`Revoked: ${service}`));
|
|
738
|
+
} catch (err) { spinner.fail(chalk.red(err.message)); }
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// ──────────────────────────────────────────────
|
|
742
|
+
// clauth scrub [target]
|
|
743
|
+
// ──────────────────────────────────────────────
|
|
744
|
+
program
|
|
745
|
+
.command("scrub [target]")
|
|
746
|
+
.description("Scrub credentials from Claude Code transcript logs (no auth required)")
|
|
747
|
+
.option("--force", "Rescrub files even if already marked clean")
|
|
748
|
+
.addHelpText("after", `
|
|
749
|
+
Examples:
|
|
750
|
+
clauth scrub Scrub the most recent (active) transcript
|
|
751
|
+
clauth scrub <file> Scrub a specific file
|
|
752
|
+
clauth scrub all Scrub every transcript + tool-result sidecar (.jsonl + .txt)
|
|
753
|
+
clauth scrub all --force Rescrub all files (ignore markers)
|
|
754
|
+
clauth scrub session Scrub ONLY the ending session (transcript + sidecars); reads SessionEnd hook JSON on stdin
|
|
755
|
+
|
|
756
|
+
Redacts: built-in token patterns, your ~/.clauth/scrub-patterns.json,
|
|
757
|
+
and this machine's live vault values (best-effort via the daemon).
|
|
758
|
+
`)
|
|
759
|
+
.action(async (target, opts) => {
|
|
760
|
+
await runScrub(target, opts);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// ──────────────────────────────────────────────
|
|
764
|
+
// clauth watchdog
|
|
765
|
+
// ──────────────────────────────────────────────
|
|
766
|
+
program
|
|
767
|
+
.command("watchdog [action] [args...]")
|
|
768
|
+
.description("Manage auto-restart watchdog (install|uninstall|status|start|register|list|events|restart)")
|
|
769
|
+
.option("--manifest <path>", "Watchdog service manifest for register")
|
|
770
|
+
.option("--service <id>", "Watchdog service id for restart")
|
|
771
|
+
.option("--limit <n>", "Event count for events", "100")
|
|
772
|
+
.action(async (action, args, opts) => {
|
|
773
|
+
const { runWatchdog } = await import("./commands/watchdog.js");
|
|
774
|
+
await runWatchdog(action, { ...opts, args });
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
// clauth doctor
|
|
778
|
+
// ──────────────────────────────────────────────
|
|
779
|
+
program
|
|
780
|
+
.command("doctor")
|
|
781
|
+
.description("Check all prerequisites and diagnose issues")
|
|
782
|
+
.action(async () => {
|
|
783
|
+
const { runDoctor } = await import("./commands/doctor.js");
|
|
784
|
+
await runDoctor();
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// ──────────────────────────────────────────────
|
|
788
|
+
// clauth invite generate|list|revoke
|
|
789
|
+
// ──────────────────────────────────────────────
|
|
790
|
+
const invite = program.command("invite").description("Manage vault invites");
|
|
791
|
+
|
|
792
|
+
invite
|
|
793
|
+
.command("generate")
|
|
794
|
+
.description("Generate an invite code for a friend")
|
|
795
|
+
.option("--uses <n>", "Max redemptions", "1")
|
|
796
|
+
.option("--expires <hours>", "Expiry in hours", "168")
|
|
797
|
+
.action(async (opts) => {
|
|
798
|
+
const { runInvite } = await import("./commands/invite.js");
|
|
799
|
+
await runInvite("generate", opts);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
invite
|
|
803
|
+
.command("list")
|
|
804
|
+
.description("List active invites")
|
|
805
|
+
.action(async () => {
|
|
806
|
+
const { runInvite } = await import("./commands/invite.js");
|
|
807
|
+
await runInvite("list", {});
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
invite
|
|
811
|
+
.command("revoke <code>")
|
|
812
|
+
.description("Revoke an invite code")
|
|
813
|
+
.action(async (code) => {
|
|
814
|
+
const { runInvite } = await import("./commands/invite.js");
|
|
815
|
+
await runInvite("revoke", { code });
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
// ──────────────────────────────────────────────
|
|
819
|
+
// clauth join <invite-code>
|
|
820
|
+
// ──────────────────────────────────────────────
|
|
821
|
+
program
|
|
822
|
+
.command("join <invite-code>")
|
|
823
|
+
.description("Join a vault using an invite code from a friend")
|
|
824
|
+
.action(async (code) => {
|
|
825
|
+
const { runJoin } = await import("./commands/join.js");
|
|
826
|
+
await runJoin(code);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// ──────────────────────────────────────────────
|
|
830
|
+
// clauth update
|
|
831
|
+
// ──────────────────────────────────────────────
|
|
832
|
+
program
|
|
833
|
+
.command("update")
|
|
834
|
+
.description("Update clauth to the latest version")
|
|
835
|
+
.action(async () => {
|
|
836
|
+
const { execSync } = await import("child_process");
|
|
837
|
+
console.log(chalk.cyan("\n Updating clauth...\n"));
|
|
838
|
+
try {
|
|
839
|
+
execSync("npm install -g @lifeaitools/clauth@latest", { stdio: "inherit" });
|
|
840
|
+
console.log(chalk.green("\n Updated successfully.\n"));
|
|
841
|
+
} catch (err) {
|
|
842
|
+
console.log(chalk.red(`\n Update failed: ${err.message}\n`));
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// ──────────────────────────────────────────────
|
|
847
|
+
// clauth tunnel start|stop|status
|
|
848
|
+
// (setup moved to in-browser wizard at http://127.0.0.1:52437)
|
|
849
|
+
// ──────────────────────────────────────────────
|
|
850
|
+
const tunnelCmd = program.command("tunnel").description("Manage Cloudflare tunnel for claude.ai web integration");
|
|
851
|
+
|
|
852
|
+
tunnelCmd
|
|
853
|
+
.command("setup")
|
|
854
|
+
.description("Open the tunnel setup wizard in your browser")
|
|
855
|
+
.action(async () => {
|
|
856
|
+
console.log(chalk.cyan("\n Tunnel setup is now handled in the browser.\n"));
|
|
857
|
+
console.log(chalk.white(" 1. Start the daemon: clauth serve start"));
|
|
858
|
+
console.log(chalk.white(" 2. Open: http://127.0.0.1:52437"));
|
|
859
|
+
console.log(chalk.white(" 3. Unlock the vault and click \"Setup Tunnel\"\n"));
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
tunnelCmd
|
|
863
|
+
.command("start")
|
|
864
|
+
.description("Tell daemon to start the tunnel")
|
|
865
|
+
.action(async () => {
|
|
866
|
+
try {
|
|
867
|
+
const r = await fetch("http://127.0.0.1:52437/tunnel/start", {
|
|
868
|
+
method: "POST",
|
|
869
|
+
headers: { "Content-Type": "application/json" },
|
|
870
|
+
signal: AbortSignal.timeout(5000),
|
|
871
|
+
});
|
|
872
|
+
const data = await r.json().catch(() => ({}));
|
|
873
|
+
if (!r.ok) {
|
|
874
|
+
console.error(` ✗ ${data.error || r.statusText}`);
|
|
875
|
+
if (r.status === 401) console.error(" Unlock the daemon first: http://127.0.0.1:52437");
|
|
876
|
+
process.exit(1);
|
|
877
|
+
}
|
|
878
|
+
console.log(` ✓ ${data.message || "Tunnel starting — check status with: clauth tunnel status"}`);
|
|
879
|
+
} catch (e) {
|
|
880
|
+
console.error(" ✗ Daemon not running. Start it with: clauth serve");
|
|
881
|
+
process.exit(1);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
tunnelCmd
|
|
886
|
+
.command("stop")
|
|
887
|
+
.description("Tell daemon to stop the tunnel")
|
|
888
|
+
.action(async () => {
|
|
889
|
+
try {
|
|
890
|
+
const r = await fetch("http://127.0.0.1:52437/tunnel/stop", {
|
|
891
|
+
method: "POST",
|
|
892
|
+
headers: { "Content-Type": "application/json" },
|
|
893
|
+
signal: AbortSignal.timeout(5000),
|
|
894
|
+
});
|
|
895
|
+
const data = await r.json().catch(() => ({}));
|
|
896
|
+
if (!r.ok) {
|
|
897
|
+
console.error(` ✗ ${data.error || r.statusText}`);
|
|
898
|
+
process.exit(1);
|
|
899
|
+
}
|
|
900
|
+
console.log(" ✓ Tunnel stopped.");
|
|
901
|
+
} catch (e) {
|
|
902
|
+
console.error(" ✗ Daemon not running.");
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
tunnelCmd
|
|
908
|
+
.command("status")
|
|
909
|
+
.description("Show current tunnel status")
|
|
910
|
+
.action(async () => {
|
|
911
|
+
try {
|
|
912
|
+
const r = await fetch("http://127.0.0.1:52437/tunnel", {
|
|
913
|
+
signal: AbortSignal.timeout(5000),
|
|
914
|
+
});
|
|
915
|
+
const data = await r.json().catch(() => ({}));
|
|
916
|
+
const icons = {
|
|
917
|
+
live: "✓", starting: "◌", not_configured: "⚠",
|
|
918
|
+
not_started: "○", error: "✗", missing_cloudflared: "✗",
|
|
919
|
+
};
|
|
920
|
+
const labels = {
|
|
921
|
+
live: `Live — ${data.url || ""}`,
|
|
922
|
+
starting: "Starting...",
|
|
923
|
+
not_configured: "Not configured — open http://127.0.0.1:52437 and click Setup Tunnel",
|
|
924
|
+
not_started: "Not started — run: clauth tunnel start",
|
|
925
|
+
error: `Error${data.error ? ": " + data.error : ""} — check cloudflared config`,
|
|
926
|
+
missing_cloudflared: "cloudflared not installed — https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/",
|
|
927
|
+
};
|
|
928
|
+
const status = data.status || "unknown";
|
|
929
|
+
console.log(`\n ${icons[status] || "?"} Tunnel: ${labels[status] || status}\n`);
|
|
930
|
+
} catch (e) {
|
|
931
|
+
console.error(" ✗ Daemon not running. Start it with: clauth serve");
|
|
932
|
+
process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
// ──────────────────────────────────────────────
|
|
937
|
+
// clauth chitchat --session <id>
|
|
938
|
+
// ──────────────────────────────────────────────
|
|
939
|
+
program
|
|
940
|
+
.command("chitchat")
|
|
941
|
+
.description("Join a chitchat collab session — auto-starts /rdc:collab via claude -p")
|
|
942
|
+
.requiredOption("--session <id>", "Session ID from claude.ai")
|
|
943
|
+
.action(async (opts) => {
|
|
944
|
+
const id = opts.session;
|
|
945
|
+
// Verify session exists in the daemon
|
|
946
|
+
try {
|
|
947
|
+
const r = await fetch(`http://127.0.0.1:52437/chitchat/${id}`, { signal: AbortSignal.timeout(3000) });
|
|
948
|
+
if (!r.ok) {
|
|
949
|
+
console.error(`\n ✗ Session ${id} not found (daemon returned ${r.status})\n`);
|
|
950
|
+
process.exit(1);
|
|
951
|
+
}
|
|
952
|
+
} catch (e) {
|
|
953
|
+
console.error(`\n ✗ Daemon not reachable at http://127.0.0.1:52437 — is clauth running?\n`);
|
|
954
|
+
process.exit(1);
|
|
955
|
+
}
|
|
956
|
+
console.log(chalk.cyan(`\n [collab] session ${id} — starting /rdc:collab...\n`));
|
|
957
|
+
|
|
958
|
+
// Find claude binary (same candidates as daemon)
|
|
959
|
+
const { execSync: es, spawn } = await import("child_process");
|
|
960
|
+
let claudeBin = null;
|
|
961
|
+
for (const c of [
|
|
962
|
+
process.env.CLAUDE_BIN,
|
|
963
|
+
path.join(process.env.APPDATA || '', 'npm', 'claude.cmd'),
|
|
964
|
+
path.join(process.env.APPDATA || '', 'npm', 'claude'),
|
|
965
|
+
'claude',
|
|
966
|
+
].filter(Boolean)) {
|
|
967
|
+
try { es(`"${c}" --version`, { stdio: 'ignore', timeout: 3000 }); claudeBin = c; break; } catch {}
|
|
968
|
+
}
|
|
969
|
+
if (!claudeBin) {
|
|
970
|
+
console.error(' ✗ claude CLI not found — is @anthropic-ai/claude-code installed globally?');
|
|
971
|
+
process.exit(1);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Auto-invoke /rdc:collab skill with streaming output to this terminal
|
|
975
|
+
const proc = spawn(claudeBin, [
|
|
976
|
+
'-p', `/rdc:collab --session ${id}`,
|
|
977
|
+
'--dangerously-skip-permissions',
|
|
978
|
+
], {
|
|
979
|
+
stdio: 'inherit',
|
|
980
|
+
cwd: 'C:/Dev/regen-root',
|
|
981
|
+
shell: true,
|
|
982
|
+
});
|
|
983
|
+
proc.on('error', e => { console.error(` ✗ spawn error: ${e.message}`); process.exit(1); });
|
|
984
|
+
proc.on('exit', code => process.exit(code ?? 0));
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
// ──────────────────────────────────────────────
|
|
988
|
+
// clauth --help override banner
|
|
989
|
+
// ──────────────────────────────────────────────
|
|
990
|
+
program.addHelpText("beforeAll", chalk.cyan(`
|
|
991
|
+
██████╗██╗ █████╗ ██╗ ██╗████████╗██╗ ██╗
|
|
992
|
+
██╔════╝██║ ██╔══██╗██║ ██║╚══██╔══╝██║ ██║
|
|
993
|
+
██║ ██║ ███████║██║ ██║ ██║ ███████║
|
|
994
|
+
██║ ██║ ██╔══██║██║ ██║ ██║ ██╔══██║
|
|
995
|
+
╚██████╗███████╗██║ ██║╚██████╔╝ ██║ ██║ ██║
|
|
996
|
+
╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
|
|
997
|
+
v${VERSION} — LIFEAI Credential Vault
|
|
998
|
+
`));
|
|
999
|
+
|
|
1000
|
+
// ──────────────────────────────────────────────
|
|
1001
|
+
// clauth serve [action]
|
|
1002
|
+
// ──────────────────────────────────────────────
|
|
1003
|
+
program
|
|
1004
|
+
.command("serve [action]")
|
|
1005
|
+
.description("Manage localhost HTTP vault daemon and supervisor (start|stop|restart|ping|supervisor|install|uninstall)")
|
|
1006
|
+
.option("--port <n>", "Port (default: 52437)")
|
|
1007
|
+
.option("-p, --pw <password>", "clauth password (optional — omit to start locked, unlock in browser)")
|
|
1008
|
+
.option("--services <list>", "Comma-separated service whitelist (default: all)")
|
|
1009
|
+
.option("--tunnel <hostname>", "Fixed tunnel hostname (e.g. clauth.prtrust.fund) — uses named Cloudflare Tunnel instead of random URL")
|
|
1010
|
+
.option("--staged", "Start on staging port (52438) for blue-green verification before make-live")
|
|
1011
|
+
.option("--isolated", "Run on a non-live port without touching live PID files, browser, or boot-key credentials")
|
|
1012
|
+
.option("--from-boot-key", "Internal: password came from boot.key auto-unlock (degrade gracefully on verify failure)")
|
|
1013
|
+
.option("--pw-env", "Internal: read the daemon password from CLAUTH_BOOT_PASSWORD")
|
|
1014
|
+
.option("--pw-stdin", "Internal: read the installer password from standard input")
|
|
1015
|
+
.option("--action <action>", "Internal: action override for daemon child")
|
|
1016
|
+
.addHelpText("after", `
|
|
1017
|
+
Actions:
|
|
1018
|
+
start Start the server as a background daemon
|
|
1019
|
+
stop Stop the running daemon
|
|
1020
|
+
restart Stop + start
|
|
1021
|
+
ping Check if the daemon is running
|
|
1022
|
+
foreground Run in foreground (Ctrl+C to stop) — default if no action given
|
|
1023
|
+
mcp Run as MCP stdio server for Claude Code (JSON-RPC over stdin/stdout)
|
|
1024
|
+
supervisor Run localhost-only supervisor control plane on port 52439
|
|
1025
|
+
install Store password securely + register auto-start service (cross-platform)
|
|
1026
|
+
Windows: DPAPI + HKCU\\Run | macOS: Keychain + LaunchAgent | Linux: libsecret/openssl + systemd
|
|
1027
|
+
uninstall Remove auto-start service + delete stored password
|
|
1028
|
+
upgrade Blue-green upgrade: start new version on staging port, verify, then make live
|
|
1029
|
+
|
|
1030
|
+
MCP SSE (built into start/foreground):
|
|
1031
|
+
The HTTP daemon also serves MCP SSE transport at GET /sse + POST /message.
|
|
1032
|
+
Connect claude.ai via Cloudflare Tunnel pointing to http://127.0.0.1:52437/sse
|
|
1033
|
+
|
|
1034
|
+
Examples:
|
|
1035
|
+
clauth serve start Start locked — unlock at http://127.0.0.1:52437
|
|
1036
|
+
clauth serve start -p mypass Start pre-unlocked (password in memory only)
|
|
1037
|
+
clauth serve stop Stop the daemon
|
|
1038
|
+
clauth serve ping Check status
|
|
1039
|
+
clauth serve restart Restart (stays locked until browser unlock)
|
|
1040
|
+
clauth serve start --services github,vercel
|
|
1041
|
+
clauth serve mcp Start MCP server for Claude Code
|
|
1042
|
+
clauth serve mcp -p mypass Start MCP server pre-unlocked
|
|
1043
|
+
clauth serve supervisor Start supervisor API on http://127.0.0.1:52439
|
|
1044
|
+
clauth serve foreground --port 53137 --isolated
|
|
1045
|
+
Start isolated passwordless server for route tests
|
|
1046
|
+
clauth serve install Set up auto-start on login (DPAPI/Keychain/libsecret)
|
|
1047
|
+
clauth serve install --tunnel host Auto-start with Cloudflare Tunnel
|
|
1048
|
+
clauth serve uninstall Remove auto-start
|
|
1049
|
+
`)
|
|
1050
|
+
.action(async (action, opts) => {
|
|
1051
|
+
const resolvedAction = opts.action || action || "foreground";
|
|
1052
|
+
await runServe({ ...opts, action: resolvedAction });
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
program
|
|
1056
|
+
.command("ops <action>")
|
|
1057
|
+
.description("Call the bearer-authenticated PM2 and Coolify operations control plane")
|
|
1058
|
+
.option("--endpoint <url>", "HTTPS control-plane endpoint (or CLAUTH_OPS_ENDPOINT)")
|
|
1059
|
+
.option("--target <name>", "PM2 process name or id")
|
|
1060
|
+
.option("--script <path>", "PM2 script path for start")
|
|
1061
|
+
.option("--instances <n>", "PM2 scale target")
|
|
1062
|
+
.option("--operation <name>", "PM2 operation for run")
|
|
1063
|
+
.option("--args-json <json>", "JSON positional arguments for a raw pm2_* operation")
|
|
1064
|
+
.option("--options-json <json>", "JSON PM2 options for a typed operation")
|
|
1065
|
+
.option("--application <uuid>", "registered Coolify application UUID for promote")
|
|
1066
|
+
.option("--ref <name>", "registered Git ref for deploy")
|
|
1067
|
+
.option("--job <id>", "job id for status lookup")
|
|
1068
|
+
.option("--config <path>", "server-side JSON policy for ops install")
|
|
1069
|
+
.option("--dry-run", "validate and print ops install configuration without changing PM2")
|
|
1070
|
+
.addHelpText("after", `\nActions: catalog | list | describe | run | deploy | promote | job | install\n\nInstall: clauth ops install --config /etc/clauth/ops-control-plane.json\nThe installer creates or updates a PM2-managed local control plane, then proves /health and the bearer gate without reading a token.\n\nThe bearer is retrieved only from local clauth service vultr-ops-api-token and is never printed.\n`)
|
|
1071
|
+
.action(async (action, opts) => { if (action === "install") await runOpsInstall(opts); else await runOps(action, opts); });
|
|
1072
|
+
|
|
1073
|
+
program.parse(process.argv);
|