@lifestreamdynamics/vault-cli 1.0.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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +759 -0
  3. package/dist/client.d.ts +12 -0
  4. package/dist/client.js +79 -0
  5. package/dist/commands/admin.d.ts +2 -0
  6. package/dist/commands/admin.js +263 -0
  7. package/dist/commands/audit.d.ts +2 -0
  8. package/dist/commands/audit.js +119 -0
  9. package/dist/commands/auth.d.ts +2 -0
  10. package/dist/commands/auth.js +256 -0
  11. package/dist/commands/config.d.ts +2 -0
  12. package/dist/commands/config.js +130 -0
  13. package/dist/commands/connectors.d.ts +2 -0
  14. package/dist/commands/connectors.js +224 -0
  15. package/dist/commands/docs.d.ts +2 -0
  16. package/dist/commands/docs.js +194 -0
  17. package/dist/commands/hooks.d.ts +2 -0
  18. package/dist/commands/hooks.js +159 -0
  19. package/dist/commands/keys.d.ts +2 -0
  20. package/dist/commands/keys.js +165 -0
  21. package/dist/commands/publish.d.ts +2 -0
  22. package/dist/commands/publish.js +138 -0
  23. package/dist/commands/search.d.ts +2 -0
  24. package/dist/commands/search.js +61 -0
  25. package/dist/commands/shares.d.ts +2 -0
  26. package/dist/commands/shares.js +121 -0
  27. package/dist/commands/subscription.d.ts +2 -0
  28. package/dist/commands/subscription.js +166 -0
  29. package/dist/commands/sync.d.ts +2 -0
  30. package/dist/commands/sync.js +565 -0
  31. package/dist/commands/teams.d.ts +2 -0
  32. package/dist/commands/teams.js +322 -0
  33. package/dist/commands/user.d.ts +2 -0
  34. package/dist/commands/user.js +48 -0
  35. package/dist/commands/vaults.d.ts +2 -0
  36. package/dist/commands/vaults.js +157 -0
  37. package/dist/commands/versions.d.ts +2 -0
  38. package/dist/commands/versions.js +219 -0
  39. package/dist/commands/webhooks.d.ts +2 -0
  40. package/dist/commands/webhooks.js +181 -0
  41. package/dist/config.d.ts +24 -0
  42. package/dist/config.js +88 -0
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.js +63 -0
  45. package/dist/lib/credential-manager.d.ts +48 -0
  46. package/dist/lib/credential-manager.js +101 -0
  47. package/dist/lib/encrypted-config.d.ts +20 -0
  48. package/dist/lib/encrypted-config.js +102 -0
  49. package/dist/lib/keychain.d.ts +8 -0
  50. package/dist/lib/keychain.js +82 -0
  51. package/dist/lib/migration.d.ts +31 -0
  52. package/dist/lib/migration.js +92 -0
  53. package/dist/lib/profiles.d.ts +43 -0
  54. package/dist/lib/profiles.js +104 -0
  55. package/dist/sync/config.d.ts +32 -0
  56. package/dist/sync/config.js +100 -0
  57. package/dist/sync/conflict.d.ts +30 -0
  58. package/dist/sync/conflict.js +60 -0
  59. package/dist/sync/daemon-worker.d.ts +1 -0
  60. package/dist/sync/daemon-worker.js +128 -0
  61. package/dist/sync/daemon.d.ts +44 -0
  62. package/dist/sync/daemon.js +174 -0
  63. package/dist/sync/diff.d.ts +43 -0
  64. package/dist/sync/diff.js +166 -0
  65. package/dist/sync/engine.d.ts +41 -0
  66. package/dist/sync/engine.js +233 -0
  67. package/dist/sync/ignore.d.ts +16 -0
  68. package/dist/sync/ignore.js +72 -0
  69. package/dist/sync/remote-poller.d.ts +23 -0
  70. package/dist/sync/remote-poller.js +145 -0
  71. package/dist/sync/state.d.ts +32 -0
  72. package/dist/sync/state.js +98 -0
  73. package/dist/sync/types.d.ts +68 -0
  74. package/dist/sync/types.js +4 -0
  75. package/dist/sync/watcher.d.ts +23 -0
  76. package/dist/sync/watcher.js +207 -0
  77. package/dist/utils/flags.d.ts +18 -0
  78. package/dist/utils/flags.js +31 -0
  79. package/dist/utils/format.d.ts +2 -0
  80. package/dist/utils/format.js +22 -0
  81. package/dist/utils/output.d.ts +87 -0
  82. package/dist/utils/output.js +229 -0
  83. package/package.json +62 -0
@@ -0,0 +1,101 @@
1
+ import { createKeychainBackend } from './keychain.js';
2
+ import { createEncryptedConfigBackend } from './encrypted-config.js';
3
+ // Default passphrase for encrypted config when no interactive prompt is available.
4
+ // This provides basic obfuscation — the real security benefit is file permissions (0600)
5
+ // and the fact that credentials aren't in a JSON file that's easy to grep for API keys.
6
+ const DEFAULT_PASSPHRASE = 'lsvault-cli-local-encryption-key';
7
+ export function createCredentialManager(options = {}) {
8
+ const keychain = options.keychain ?? createKeychainBackend();
9
+ const encryptedConfig = options.encryptedConfig ?? createEncryptedConfigBackend();
10
+ const passphrase = options.passphrase ?? DEFAULT_PASSPHRASE;
11
+ return {
12
+ async getCredentials() {
13
+ const result = {};
14
+ // 1. Environment variables (highest priority)
15
+ if (process.env.LSVAULT_API_KEY) {
16
+ result.apiKey = process.env.LSVAULT_API_KEY;
17
+ }
18
+ if (process.env.LSVAULT_API_URL) {
19
+ result.apiUrl = process.env.LSVAULT_API_URL;
20
+ }
21
+ // If env fully satisfies, return early
22
+ if (result.apiKey)
23
+ return result;
24
+ // 2. OS Keychain
25
+ if (await keychain.isAvailable()) {
26
+ const keychainCreds = await keychain.getCredentials();
27
+ if (keychainCreds.apiKey && !result.apiKey)
28
+ result.apiKey = keychainCreds.apiKey;
29
+ if (keychainCreds.apiUrl && !result.apiUrl)
30
+ result.apiUrl = keychainCreds.apiUrl;
31
+ }
32
+ if (result.apiKey)
33
+ return result;
34
+ // 3. Encrypted config
35
+ const encCreds = encryptedConfig.getCredentials(passphrase);
36
+ if (encCreds) {
37
+ if (encCreds.apiKey && !result.apiKey)
38
+ result.apiKey = encCreds.apiKey;
39
+ if (encCreds.apiUrl && !result.apiUrl)
40
+ result.apiUrl = encCreds.apiUrl;
41
+ }
42
+ return result;
43
+ },
44
+ async saveCredentials(config) {
45
+ // Prefer keychain if available
46
+ if (await keychain.isAvailable()) {
47
+ await keychain.saveCredentials(config);
48
+ return;
49
+ }
50
+ // Fall back to encrypted config
51
+ encryptedConfig.saveCredentials(config, passphrase);
52
+ },
53
+ async clearCredentials() {
54
+ // Clear from all backends
55
+ await keychain.clearCredentials();
56
+ encryptedConfig.clearCredentials();
57
+ },
58
+ async getStorageMethod() {
59
+ if (process.env.LSVAULT_API_KEY)
60
+ return 'env';
61
+ if (await keychain.isAvailable()) {
62
+ const creds = await keychain.getCredentials();
63
+ if (creds.apiKey)
64
+ return 'keychain';
65
+ }
66
+ if (encryptedConfig.hasCredentials()) {
67
+ const creds = encryptedConfig.getCredentials(passphrase);
68
+ if (creds?.apiKey)
69
+ return 'encrypted-config';
70
+ }
71
+ return 'none';
72
+ },
73
+ async getVaultKey(vaultId) {
74
+ // 1. Environment variable: LSVAULT_VAULT_KEY_<vaultId> (hyphens to underscores, uppercase)
75
+ const envKey = `LSVAULT_VAULT_KEY_${vaultId.replace(/-/g, '_').toUpperCase()}`;
76
+ if (process.env[envKey]) {
77
+ return process.env[envKey];
78
+ }
79
+ // 2. Encrypted config: vaultKeys map
80
+ const creds = encryptedConfig.getCredentials(passphrase);
81
+ const vaultKeys = creds?.vaultKeys;
82
+ if (vaultKeys?.[vaultId]) {
83
+ return vaultKeys[vaultId];
84
+ }
85
+ return null;
86
+ },
87
+ async saveVaultKey(vaultId, keyHex) {
88
+ // Read existing config, merge vault key, save
89
+ const existing = encryptedConfig.getCredentials(passphrase) ?? {};
90
+ const vaultKeys = existing.vaultKeys ?? {};
91
+ vaultKeys[vaultId] = keyHex;
92
+ encryptedConfig.saveCredentials({ ...existing, vaultKeys }, passphrase);
93
+ },
94
+ async deleteVaultKey(vaultId) {
95
+ const existing = encryptedConfig.getCredentials(passphrase) ?? {};
96
+ const vaultKeys = existing.vaultKeys ?? {};
97
+ delete vaultKeys[vaultId];
98
+ encryptedConfig.saveCredentials({ ...existing, vaultKeys }, passphrase);
99
+ },
100
+ };
101
+ }
@@ -0,0 +1,20 @@
1
+ import type { CliConfig } from '../config.js';
2
+ declare const CONFIG_DIR: string;
3
+ declare const ENCRYPTED_FILE: string;
4
+ declare const PBKDF2_ITERATIONS = 600000;
5
+ export interface EncryptedCredentials {
6
+ version: 1;
7
+ salt: string;
8
+ iv: string;
9
+ authTag: string;
10
+ ciphertext: string;
11
+ }
12
+ export interface EncryptedConfigBackend {
13
+ isAvailable(): boolean;
14
+ getCredentials(passphrase: string): Partial<CliConfig> | null;
15
+ saveCredentials(config: Partial<CliConfig>, passphrase: string): void;
16
+ clearCredentials(): void;
17
+ hasCredentials(): boolean;
18
+ }
19
+ export declare function createEncryptedConfigBackend(): EncryptedConfigBackend;
20
+ export { ENCRYPTED_FILE, CONFIG_DIR, PBKDF2_ITERATIONS };
@@ -0,0 +1,102 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import crypto from 'node:crypto';
5
+ const CONFIG_DIR = path.join(os.homedir(), '.lsvault');
6
+ const ENCRYPTED_FILE = path.join(CONFIG_DIR, 'credentials.enc');
7
+ const PBKDF2_ITERATIONS = 600_000;
8
+ const SALT_LENGTH = 16;
9
+ const IV_LENGTH = 12;
10
+ const KEY_LENGTH = 32; // AES-256
11
+ const AUTH_TAG_LENGTH = 16;
12
+ function deriveKey(passphrase, salt) {
13
+ return crypto.pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha256');
14
+ }
15
+ function encrypt(plaintext, passphrase) {
16
+ const salt = crypto.randomBytes(SALT_LENGTH);
17
+ const iv = crypto.randomBytes(IV_LENGTH);
18
+ const key = deriveKey(passphrase, salt);
19
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
20
+ const encrypted = Buffer.concat([
21
+ cipher.update(plaintext, 'utf8'),
22
+ cipher.final(),
23
+ ]);
24
+ const authTag = cipher.getAuthTag();
25
+ return {
26
+ version: 1,
27
+ salt: salt.toString('hex'),
28
+ iv: iv.toString('hex'),
29
+ authTag: authTag.toString('hex'),
30
+ ciphertext: encrypted.toString('hex'),
31
+ };
32
+ }
33
+ function decrypt(data, passphrase) {
34
+ const salt = Buffer.from(data.salt, 'hex');
35
+ const iv = Buffer.from(data.iv, 'hex');
36
+ const authTag = Buffer.from(data.authTag, 'hex');
37
+ const ciphertext = Buffer.from(data.ciphertext, 'hex');
38
+ const key = deriveKey(passphrase, salt);
39
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
40
+ decipher.setAuthTag(authTag);
41
+ const decrypted = Buffer.concat([
42
+ decipher.update(ciphertext),
43
+ decipher.final(),
44
+ ]);
45
+ return decrypted.toString('utf8');
46
+ }
47
+ export function createEncryptedConfigBackend() {
48
+ return {
49
+ isAvailable() {
50
+ return true; // Node.js crypto is always available
51
+ },
52
+ getCredentials(passphrase) {
53
+ if (!fs.existsSync(ENCRYPTED_FILE))
54
+ return null;
55
+ try {
56
+ const raw = fs.readFileSync(ENCRYPTED_FILE, 'utf-8');
57
+ const data = JSON.parse(raw);
58
+ if (data.version !== 1)
59
+ return null;
60
+ const plaintext = decrypt(data, passphrase);
61
+ return JSON.parse(plaintext);
62
+ }
63
+ catch {
64
+ // Wrong passphrase or corrupt file
65
+ return null;
66
+ }
67
+ },
68
+ saveCredentials(config, passphrase) {
69
+ if (!fs.existsSync(CONFIG_DIR)) {
70
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
71
+ }
72
+ // Merge with existing credentials if possible
73
+ let existing = {};
74
+ if (fs.existsSync(ENCRYPTED_FILE)) {
75
+ try {
76
+ const raw = fs.readFileSync(ENCRYPTED_FILE, 'utf-8');
77
+ const data = JSON.parse(raw);
78
+ const plaintext = decrypt(data, passphrase);
79
+ existing = JSON.parse(plaintext);
80
+ }
81
+ catch {
82
+ // Can't decrypt existing — overwrite
83
+ }
84
+ }
85
+ const merged = { ...existing, ...config };
86
+ const encrypted = encrypt(JSON.stringify(merged), passphrase);
87
+ const tmpFile = ENCRYPTED_FILE + '.tmp.' + crypto.randomBytes(4).toString('hex');
88
+ fs.writeFileSync(tmpFile, JSON.stringify(encrypted, null, 2) + '\n', { mode: 0o600 });
89
+ fs.renameSync(tmpFile, ENCRYPTED_FILE);
90
+ },
91
+ clearCredentials() {
92
+ if (fs.existsSync(ENCRYPTED_FILE)) {
93
+ fs.unlinkSync(ENCRYPTED_FILE);
94
+ }
95
+ },
96
+ hasCredentials() {
97
+ return fs.existsSync(ENCRYPTED_FILE);
98
+ },
99
+ };
100
+ }
101
+ // Export for testing
102
+ export { ENCRYPTED_FILE, CONFIG_DIR, PBKDF2_ITERATIONS };
@@ -0,0 +1,8 @@
1
+ import type { CliConfig } from '../config.js';
2
+ export interface KeychainBackend {
3
+ isAvailable(): Promise<boolean>;
4
+ getCredentials(): Promise<Partial<CliConfig>>;
5
+ saveCredentials(config: Partial<CliConfig>): Promise<void>;
6
+ clearCredentials(): Promise<void>;
7
+ }
8
+ export declare function createKeychainBackend(): KeychainBackend;
@@ -0,0 +1,82 @@
1
+ const SERVICE_NAME = 'lifestream-vault-cli';
2
+ const ACCOUNT_API_KEY = 'api-key';
3
+ const ACCOUNT_API_URL = 'api-url';
4
+ /**
5
+ * Dynamically loads keytar. Returns null if unavailable (not installed or
6
+ * native module fails to load, e.g. missing libsecret on Linux).
7
+ */
8
+ async function loadKeytar() {
9
+ try {
10
+ return await import('keytar');
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ export function createKeychainBackend() {
17
+ let keytarModule;
18
+ async function getKeytar() {
19
+ if (keytarModule === undefined) {
20
+ keytarModule = await loadKeytar();
21
+ }
22
+ return keytarModule;
23
+ }
24
+ return {
25
+ async isAvailable() {
26
+ const kt = await getKeytar();
27
+ if (!kt)
28
+ return false;
29
+ // Verify we can actually use the keychain (libsecret may be missing)
30
+ try {
31
+ await kt.getPassword(SERVICE_NAME, '__probe__');
32
+ return true;
33
+ }
34
+ catch {
35
+ keytarModule = null;
36
+ return false;
37
+ }
38
+ },
39
+ async getCredentials() {
40
+ const kt = await getKeytar();
41
+ if (!kt)
42
+ return {};
43
+ const result = {};
44
+ try {
45
+ const apiKey = await kt.getPassword(SERVICE_NAME, ACCOUNT_API_KEY);
46
+ if (apiKey)
47
+ result.apiKey = apiKey;
48
+ const apiUrl = await kt.getPassword(SERVICE_NAME, ACCOUNT_API_URL);
49
+ if (apiUrl)
50
+ result.apiUrl = apiUrl;
51
+ }
52
+ catch {
53
+ // Keychain access failed silently
54
+ }
55
+ return result;
56
+ },
57
+ async saveCredentials(config) {
58
+ const kt = await getKeytar();
59
+ if (!kt)
60
+ throw new Error('Keychain is not available');
61
+ if (config.apiKey) {
62
+ await kt.setPassword(SERVICE_NAME, ACCOUNT_API_KEY, config.apiKey);
63
+ }
64
+ if (config.apiUrl) {
65
+ await kt.setPassword(SERVICE_NAME, ACCOUNT_API_URL, config.apiUrl);
66
+ }
67
+ },
68
+ async clearCredentials() {
69
+ const kt = await getKeytar();
70
+ if (!kt)
71
+ return;
72
+ try {
73
+ await kt.deletePassword(SERVICE_NAME, ACCOUNT_API_KEY);
74
+ }
75
+ catch { /* ignore */ }
76
+ try {
77
+ await kt.deletePassword(SERVICE_NAME, ACCOUNT_API_URL);
78
+ }
79
+ catch { /* ignore */ }
80
+ },
81
+ };
82
+ }
@@ -0,0 +1,31 @@
1
+ import type { CliConfig } from '../config.js';
2
+ import type { CredentialManager } from './credential-manager.js';
3
+ declare const CONFIG_DIR: string;
4
+ declare const CONFIG_FILE: string;
5
+ export interface MigrationResult {
6
+ migrated: boolean;
7
+ method: string;
8
+ error?: string;
9
+ }
10
+ /**
11
+ * Checks if plaintext config contains an API key that should be migrated.
12
+ */
13
+ export declare function hasPlaintextCredentials(): boolean;
14
+ /**
15
+ * Reads the plaintext config file.
16
+ */
17
+ export declare function readPlaintextConfig(): Partial<CliConfig>;
18
+ /**
19
+ * Removes the apiKey from the plaintext config, keeping other fields (apiUrl).
20
+ */
21
+ export declare function removePlaintextApiKey(): void;
22
+ /**
23
+ * Migrates credentials from plaintext config to secure storage.
24
+ */
25
+ export declare function migrateCredentials(credentialManager: CredentialManager, promptFn?: () => Promise<boolean>): Promise<MigrationResult>;
26
+ /**
27
+ * Prints migration warnings if plaintext credentials are detected.
28
+ * Returns true if migration was performed.
29
+ */
30
+ export declare function checkAndPromptMigration(credentialManager: CredentialManager): Promise<boolean>;
31
+ export { CONFIG_FILE, CONFIG_DIR };
@@ -0,0 +1,92 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import chalk from 'chalk';
5
+ const CONFIG_DIR = path.join(os.homedir(), '.lsvault');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+ /**
8
+ * Checks if plaintext config contains an API key that should be migrated.
9
+ */
10
+ export function hasPlaintextCredentials() {
11
+ if (!fs.existsSync(CONFIG_FILE))
12
+ return false;
13
+ try {
14
+ const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
15
+ const config = JSON.parse(raw);
16
+ return typeof config.apiKey === 'string' && config.apiKey.length > 0;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ /**
23
+ * Reads the plaintext config file.
24
+ */
25
+ export function readPlaintextConfig() {
26
+ if (!fs.existsSync(CONFIG_FILE))
27
+ return {};
28
+ try {
29
+ const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
30
+ return JSON.parse(raw);
31
+ }
32
+ catch {
33
+ return {};
34
+ }
35
+ }
36
+ /**
37
+ * Removes the apiKey from the plaintext config, keeping other fields (apiUrl).
38
+ */
39
+ export function removePlaintextApiKey() {
40
+ if (!fs.existsSync(CONFIG_FILE))
41
+ return;
42
+ try {
43
+ const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
44
+ const config = JSON.parse(raw);
45
+ delete config.apiKey;
46
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
47
+ }
48
+ catch {
49
+ // ignore errors
50
+ }
51
+ }
52
+ /**
53
+ * Migrates credentials from plaintext config to secure storage.
54
+ */
55
+ export async function migrateCredentials(credentialManager, promptFn) {
56
+ if (!hasPlaintextCredentials()) {
57
+ return { migrated: false, method: 'none', error: 'No plaintext credentials found' };
58
+ }
59
+ // Ask user for confirmation if prompt function provided
60
+ if (promptFn) {
61
+ const confirmed = await promptFn();
62
+ if (!confirmed) {
63
+ return { migrated: false, method: 'skipped' };
64
+ }
65
+ }
66
+ const plaintextConfig = readPlaintextConfig();
67
+ try {
68
+ await credentialManager.saveCredentials({
69
+ apiKey: plaintextConfig.apiKey,
70
+ });
71
+ // Remove apiKey from plaintext config
72
+ removePlaintextApiKey();
73
+ const method = await credentialManager.getStorageMethod();
74
+ return { migrated: true, method };
75
+ }
76
+ catch (err) {
77
+ const message = err instanceof Error ? err.message : String(err);
78
+ return { migrated: false, method: 'error', error: message };
79
+ }
80
+ }
81
+ /**
82
+ * Prints migration warnings if plaintext credentials are detected.
83
+ * Returns true if migration was performed.
84
+ */
85
+ export async function checkAndPromptMigration(credentialManager) {
86
+ if (!hasPlaintextCredentials())
87
+ return false;
88
+ console.log(chalk.yellow('\nWarning: API key found in plaintext config (~/.lsvault/config.json)'));
89
+ console.log(chalk.yellow('Run `lsvault auth migrate` to migrate to secure storage.\n'));
90
+ return false;
91
+ }
92
+ export { CONFIG_FILE, CONFIG_DIR };
@@ -0,0 +1,43 @@
1
+ export interface ProfileConfig {
2
+ apiUrl?: string;
3
+ apiKey?: string;
4
+ [key: string]: string | undefined;
5
+ }
6
+ /**
7
+ * Returns the path to a named profile's config file.
8
+ */
9
+ export declare function getProfilePath(name: string): string;
10
+ /**
11
+ * Returns the name of the currently active profile, or 'default' if none set.
12
+ */
13
+ export declare function getActiveProfile(): string;
14
+ /**
15
+ * Sets the active profile name.
16
+ */
17
+ export declare function setActiveProfile(name: string): void;
18
+ /**
19
+ * Loads a profile's configuration. Returns an empty object if the profile
20
+ * does not exist.
21
+ */
22
+ export declare function loadProfile(name: string): ProfileConfig;
23
+ /**
24
+ * Saves a value to a profile. Creates the profile directory and file if needed.
25
+ */
26
+ export declare function setProfileValue(name: string, key: string, value: string): void;
27
+ /**
28
+ * Gets a single value from a profile.
29
+ */
30
+ export declare function getProfileValue(name: string, key: string): string | undefined;
31
+ /**
32
+ * Lists all available profile names (derived from filenames in the profiles dir).
33
+ */
34
+ export declare function listProfiles(): string[];
35
+ /**
36
+ * Deletes a profile.
37
+ */
38
+ export declare function deleteProfile(name: string): boolean;
39
+ /**
40
+ * Resolves the effective profile name: explicit --profile flag takes precedence,
41
+ * otherwise the active profile is used.
42
+ */
43
+ export declare function resolveProfileName(explicitProfile?: string): string;
@@ -0,0 +1,104 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ const CONFIG_DIR = path.join(os.homedir(), '.lsvault');
5
+ const PROFILES_DIR = path.join(CONFIG_DIR, 'profiles');
6
+ const ACTIVE_PROFILE_FILE = path.join(CONFIG_DIR, 'active-profile');
7
+ /**
8
+ * Returns the path to a named profile's config file.
9
+ */
10
+ export function getProfilePath(name) {
11
+ return path.join(PROFILES_DIR, `${name}.json`);
12
+ }
13
+ /**
14
+ * Returns the name of the currently active profile, or 'default' if none set.
15
+ */
16
+ export function getActiveProfile() {
17
+ try {
18
+ if (fs.existsSync(ACTIVE_PROFILE_FILE)) {
19
+ return fs.readFileSync(ACTIVE_PROFILE_FILE, 'utf-8').trim() || 'default';
20
+ }
21
+ }
22
+ catch {
23
+ // Fall through
24
+ }
25
+ return 'default';
26
+ }
27
+ /**
28
+ * Sets the active profile name.
29
+ */
30
+ export function setActiveProfile(name) {
31
+ if (!fs.existsSync(CONFIG_DIR)) {
32
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
33
+ }
34
+ fs.writeFileSync(ACTIVE_PROFILE_FILE, name + '\n');
35
+ }
36
+ /**
37
+ * Loads a profile's configuration. Returns an empty object if the profile
38
+ * does not exist.
39
+ */
40
+ export function loadProfile(name) {
41
+ const filePath = getProfilePath(name);
42
+ try {
43
+ if (fs.existsSync(filePath)) {
44
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
45
+ }
46
+ }
47
+ catch {
48
+ // Fall through — treat as empty
49
+ }
50
+ return {};
51
+ }
52
+ /**
53
+ * Saves a value to a profile. Creates the profile directory and file if needed.
54
+ */
55
+ export function setProfileValue(name, key, value) {
56
+ if (!fs.existsSync(PROFILES_DIR)) {
57
+ fs.mkdirSync(PROFILES_DIR, { recursive: true });
58
+ }
59
+ const config = loadProfile(name);
60
+ config[key] = value;
61
+ fs.writeFileSync(getProfilePath(name), JSON.stringify(config, null, 2) + '\n');
62
+ }
63
+ /**
64
+ * Gets a single value from a profile.
65
+ */
66
+ export function getProfileValue(name, key) {
67
+ const config = loadProfile(name);
68
+ return config[key];
69
+ }
70
+ /**
71
+ * Lists all available profile names (derived from filenames in the profiles dir).
72
+ */
73
+ export function listProfiles() {
74
+ try {
75
+ if (!fs.existsSync(PROFILES_DIR)) {
76
+ return [];
77
+ }
78
+ return fs.readdirSync(PROFILES_DIR)
79
+ .filter(f => f.endsWith('.json'))
80
+ .map(f => f.replace(/\.json$/, ''))
81
+ .sort();
82
+ }
83
+ catch {
84
+ return [];
85
+ }
86
+ }
87
+ /**
88
+ * Deletes a profile.
89
+ */
90
+ export function deleteProfile(name) {
91
+ const filePath = getProfilePath(name);
92
+ if (fs.existsSync(filePath)) {
93
+ fs.unlinkSync(filePath);
94
+ return true;
95
+ }
96
+ return false;
97
+ }
98
+ /**
99
+ * Resolves the effective profile name: explicit --profile flag takes precedence,
100
+ * otherwise the active profile is used.
101
+ */
102
+ export function resolveProfileName(explicitProfile) {
103
+ return explicitProfile || getActiveProfile();
104
+ }
@@ -0,0 +1,32 @@
1
+ import type { SyncConfig, CreateSyncOptions } from './types.js';
2
+ /**
3
+ * Read all sync configurations from disk.
4
+ */
5
+ export declare function loadSyncConfigs(): SyncConfig[];
6
+ /**
7
+ * Write all sync configurations to disk.
8
+ */
9
+ export declare function saveSyncConfigs(configs: SyncConfig[]): void;
10
+ /**
11
+ * Find a sync config by its ID.
12
+ */
13
+ export declare function getSyncConfig(id: string): SyncConfig | undefined;
14
+ /**
15
+ * Find a sync config by vault ID.
16
+ * Returns the first match (a vault should typically only have one sync config).
17
+ */
18
+ export declare function getSyncConfigByVaultId(vaultId: string): SyncConfig | undefined;
19
+ /**
20
+ * Create a new sync configuration.
21
+ * Returns the created config with a generated ID.
22
+ */
23
+ export declare function createSyncConfig(opts: CreateSyncOptions): SyncConfig;
24
+ /**
25
+ * Delete a sync configuration by ID.
26
+ * Returns true if the config was found and deleted.
27
+ */
28
+ export declare function deleteSyncConfig(id: string): boolean;
29
+ /**
30
+ * Update the lastSyncAt timestamp for a sync config.
31
+ */
32
+ export declare function updateLastSync(id: string, timestamp?: string): void;