@lifeaitools/clauth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clauth-skill/SKILL.md +141 -0
- package/.clauth-skill/references/keys-guide.md +270 -0
- package/.clauth-skill/references/operator-guide.md +148 -0
- package/README.md +101 -0
- package/cli/api.js +108 -0
- package/cli/commands/install.js +258 -0
- package/cli/fingerprint.js +91 -0
- package/cli/index.js +403 -0
- package/install.ps1 +44 -0
- package/install.sh +38 -0
- package/package.json +54 -0
- package/scripts/bin/bootstrap-linux +0 -0
- package/scripts/bin/bootstrap-macos +0 -0
- package/scripts/bin/bootstrap-win.exe +0 -0
- package/scripts/bootstrap.cjs +43 -0
- package/scripts/build.sh +45 -0
- package/supabase/functions/auth-vault/index.ts +326 -0
- package/supabase/migrations/001_clauth_schema.sql +94 -0
- package/supabase/migrations/002_vault_helpers.sql +90 -0
package/cli/index.js
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
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 { getMachineHash, deriveToken, deriveSeedHash } from "./fingerprint.js";
|
|
10
|
+
import * as api from "./api.js";
|
|
11
|
+
import os from "os";
|
|
12
|
+
|
|
13
|
+
const config = new Conf({ projectName: "clauth" });
|
|
14
|
+
const VERSION = "0.1.0";
|
|
15
|
+
|
|
16
|
+
// ============================================================
|
|
17
|
+
// Password prompt helper
|
|
18
|
+
// ============================================================
|
|
19
|
+
async function promptPassword(message = "clauth password") {
|
|
20
|
+
const { pw } = await inquirer.prompt([{
|
|
21
|
+
type: "password",
|
|
22
|
+
name: "pw",
|
|
23
|
+
message,
|
|
24
|
+
mask: "*",
|
|
25
|
+
validate: v => v.length >= 8 || "Password must be at least 8 characters"
|
|
26
|
+
}]);
|
|
27
|
+
return pw;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ============================================================
|
|
31
|
+
// Auth helper ā get pw + derive token
|
|
32
|
+
// ============================================================
|
|
33
|
+
async function getAuth(pw) {
|
|
34
|
+
const password = pw || await promptPassword();
|
|
35
|
+
const machineHash = getMachineHash();
|
|
36
|
+
const { token, timestamp } = deriveToken(password, machineHash);
|
|
37
|
+
return { password, machineHash, token, timestamp };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================================
|
|
41
|
+
// Program
|
|
42
|
+
// ============================================================
|
|
43
|
+
const program = new Command();
|
|
44
|
+
|
|
45
|
+
program
|
|
46
|
+
.name("clauth")
|
|
47
|
+
.version(VERSION)
|
|
48
|
+
.description(chalk.cyan("š clauth") + " ā Hardware-bound credential vault for LIFEAI infrastructure");
|
|
49
|
+
|
|
50
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
51
|
+
// clauth install (Supabase provisioning + skill install + test)
|
|
52
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
53
|
+
import { runInstall } from './commands/install.js';
|
|
54
|
+
|
|
55
|
+
program
|
|
56
|
+
.command('install')
|
|
57
|
+
.description('Provision Supabase, deploy Edge Function, install Claude skill')
|
|
58
|
+
.action(async () => {
|
|
59
|
+
await runInstall();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
63
|
+
// clauth setup
|
|
64
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
65
|
+
program
|
|
66
|
+
.command("setup")
|
|
67
|
+
.description("Register this machine with the vault (run after clauth install)")
|
|
68
|
+
.option("--admin-token <token>", "Bootstrap token (from clauth install output)")
|
|
69
|
+
.option("--label <label>", "Human label for this machine")
|
|
70
|
+
.action(async (opts) => {
|
|
71
|
+
console.log(chalk.cyan("\nš clauth setup\n"));
|
|
72
|
+
|
|
73
|
+
// URL + anon key already saved by clauth install ā fail fast if missing
|
|
74
|
+
const savedUrl = config.get("supabase_url");
|
|
75
|
+
const savedAnon = config.get("supabase_anon_key");
|
|
76
|
+
if (!savedUrl || !savedAnon) {
|
|
77
|
+
console.log(chalk.yellow(" Supabase config not found. Run clauth install first.\n"));
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
console.log(chalk.gray(` Project: ${savedUrl}\n`));
|
|
81
|
+
|
|
82
|
+
const answers = await inquirer.prompt([
|
|
83
|
+
{ type: "input", name: "label", message: "Machine label:", default: opts.label || os.hostname() },
|
|
84
|
+
{ type: "password", name: "pw", message: "Set password:", mask: "*" },
|
|
85
|
+
{ type: "password", name: "adminTk", message: "Bootstrap token:", mask: "*",
|
|
86
|
+
default: opts.adminToken || "" },
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const spinner = ora("Registering machine with vault...").start();
|
|
90
|
+
try {
|
|
91
|
+
const machineHash = getMachineHash();
|
|
92
|
+
const seedHash = deriveSeedHash(machineHash, answers.pw);
|
|
93
|
+
const result = await api.registerMachine(machineHash, seedHash, answers.label, answers.adminTk);
|
|
94
|
+
if (result.error) throw new Error(result.error);
|
|
95
|
+
spinner.succeed(chalk.green(`Machine registered: ${machineHash.slice(0,12)}...`));
|
|
96
|
+
console.log(chalk.green("\nā clauth is ready.\n"));
|
|
97
|
+
console.log(chalk.cyan(" clauth test ā verify connection"));
|
|
98
|
+
console.log(chalk.cyan(" clauth status ā see all services\n"));
|
|
99
|
+
} catch (err) {
|
|
100
|
+
spinner.fail(chalk.red(`Setup failed: ${err.message}`));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
106
|
+
// clauth status
|
|
107
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
108
|
+
program
|
|
109
|
+
.command("status")
|
|
110
|
+
.description("Show all services and their state")
|
|
111
|
+
.option("-p, --pw <password>", "Password (or will prompt)")
|
|
112
|
+
.action(async (opts) => {
|
|
113
|
+
const auth = await getAuth(opts.pw);
|
|
114
|
+
const spinner = ora("Fetching service status...").start();
|
|
115
|
+
try {
|
|
116
|
+
const result = await api.status(auth.password, auth.machineHash, auth.token, auth.timestamp);
|
|
117
|
+
spinner.stop();
|
|
118
|
+
if (result.error) { console.log(chalk.red(`Error: ${result.error}`)); return; }
|
|
119
|
+
|
|
120
|
+
console.log(chalk.cyan("\nš clauth service status\n"));
|
|
121
|
+
console.log(
|
|
122
|
+
chalk.bold(
|
|
123
|
+
" " + "SERVICE".padEnd(20) + "TYPE".padEnd(12) + "STATUS".padEnd(12) +
|
|
124
|
+
"KEY STORED".padEnd(12) + "LAST RETRIEVED"
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
console.log(" " + "ā".repeat(72));
|
|
128
|
+
|
|
129
|
+
for (const s of result.services || []) {
|
|
130
|
+
const status = s.enabled
|
|
131
|
+
? chalk.green("ACTIVE".padEnd(12))
|
|
132
|
+
: s.vault_key
|
|
133
|
+
? chalk.yellow("SUSPENDED".padEnd(12))
|
|
134
|
+
: chalk.gray("NO KEY".padEnd(12));
|
|
135
|
+
const hasKey = s.vault_key ? chalk.green("ā".padEnd(12)) : chalk.gray("ā".padEnd(12));
|
|
136
|
+
const lastGet = s.last_retrieved
|
|
137
|
+
? new Date(s.last_retrieved).toLocaleDateString()
|
|
138
|
+
: chalk.gray("never");
|
|
139
|
+
|
|
140
|
+
console.log(` ${s.name.padEnd(20)}${s.key_type.padEnd(12)}${status}${hasKey}${lastGet}`);
|
|
141
|
+
}
|
|
142
|
+
console.log();
|
|
143
|
+
} catch (err) {
|
|
144
|
+
spinner.fail(chalk.red(err.message));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
149
|
+
// clauth write pw <new_password>
|
|
150
|
+
// clauth write params
|
|
151
|
+
// clauth write key <service> <value>
|
|
152
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
153
|
+
const writeCmd = program.command("write").description("Write credentials or update auth parameters");
|
|
154
|
+
|
|
155
|
+
writeCmd
|
|
156
|
+
.command("pw [newpw]")
|
|
157
|
+
.description("Set or update clauth master password")
|
|
158
|
+
.action(async (newpw) => {
|
|
159
|
+
console.log(chalk.cyan("\nš clauth write pw\n"));
|
|
160
|
+
const current = await promptPassword("Current password (to verify)");
|
|
161
|
+
const pw = newpw || (await inquirer.prompt([
|
|
162
|
+
{ type: "password", name: "p", message: "New password:", mask: "*" },
|
|
163
|
+
{ type: "password", name: "c", message: "Confirm new password:", mask: "*" }
|
|
164
|
+
]).then(a => { if (a.p !== a.c) { console.log(chalk.red("Passwords don't match")); process.exit(1); } return a.p; }));
|
|
165
|
+
|
|
166
|
+
// Re-register machine with new seed hash
|
|
167
|
+
const machineHash = getMachineHash();
|
|
168
|
+
const newSeedHash = deriveSeedHash(machineHash, pw);
|
|
169
|
+
const { token, timestamp } = deriveToken(current, machineHash);
|
|
170
|
+
const adminToken = await inquirer.prompt([{
|
|
171
|
+
type: "password", name: "t", message: "Admin bootstrap token (required for re-registration):", mask: "*"
|
|
172
|
+
}]).then(a => a.t);
|
|
173
|
+
|
|
174
|
+
const spinner = ora("Updating password and re-registering machine...").start();
|
|
175
|
+
try {
|
|
176
|
+
const result = await api.registerMachine(machineHash, newSeedHash, null, adminToken);
|
|
177
|
+
if (result.error) throw new Error(result.error);
|
|
178
|
+
spinner.succeed(chalk.green("Password updated and machine re-registered."));
|
|
179
|
+
} catch (err) {
|
|
180
|
+
spinner.fail(chalk.red(err.message));
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
writeCmd
|
|
185
|
+
.command("params")
|
|
186
|
+
.description("Re-read hardware fingerprint (use after hardware change)")
|
|
187
|
+
.action(async () => {
|
|
188
|
+
const spinner = ora("Reading hardware fingerprint...").start();
|
|
189
|
+
try {
|
|
190
|
+
const hash = getMachineHash();
|
|
191
|
+
spinner.succeed(chalk.green(`Machine hash: ${hash.slice(0,16)}...`));
|
|
192
|
+
console.log(chalk.gray("Full hash: " + hash));
|
|
193
|
+
} catch (err) {
|
|
194
|
+
spinner.fail(chalk.red(err.message));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
writeCmd
|
|
199
|
+
.command("key <service> [value]")
|
|
200
|
+
.description("Write a credential into vault for a service")
|
|
201
|
+
.option("-p, --pw <password>", "Password")
|
|
202
|
+
.action(async (service, value, opts) => {
|
|
203
|
+
const auth = await getAuth(opts.pw);
|
|
204
|
+
let val = value;
|
|
205
|
+
if (!val) {
|
|
206
|
+
const { v } = await inquirer.prompt([{ type: "password", name: "v", message: `Value for ${service}:`, mask: "*" }]);
|
|
207
|
+
val = v;
|
|
208
|
+
}
|
|
209
|
+
const spinner = ora(`Writing key for ${service}...`).start();
|
|
210
|
+
try {
|
|
211
|
+
const result = await api.write(auth.password, auth.machineHash, auth.token, auth.timestamp, service, val);
|
|
212
|
+
if (result.error) throw new Error(result.error);
|
|
213
|
+
spinner.succeed(chalk.green(`Key stored in vault: auth.${service}`));
|
|
214
|
+
} catch (err) {
|
|
215
|
+
spinner.fail(chalk.red(err.message));
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
220
|
+
// clauth enable <service|all>
|
|
221
|
+
// clauth disable <service|all>
|
|
222
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
223
|
+
program
|
|
224
|
+
.command("enable <service>")
|
|
225
|
+
.description("Enable a service (or 'all')")
|
|
226
|
+
.option("-p, --pw <password>")
|
|
227
|
+
.action(async (service, opts) => {
|
|
228
|
+
const auth = await getAuth(opts.pw);
|
|
229
|
+
const spinner = ora(`Enabling ${service}...`).start();
|
|
230
|
+
try {
|
|
231
|
+
const result = await api.enable(auth.password, auth.machineHash, auth.token, auth.timestamp, service, true);
|
|
232
|
+
if (result.error) throw new Error(result.error);
|
|
233
|
+
spinner.succeed(chalk.green(`Enabled: ${service}`));
|
|
234
|
+
} catch (err) { spinner.fail(chalk.red(err.message)); }
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
program
|
|
238
|
+
.command("disable <service>")
|
|
239
|
+
.description("Disable a service (or 'all')")
|
|
240
|
+
.option("-p, --pw <password>")
|
|
241
|
+
.action(async (service, opts) => {
|
|
242
|
+
const auth = await getAuth(opts.pw);
|
|
243
|
+
const spinner = ora(`Disabling ${service}...`).start();
|
|
244
|
+
try {
|
|
245
|
+
const result = await api.enable(auth.password, auth.machineHash, auth.token, auth.timestamp, service, false);
|
|
246
|
+
if (result.error) throw new Error(result.error);
|
|
247
|
+
spinner.succeed(chalk.yellow(`Disabled: ${service}`));
|
|
248
|
+
} catch (err) { spinner.fail(chalk.red(err.message)); }
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
252
|
+
// clauth add service <name>
|
|
253
|
+
// clauth remove service <name>
|
|
254
|
+
// clauth list services
|
|
255
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
256
|
+
const addCmd = program.command("add").description("Add resources to the registry");
|
|
257
|
+
|
|
258
|
+
addCmd
|
|
259
|
+
.command("service <name>")
|
|
260
|
+
.description("Register a new service slot")
|
|
261
|
+
.option("--type <type>", "Key type: token | keypair | connstring | oauth")
|
|
262
|
+
.option("--label <label>", "Human-readable label")
|
|
263
|
+
.option("--description <desc>", "Description")
|
|
264
|
+
.option("-p, --pw <password>")
|
|
265
|
+
.action(async (name, opts) => {
|
|
266
|
+
const auth = await getAuth(opts.pw);
|
|
267
|
+
const answers = await inquirer.prompt([
|
|
268
|
+
{ type: "input", name: "label", message: "Label:", default: opts.label || name },
|
|
269
|
+
{ type: "list", name: "key_type", message: "Key type:", choices: ["token","keypair","connstring","oauth"], default: opts.type || "token" },
|
|
270
|
+
{ type: "input", name: "desc", message: "Description (optional):", default: opts.description || "" }
|
|
271
|
+
]);
|
|
272
|
+
const spinner = ora(`Adding service: ${name}...`).start();
|
|
273
|
+
try {
|
|
274
|
+
const result = await api.addService(
|
|
275
|
+
auth.password, auth.machineHash, auth.token, auth.timestamp,
|
|
276
|
+
name, answers.label, answers.key_type, answers.desc
|
|
277
|
+
);
|
|
278
|
+
if (result.error) throw new Error(result.error);
|
|
279
|
+
spinner.succeed(chalk.green(`Service added: ${name} (${answers.key_type})`));
|
|
280
|
+
console.log(chalk.gray(` Next: clauth write key ${name}`));
|
|
281
|
+
} catch (err) { spinner.fail(chalk.red(err.message)); }
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const removeCmd = program.command("remove").description("Remove resources from the registry");
|
|
285
|
+
|
|
286
|
+
removeCmd
|
|
287
|
+
.command("service <name>")
|
|
288
|
+
.description("Remove a service and its key from vault")
|
|
289
|
+
.option("-p, --pw <password>")
|
|
290
|
+
.action(async (name, opts) => {
|
|
291
|
+
const { confirm } = await inquirer.prompt([{
|
|
292
|
+
type: "input", name: "confirm",
|
|
293
|
+
message: chalk.red(`Type "CONFIRM REMOVE ${name.toUpperCase()}" to proceed:`)
|
|
294
|
+
}]);
|
|
295
|
+
const auth = await getAuth(opts.pw);
|
|
296
|
+
const spinner = ora(`Removing ${name}...`).start();
|
|
297
|
+
try {
|
|
298
|
+
const result = await api.removeService(auth.password, auth.machineHash, auth.token, auth.timestamp, name, confirm);
|
|
299
|
+
if (result.error) throw new Error(result.error);
|
|
300
|
+
spinner.succeed(chalk.yellow(`Removed: ${name}`));
|
|
301
|
+
} catch (err) { spinner.fail(chalk.red(err.message)); }
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
program
|
|
305
|
+
.command("list")
|
|
306
|
+
.description("List all registered services")
|
|
307
|
+
.option("-p, --pw <password>")
|
|
308
|
+
.action(async (opts) => {
|
|
309
|
+
const auth = await getAuth(opts.pw);
|
|
310
|
+
const result = await api.status(auth.password, auth.machineHash, auth.token, auth.timestamp);
|
|
311
|
+
if (result.error) { console.log(chalk.red(result.error)); return; }
|
|
312
|
+
console.log(chalk.cyan("\n Registered services:\n"));
|
|
313
|
+
for (const s of result.services || []) {
|
|
314
|
+
console.log(` ${chalk.bold(s.name.padEnd(20))} ${chalk.gray(s.key_type.padEnd(12))} ${chalk.gray(s.description || "")}`);
|
|
315
|
+
}
|
|
316
|
+
console.log();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
320
|
+
// clauth test <service|all>
|
|
321
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
322
|
+
program
|
|
323
|
+
.command("test [service]")
|
|
324
|
+
.description("Test HMAC handshake ā no key returned")
|
|
325
|
+
.option("-p, --pw <password>")
|
|
326
|
+
.action(async (service, opts) => {
|
|
327
|
+
const auth = await getAuth(opts.pw);
|
|
328
|
+
const spinner = ora("Testing auth handshake...").start();
|
|
329
|
+
try {
|
|
330
|
+
const result = await api.test(auth.password, auth.machineHash, auth.token, auth.timestamp);
|
|
331
|
+
if (result.error) throw new Error(`${result.error}: ${result.reason}`);
|
|
332
|
+
spinner.succeed(chalk.green("PASS ā HMAC validated"));
|
|
333
|
+
console.log(chalk.gray(` Machine: ${auth.machineHash.slice(0,16)}...`));
|
|
334
|
+
console.log(chalk.gray(` Window: ${new Date(result.timestamp).toISOString()}`));
|
|
335
|
+
} catch (err) {
|
|
336
|
+
spinner.fail(chalk.red("FAIL ā " + err.message));
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
341
|
+
// clauth get <service>
|
|
342
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
343
|
+
program
|
|
344
|
+
.command("get <service>")
|
|
345
|
+
.description("Retrieve a key from vault")
|
|
346
|
+
.option("-p, --pw <password>")
|
|
347
|
+
.option("--json", "Output raw JSON")
|
|
348
|
+
.action(async (service, opts) => {
|
|
349
|
+
const auth = await getAuth(opts.pw);
|
|
350
|
+
const spinner = ora(`Retrieving ${service}...`).start();
|
|
351
|
+
try {
|
|
352
|
+
const result = await api.retrieve(auth.password, auth.machineHash, auth.token, auth.timestamp, service);
|
|
353
|
+
spinner.stop();
|
|
354
|
+
if (result.error) { console.log(chalk.red(`Error: ${result.error}`)); return; }
|
|
355
|
+
if (opts.json) {
|
|
356
|
+
console.log(JSON.stringify(result, null, 2));
|
|
357
|
+
} else {
|
|
358
|
+
console.log(chalk.cyan(`\nš ${service} (${result.key_type})\n`));
|
|
359
|
+
const val = typeof result.value === "string" ? result.value : JSON.stringify(result.value, null, 2);
|
|
360
|
+
console.log(val);
|
|
361
|
+
console.log();
|
|
362
|
+
}
|
|
363
|
+
} catch (err) {
|
|
364
|
+
spinner.fail(chalk.red(err.message));
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
369
|
+
// clauth revoke <service|all>
|
|
370
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
371
|
+
program
|
|
372
|
+
.command("revoke <service>")
|
|
373
|
+
.description("Delete key from vault (destructive)")
|
|
374
|
+
.option("-p, --pw <password>")
|
|
375
|
+
.action(async (service, opts) => {
|
|
376
|
+
const phrase = service === "all" ? "CONFIRM REVOKE ALL" : `CONFIRM REVOKE ${service.toUpperCase()}`;
|
|
377
|
+
const { confirm } = await inquirer.prompt([{
|
|
378
|
+
type: "input", name: "confirm",
|
|
379
|
+
message: chalk.red(`Type "${phrase}" to proceed:`)
|
|
380
|
+
}]);
|
|
381
|
+
const auth = await getAuth(opts.pw);
|
|
382
|
+
const spinner = ora(`Revoking ${service}...`).start();
|
|
383
|
+
try {
|
|
384
|
+
const result = await api.revoke(auth.password, auth.machineHash, auth.token, auth.timestamp, service, confirm);
|
|
385
|
+
if (result.error) throw new Error(result.error);
|
|
386
|
+
spinner.succeed(chalk.yellow(`Revoked: ${service}`));
|
|
387
|
+
} catch (err) { spinner.fail(chalk.red(err.message)); }
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
391
|
+
// clauth --help override banner
|
|
392
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
393
|
+
program.addHelpText("beforeAll", chalk.cyan(`
|
|
394
|
+
āāāāāāāāāā āāāāāā āāā āāāāāāāāāāāāāāā āāā
|
|
395
|
+
āāāāāāāāāāā āāāāāāāāāāā āāāāāāāāāāāāāāā āāā
|
|
396
|
+
āāā āāā āāāāāāāāāāā āāā āāā āāāāāāāā
|
|
397
|
+
āāā āāā āāāāāāāāāāā āāā āāā āāāāāāāā
|
|
398
|
+
āāāāāāāāāāāāāāāāāāā āāāāāāāāāāāā āāā āāā āāā
|
|
399
|
+
āāāāāāāāāāāāāāāāāā āāā āāāāāāā āāā āāā āāā
|
|
400
|
+
v${VERSION} ā LIFEAI Credential Vault
|
|
401
|
+
`));
|
|
402
|
+
|
|
403
|
+
program.parse(process.argv);
|
package/install.ps1
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# clauth installer ā Windows
|
|
2
|
+
# One-liner: iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/LIFEAI/clauth/main/install.ps1'))
|
|
3
|
+
|
|
4
|
+
$ErrorActionPreference = "Stop"
|
|
5
|
+
$REPO = "https://github.com/LIFEAI/clauth.git"
|
|
6
|
+
$DIR = "$env:USERPROFILE\.clauth"
|
|
7
|
+
|
|
8
|
+
# Check git
|
|
9
|
+
try { git --version | Out-Null } catch {
|
|
10
|
+
Write-Host ""
|
|
11
|
+
Write-Host " x git is required." -ForegroundColor Red
|
|
12
|
+
Write-Host " Install from https://git-scm.com then re-run this script."
|
|
13
|
+
Write-Host ""
|
|
14
|
+
exit 1
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Check Node
|
|
18
|
+
try { node --version | Out-Null } catch {
|
|
19
|
+
Write-Host ""
|
|
20
|
+
Write-Host " x Node.js v18+ is required." -ForegroundColor Red
|
|
21
|
+
Write-Host " Install from https://nodejs.org then re-run this script."
|
|
22
|
+
Write-Host ""
|
|
23
|
+
exit 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Clone or update
|
|
27
|
+
if (Test-Path "$DIR\.git") {
|
|
28
|
+
Write-Host " Updating clauth..."
|
|
29
|
+
Set-Location $DIR; git pull --quiet
|
|
30
|
+
} else {
|
|
31
|
+
Write-Host " Cloning clauth..."
|
|
32
|
+
git clone --quiet $REPO $DIR
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Run compiled bootstrap binary
|
|
36
|
+
$bootstrap = "$DIR\scripts\bin\bootstrap-win.exe"
|
|
37
|
+
if (-not (Test-Path $bootstrap)) {
|
|
38
|
+
Write-Host " x Bootstrap binary not found at $bootstrap" -ForegroundColor Red
|
|
39
|
+
exit 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Set-Location $DIR
|
|
43
|
+
& $bootstrap
|
|
44
|
+
exit $LASTEXITCODE
|
package/install.sh
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# clauth installer
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
REPO="https://github.com/LIFEAI/clauth.git"
|
|
6
|
+
DIR="$HOME/.clauth"
|
|
7
|
+
|
|
8
|
+
# Check git
|
|
9
|
+
if ! command -v git &>/dev/null; then
|
|
10
|
+
echo ""; echo " x git is required."
|
|
11
|
+
echo " Install git from https://git-scm.com then re-run this script."; echo ""
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# Check Node
|
|
16
|
+
if ! command -v node &>/dev/null; then
|
|
17
|
+
echo ""; echo " x Node.js v18+ is required."
|
|
18
|
+
echo " Install from https://nodejs.org then re-run this script."; echo ""
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Clone or update
|
|
23
|
+
if [ -d "$DIR/.git" ]; then
|
|
24
|
+
echo " Updating clauth..."; cd "$DIR" && git pull --quiet
|
|
25
|
+
else
|
|
26
|
+
echo " Cloning clauth..."; git clone --quiet "$REPO" "$DIR"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Run compiled bootstrap
|
|
30
|
+
UNAME=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
31
|
+
if [[ "$UNAME" == *"darwin"* ]]; then
|
|
32
|
+
BOOTSTRAP="$DIR/scripts/bin/bootstrap-macos"
|
|
33
|
+
else
|
|
34
|
+
BOOTSTRAP="$DIR/scripts/bin/bootstrap-linux"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
chmod +x "$BOOTSTRAP"
|
|
38
|
+
cd "$DIR" && "$BOOTSTRAP"
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lifeaitools/clauth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"clauth": "./cli/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "bash scripts/build.sh"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"chalk": "^5.3.0",
|
|
14
|
+
"commander": "^12.1.0",
|
|
15
|
+
"conf": "^13.0.0",
|
|
16
|
+
"inquirer": "^10.1.0",
|
|
17
|
+
"node-fetch": "^3.3.2",
|
|
18
|
+
"ora": "^8.1.0"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"lifeai",
|
|
25
|
+
"credentials",
|
|
26
|
+
"vault",
|
|
27
|
+
"cli",
|
|
28
|
+
"prt"
|
|
29
|
+
],
|
|
30
|
+
"author": "Dave Ladouceur <dave@life.ai>",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/LIFEAI/clauth.git"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"javascript-obfuscator": "^5.3.0"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"cli/",
|
|
41
|
+
"scripts/bin/",
|
|
42
|
+
"scripts/bootstrap.cjs",
|
|
43
|
+
"scripts/build.sh",
|
|
44
|
+
"supabase/",
|
|
45
|
+
".clauth-skill/",
|
|
46
|
+
"install.sh",
|
|
47
|
+
"install.ps1",
|
|
48
|
+
"README.md"
|
|
49
|
+
],
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/LIFEAI/clauth"
|
|
54
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// bootstrap.cjs ā CommonJS, compiled to binary by pkg
|
|
2
|
+
// Installs npm deps, links clauth, chains to: clauth install
|
|
3
|
+
'use strict';
|
|
4
|
+
const { execSync, spawnSync } = require('child_process');
|
|
5
|
+
const { join } = require('path');
|
|
6
|
+
const ROOT = join(__dirname, '..');
|
|
7
|
+
|
|
8
|
+
function run(cmd, opts) {
|
|
9
|
+
return spawnSync(cmd, Object.assign({ shell: true, stdio: 'inherit', cwd: ROOT }, opts || {}));
|
|
10
|
+
}
|
|
11
|
+
function check(cmd) {
|
|
12
|
+
try { execSync(cmd + ' --version', { stdio: 'pipe' }); return true; } catch { return false; }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
console.log('\n Installing clauth runtime...\n');
|
|
16
|
+
|
|
17
|
+
if (!check('git')) {
|
|
18
|
+
console.error(' x git not found. Install from https://git-scm.com');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
var nodeVer = parseInt(process.version.slice(1));
|
|
22
|
+
if (nodeVer < 18) {
|
|
23
|
+
console.error(' x Node.js v18+ required (found ' + process.version + ')');
|
|
24
|
+
console.error(' Install from https://nodejs.org');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(' -> Installing dependencies...');
|
|
29
|
+
var inst = run('npm install --no-fund --no-audit', { stdio: ['ignore','ignore','pipe'] });
|
|
30
|
+
if (inst.status !== 0) { console.error(' x npm install failed'); process.exit(1); }
|
|
31
|
+
console.log(' + Dependencies ready');
|
|
32
|
+
|
|
33
|
+
console.log(' -> Linking clauth command...');
|
|
34
|
+
var lnk = run('npm link', { stdio: ['ignore','ignore','pipe'] });
|
|
35
|
+
if (lnk.status !== 0) {
|
|
36
|
+
var slnk = run('sudo npm link', { stdio: 'inherit' });
|
|
37
|
+
if (slnk.status !== 0) console.warn(' ! Could not link globally ā add ' + ROOT + '/node_modules/.bin to PATH');
|
|
38
|
+
}
|
|
39
|
+
console.log(' + clauth linked');
|
|
40
|
+
|
|
41
|
+
console.log('\n -> Launching clauth install...\n');
|
|
42
|
+
var r = run('clauth install');
|
|
43
|
+
process.exit(r.status || 0);
|
package/scripts/build.sh
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/build.sh ā regenerate bootstrap binaries from source
|
|
3
|
+
# Run: npm run build
|
|
4
|
+
set -e
|
|
5
|
+
cd "$(dirname "$0")/.."
|
|
6
|
+
|
|
7
|
+
echo "ā Obfuscating bootstrap.cjs..."
|
|
8
|
+
node -e "
|
|
9
|
+
const J = require('javascript-obfuscator');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const src = fs.readFileSync('scripts/bootstrap.cjs', 'utf8');
|
|
12
|
+
const out = J.obfuscate(src, {
|
|
13
|
+
compact: true,
|
|
14
|
+
controlFlowFlattening: true, controlFlowFlatteningThreshold: 0.75,
|
|
15
|
+
deadCodeInjection: true, deadCodeInjectionThreshold: 0.4,
|
|
16
|
+
identifierNamesGenerator: 'hexadecimal',
|
|
17
|
+
rotateStringArray: true, shuffleStringArray: true,
|
|
18
|
+
splitStrings: true, splitStringsChunkLength: 8,
|
|
19
|
+
stringArray: true, stringArrayEncoding: ['base64'],
|
|
20
|
+
stringArrayThreshold: 0.85,
|
|
21
|
+
transformObjectKeys: true, target: 'node'
|
|
22
|
+
});
|
|
23
|
+
fs.writeFileSync('scripts/bootstrap.ob.cjs', out.getObfuscatedCode());
|
|
24
|
+
console.log(' ā Obfuscated');
|
|
25
|
+
"
|
|
26
|
+
|
|
27
|
+
echo "ā Compiling binaries..."
|
|
28
|
+
mkdir -p scripts/bin
|
|
29
|
+
npx pkg scripts/bootstrap.ob.cjs \
|
|
30
|
+
--targets node18-win-x64,node18-linux-x64,node18-macos-x64 \
|
|
31
|
+
--out-path scripts/bin/tmp/ \
|
|
32
|
+
2>&1 | grep -v "^$" | grep -v "Warning" || true
|
|
33
|
+
|
|
34
|
+
# Rename to clean names
|
|
35
|
+
mv -f scripts/bin/tmp/bootstrap.ob-linux scripts/bin/bootstrap-linux 2>/dev/null || true
|
|
36
|
+
mv -f scripts/bin/tmp/bootstrap.ob-macos scripts/bin/bootstrap-macos 2>/dev/null || true
|
|
37
|
+
mv -f scripts/bin/tmp/bootstrap.ob-win.exe scripts/bin/bootstrap-win.exe 2>/dev/null || true
|
|
38
|
+
rm -rf scripts/bin/tmp
|
|
39
|
+
chmod +x scripts/bin/bootstrap-linux scripts/bin/bootstrap-macos 2>/dev/null || true
|
|
40
|
+
|
|
41
|
+
# Clean intermediate
|
|
42
|
+
rm -f scripts/bootstrap.ob.cjs
|
|
43
|
+
|
|
44
|
+
ls -lh scripts/bin/
|
|
45
|
+
echo "ā Build complete"
|