ragevault 0.2.3

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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/dist/assets.d.ts +14 -0
  4. package/dist/assets.js +47 -0
  5. package/dist/assets.js.map +1 -0
  6. package/dist/audit.d.ts +35 -0
  7. package/dist/audit.js +79 -0
  8. package/dist/audit.js.map +1 -0
  9. package/dist/branding.d.ts +36 -0
  10. package/dist/branding.js +55 -0
  11. package/dist/branding.js.map +1 -0
  12. package/dist/crypto/cipher.d.ts +74 -0
  13. package/dist/crypto/cipher.js +95 -0
  14. package/dist/crypto/cipher.js.map +1 -0
  15. package/dist/crypto/cipher.test.d.ts +7 -0
  16. package/dist/crypto/cipher.test.js +50 -0
  17. package/dist/crypto/cipher.test.js.map +1 -0
  18. package/dist/generator.d.ts +36 -0
  19. package/dist/generator.js +114 -0
  20. package/dist/generator.js.map +1 -0
  21. package/dist/model.d.ts +96 -0
  22. package/dist/model.js +43 -0
  23. package/dist/model.js.map +1 -0
  24. package/dist/porters.d.ts +35 -0
  25. package/dist/porters.js +175 -0
  26. package/dist/porters.js.map +1 -0
  27. package/dist/prompt.d.ts +14 -0
  28. package/dist/prompt.js +56 -0
  29. package/dist/prompt.js.map +1 -0
  30. package/dist/ragevault.d.ts +22 -0
  31. package/dist/ragevault.js +814 -0
  32. package/dist/ragevault.js.map +1 -0
  33. package/dist/server/public/app.js +420 -0
  34. package/dist/server/public/apple-touch-icon.png +0 -0
  35. package/dist/server/public/favicon.png +0 -0
  36. package/dist/server/public/fonts/archivo.woff2 +0 -0
  37. package/dist/server/public/fonts/space-grotesk.woff2 +0 -0
  38. package/dist/server/public/icon-192.png +0 -0
  39. package/dist/server/public/icon-512.png +0 -0
  40. package/dist/server/public/icon-maskable.png +0 -0
  41. package/dist/server/public/index.html +225 -0
  42. package/dist/server/public/manifest.webmanifest +16 -0
  43. package/dist/server/public/styles.css +362 -0
  44. package/dist/server/public/sw.js +30 -0
  45. package/dist/server/server.d.ts +30 -0
  46. package/dist/server/server.js +410 -0
  47. package/dist/server/server.js.map +1 -0
  48. package/dist/server/session.d.ts +36 -0
  49. package/dist/server/session.js +89 -0
  50. package/dist/server/session.js.map +1 -0
  51. package/dist/store/vaultfile.d.ts +64 -0
  52. package/dist/store/vaultfile.js +110 -0
  53. package/dist/store/vaultfile.js.map +1 -0
  54. package/dist/store/vaultfile.test.d.ts +5 -0
  55. package/dist/store/vaultfile.test.js +45 -0
  56. package/dist/store/vaultfile.test.js.map +1 -0
  57. package/dist/totp.d.ts +26 -0
  58. package/dist/totp.js +88 -0
  59. package/dist/totp.js.map +1 -0
  60. package/dist/totp.test.d.ts +5 -0
  61. package/dist/totp.test.js +41 -0
  62. package/dist/totp.test.js.map +1 -0
  63. package/dist/tui/ansi.d.ts +34 -0
  64. package/dist/tui/ansi.js +52 -0
  65. package/dist/tui/ansi.js.map +1 -0
  66. package/dist/tui/app.d.ts +83 -0
  67. package/dist/tui/app.js +607 -0
  68. package/dist/tui/app.js.map +1 -0
  69. package/dist/update/channel.d.ts +53 -0
  70. package/dist/update/channel.js +135 -0
  71. package/dist/update/channel.js.map +1 -0
  72. package/package.json +68 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The on-disk (or over-the-wire) vault format.
3
+ *
4
+ * A vault file is a small JSON envelope:
5
+ *
6
+ * {
7
+ * "magic": "ragevault", "format": 1,
8
+ * "kdf": { "algo": "scrypt", "N": 65536, "r": 8, "p": 1, "keyLen": 32 },
9
+ * "salt": "<base64>", // for deriving the KEK from the password
10
+ * "wrappedKey": { iv, tag, ct }, // the DEK, encrypted under the KEK
11
+ * "payload": { iv, tag, ct } // the whole VaultData, encrypted under the DEK
12
+ * }
13
+ *
14
+ * Only the KDF salt/params and two ciphertexts ever touch disk. The header is
15
+ * enough to *attempt* an unlock, and AES-GCM's auth tag makes a wrong password
16
+ * fail cleanly rather than silently corrupting data.
17
+ *
18
+ * Part of RageVault.
19
+ * Crafted by SoyRage Agency — https://soyrage.es/
20
+ * MIT licensed (see LICENSE).
21
+ */
22
+ import { type KdfParams, type Sealed } from "../crypto/cipher.js";
23
+ import type { VaultData } from "../model.js";
24
+ export declare const MAGIC = "ragevault";
25
+ export declare const FORMAT = 1;
26
+ export interface VaultFile {
27
+ magic: string;
28
+ format: number;
29
+ kdf: KdfParams;
30
+ salt: string;
31
+ wrappedKey: Sealed;
32
+ payload: Sealed;
33
+ }
34
+ /** An unlocked, in-memory handle: the decrypted data plus the live keys. */
35
+ export interface OpenVault {
36
+ data: VaultData;
37
+ /** The Data-Encryption-Key held only in memory while unlocked. */
38
+ dek: Buffer;
39
+ /** The current wrapped DEK, so a normal save needs no password. */
40
+ wrappedKey: Sealed;
41
+ kdf: KdfParams;
42
+ salt: Buffer;
43
+ }
44
+ /** Create a brand-new encrypted vault from a master password. */
45
+ export declare function createVault(data: VaultData, password: string, kdf?: KdfParams): OpenVault;
46
+ /** Attempt to unlock a vault file. Throws "wrong password" on failure. */
47
+ export declare function unlockVault(file: VaultFile, password: string): OpenVault;
48
+ /** Serialize the current data to a VaultFile (re-encrypts the payload; fast). */
49
+ export declare function toFile(vault: OpenVault): VaultFile;
50
+ /** Change the master password: re-wrap the DEK under a new KEK (fast). */
51
+ export declare function rewrapForPassword(vault: OpenVault, newPassword: string, kdf?: KdfParams): void;
52
+ /** Read + parse a vault file from disk. */
53
+ export declare function readVaultFile(path: string): VaultFile;
54
+ /**
55
+ * Write a vault file to disk atomically and owner-readable only (0600).
56
+ *
57
+ * A password vault is the single copy of irreplaceable data, so we never write
58
+ * over the live file in place — a crash, full disk or power loss mid-write would
59
+ * truncate it beyond recovery. Instead: write a sibling temp file, fsync it,
60
+ * then rename over the target (an atomic replace on the same filesystem).
61
+ */
62
+ export declare function writeVaultFile(path: string, file: VaultFile): void;
63
+ /** Encrypt the current state and persist it atomically (see writeVaultFile). */
64
+ export declare function saveVault(path: string, vault: OpenVault): void;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * The on-disk (or over-the-wire) vault format.
3
+ *
4
+ * A vault file is a small JSON envelope:
5
+ *
6
+ * {
7
+ * "magic": "ragevault", "format": 1,
8
+ * "kdf": { "algo": "scrypt", "N": 65536, "r": 8, "p": 1, "keyLen": 32 },
9
+ * "salt": "<base64>", // for deriving the KEK from the password
10
+ * "wrappedKey": { iv, tag, ct }, // the DEK, encrypted under the KEK
11
+ * "payload": { iv, tag, ct } // the whole VaultData, encrypted under the DEK
12
+ * }
13
+ *
14
+ * Only the KDF salt/params and two ciphertexts ever touch disk. The header is
15
+ * enough to *attempt* an unlock, and AES-GCM's auth tag makes a wrong password
16
+ * fail cleanly rather than silently corrupting data.
17
+ *
18
+ * Part of RageVault.
19
+ * Crafted by SoyRage Agency — https://soyrage.es/
20
+ * MIT licensed (see LICENSE).
21
+ */
22
+ import { chmodSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, writeSync } from "node:fs";
23
+ import { dirname } from "node:path";
24
+ import { DEFAULT_KDF, deriveKek, newKey, newSalt, open, seal, unwrapKey, wipe, wrapKey, } from "../crypto/cipher.js";
25
+ export const MAGIC = "ragevault";
26
+ export const FORMAT = 1;
27
+ /** Create a brand-new encrypted vault from a master password. */
28
+ export function createVault(data, password, kdf = DEFAULT_KDF) {
29
+ const salt = newSalt();
30
+ const kek = deriveKek(password, salt, kdf);
31
+ const dek = newKey();
32
+ const wrappedKey = wrapKey(dek, kek);
33
+ wipe(kek);
34
+ return { data, dek, wrappedKey, kdf, salt };
35
+ }
36
+ /** Attempt to unlock a vault file. Throws "wrong password" on failure. */
37
+ export function unlockVault(file, password) {
38
+ if (file.magic !== MAGIC)
39
+ throw new Error("Not a RageVault file.");
40
+ if (file.format !== FORMAT)
41
+ throw new Error(`Unsupported vault format (${file.format}).`);
42
+ const salt = Buffer.from(file.salt, "base64");
43
+ const kek = deriveKek(password, salt, file.kdf);
44
+ let dek;
45
+ try {
46
+ dek = unwrapKey(file.wrappedKey, kek);
47
+ }
48
+ catch {
49
+ wipe(kek);
50
+ throw new Error("Wrong master password (or the vault file is corrupt).");
51
+ }
52
+ wipe(kek);
53
+ const data = JSON.parse(open(file.payload, dek, "ragevault:payload").toString("utf8"));
54
+ return { data, dek, wrappedKey: file.wrappedKey, kdf: file.kdf, salt };
55
+ }
56
+ /** Serialize the current data to a VaultFile (re-encrypts the payload; fast). */
57
+ export function toFile(vault) {
58
+ return {
59
+ magic: MAGIC,
60
+ format: FORMAT,
61
+ kdf: vault.kdf,
62
+ salt: vault.salt.toString("base64"),
63
+ wrappedKey: vault.wrappedKey,
64
+ payload: seal(JSON.stringify(vault.data), vault.dek, "ragevault:payload"),
65
+ };
66
+ }
67
+ /** Change the master password: re-wrap the DEK under a new KEK (fast). */
68
+ export function rewrapForPassword(vault, newPassword, kdf = DEFAULT_KDF) {
69
+ const salt = newSalt();
70
+ const kek = deriveKek(newPassword, salt, kdf);
71
+ vault.wrappedKey = wrapKey(vault.dek, kek);
72
+ wipe(kek);
73
+ vault.kdf = kdf;
74
+ vault.salt = salt;
75
+ }
76
+ /** Read + parse a vault file from disk. */
77
+ export function readVaultFile(path) {
78
+ return JSON.parse(readFileSync(path, "utf8"));
79
+ }
80
+ /**
81
+ * Write a vault file to disk atomically and owner-readable only (0600).
82
+ *
83
+ * A password vault is the single copy of irreplaceable data, so we never write
84
+ * over the live file in place — a crash, full disk or power loss mid-write would
85
+ * truncate it beyond recovery. Instead: write a sibling temp file, fsync it,
86
+ * then rename over the target (an atomic replace on the same filesystem).
87
+ */
88
+ export function writeVaultFile(path, file) {
89
+ mkdirSync(dirname(path), { recursive: true });
90
+ const tmp = `${path}.tmp-${process.pid}`;
91
+ const data = `${JSON.stringify(file, null, 2)}\n`;
92
+ const fd = openSync(tmp, "w", 0o600);
93
+ try {
94
+ writeSync(fd, data, null, "utf8");
95
+ fsyncSync(fd); // flush to the platter before we swap it in
96
+ }
97
+ finally {
98
+ closeSync(fd);
99
+ }
100
+ try {
101
+ chmodSync(tmp, 0o600);
102
+ }
103
+ catch { /* Windows / unsupported fs */ }
104
+ renameSync(tmp, path); // atomic replace
105
+ }
106
+ /** Encrypt the current state and persist it atomically (see writeVaultFile). */
107
+ export function saveVault(path, vault) {
108
+ writeVaultFile(path, toFile(vault));
109
+ }
110
+ //# sourceMappingURL=vaultfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vaultfile.js","sourceRoot":"","sources":["../../src/store/vaultfile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Vault-file tests — create, unlock, save, change-password, tamper.
3
+ * Part of RageVault. Crafted by SoyRage Agency — https://soyrage.es/
4
+ */
5
+ export {};
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Vault-file tests — create, unlock, save, change-password, tamper.
3
+ * Part of RageVault. Crafted by SoyRage Agency — https://soyrage.es/
4
+ */
5
+ import { test } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { emptyVault, newId } from "../model.js";
8
+ import { createVault, rewrapForPassword, toFile, unlockVault } from "./vaultfile.js";
9
+ const FAST = { algo: "scrypt", N: 1 << 10, r: 8, p: 1, keyLen: 32 };
10
+ const NOW = "2026-07-31T00:00:00.000Z";
11
+ function seedVault() {
12
+ const data = emptyVault("Personal", "SoyRage", NOW);
13
+ data.items.push({
14
+ id: newId(), title: "GitHub", username: "soyrage", password: "s3cr3t-p@ss",
15
+ url: "https://github.com", tags: ["dev"], favorite: true, createdAt: NOW, updatedAt: NOW, history: [],
16
+ });
17
+ return createVault(data, "master-pw", FAST);
18
+ }
19
+ test("create → toFile → unlock recovers the same data", () => {
20
+ const v = seedVault();
21
+ const file = toFile(v);
22
+ const reopened = unlockVault(file, "master-pw");
23
+ assert.equal(reopened.data.items[0].password, "s3cr3t-p@ss");
24
+ assert.equal(reopened.data.users[0].role, "owner");
25
+ });
26
+ test("wrong master password is rejected", () => {
27
+ const file = toFile(seedVault());
28
+ assert.throws(() => unlockVault(file, "not-the-password"), /Wrong master password/);
29
+ });
30
+ test("changing the password re-wraps the DEK without losing data", () => {
31
+ const v = seedVault();
32
+ rewrapForPassword(v, "new-master-pw", FAST);
33
+ const file = toFile(v);
34
+ assert.throws(() => unlockVault(file, "master-pw"), "old password no longer works");
35
+ const reopened = unlockVault(file, "new-master-pw");
36
+ assert.equal(reopened.data.items[0].title, "GitHub");
37
+ });
38
+ test("a tampered payload fails to open", () => {
39
+ const file = toFile(seedVault());
40
+ const bytes = Buffer.from(file.payload.ct, "base64");
41
+ bytes[10] ^= 0x01;
42
+ file.payload.ct = bytes.toString("base64");
43
+ assert.throws(() => unlockVault(file, "master-pw"));
44
+ });
45
+ //# sourceMappingURL=vaultfile.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vaultfile.test.js","sourceRoot":"","sources":["../../src/store/vaultfile.test.ts"],"names":[],"mappings":"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"}
package/dist/totp.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * TOTP (RFC 6238) — turn a stored 2FA secret into the live 6-digit code.
3
+ *
4
+ * Standard HMAC-based one-time passwords: base32-decode the secret, HMAC it with
5
+ * a time counter, and dynamically truncate to N digits. Uses only node:crypto.
6
+ *
7
+ * Part of RageVault.
8
+ * Crafted by SoyRage Agency — https://soyrage.es/
9
+ * MIT licensed (see LICENSE).
10
+ */
11
+ /** Decode an RFC 4648 base32 string (padding & spaces tolerated). */
12
+ export declare function base32Decode(input: string): Buffer;
13
+ export interface TotpOptions {
14
+ digits: number;
15
+ period: number;
16
+ algorithm: "sha1" | "sha256" | "sha512";
17
+ }
18
+ /** The current TOTP code, plus seconds until it rolls over. Accepts either a
19
+ * bare base32 secret or a full otpauth:// URI (its digits/period/algorithm are
20
+ * honoured; an explicit `opts` argument still wins over the URI's values). */
21
+ export declare function totp(secret: string, opts?: Partial<TotpOptions>, nowMs?: number): {
22
+ code: string;
23
+ remaining: number;
24
+ };
25
+ /** Accept a raw base32 secret or an otpauth:// URI and return the base32 secret. */
26
+ export declare function extractTotpSecret(input: string): string;
package/dist/totp.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * TOTP (RFC 6238) — turn a stored 2FA secret into the live 6-digit code.
3
+ *
4
+ * Standard HMAC-based one-time passwords: base32-decode the secret, HMAC it with
5
+ * a time counter, and dynamically truncate to N digits. Uses only node:crypto.
6
+ *
7
+ * Part of RageVault.
8
+ * Crafted by SoyRage Agency — https://soyrage.es/
9
+ * MIT licensed (see LICENSE).
10
+ */
11
+ import { createHmac } from "node:crypto";
12
+ const B32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
13
+ /** Decode an RFC 4648 base32 string (padding & spaces tolerated). */
14
+ export function base32Decode(input) {
15
+ const clean = input.toUpperCase().replace(/=+$/, "").replace(/\s+/g, "");
16
+ let bits = 0, value = 0;
17
+ const out = [];
18
+ for (const ch of clean) {
19
+ const idx = B32.indexOf(ch);
20
+ if (idx === -1)
21
+ throw new Error("Invalid base32 in TOTP secret.");
22
+ value = (value << 5) | idx;
23
+ bits += 5;
24
+ if (bits >= 8) {
25
+ bits -= 8;
26
+ out.push((value >>> bits) & 0xff);
27
+ }
28
+ }
29
+ return Buffer.from(out);
30
+ }
31
+ const DEFAULTS = { digits: 6, period: 30, algorithm: "sha1" };
32
+ /** Pull the secret + digits/period/algorithm out of an otpauth:// URI. */
33
+ function parseOtpauth(uri) {
34
+ try {
35
+ const p = new URL(uri).searchParams;
36
+ const opts = {};
37
+ const digits = Number(p.get("digits"));
38
+ if (Number.isInteger(digits) && digits >= 6 && digits <= 10)
39
+ opts.digits = digits;
40
+ const period = Number(p.get("period"));
41
+ if (Number.isInteger(period) && period > 0)
42
+ opts.period = period;
43
+ const alg = (p.get("algorithm") ?? "").toLowerCase();
44
+ if (alg === "sha1" || alg === "sha256" || alg === "sha512")
45
+ opts.algorithm = alg;
46
+ return { secret: p.get("secret") ?? "", opts };
47
+ }
48
+ catch {
49
+ return { secret: "", opts: {} };
50
+ }
51
+ }
52
+ /** The current TOTP code, plus seconds until it rolls over. Accepts either a
53
+ * bare base32 secret or a full otpauth:// URI (its digits/period/algorithm are
54
+ * honoured; an explicit `opts` argument still wins over the URI's values). */
55
+ export function totp(secret, opts = {}, nowMs = Date.now()) {
56
+ let base32 = secret.trim();
57
+ let uriOpts = {};
58
+ if (base32.toLowerCase().startsWith("otpauth://")) {
59
+ const parsed = parseOtpauth(base32);
60
+ base32 = parsed.secret;
61
+ uriOpts = parsed.opts;
62
+ }
63
+ const o = { ...DEFAULTS, ...uriOpts, ...opts };
64
+ const key = base32Decode(base32);
65
+ const counter = Math.floor(nowMs / 1000 / o.period);
66
+ const buf = Buffer.alloc(8);
67
+ buf.writeBigUInt64BE(BigInt(counter));
68
+ const hmac = createHmac(o.algorithm, key).update(buf).digest();
69
+ const offset = hmac[hmac.length - 1] & 0x0f;
70
+ const bin = ((hmac[offset] & 0x7f) << 24) | (hmac[offset + 1] << 16) | (hmac[offset + 2] << 8) | hmac[offset + 3];
71
+ const code = (bin % 10 ** o.digits).toString().padStart(o.digits, "0");
72
+ const remaining = o.period - Math.floor((nowMs / 1000) % o.period);
73
+ return { code, remaining };
74
+ }
75
+ /** Accept a raw base32 secret or an otpauth:// URI and return the base32 secret. */
76
+ export function extractTotpSecret(input) {
77
+ const s = input.trim();
78
+ if (s.toLowerCase().startsWith("otpauth://")) {
79
+ try {
80
+ return new URL(s).searchParams.get("secret") ?? "";
81
+ }
82
+ catch {
83
+ return "";
84
+ }
85
+ }
86
+ return s;
87
+ }
88
+ //# sourceMappingURL=totp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totp.js","sourceRoot":"","sources":["../src/totp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * TOTP tests against the RFC 6238 published test vectors.
3
+ * Part of RageVault. Crafted by SoyRage Agency — https://soyrage.es/
4
+ */
5
+ export {};
@@ -0,0 +1,41 @@
1
+ /**
2
+ * TOTP tests against the RFC 6238 published test vectors.
3
+ * Part of RageVault. Crafted by SoyRage Agency — https://soyrage.es/
4
+ */
5
+ import { test } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { base32Decode, extractTotpSecret, totp } from "./totp.js";
8
+ // RFC 6238 uses the ASCII seed "12345678901234567890"; as base32 that is:
9
+ const SEED_B32 = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ";
10
+ test("base32 decodes to the RFC seed", () => {
11
+ assert.equal(base32Decode(SEED_B32).toString("ascii"), "12345678901234567890");
12
+ });
13
+ test("TOTP matches RFC 6238 SHA-1 vectors", () => {
14
+ // (unix time, expected 8-digit code) from RFC 6238 Appendix B.
15
+ const cases = [
16
+ [59, "94287082"],
17
+ [1111111109, "07081804"],
18
+ [1111111111, "14050471"],
19
+ [1234567890, "89005924"],
20
+ [2000000000, "69279037"],
21
+ ];
22
+ for (const [t, expected] of cases) {
23
+ const { code } = totp(SEED_B32, { digits: 8, period: 30, algorithm: "sha1" }, t * 1000);
24
+ assert.equal(code, expected, `t=${t}`);
25
+ }
26
+ });
27
+ test("extractTotpSecret unwraps an otpauth URI", () => {
28
+ assert.equal(extractTotpSecret("otpauth://totp/Example:me?secret=JBSWY3DPEHPK3PXP&issuer=Example"), "JBSWY3DPEHPK3PXP");
29
+ assert.equal(extractTotpSecret("JBSWY3DPEHPK3PXP"), "JBSWY3DPEHPK3PXP");
30
+ });
31
+ test("totp() honours digits/period/algorithm from an otpauth:// URI", () => {
32
+ // An otpauth URI carrying non-default params must produce the same code as
33
+ // passing those params explicitly — the params must not be silently dropped.
34
+ const uri = `otpauth://totp/Example:me?secret=${SEED_B32}&digits=8&period=30&algorithm=SHA1`;
35
+ const t = 1111111109;
36
+ assert.equal(totp(uri, {}, t * 1000).code, "07081804");
37
+ assert.equal(totp(uri, {}, t * 1000).code, totp(SEED_B32, { digits: 8, period: 30 }, t * 1000).code);
38
+ // A bare secret still defaults to 6 digits.
39
+ assert.equal(totp(SEED_B32, {}, 59 * 1000).code.length, 6);
40
+ });
41
+ //# sourceMappingURL=totp.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totp.test.js","sourceRoot":"","sources":["../src/totp.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAElE,0EAA0E;AAC1E,MAAM,QAAQ,GAAG,kCAAkC,CAAC;AAEpD,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC1C,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,sBAAsB,CAAC,CAAC;AACjF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qCAAqC,EAAE,GAAG,EAAE;IAC/C,+DAA+D;IAC/D,MAAM,KAAK,GAA4B;QACrC,CAAC,EAAE,EAAE,UAAU,CAAC;QAChB,CAAC,UAAU,EAAE,UAAU,CAAC;QACxB,CAAC,UAAU,EAAE,UAAU,CAAC;QACxB,CAAC,UAAU,EAAE,UAAU,CAAC;QACxB,CAAC,UAAU,EAAE,UAAU,CAAC;KACzB,CAAC;IACF,KAAK,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,KAAK,EAAE,CAAC;QAClC,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QACxF,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0CAA0C,EAAE,GAAG,EAAE;IACpD,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,kEAAkE,CAAC,EAAE,kBAAkB,CAAC,CAAC;IACxH,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC,CAAC;AAC1E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+DAA+D,EAAE,GAAG,EAAE;IACzE,2EAA2E;IAC3E,6EAA6E;IAC7E,MAAM,GAAG,GAAG,oCAAoC,QAAQ,oCAAoC,CAAC;IAC7F,MAAM,CAAC,GAAG,UAAU,CAAC;IACrB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACvD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;IACrG,4CAA4C;IAC5C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Minimal ANSI helpers for the terminal UI — colours, cursor control, and
3
+ * width-aware string helpers. Dependency-free by design.
4
+ *
5
+ * Part of RageVault.
6
+ * Crafted by SoyRage Agency — https://soyrage.es/
7
+ * MIT licensed (see LICENSE).
8
+ */
9
+ export declare const color: {
10
+ bold: (s: string) => string;
11
+ dim: (s: string) => string;
12
+ gray: (s: string) => string;
13
+ red: (s: string) => string;
14
+ green: (s: string) => string;
15
+ yellow: (s: string) => string;
16
+ brightBlue: (s: string) => string;
17
+ /** SoyRage accent (256-colour blue). */
18
+ accent: (s: string) => string;
19
+ bgAccent: (s: string) => string;
20
+ };
21
+ export declare const ctl: {
22
+ enterAlt: string;
23
+ exitAlt: string;
24
+ hideCursor: string;
25
+ showCursor: string;
26
+ clear: string;
27
+ home: string;
28
+ clearLine: string;
29
+ clearBelow: string;
30
+ };
31
+ export declare const visLen: (s: string) => number;
32
+ export declare function truncate(s: string, max: number): string;
33
+ export declare function padEnd(s: string, width: number): string;
34
+ export declare function padStart(s: string, width: number): string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Minimal ANSI helpers for the terminal UI — colours, cursor control, and
3
+ * width-aware string helpers. Dependency-free by design.
4
+ *
5
+ * Part of RageVault.
6
+ * Crafted by SoyRage Agency — https://soyrage.es/
7
+ * MIT licensed (see LICENSE).
8
+ */
9
+ const E = "\x1b[";
10
+ const sgr = (n) => (s) => `${E}${n}m${s}${E}0m`;
11
+ export const color = {
12
+ bold: sgr(1), dim: sgr(2), gray: sgr(90),
13
+ red: sgr(31), green: sgr(32), yellow: sgr(33), brightBlue: sgr(94),
14
+ /** SoyRage accent (256-colour blue). */
15
+ accent: (s) => `${E}38;5;39m${s}${E}0m`,
16
+ bgAccent: (s) => `${E}48;5;24m${s}${E}0m`,
17
+ };
18
+ export const ctl = {
19
+ enterAlt: `${E}?1049h`, exitAlt: `${E}?1049l`,
20
+ hideCursor: `${E}?25l`, showCursor: `${E}?25h`,
21
+ clear: `${E}2J`, home: `${E}H`, clearLine: `${E}K`, clearBelow: `${E}J`,
22
+ };
23
+ const ANSI = /\x1b\[[0-9;]*m/g;
24
+ export const visLen = (s) => s.replace(ANSI, "").length;
25
+ export function truncate(s, max) {
26
+ if (visLen(s) <= max)
27
+ return s;
28
+ let out = "", n = 0, i = 0;
29
+ while (i < s.length && n < max - 1) {
30
+ if (s[i] === "\x1b") {
31
+ const m = s.slice(i).match(/^\x1b\[[0-9;]*m/);
32
+ if (m) {
33
+ out += m[0];
34
+ i += m[0].length;
35
+ continue;
36
+ }
37
+ }
38
+ out += s[i];
39
+ n++;
40
+ i++;
41
+ }
42
+ return out + "…";
43
+ }
44
+ export function padEnd(s, width) {
45
+ const pad = width - visLen(s);
46
+ return pad > 0 ? s + " ".repeat(pad) : truncate(s, width);
47
+ }
48
+ export function padStart(s, width) {
49
+ const pad = width - visLen(s);
50
+ return pad > 0 ? " ".repeat(pad) + s : s;
51
+ }
52
+ //# sourceMappingURL=ansi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ansi.js","sourceRoot":"","sources":["../../src/tui/ansi.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,CAAC,GAAG,OAAO,CAAC;AAClB,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;AAEhE,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;IACxC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC;IAClE,wCAAwC;IACxC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI;IAC/C,QAAQ,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI;CAClD,CAAC;AAEF,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,QAAQ;IAC7C,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM;IAC9C,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,CAAC,GAAG;CACxE,CAAC;AAEF,MAAM,IAAI,GAAG,iBAAiB,CAAC;AAC/B,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC;AAExE,MAAM,UAAU,QAAQ,CAAC,CAAS,EAAE,GAAW;IAC7C,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG;QAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,GAAG,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;YAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC;gBAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;gBAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBAAC,SAAS;YAAC,CAAC;QAAC,CAAC;QAC3H,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAAC,CAAC,EAAE,CAAC;QAAC,CAAC,EAAE,CAAC;IACxB,CAAC;IACD,OAAO,GAAG,GAAG,GAAG,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,CAAS,EAAE,KAAa;IAC7C,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9B,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,CAAS,EAAE,KAAa;IAC/C,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9B,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * RageVault terminal UI — a menu-driven, searchable vault browser.
3
+ *
4
+ * No shortcuts to memorise: you move with the arrow keys, **type to search**, and
5
+ * press Enter to open a **menu** of actions. Secrets stay blurred until you pick
6
+ * "Reveal". Passwords live in a folder tree (branches), each item shows an icon,
7
+ * and there's a built-in generator and one-key copy. Auto-locks after idle.
8
+ *
9
+ * Part of RageVault.
10
+ * Crafted by SoyRage Agency — https://soyrage.es/
11
+ * MIT licensed (see LICENSE).
12
+ */
13
+ export declare class VaultTui {
14
+ private readonly path;
15
+ private readonly out;
16
+ private readonly inp;
17
+ private mode;
18
+ private vault;
19
+ private pwBuffer;
20
+ private unlockError;
21
+ private search;
22
+ private sel;
23
+ private expanded;
24
+ private revealed;
25
+ private menu;
26
+ private prompt;
27
+ private overlay;
28
+ private status;
29
+ private lastActivity;
30
+ private timer;
31
+ private clearTimer;
32
+ /** Breach-check cache: password → count (-2 checking, -1 unknown/offline). */
33
+ private breach;
34
+ private breachTimer;
35
+ private readonly onData;
36
+ constructor(path: string, out?: NodeJS.WriteStream & {
37
+ fd: 1;
38
+ }, inp?: NodeJS.ReadStream & {
39
+ fd: 0;
40
+ });
41
+ start(): void;
42
+ private quit;
43
+ private lock;
44
+ private save;
45
+ private onKey;
46
+ private onUnlockKey;
47
+ private tryUnlock;
48
+ private onPromptKey;
49
+ private onMenuKey;
50
+ private onBrowseKey;
51
+ private move;
52
+ private activate;
53
+ private openMainMenu;
54
+ private openItemMenu;
55
+ private openMoveMenu;
56
+ private confirmDelete;
57
+ private currentFolderId;
58
+ private newItem;
59
+ private newFolder;
60
+ private rename;
61
+ private regenerate;
62
+ private generate;
63
+ private flash;
64
+ private copy;
65
+ private isRisky;
66
+ private breachStatus;
67
+ /** Debounced HIBP check for the selected item (k-anonymity — only a hash prefix leaves). */
68
+ private scheduleBreach;
69
+ private matches;
70
+ private rows;
71
+ private cols;
72
+ private rows_;
73
+ private render;
74
+ private unlockLines;
75
+ private mainLines;
76
+ private rowText;
77
+ /** The live detail pane for whatever is selected. */
78
+ private detailRows;
79
+ private itemIcon;
80
+ private menuText;
81
+ private promptText;
82
+ private overlayBox;
83
+ }