@reegaviljoen/eldlock 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.
Files changed (91) hide show
  1. package/README.md +285 -0
  2. package/bin/eldlock +11 -0
  3. package/docs/architecture.md +164 -0
  4. package/docs/threat-model.md +47 -0
  5. package/eldlock-cli/README.md +56 -0
  6. package/eldlock-cli/bin/eldlock +3 -0
  7. package/eldlock-cli/package-lock.json +805 -0
  8. package/eldlock-cli/package.json +71 -0
  9. package/eldlock-cli/src/api.ts +250 -0
  10. package/eldlock-cli/src/cli.ts +490 -0
  11. package/eldlock-cli/src/main.ts +10 -0
  12. package/eldlock-cli/src/tui.ts +676 -0
  13. package/eldlock-cli/tsconfig.json +13 -0
  14. package/eldlock-cli/vendor/npm/ansi-regex-6.2.2.tgz +0 -0
  15. package/eldlock-cli/vendor/npm/bun-ffi-structs-0.2.2.tgz +0 -0
  16. package/eldlock-cli/vendor/npm/diff-9.0.0.tgz +0 -0
  17. package/eldlock-cli/vendor/npm/emoji-regex-10.6.0.tgz +0 -0
  18. package/eldlock-cli/vendor/npm/esbuild-0.28.0.tgz +0 -0
  19. package/eldlock-cli/vendor/npm/esbuild-darwin-arm64-0.28.0.tgz +0 -0
  20. package/eldlock-cli/vendor/npm/esbuild-darwin-x64-0.28.0.tgz +0 -0
  21. package/eldlock-cli/vendor/npm/esbuild-linux-arm64-0.28.0.tgz +0 -0
  22. package/eldlock-cli/vendor/npm/esbuild-linux-x64-0.28.0.tgz +0 -0
  23. package/eldlock-cli/vendor/npm/fsevents-2.3.3.tgz +0 -0
  24. package/eldlock-cli/vendor/npm/get-east-asian-width-1.6.0.tgz +0 -0
  25. package/eldlock-cli/vendor/npm/marked-17.0.1.tgz +0 -0
  26. package/eldlock-cli/vendor/npm/opentui-core-0.3.1.tgz +0 -0
  27. package/eldlock-cli/vendor/npm/opentui-core-darwin-arm64-0.3.1.tgz +0 -0
  28. package/eldlock-cli/vendor/npm/opentui-core-darwin-x64-0.3.1.tgz +0 -0
  29. package/eldlock-cli/vendor/npm/opentui-core-linux-arm64-0.3.1.tgz +0 -0
  30. package/eldlock-cli/vendor/npm/opentui-core-linux-x64-0.3.1.tgz +0 -0
  31. package/eldlock-cli/vendor/npm/string-width-7.2.0.tgz +0 -0
  32. package/eldlock-cli/vendor/npm/strip-ansi-7.1.2.tgz +0 -0
  33. package/eldlock-cli/vendor/npm/tsx-4.22.4.tgz +0 -0
  34. package/eldlock-cli/vendor/npm/types-node-22.19.19.tgz +0 -0
  35. package/eldlock-cli/vendor/npm/typescript-5.9.3.tgz +0 -0
  36. package/eldlock-cli/vendor/npm/undici-types-6.21.0.tgz +0 -0
  37. package/eldlock-cli/vendor/npm/web-tree-sitter-0.25.10.tgz +0 -0
  38. package/eldlock-cli/vendor/npm/yoga-layout-3.2.1.tgz +0 -0
  39. package/eldlock-server/cmd/eldlock-server/main.go +132 -0
  40. package/eldlock-server/go.mod +10 -0
  41. package/eldlock-server/go.sum +11 -0
  42. package/eldlock-server/internal/api/README.md +14 -0
  43. package/eldlock-server/internal/api/core.go +126 -0
  44. package/eldlock-server/internal/api/exec.go +97 -0
  45. package/eldlock-server/internal/api/secrets.go +358 -0
  46. package/eldlock-server/internal/api/server.go +72 -0
  47. package/eldlock-server/internal/api/service_test.go +416 -0
  48. package/eldlock-server/internal/api/types.go +48 -0
  49. package/eldlock-server/internal/api/vault.go +69 -0
  50. package/eldlock-server/internal/api/vendor.go +44 -0
  51. package/eldlock-server/internal/libfido2/LICENSE +21 -0
  52. package/eldlock-server/internal/libfido2/README.md +127 -0
  53. package/eldlock-server/internal/libfido2/examples_test.go +614 -0
  54. package/eldlock-server/internal/libfido2/fido2.go +1234 -0
  55. package/eldlock-server/internal/libfido2/fido2_darwin.go +7 -0
  56. package/eldlock-server/internal/libfido2/fido2_other.go +9 -0
  57. package/eldlock-server/internal/libfido2/fido2_test.go +101 -0
  58. package/eldlock-server/internal/libfido2/go.mod +10 -0
  59. package/eldlock-server/internal/libfido2/go.sum +16 -0
  60. package/eldlock-server/internal/libfido2/log.go +87 -0
  61. package/eldlock-server/internal/store/README.md +7 -0
  62. package/eldlock-server/internal/store/store.go +434 -0
  63. package/eldlock-server/internal/store/store_test.go +125 -0
  64. package/eldlock-server/internal/yubikey/README.md +25 -0
  65. package/eldlock-server/internal/yubikey/default_fido2.go +7 -0
  66. package/eldlock-server/internal/yubikey/default_stub.go +7 -0
  67. package/eldlock-server/internal/yubikey/fido2_disabled.go +9 -0
  68. package/eldlock-server/internal/yubikey/fido2_libfido2.go +225 -0
  69. package/eldlock-server/internal/yubikey/fido2_libfido2_test.go +66 -0
  70. package/eldlock-server/internal/yubikey/passkey.go +139 -0
  71. package/eldlock-server/internal/yubikey/passkey_test.go +36 -0
  72. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/LICENSE +21 -0
  73. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/README.md +127 -0
  74. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/fido2.go +1234 -0
  75. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/fido2_darwin.go +7 -0
  76. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/fido2_other.go +9 -0
  77. package/eldlock-server/vendor/github.com/keys-pub/go-libfido2/log.go +87 -0
  78. package/eldlock-server/vendor/github.com/pkg/errors/.travis.yml +10 -0
  79. package/eldlock-server/vendor/github.com/pkg/errors/LICENSE +23 -0
  80. package/eldlock-server/vendor/github.com/pkg/errors/Makefile +44 -0
  81. package/eldlock-server/vendor/github.com/pkg/errors/README.md +59 -0
  82. package/eldlock-server/vendor/github.com/pkg/errors/appveyor.yml +32 -0
  83. package/eldlock-server/vendor/github.com/pkg/errors/errors.go +288 -0
  84. package/eldlock-server/vendor/github.com/pkg/errors/go113.go +38 -0
  85. package/eldlock-server/vendor/github.com/pkg/errors/stack.go +177 -0
  86. package/eldlock-server/vendor/modules.txt +7 -0
  87. package/examples/eldlock.toml +17 -0
  88. package/install.sh +66 -0
  89. package/package.json +66 -0
  90. package/scripts/build-production.mjs +177 -0
  91. package/scripts/postinstall-production.mjs +23 -0
@@ -0,0 +1,490 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+ import { fileURLToPath } from "node:url";
6
+ import {
7
+ checkServerHealth,
8
+ cleanupPidFile,
9
+ defaultLogPath,
10
+ defaultPidPath,
11
+ defaultSocketPath,
12
+ defaultStateDir,
13
+ isNodeError,
14
+ isPidAlive,
15
+ printResponse,
16
+ readPid,
17
+ requestWithPINPrompt,
18
+ waitForHealth,
19
+ waitForStop,
20
+ type OutputMode,
21
+ } from "./api.js";
22
+ import { openTUI } from "./tui.js";
23
+
24
+ export async function runCli(argv: string[]): Promise<void> {
25
+ const [command, ...args] = argv;
26
+
27
+ switch (command ?? "tui") {
28
+ case "version":
29
+ console.log("eldlock-cli dev");
30
+ return;
31
+ case "start":
32
+ await startServer();
33
+ return;
34
+ case "stop":
35
+ await stopServer();
36
+ return;
37
+ case "status":
38
+ await statusServer();
39
+ return;
40
+ case "init":
41
+ await initVault(args);
42
+ return;
43
+ case "tui":
44
+ await openTUI();
45
+ return;
46
+ case "secret":
47
+ await handleSecret(args);
48
+ return;
49
+ case "import":
50
+ await importEnvFile(args);
51
+ return;
52
+ case "exec":
53
+ await execProcess(args);
54
+ return;
55
+ case "shell":
56
+ await openShell(args);
57
+ return;
58
+ case "help":
59
+ default:
60
+ printHelp();
61
+ }
62
+ }
63
+
64
+ async function startServer(): Promise<void> {
65
+ const health = await checkServerHealth();
66
+ if (health.ok) {
67
+ console.log(`eldlock-server is already running at ${defaultSocketPath()}`);
68
+ return;
69
+ }
70
+
71
+ const serverPath = resolveServerPath();
72
+
73
+ fs.mkdirSync(defaultStateDir(), { recursive: true });
74
+ const logFd = fs.openSync(defaultLogPath(), "a");
75
+ const child = spawn(serverPath, ["serve"], {
76
+ cwd: process.cwd(),
77
+ detached: true,
78
+ env: {
79
+ ...process.env,
80
+ ELDLOCK_STATE_DIR: defaultStateDir(),
81
+ ELDLOCK_SOCKET: defaultSocketPath(),
82
+ },
83
+ stdio: ["ignore", logFd, logFd],
84
+ });
85
+
86
+ child.unref();
87
+ fs.closeSync(logFd);
88
+ fs.writeFileSync(defaultPidPath(), `${child.pid}\n`, { mode: 0o600 });
89
+
90
+ const started = await waitForHealth(5000);
91
+ if (!started.ok) {
92
+ throw new Error(`eldlock-server did not become ready; see ${defaultLogPath()}`);
93
+ }
94
+
95
+ console.log(`started eldlock-server pid ${child.pid}`);
96
+ console.log(`socket ${defaultSocketPath()}`);
97
+ console.log(`log ${defaultLogPath()}`);
98
+ }
99
+
100
+ async function initVault(args: string[]): Promise<void> {
101
+ const { values, vault } = parseVaultFlag(args);
102
+ if (values.length > 1) {
103
+ throw new Error("usage: eldlock init [name]");
104
+ }
105
+ const response = await requestWithPINPrompt({ action: "init", vault: values[0] ?? vault });
106
+ printResponse(response);
107
+ }
108
+
109
+ async function stopServer(): Promise<void> {
110
+ const pid = readPid();
111
+ const health = await checkServerHealth();
112
+
113
+ if (!pid) {
114
+ if (health.ok) {
115
+ throw new Error(`eldlock-server is running at ${defaultSocketPath()}, but no pid file exists at ${defaultPidPath()}`);
116
+ }
117
+ console.log("eldlock-server is not running");
118
+ return;
119
+ }
120
+
121
+ if (!isPidAlive(pid)) {
122
+ cleanupPidFile();
123
+ console.log(`removed stale pid file for pid ${pid}`);
124
+ return;
125
+ }
126
+
127
+ process.kill(-pid, "SIGTERM");
128
+ await waitForStop(pid, 5000);
129
+ cleanupPidFile();
130
+ console.log(`stopped eldlock-server pid ${pid}`);
131
+ }
132
+
133
+ async function statusServer(): Promise<void> {
134
+ const pid = readPid();
135
+ const health = await checkServerHealth();
136
+
137
+ if (health.ok) {
138
+ console.log("eldlock-server is running");
139
+ } else {
140
+ console.log("eldlock-server is not responding");
141
+ }
142
+ console.log(`state ${defaultStateDir()}`);
143
+ console.log(`socket ${defaultSocketPath()}`);
144
+ console.log(`pid ${pid ?? "none"}`);
145
+ console.log(`log ${defaultLogPath()}`);
146
+
147
+ if (pid && !isPidAlive(pid)) {
148
+ console.log("pid file is stale");
149
+ }
150
+ }
151
+
152
+ async function handleSecret(args: string[]): Promise<void> {
153
+ const [subcommand, ...rest] = args;
154
+ switch (subcommand) {
155
+ case "add":
156
+ await addSecret(rest);
157
+ return;
158
+ case "read":
159
+ await readSecret(rest);
160
+ return;
161
+ case "remove":
162
+ await removeSecret(rest);
163
+ return;
164
+ case "list":
165
+ await listSecrets(rest);
166
+ return;
167
+ default:
168
+ throw new Error("usage: eldlock secret add|read|remove|list");
169
+ }
170
+ }
171
+
172
+ async function addSecret(args: string[]): Promise<void> {
173
+ const { values, vault } = parseVaultFlag(args);
174
+ const [assignment, ...flags] = values;
175
+ if (!assignment) {
176
+ throw new Error("usage: eldlock secret add <key>=<value> [--vault <name>]");
177
+ }
178
+
179
+ if (flags.length > 0) {
180
+ throw new Error(`unknown flag: ${flags[0]}`);
181
+ }
182
+
183
+ const { key, value } = parseAssignment(assignment);
184
+
185
+ const response = await requestWithPINPrompt({
186
+ action: "secret.add",
187
+ vault,
188
+ type: "env",
189
+ name: key,
190
+ value,
191
+ });
192
+ printResponse(response);
193
+ }
194
+
195
+ async function readSecret(args: string[]): Promise<void> {
196
+ const { values, vault } = parseVaultFlag(args);
197
+ const [name, ...flags] = values;
198
+ if (!name) {
199
+ throw new Error("usage: eldlock secret read <key> [--plain] [--vault <name>]");
200
+ }
201
+
202
+ let output: OutputMode = "clipboard";
203
+ for (const flag of flags) {
204
+ if (flag === "--plain") {
205
+ output = "plain";
206
+ } else {
207
+ throw new Error(`unknown flag: ${flag}`);
208
+ }
209
+ }
210
+
211
+ const response = await requestWithPINPrompt({ action: "secret.read", vault, name, output });
212
+ printResponse(response);
213
+ }
214
+
215
+ async function removeSecret(args: string[]): Promise<void> {
216
+ const { values, vault } = parseVaultFlag(args);
217
+ const [name, ...flags] = values;
218
+ if (!name) {
219
+ throw new Error("usage: eldlock secret remove <key> [--vault <name>]");
220
+ }
221
+ if (flags.length > 0) {
222
+ throw new Error(`unknown flag: ${flags[0]}`);
223
+ }
224
+
225
+ const response = await requestWithPINPrompt({ action: "secret.remove", vault, name });
226
+ printResponse(response);
227
+ }
228
+
229
+ async function listSecrets(args: string[] = []): Promise<void> {
230
+ const { values, vault } = parseVaultFlag(args);
231
+ if (values.length > 0) {
232
+ throw new Error(`unknown argument: ${values[0]}`);
233
+ }
234
+ const response = await requestWithPINPrompt({ action: "secret.list", vault });
235
+ if (!response.ok) {
236
+ throw new Error(response.error ?? "request failed");
237
+ }
238
+ for (const secret of response.secrets ?? []) {
239
+ console.log(`${secret.type}\t${secret.name}`);
240
+ }
241
+ }
242
+
243
+ async function execProcess(args: string[]): Promise<void> {
244
+ const { values, vault } = parseVaultFlag(args);
245
+ const separator = values.indexOf("--");
246
+ const command = separator >= 0 ? values.slice(separator + 1) : values;
247
+ if (command.length === 0) {
248
+ throw new Error("usage: eldlock exec [--vault <name>] -- <command>");
249
+ }
250
+
251
+ const response = await requestWithPINPrompt({
252
+ action: "exec",
253
+ vault,
254
+ command,
255
+ shell: process.env.SHELL,
256
+ shell_command: command.join(" "),
257
+ cwd: process.cwd(),
258
+ env: currentEnv(),
259
+ });
260
+ if (!response.ok) {
261
+ throw new Error(response.error ?? "request failed");
262
+ }
263
+ if (response.stdout) {
264
+ process.stdout.write(response.stdout);
265
+ }
266
+ if (response.stderr) {
267
+ process.stderr.write(response.stderr);
268
+ }
269
+ process.exitCode = response.exit_code ?? 0;
270
+ }
271
+
272
+ async function openShell(args: string[]): Promise<void> {
273
+ const { values, vault } = parseVaultFlag(args);
274
+ if (values.length > 0) {
275
+ throw new Error(`unknown argument: ${values[0]}`);
276
+ }
277
+
278
+ const shell = process.env.SHELL || "/bin/sh";
279
+ const env = shellEnv(currentEnv(), vault);
280
+ console.error("eldlock: unlocking vault; use your YubiKey/fingerprint if prompted.");
281
+ const response = await requestWithPINPrompt({
282
+ action: "exec",
283
+ vault,
284
+ interactive: true,
285
+ });
286
+ if (!response.ok) {
287
+ throw new Error(response.error ?? "request failed");
288
+ }
289
+
290
+ const vaultName = vault && vault.trim() ? vault.trim() : "default";
291
+ console.error(`eldlock: unlocked vault "${vaultName}"; starting shell. Type exit to leave.`);
292
+ const shellCommand = interactiveShellCommand(shell);
293
+ const result = spawnSync(shellCommand[0], shellCommand.slice(1), {
294
+ cwd: process.cwd(),
295
+ env: {
296
+ ...env,
297
+ ...(response.env ?? {}),
298
+ },
299
+ stdio: "inherit",
300
+ });
301
+ if (typeof result.status === "number") {
302
+ process.exitCode = result.status;
303
+ return;
304
+ }
305
+ if (result.error) {
306
+ throw result.error;
307
+ }
308
+ process.exitCode = 1;
309
+ }
310
+
311
+ async function importEnvFile(args: string[]): Promise<void> {
312
+ const { values, vault } = parseVaultFlag(args);
313
+ const [sourcePath = ".env", ...flags] = values;
314
+ if (flags.length > 0) {
315
+ throw new Error(`unknown argument: ${flags[0]}`);
316
+ }
317
+
318
+ const response = await requestWithPINPrompt({
319
+ action: "secret.import_env",
320
+ vault,
321
+ source_path: sourcePath,
322
+ cwd: process.cwd(),
323
+ });
324
+ printResponse(response);
325
+ }
326
+
327
+ function parseAssignment(assignment: string): { key: string; value: string } {
328
+ const equalsIndex = assignment.indexOf("=");
329
+ if (equalsIndex <= 0) {
330
+ throw new Error("usage: eldlock secret add <key>=<value>");
331
+ }
332
+ const key = assignment.slice(0, equalsIndex).trim();
333
+ const value = assignment.slice(equalsIndex + 1);
334
+ if (!key) {
335
+ throw new Error("secret key is required");
336
+ }
337
+ if (!value) {
338
+ throw new Error("secret value is required");
339
+ }
340
+ return { key, value };
341
+ }
342
+
343
+ function parseVaultFlag(args: string[]): { values: string[]; vault?: string } {
344
+ const values: string[] = [];
345
+ let vault: string | undefined;
346
+
347
+ for (let index = 0; index < args.length; index += 1) {
348
+ const arg = args[index];
349
+ if (arg === "--vault") {
350
+ const next = args[index + 1];
351
+ if (next === undefined || next.startsWith("--")) {
352
+ vault = "";
353
+ continue;
354
+ }
355
+ vault = next;
356
+ index += 1;
357
+ continue;
358
+ }
359
+ if (arg.startsWith("--vault=")) {
360
+ vault = arg.slice("--vault=".length);
361
+ continue;
362
+ }
363
+ values.push(arg);
364
+ }
365
+
366
+ return { values, vault };
367
+ }
368
+
369
+ function currentEnv(): Record<string, string> {
370
+ const env: Record<string, string> = {};
371
+ for (const [key, value] of Object.entries(process.env)) {
372
+ if (value !== undefined) {
373
+ env[key] = value;
374
+ }
375
+ }
376
+ return env;
377
+ }
378
+
379
+ function shellEnv(env: Record<string, string>, vault?: string): Record<string, string> {
380
+ const vaultName = vault && vault.trim() ? vault.trim() : "default";
381
+ const marker = `(eldlock:${vaultName}) `;
382
+ return {
383
+ ...env,
384
+ ELDLOCK_SHELL: "1",
385
+ ELDLOCK_VAULT: vaultName,
386
+ PS1: `${marker}${env.PS1 ?? "\\$ "}`,
387
+ PROMPT: `${marker}${env.PROMPT ?? "%# "}`,
388
+ };
389
+ }
390
+
391
+ function interactiveShellCommand(shell: string): string[] {
392
+ const name = path.basename(shell);
393
+ switch (name) {
394
+ case "bash":
395
+ return [shell, "--noprofile", "--norc", "-i"];
396
+ case "zsh":
397
+ return [shell, "-f", "-i"];
398
+ case "fish":
399
+ return [shell, "--no-config", "--interactive"];
400
+ default:
401
+ return [shell, "-i"];
402
+ }
403
+ }
404
+
405
+ function resolveServerPath(): string {
406
+ const configured = process.env.ELDLOCK_SERVER_PATH;
407
+ if (configured) {
408
+ if (!isExecutableFile(configured)) {
409
+ throw new Error(`ELDLOCK_SERVER_PATH points to a missing or non-executable file: ${configured}`);
410
+ }
411
+ return configured;
412
+ }
413
+
414
+ const pathMatch = findExecutableOnPath("eldlock-server");
415
+ if (pathMatch) {
416
+ return pathMatch;
417
+ }
418
+
419
+ const localMatch = localServerCandidates().find(isExecutableFile);
420
+ if (localMatch) {
421
+ return localMatch;
422
+ }
423
+
424
+ throw new Error("could not find eldlock-server; set ELDLOCK_SERVER_PATH to the eldlock-server binary");
425
+ }
426
+
427
+ function localServerCandidates(): string[] {
428
+ const thisFile = fileURLToPath(import.meta.url);
429
+ const thisDir = path.dirname(thisFile);
430
+ return [
431
+ path.join(process.cwd(), "eldlock-server", "bin", "eldlock-server-dev"),
432
+ path.join(process.cwd(), "../eldlock-server", "bin", "eldlock-server-dev"),
433
+ path.join(thisDir, "../../eldlock-server", "bin", "eldlock-server-dev"),
434
+ path.join(thisDir, "../eldlock-server"),
435
+ ];
436
+ }
437
+
438
+ function findExecutableOnPath(name: string): string | undefined {
439
+ const pathEnv = process.env.PATH ?? "";
440
+ for (const dir of pathEnv.split(path.delimiter)) {
441
+ if (!dir) {
442
+ continue;
443
+ }
444
+ const candidate = path.join(dir, name);
445
+ if (isExecutableFile(candidate)) {
446
+ return candidate;
447
+ }
448
+ }
449
+ return undefined;
450
+ }
451
+
452
+ function isExecutableFile(candidate: string): boolean {
453
+ try {
454
+ fs.accessSync(candidate, fs.constants.X_OK);
455
+ return fs.statSync(candidate).isFile();
456
+ } catch (error) {
457
+ if (isNodeError(error)) {
458
+ return false;
459
+ }
460
+ return false;
461
+ }
462
+ }
463
+
464
+ function printHelp(): void {
465
+ console.log(`Eldlock CLI
466
+
467
+ Usage:
468
+ eldlock
469
+ eldlock version
470
+ eldlock start
471
+ eldlock stop
472
+ eldlock status
473
+ eldlock init [name]
474
+ eldlock secret add <key>=<value> [--vault <name>]
475
+ eldlock secret read <key> [--vault <name>]
476
+ eldlock secret read <key> --plain [--vault <name>]
477
+ eldlock secret remove <key> [--vault <name>]
478
+ eldlock secret list [--vault <name>]
479
+ eldlock import [path-to-.env] [--vault <name>]
480
+ eldlock exec [--vault <name>] -- <command>
481
+ eldlock shell [--vault <name>]
482
+
483
+ Running eldlock without a command opens the TUI.
484
+ start runs the local daemon in the background. Set ELDLOCK_SERVER_PATH to choose the server binary.
485
+ init without a name uses the default vault.
486
+ exec launches the command from eldlock-server through your current shell with env secrets injected.
487
+ shell unlocks the vault through eldlock-server, then opens your current shell locally with env secrets injected.
488
+ By default, read copies to the clipboard. --plain writes the value to stdout.
489
+ This CLI is a frontend only. Vault initialization, secret storage, release behavior, and clipboard writes happen in eldlock-server.`);
490
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import process from "node:process";
4
+ import { runCli } from "./cli.js";
5
+
6
+ runCli(process.argv.slice(2)).catch((error: unknown) => {
7
+ const message = error instanceof Error ? error.message : String(error);
8
+ console.error(`eldlock: ${message}`);
9
+ process.exit(1);
10
+ });