@lifeaitools/clauth 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,13 +1,13 @@
1
- # @lifeai/clauth
1
+ # @lifeaitools/clauth
2
2
 
3
- Hardware-bound credential vault for the LIFEAI stack. Your machine is the second factor. Keys live in Supabase Vault (AES-256).
3
+ Hardware-bound credential vault for the LIFEAI stack. Your machine is the second factor. Keys live in Supabase Vault (AES-256). Nothing sensitive ever touches a config file.
4
4
 
5
5
  ---
6
6
 
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install -g @lifeai/clauth
10
+ npm install -g @lifeaitools/clauth
11
11
  ```
12
12
 
13
13
  Then provision your Supabase project:
@@ -17,10 +17,10 @@ clauth install
17
17
  ```
18
18
 
19
19
  That's it. `clauth install` handles everything:
20
- - Creates database tables
21
- - Deploys the Edge Function
20
+ - Creates all database tables
21
+ - Deploys the `auth-vault` Edge Function
22
22
  - Generates HMAC salt + bootstrap token
23
- - Tests the connection
23
+ - Tests the connection end-to-end
24
24
  - Installs the Claude skill
25
25
 
26
26
  At the end it prints a **bootstrap token** — save it for the next step.
@@ -47,7 +47,7 @@ clauth status # → 12 services, all NO KEY
47
47
 
48
48
  Two things from Supabase:
49
49
 
50
- **1. Project ref** — the last segment of your project URL:
50
+ **1. Project ref** — the last segment of your Supabase project URL:
51
51
  `https://supabase.com/dashboard/project/` **`your-ref-here`**
52
52
 
53
53
  **2. Personal Access Token (PAT)**:
@@ -57,6 +57,16 @@ Two things from Supabase:
57
57
 
58
58
  ---
59
59
 
60
+ ## Writing Your First Key
61
+
62
+ ```bash
63
+ clauth write key github # prompts for value
64
+ clauth enable github
65
+ clauth get github
66
+ ```
67
+
68
+ ---
69
+
60
70
  ## Command Reference
61
71
 
62
72
  ```
@@ -71,9 +81,9 @@ clauth enable <svc|all> Activate service
71
81
  clauth disable <svc|all> Suspend service
72
82
  clauth get <service> Retrieve a key
73
83
 
74
- clauth add service <name> Register new service
75
- clauth remove service <name>Remove service
76
- clauth revoke <svc|all> Delete key (destructive, confirms first)
84
+ clauth add service <n> Register new service
85
+ clauth remove service <n> Remove service
86
+ clauth revoke <svc|all> Delete key (destructive)
77
87
  ```
78
88
 
79
89
  ## Built-in Services
@@ -98,4 +108,18 @@ Nothing stored locally. Password never persisted. Machine hash is one-way only.
98
108
 
99
109
  ---
100
110
 
111
+ ## Releasing a New Version (maintainers)
112
+
113
+ ```bash
114
+ # 1. Bump version in package.json
115
+ # 2. Commit and tag
116
+ git tag v0.1.1
117
+ git push --tags
118
+ # GitHub Actions publishes automatically via Trusted Publishing
119
+ ```
120
+
121
+ ---
122
+
101
123
  > Life before Profits. — LIFEAI / PRT
124
+ >
125
+ > ☕ [Support this project](https://github.com/sponsors/DaveLadouceur)
@@ -33,45 +33,28 @@ const SKILLS_DIR = process.env.CLAUTH_SKILLS_DIR ||
33
33
  : join(process.env.HOME || '', '.claude', 'skills'));
34
34
 
35
35
  // ─────────────────────────────────────────────
36
- // Prompt helpers
36
+ // Prompt helpers — single shared readline to
37
+ // avoid losing buffered stdin between prompts
37
38
  // ─────────────────────────────────────────────
38
39
 
40
+ let _rl;
41
+ function getRL() {
42
+ if (!_rl) {
43
+ _rl = createInterface({ input: process.stdin, output: process.stdout });
44
+ }
45
+ return _rl;
46
+ }
47
+ function closeRL() {
48
+ if (_rl) { _rl.close(); _rl = null; }
49
+ }
50
+
39
51
  function ask(question) {
40
- const rl = createInterface({ input: process.stdin, output: process.stdout });
41
- return new Promise(res => rl.question(question, a => { rl.close(); res(a.trim()); }));
52
+ return new Promise(res => getRL().question(question, a => res(a.trim())));
42
53
  }
43
54
 
44
55
  function askSecret(label) {
45
- return new Promise(res => {
46
- const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
47
- process.stdout.write(label);
48
- let val = '';
49
- if (process.stdin.isTTY) {
50
- process.stdin.setRawMode(true);
51
- process.stdin.resume();
52
- process.stdin.setEncoding('utf8');
53
- const onData = ch => {
54
- if (ch === '\r' || ch === '\n') {
55
- process.stdin.setRawMode(false);
56
- process.stdin.pause();
57
- process.stdin.removeListener('data', onData);
58
- process.stdout.write('\n');
59
- rl.close();
60
- res(val);
61
- } else if (ch === '\u0003') {
62
- process.exit();
63
- } else if (ch === '\u007f') {
64
- val = val.slice(0, -1);
65
- } else {
66
- val += ch;
67
- process.stdout.write('*');
68
- }
69
- };
70
- process.stdin.on('data', onData);
71
- } else {
72
- rl.question('', a => { rl.close(); res(a.trim()); });
73
- }
74
- });
56
+ // In non-TTY (piped input, CI, Git Bash on Windows), just read a line
57
+ return ask(label);
75
58
  }
76
59
 
77
60
  // ─────────────────────────────────────────────
@@ -89,14 +72,16 @@ async function mgmt(pat, method, path, body) {
89
72
  throw new Error(`${method} ${path} → HTTP ${res.status}: ${text}`);
90
73
  }
91
74
  if (res.status === 204) return {};
92
- return res.json();
75
+ const text = await res.text();
76
+ if (!text) return {};
77
+ return JSON.parse(text);
93
78
  }
94
79
 
95
80
  // ─────────────────────────────────────────────
96
81
  // Main install command
97
82
  // ─────────────────────────────────────────────
98
83
 
99
- export async function runInstall() {
84
+ export async function runInstall(opts = {}) {
100
85
  console.log(chalk.cyan('\n🔐 clauth install\n'));
101
86
 
102
87
  // ── Step 1: Check internet ────────────────
@@ -112,16 +97,22 @@ export async function runInstall() {
112
97
  }
113
98
 
114
99
  // ── Step 2: Collect credentials ───────────
115
- console.log(chalk.cyan('\nYou need two things from Supabase:\n'));
116
- console.log(chalk.gray(' Project ref: last segment of your project URL'));
117
- console.log(chalk.gray(' e.g. supabase.com/dashboard/project/') + chalk.white('uvojezuorjgqzmhhgluu'));
118
- console.log('');
119
- console.log(chalk.gray(' Personal Access Token (PAT):'));
120
- console.log(chalk.gray(' supabase.com/dashboard/account/tokens → Generate new token'));
121
- console.log(chalk.gray(' (This is NOT your anon key or service_role key)\n'));
100
+ let ref = opts.ref;
101
+ let pat = opts.pat;
122
102
 
123
- const ref = await ask(chalk.white('Supabase project ref: '));
124
- const pat = await askSecret(chalk.white('Supabase PAT: '));
103
+ if (!ref || !pat) {
104
+ console.log(chalk.cyan('\nYou need two things from Supabase:\n'));
105
+ console.log(chalk.gray(' Project ref: last segment of your project URL'));
106
+ console.log(chalk.gray(' e.g. supabase.com/dashboard/project/') + chalk.white('uvojezuorjgqzmhhgluu'));
107
+ console.log('');
108
+ console.log(chalk.gray(' Personal Access Token (PAT):'));
109
+ console.log(chalk.gray(' supabase.com/dashboard/account/tokens → Generate new token'));
110
+ console.log(chalk.gray(' (This is NOT your anon key or service_role key)\n'));
111
+
112
+ if (!ref) ref = await ask(chalk.white('Supabase project ref: '));
113
+ if (!pat) pat = await askSecret(chalk.white('Supabase PAT: '));
114
+ closeRL();
115
+ }
125
116
 
126
117
  if (!ref || !pat) {
127
118
  console.log(chalk.red('\n✗ Both required.\n')); process.exit(1);
package/cli/index.js CHANGED
@@ -11,7 +11,7 @@ import * as api from "./api.js";
11
11
  import os from "os";
12
12
 
13
13
  const config = new Conf({ projectName: "clauth" });
14
- const VERSION = "0.1.0";
14
+ const VERSION = "0.2.0";
15
15
 
16
16
  // ============================================================
17
17
  // Password prompt helper
@@ -55,8 +55,10 @@ import { runInstall } from './commands/install.js';
55
55
  program
56
56
  .command('install')
57
57
  .description('Provision Supabase, deploy Edge Function, install Claude skill')
58
- .action(async () => {
59
- await runInstall();
58
+ .option('--ref <ref>', 'Supabase project ref')
59
+ .option('--pat <pat>', 'Supabase Personal Access Token')
60
+ .action(async (opts) => {
61
+ await runInstall(opts);
60
62
  });
61
63
 
62
64
  // ──────────────────────────────────────────────
@@ -67,6 +69,7 @@ program
67
69
  .description("Register this machine with the vault (run after clauth install)")
68
70
  .option("--admin-token <token>", "Bootstrap token (from clauth install output)")
69
71
  .option("--label <label>", "Human label for this machine")
72
+ .option("-p, --pw <password>", "Password (skip interactive prompt)")
70
73
  .action(async (opts) => {
71
74
  console.log(chalk.cyan("\n🔐 clauth setup\n"));
72
75
 
@@ -79,12 +82,18 @@ program
79
82
  }
80
83
  console.log(chalk.gray(` Project: ${savedUrl}\n`));
81
84
 
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
- ]);
85
+ let answers;
86
+ if (opts.pw && opts.adminToken) {
87
+ // Non-interactive mode all flags provided
88
+ answers = { label: opts.label || os.hostname(), pw: opts.pw, adminTk: opts.adminToken };
89
+ } else {
90
+ answers = await inquirer.prompt([
91
+ { type: "input", name: "label", message: "Machine label:", default: opts.label || os.hostname() },
92
+ { type: "password", name: "pw", message: "Set password:", mask: "*", default: opts.pw || "" },
93
+ { type: "password", name: "adminTk", message: "Bootstrap token:", mask: "*",
94
+ default: opts.adminToken || "" },
95
+ ]);
96
+ }
88
97
 
89
98
  const spinner = ora("Registering machine with vault...").start();
90
99
  try {
package/package.json CHANGED
@@ -1,54 +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
- }
1
+ {
2
+ "name": "@lifeaitools/clauth",
3
+ "version": "0.2.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
+ }
@@ -81,11 +81,11 @@ async function validateHMAC(body: {
81
81
  return { valid: false, reason: "machine_disabled" };
82
82
  }
83
83
 
84
- // Expected token = HMAC(machine_hash + timestamp_window, password + SALT)
84
+ // Expected token = HMAC(machine_hash:window, password)
85
+ // Client derives token with password only; server verifies the same way
85
86
  const window = Math.floor(body.timestamp / REPLAY_WINDOW_MS);
86
87
  const message = `${body.machine_hash}:${window}`;
87
- const hmacKey = `${body.password}:${CLAUTH_HMAC_SALT}`;
88
- const expected = await hmacSha256(hmacKey, message);
88
+ const expected = await hmacSha256(body.password, message);
89
89
 
90
90
  if (expected !== body.token) {
91
91
  return { valid: false, reason: "invalid_token" };