@vouchagents/cli 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/.turbo/turbo-build.log +4 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +59 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +32 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +50 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +55 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +50 -0
- package/dist/prompt.js.map +1 -0
- package/package.json +21 -0
- package/src/bin.ts +67 -0
- package/src/commands/export.ts +36 -0
- package/src/commands/init.ts +59 -0
- package/src/commands/status.ts +61 -0
- package/src/prompt.ts +49 -0
- package/tsconfig.json +9 -0
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { initCommand } from "./commands/init.js";
|
|
3
|
+
import { statusCommand } from "./commands/status.js";
|
|
4
|
+
import { exportCommand } from "./commands/export.js";
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const command = args[0];
|
|
7
|
+
// Parse --vault flag
|
|
8
|
+
const vaultIdx = args.indexOf("--vault");
|
|
9
|
+
const vaultPath = vaultIdx !== -1 ? args[vaultIdx + 1] : undefined;
|
|
10
|
+
switch (command) {
|
|
11
|
+
case "init":
|
|
12
|
+
await initCommand(vaultPath);
|
|
13
|
+
break;
|
|
14
|
+
case "status":
|
|
15
|
+
await statusCommand(vaultPath);
|
|
16
|
+
break;
|
|
17
|
+
case "export": {
|
|
18
|
+
const output = args[1];
|
|
19
|
+
if (!output || output.startsWith("--")) {
|
|
20
|
+
console.log("\n Usage: vouch export <output-path>\n");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
await exportCommand(output, vaultPath);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
case "help":
|
|
27
|
+
case "--help":
|
|
28
|
+
case "-h":
|
|
29
|
+
case undefined:
|
|
30
|
+
printHelp();
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
console.log(`\n Unknown command: ${command}`);
|
|
34
|
+
printHelp();
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
function printHelp() {
|
|
38
|
+
console.log(`
|
|
39
|
+
\x1b[1mvouch\x1b[0m - Manage your Vouch identity vault
|
|
40
|
+
|
|
41
|
+
\x1b[1mUsage:\x1b[0m
|
|
42
|
+
vouch <command> [options]
|
|
43
|
+
|
|
44
|
+
\x1b[1mCommands:\x1b[0m
|
|
45
|
+
init Create a new encrypted vault
|
|
46
|
+
status Show vault contents and recent signups
|
|
47
|
+
export Export vault to a file (still encrypted)
|
|
48
|
+
help Show this help
|
|
49
|
+
|
|
50
|
+
\x1b[1mOptions:\x1b[0m
|
|
51
|
+
--vault <path> Custom vault path (default: ~/.vouch/vault.json)
|
|
52
|
+
|
|
53
|
+
\x1b[1mExamples:\x1b[0m
|
|
54
|
+
vouch init
|
|
55
|
+
vouch status
|
|
56
|
+
vouch export ~/backup-vault.json
|
|
57
|
+
`);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAExB,qBAAqB;AACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AACzC,MAAM,SAAS,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAEnE,QAAQ,OAAO,EAAE,CAAC;IAChB,KAAK,MAAM;QACT,MAAM,WAAW,CAAC,SAAS,CAAC,CAAC;QAC7B,MAAM;IAER,KAAK,QAAQ;QACX,MAAM,aAAa,CAAC,SAAS,CAAC,CAAC;QAC/B,MAAM;IAER,KAAK,QAAQ,CAAC,CAAC,CAAC;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;YACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACvC,MAAM;IACR,CAAC;IAED,KAAK,MAAM,CAAC;IACZ,KAAK,QAAQ,CAAC;IACd,KAAK,IAAI,CAAC;IACV,KAAK,SAAS;QACZ,SAAS,EAAE,CAAC;QACZ,MAAM;IAER;QACE,OAAO,CAAC,GAAG,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;QAC/C,SAAS,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;CAmBb,CAAC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/commands/export.ts"],"names":[],"mappings":"AAIA,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,iBA+BzE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Vault, FileStorage, defaultVaultPath } from "@vouchagents/client";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { askSecret, close } from "../prompt.js";
|
|
4
|
+
export async function exportCommand(outputPath, vaultPath) {
|
|
5
|
+
const path = vaultPath ?? defaultVaultPath();
|
|
6
|
+
const storage = new FileStorage(path);
|
|
7
|
+
if (!(await storage.exists())) {
|
|
8
|
+
console.log(`\n No vault found at ${path}\n`);
|
|
9
|
+
close();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const password = await askSecret(" Vault password: ");
|
|
13
|
+
close();
|
|
14
|
+
try {
|
|
15
|
+
// Verify the password works by opening
|
|
16
|
+
await Vault.open({ passphrase: password, storage });
|
|
17
|
+
// Export the raw encrypted file
|
|
18
|
+
const data = await storage.read();
|
|
19
|
+
if (!data) {
|
|
20
|
+
console.log("\n Failed to read vault.\n");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
await writeFile(outputPath, data, "utf-8");
|
|
24
|
+
console.log(`\n \x1b[32mVault exported to ${outputPath}\x1b[0m`);
|
|
25
|
+
console.log(" This file is encrypted. You need your password to use it.\n");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
console.log("\n Wrong password.\n");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/commands/export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAkB,EAAE,SAAkB;IACxE,MAAM,IAAI,GAAG,SAAS,IAAI,gBAAgB,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;IAEtC,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,IAAI,CAAC,CAAC;QAC/C,KAAK,EAAE,CAAC;QACR,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,oBAAoB,CAAC,CAAC;IACvD,KAAK,EAAE,CAAC;IAER,IAAI,CAAC;QACH,uCAAuC;QACvC,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QAEpD,gCAAgC;QAChC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,iCAAiC,UAAU,SAAS,CAAC,CAAC;QAClE,OAAO,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,iBAuDnD"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Vault, FileStorage, defaultVaultPath } from "@vouchagents/client";
|
|
2
|
+
import { ask, askSecret, close } from "../prompt.js";
|
|
3
|
+
export async function initCommand(vaultPath) {
|
|
4
|
+
const path = vaultPath ?? defaultVaultPath();
|
|
5
|
+
const storage = new FileStorage(path);
|
|
6
|
+
// Check if vault already exists
|
|
7
|
+
if (await storage.exists()) {
|
|
8
|
+
console.log(`\n A vault already exists at ${path}`);
|
|
9
|
+
console.log(" Run `vouch status` to see what's stored.");
|
|
10
|
+
console.log(" Run `vouch init --force` to overwrite.\n");
|
|
11
|
+
close();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
console.log("\n Setting up Vouch...\n");
|
|
15
|
+
const name = await ask(" What name should we use? ");
|
|
16
|
+
if (!name) {
|
|
17
|
+
console.log(" Name is required.");
|
|
18
|
+
close();
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const email = await ask(" Email? ");
|
|
22
|
+
if (!email) {
|
|
23
|
+
console.log(" Email is required.");
|
|
24
|
+
close();
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const password = await askSecret(" Set a password to protect your vault: ");
|
|
28
|
+
if (!password || password.length < 6) {
|
|
29
|
+
console.log("\n Password must be at least 6 characters.");
|
|
30
|
+
close();
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
console.log("\n Creating vault...");
|
|
34
|
+
try {
|
|
35
|
+
const vault = await Vault.create({ passphrase: password, storage });
|
|
36
|
+
await vault.setField("name", name);
|
|
37
|
+
await vault.setField("email", email);
|
|
38
|
+
const identity = vault.getIdentity();
|
|
39
|
+
console.log(`\n \x1b[32mVault created at ${path}\x1b[0m`);
|
|
40
|
+
console.log(` \x1b[32mEd25519 signing key generated\x1b[0m`);
|
|
41
|
+
console.log(`\n Key ID: ${identity.keyId}`);
|
|
42
|
+
console.log(" Your details are encrypted. Run `vouch status` to check.\n");
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error(`\n Failed to create vault: ${err instanceof Error ? err.message : err}\n`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
close();
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,SAAkB;IAClD,MAAM,IAAI,GAAG,SAAS,IAAI,gBAAgB,EAAE,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;IAEtC,gCAAgC;IAChC,IAAI,MAAM,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;QAC1D,KAAK,EAAE,CAAC;QACR,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;IAEzC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,6BAA6B,CAAC,CAAC;IACtD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;QACnC,KAAK,EAAE,CAAC;QACR,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;QACpC,KAAK,EAAE,CAAC;QACR,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,0CAA0C,CAAC,CAAC;IAC7E,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;QAC3D,KAAK,EAAE,CAAC;QACR,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IAErC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;QACpE,MAAM,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACnC,MAAM,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAErC,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAErC,OAAO,CAAC,GAAG,CAAC,gCAAgC,IAAI,SAAS,CAAC,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;QAC9D,OAAO,CAAC,GAAG,CAAC,eAAe,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;IAC9E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,+BAA+B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC3F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,KAAK,EAAE,CAAC;AACV,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAGA,wBAAsB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,iBAmDrD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Vault, FileStorage, defaultVaultPath } from "@vouchagents/client";
|
|
2
|
+
import { askSecret, close } from "../prompt.js";
|
|
3
|
+
export async function statusCommand(vaultPath) {
|
|
4
|
+
const path = vaultPath ?? defaultVaultPath();
|
|
5
|
+
const storage = new FileStorage(path);
|
|
6
|
+
if (!(await storage.exists())) {
|
|
7
|
+
console.log(`\n No vault found at ${path}`);
|
|
8
|
+
console.log(" Run `vouch init` to create one.\n");
|
|
9
|
+
close();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const password = await askSecret(" Vault password: ");
|
|
13
|
+
close();
|
|
14
|
+
try {
|
|
15
|
+
const vault = await Vault.open({ passphrase: password, storage });
|
|
16
|
+
const fields = await vault.listFields();
|
|
17
|
+
const identity = vault.getIdentity();
|
|
18
|
+
const history = await vault.getHistory({ limit: 5 });
|
|
19
|
+
console.log("\n \x1b[1mVouch Vault\x1b[0m");
|
|
20
|
+
console.log(` Location: ${path}`);
|
|
21
|
+
console.log(` Key ID: ${identity.keyId}\n`);
|
|
22
|
+
console.log(" \x1b[1mStored fields:\x1b[0m");
|
|
23
|
+
for (const field of fields) {
|
|
24
|
+
const val = await vault.getField(field);
|
|
25
|
+
const masked = val && field === "email"
|
|
26
|
+
? maskEmail(val.unsafeUnwrap())
|
|
27
|
+
: val
|
|
28
|
+
? val.unsafeUnwrap().slice(0, 2) + "***"
|
|
29
|
+
: "(empty)";
|
|
30
|
+
console.log(` ${field}: ${masked}`);
|
|
31
|
+
}
|
|
32
|
+
if (history.length > 0) {
|
|
33
|
+
console.log(`\n \x1b[1mRecent signups:\x1b[0m`);
|
|
34
|
+
for (const entry of history) {
|
|
35
|
+
const date = new Date(entry.timestamp).toLocaleDateString();
|
|
36
|
+
console.log(` ${date} ${entry.site} ${entry.status}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
console.log("\n No signups yet.");
|
|
41
|
+
}
|
|
42
|
+
console.log();
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
console.log("\n Wrong password.\n");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function maskEmail(email) {
|
|
50
|
+
const [local, domain] = email.split("@");
|
|
51
|
+
if (!local || !domain)
|
|
52
|
+
return "***";
|
|
53
|
+
return local.slice(0, 2) + "***@" + domain;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAOA,wBAAgB,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAIrD;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA+B3D;AAED,wBAAgB,KAAK,SAEpB"}
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as readline from "node:readline";
|
|
2
|
+
const rl = readline.createInterface({
|
|
3
|
+
input: process.stdin,
|
|
4
|
+
output: process.stderr, // prompts to stderr so stdout stays clean
|
|
5
|
+
});
|
|
6
|
+
export function ask(question) {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function askSecret(question) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
process.stderr.write(question);
|
|
14
|
+
const stdin = process.stdin;
|
|
15
|
+
const wasRaw = stdin.isRaw;
|
|
16
|
+
if (stdin.isTTY)
|
|
17
|
+
stdin.setRawMode(true);
|
|
18
|
+
let input = "";
|
|
19
|
+
const onData = (chunk) => {
|
|
20
|
+
const char = chunk.toString();
|
|
21
|
+
if (char === "\n" || char === "\r") {
|
|
22
|
+
if (stdin.isTTY)
|
|
23
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
24
|
+
stdin.removeListener("data", onData);
|
|
25
|
+
process.stderr.write("\n");
|
|
26
|
+
resolve(input);
|
|
27
|
+
}
|
|
28
|
+
else if (char === "\u007f" || char === "\b") {
|
|
29
|
+
// backspace
|
|
30
|
+
if (input.length > 0) {
|
|
31
|
+
input = input.slice(0, -1);
|
|
32
|
+
process.stderr.write("\b \b");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (char === "\u0003") {
|
|
36
|
+
// ctrl+c
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
input += char;
|
|
41
|
+
process.stderr.write("*");
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
stdin.on("data", onData);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export function close() {
|
|
48
|
+
rl.close();
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAE1C,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;IAClC,KAAK,EAAE,OAAO,CAAC,KAAK;IACpB,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,0CAA0C;CACnE,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,CAAC,QAAgB;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC;QAC3B,IAAI,KAAK,CAAC,KAAK;YAAE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAExC,IAAI,KAAK,GAAG,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE;YAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC9B,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBACnC,IAAI,KAAK,CAAC,KAAK;oBAAE,KAAK,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;gBACnD,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC3B,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;iBAAM,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAC9C,YAAY;gBACZ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACrB,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;oBAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;iBAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,SAAS;gBACT,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACN,KAAK,IAAI,IAAI,CAAC;gBACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC,CAAC;QACF,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,KAAK;IACnB,EAAE,CAAC,KAAK,EAAE,CAAC;AACb,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vouchagents/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"vouch": "./dist/bin.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"clean": "rm -rf dist"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@vouchagents/protocol": "0.1.0",
|
|
15
|
+
"@vouchagents/client": "0.1.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.0",
|
|
19
|
+
"@types/node": "^25.5.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { initCommand } from "./commands/init.js";
|
|
4
|
+
import { statusCommand } from "./commands/status.js";
|
|
5
|
+
import { exportCommand } from "./commands/export.js";
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const command = args[0];
|
|
9
|
+
|
|
10
|
+
// Parse --vault flag
|
|
11
|
+
const vaultIdx = args.indexOf("--vault");
|
|
12
|
+
const vaultPath = vaultIdx !== -1 ? args[vaultIdx + 1] : undefined;
|
|
13
|
+
|
|
14
|
+
switch (command) {
|
|
15
|
+
case "init":
|
|
16
|
+
await initCommand(vaultPath);
|
|
17
|
+
break;
|
|
18
|
+
|
|
19
|
+
case "status":
|
|
20
|
+
await statusCommand(vaultPath);
|
|
21
|
+
break;
|
|
22
|
+
|
|
23
|
+
case "export": {
|
|
24
|
+
const output = args[1];
|
|
25
|
+
if (!output || output.startsWith("--")) {
|
|
26
|
+
console.log("\n Usage: vouch export <output-path>\n");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
await exportCommand(output, vaultPath);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
case "help":
|
|
34
|
+
case "--help":
|
|
35
|
+
case "-h":
|
|
36
|
+
case undefined:
|
|
37
|
+
printHelp();
|
|
38
|
+
break;
|
|
39
|
+
|
|
40
|
+
default:
|
|
41
|
+
console.log(`\n Unknown command: ${command}`);
|
|
42
|
+
printHelp();
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function printHelp() {
|
|
47
|
+
console.log(`
|
|
48
|
+
\x1b[1mvouch\x1b[0m - Manage your Vouch identity vault
|
|
49
|
+
|
|
50
|
+
\x1b[1mUsage:\x1b[0m
|
|
51
|
+
vouch <command> [options]
|
|
52
|
+
|
|
53
|
+
\x1b[1mCommands:\x1b[0m
|
|
54
|
+
init Create a new encrypted vault
|
|
55
|
+
status Show vault contents and recent signups
|
|
56
|
+
export Export vault to a file (still encrypted)
|
|
57
|
+
help Show this help
|
|
58
|
+
|
|
59
|
+
\x1b[1mOptions:\x1b[0m
|
|
60
|
+
--vault <path> Custom vault path (default: ~/.vouch/vault.json)
|
|
61
|
+
|
|
62
|
+
\x1b[1mExamples:\x1b[0m
|
|
63
|
+
vouch init
|
|
64
|
+
vouch status
|
|
65
|
+
vouch export ~/backup-vault.json
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Vault, FileStorage, defaultVaultPath } from "@vouchagents/client";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { askSecret, close } from "../prompt.js";
|
|
4
|
+
|
|
5
|
+
export async function exportCommand(outputPath: string, vaultPath?: string) {
|
|
6
|
+
const path = vaultPath ?? defaultVaultPath();
|
|
7
|
+
const storage = new FileStorage(path);
|
|
8
|
+
|
|
9
|
+
if (!(await storage.exists())) {
|
|
10
|
+
console.log(`\n No vault found at ${path}\n`);
|
|
11
|
+
close();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const password = await askSecret(" Vault password: ");
|
|
16
|
+
close();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Verify the password works by opening
|
|
20
|
+
await Vault.open({ passphrase: password, storage });
|
|
21
|
+
|
|
22
|
+
// Export the raw encrypted file
|
|
23
|
+
const data = await storage.read();
|
|
24
|
+
if (!data) {
|
|
25
|
+
console.log("\n Failed to read vault.\n");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
await writeFile(outputPath, data, "utf-8");
|
|
30
|
+
console.log(`\n \x1b[32mVault exported to ${outputPath}\x1b[0m`);
|
|
31
|
+
console.log(" This file is encrypted. You need your password to use it.\n");
|
|
32
|
+
} catch {
|
|
33
|
+
console.log("\n Wrong password.\n");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Vault, FileStorage, defaultVaultPath } from "@vouchagents/client";
|
|
2
|
+
import { ask, askSecret, close } from "../prompt.js";
|
|
3
|
+
|
|
4
|
+
export async function initCommand(vaultPath?: string) {
|
|
5
|
+
const path = vaultPath ?? defaultVaultPath();
|
|
6
|
+
const storage = new FileStorage(path);
|
|
7
|
+
|
|
8
|
+
// Check if vault already exists
|
|
9
|
+
if (await storage.exists()) {
|
|
10
|
+
console.log(`\n A vault already exists at ${path}`);
|
|
11
|
+
console.log(" Run `vouch status` to see what's stored.");
|
|
12
|
+
console.log(" Run `vouch init --force` to overwrite.\n");
|
|
13
|
+
close();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log("\n Setting up Vouch...\n");
|
|
18
|
+
|
|
19
|
+
const name = await ask(" What name should we use? ");
|
|
20
|
+
if (!name) {
|
|
21
|
+
console.log(" Name is required.");
|
|
22
|
+
close();
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const email = await ask(" Email? ");
|
|
27
|
+
if (!email) {
|
|
28
|
+
console.log(" Email is required.");
|
|
29
|
+
close();
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const password = await askSecret(" Set a password to protect your vault: ");
|
|
34
|
+
if (!password || password.length < 6) {
|
|
35
|
+
console.log("\n Password must be at least 6 characters.");
|
|
36
|
+
close();
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log("\n Creating vault...");
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const vault = await Vault.create({ passphrase: password, storage });
|
|
44
|
+
await vault.setField("name", name);
|
|
45
|
+
await vault.setField("email", email);
|
|
46
|
+
|
|
47
|
+
const identity = vault.getIdentity();
|
|
48
|
+
|
|
49
|
+
console.log(`\n \x1b[32mVault created at ${path}\x1b[0m`);
|
|
50
|
+
console.log(` \x1b[32mEd25519 signing key generated\x1b[0m`);
|
|
51
|
+
console.log(`\n Key ID: ${identity.keyId}`);
|
|
52
|
+
console.log(" Your details are encrypted. Run `vouch status` to check.\n");
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error(`\n Failed to create vault: ${err instanceof Error ? err.message : err}\n`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
close();
|
|
59
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Vault, FileStorage, defaultVaultPath } from "@vouchagents/client";
|
|
2
|
+
import { askSecret, close } from "../prompt.js";
|
|
3
|
+
|
|
4
|
+
export async function statusCommand(vaultPath?: string) {
|
|
5
|
+
const path = vaultPath ?? defaultVaultPath();
|
|
6
|
+
const storage = new FileStorage(path);
|
|
7
|
+
|
|
8
|
+
if (!(await storage.exists())) {
|
|
9
|
+
console.log(`\n No vault found at ${path}`);
|
|
10
|
+
console.log(" Run `vouch init` to create one.\n");
|
|
11
|
+
close();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const password = await askSecret(" Vault password: ");
|
|
16
|
+
close();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const vault = await Vault.open({ passphrase: password, storage });
|
|
20
|
+
const fields = await vault.listFields();
|
|
21
|
+
const identity = vault.getIdentity();
|
|
22
|
+
const history = await vault.getHistory({ limit: 5 });
|
|
23
|
+
|
|
24
|
+
console.log("\n \x1b[1mVouch Vault\x1b[0m");
|
|
25
|
+
console.log(` Location: ${path}`);
|
|
26
|
+
console.log(` Key ID: ${identity.keyId}\n`);
|
|
27
|
+
|
|
28
|
+
console.log(" \x1b[1mStored fields:\x1b[0m");
|
|
29
|
+
for (const field of fields) {
|
|
30
|
+
const val = await vault.getField(field);
|
|
31
|
+
const masked =
|
|
32
|
+
val && field === "email"
|
|
33
|
+
? maskEmail(val.unsafeUnwrap())
|
|
34
|
+
: val
|
|
35
|
+
? val.unsafeUnwrap().slice(0, 2) + "***"
|
|
36
|
+
: "(empty)";
|
|
37
|
+
console.log(` ${field}: ${masked}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (history.length > 0) {
|
|
41
|
+
console.log(`\n \x1b[1mRecent signups:\x1b[0m`);
|
|
42
|
+
for (const entry of history) {
|
|
43
|
+
const date = new Date(entry.timestamp).toLocaleDateString();
|
|
44
|
+
console.log(` ${date} ${entry.site} ${entry.status}`);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
console.log("\n No signups yet.");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log();
|
|
51
|
+
} catch {
|
|
52
|
+
console.log("\n Wrong password.\n");
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function maskEmail(email: string): string {
|
|
58
|
+
const [local, domain] = email.split("@");
|
|
59
|
+
if (!local || !domain) return "***";
|
|
60
|
+
return local.slice(0, 2) + "***@" + domain;
|
|
61
|
+
}
|
package/src/prompt.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as readline from "node:readline";
|
|
2
|
+
|
|
3
|
+
const rl = readline.createInterface({
|
|
4
|
+
input: process.stdin,
|
|
5
|
+
output: process.stderr, // prompts to stderr so stdout stays clean
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export function ask(question: string): Promise<string> {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function askSecret(question: string): Promise<string> {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
process.stderr.write(question);
|
|
17
|
+
const stdin = process.stdin;
|
|
18
|
+
const wasRaw = stdin.isRaw;
|
|
19
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
20
|
+
|
|
21
|
+
let input = "";
|
|
22
|
+
const onData = (chunk: Buffer) => {
|
|
23
|
+
const char = chunk.toString();
|
|
24
|
+
if (char === "\n" || char === "\r") {
|
|
25
|
+
if (stdin.isTTY) stdin.setRawMode(wasRaw ?? false);
|
|
26
|
+
stdin.removeListener("data", onData);
|
|
27
|
+
process.stderr.write("\n");
|
|
28
|
+
resolve(input);
|
|
29
|
+
} else if (char === "\u007f" || char === "\b") {
|
|
30
|
+
// backspace
|
|
31
|
+
if (input.length > 0) {
|
|
32
|
+
input = input.slice(0, -1);
|
|
33
|
+
process.stderr.write("\b \b");
|
|
34
|
+
}
|
|
35
|
+
} else if (char === "\u0003") {
|
|
36
|
+
// ctrl+c
|
|
37
|
+
process.exit(1);
|
|
38
|
+
} else {
|
|
39
|
+
input += char;
|
|
40
|
+
process.stderr.write("*");
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
stdin.on("data", onData);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function close() {
|
|
48
|
+
rl.close();
|
|
49
|
+
}
|