@lifestreamdynamics/vault-cli 1.0.0 → 1.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/README.md +52 -14
- package/dist/commands/admin.js +9 -9
- package/dist/commands/auth.js +70 -4
- package/dist/commands/calendar.d.ts +2 -0
- package/dist/commands/calendar.js +167 -0
- package/dist/commands/connectors.js +9 -9
- package/dist/commands/docs.js +6 -6
- package/dist/commands/hooks.js +5 -5
- package/dist/commands/keys.js +6 -6
- package/dist/commands/links.d.ts +2 -0
- package/dist/commands/links.js +126 -0
- package/dist/commands/mfa.d.ts +2 -0
- package/dist/commands/mfa.js +224 -0
- package/dist/commands/publish.js +5 -5
- package/dist/commands/search.js +9 -4
- package/dist/commands/shares.js +4 -4
- package/dist/commands/subscription.js +8 -8
- package/dist/commands/sync.js +11 -8
- package/dist/commands/teams.js +15 -15
- package/dist/commands/user.js +2 -2
- package/dist/commands/vaults.js +4 -4
- package/dist/commands/versions.js +7 -7
- package/dist/commands/webhooks.js +6 -6
- package/dist/config.d.ts +1 -0
- package/dist/config.js +3 -2
- package/dist/index.js +6 -0
- package/dist/lib/keychain.js +22 -0
- package/dist/sync/daemon-worker.js +62 -0
- package/dist/sync/daemon.d.ts +9 -1
- package/dist/sync/daemon.js +22 -1
- package/package.json +8 -5
package/dist/commands/hooks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
2
|
+
import { getClientAsync } from '../client.js';
|
|
3
3
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
4
|
import { createOutput, handleError } from '../utils/output.js';
|
|
5
5
|
export function registerHookCommands(program) {
|
|
@@ -12,7 +12,7 @@ export function registerHookCommands(program) {
|
|
|
12
12
|
const out = createOutput(flags);
|
|
13
13
|
out.startSpinner('Fetching hooks...');
|
|
14
14
|
try {
|
|
15
|
-
const client =
|
|
15
|
+
const client = await getClientAsync();
|
|
16
16
|
const hookList = await client.hooks.list(vaultId);
|
|
17
17
|
out.stopSpinner();
|
|
18
18
|
out.list(hookList.map(hook => ({
|
|
@@ -76,7 +76,7 @@ export function registerHookCommands(program) {
|
|
|
76
76
|
}
|
|
77
77
|
out.startSpinner('Creating hook...');
|
|
78
78
|
try {
|
|
79
|
-
const client =
|
|
79
|
+
const client = await getClientAsync();
|
|
80
80
|
const params = {
|
|
81
81
|
name,
|
|
82
82
|
triggerEvent: String(_opts.trigger),
|
|
@@ -106,7 +106,7 @@ export function registerHookCommands(program) {
|
|
|
106
106
|
const out = createOutput(flags);
|
|
107
107
|
out.startSpinner('Deleting hook...');
|
|
108
108
|
try {
|
|
109
|
-
const client =
|
|
109
|
+
const client = await getClientAsync();
|
|
110
110
|
await client.hooks.delete(vaultId, hookId);
|
|
111
111
|
out.success('Hook deleted successfully', { id: hookId, deleted: true });
|
|
112
112
|
}
|
|
@@ -123,7 +123,7 @@ export function registerHookCommands(program) {
|
|
|
123
123
|
const out = createOutput(flags);
|
|
124
124
|
out.startSpinner('Fetching executions...');
|
|
125
125
|
try {
|
|
126
|
-
const client =
|
|
126
|
+
const client = await getClientAsync();
|
|
127
127
|
const executions = await client.hooks.listExecutions(vaultId, hookId);
|
|
128
128
|
out.stopSpinner();
|
|
129
129
|
out.list(executions.map(exec => ({
|
package/dist/commands/keys.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
2
|
+
import { getClientAsync } from '../client.js';
|
|
3
3
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
4
|
import { createOutput, handleError } from '../utils/output.js';
|
|
5
5
|
export function registerKeyCommands(program) {
|
|
@@ -11,7 +11,7 @@ export function registerKeyCommands(program) {
|
|
|
11
11
|
const out = createOutput(flags);
|
|
12
12
|
out.startSpinner('Fetching API keys...');
|
|
13
13
|
try {
|
|
14
|
-
const client =
|
|
14
|
+
const client = await getClientAsync();
|
|
15
15
|
const apiKeys = await client.apiKeys.list();
|
|
16
16
|
out.stopSpinner();
|
|
17
17
|
out.list(apiKeys.map(key => ({
|
|
@@ -54,7 +54,7 @@ export function registerKeyCommands(program) {
|
|
|
54
54
|
const out = createOutput(flags);
|
|
55
55
|
out.startSpinner('Fetching API key...');
|
|
56
56
|
try {
|
|
57
|
-
const client =
|
|
57
|
+
const client = await getClientAsync();
|
|
58
58
|
const key = await client.apiKeys.get(keyId);
|
|
59
59
|
out.stopSpinner();
|
|
60
60
|
out.record({
|
|
@@ -89,7 +89,7 @@ EXAMPLES
|
|
|
89
89
|
const out = createOutput(flags);
|
|
90
90
|
out.startSpinner('Creating API key...');
|
|
91
91
|
try {
|
|
92
|
-
const client =
|
|
92
|
+
const client = await getClientAsync();
|
|
93
93
|
const params = {
|
|
94
94
|
name,
|
|
95
95
|
scopes: String(_opts.scopes || 'read,write').split(',').map((s) => s.trim()),
|
|
@@ -131,7 +131,7 @@ EXAMPLES
|
|
|
131
131
|
}
|
|
132
132
|
out.startSpinner('Updating API key...');
|
|
133
133
|
try {
|
|
134
|
-
const client =
|
|
134
|
+
const client = await getClientAsync();
|
|
135
135
|
const params = {};
|
|
136
136
|
if (_opts.name)
|
|
137
137
|
params.name = String(_opts.name);
|
|
@@ -154,7 +154,7 @@ EXAMPLES
|
|
|
154
154
|
const out = createOutput(flags);
|
|
155
155
|
out.startSpinner('Revoking API key...');
|
|
156
156
|
try {
|
|
157
|
-
const client =
|
|
157
|
+
const client = await getClientAsync();
|
|
158
158
|
await client.apiKeys.delete(keyId);
|
|
159
159
|
out.success('API key revoked successfully', { id: keyId, revoked: true });
|
|
160
160
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getClientAsync } from '../client.js';
|
|
3
|
+
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
|
+
import { createOutput, handleError } from '../utils/output.js';
|
|
5
|
+
export function registerLinkCommands(program) {
|
|
6
|
+
const links = program.command('links').description('Manage document links and backlinks');
|
|
7
|
+
// lsvault links list <vaultId> <path> — forward links
|
|
8
|
+
addGlobalFlags(links.command('list')
|
|
9
|
+
.description('List forward links from a document')
|
|
10
|
+
.argument('<vaultId>', 'Vault ID')
|
|
11
|
+
.argument('<path>', 'Document path'))
|
|
12
|
+
.action(async (vaultId, docPath, _opts) => {
|
|
13
|
+
const flags = resolveFlags(_opts);
|
|
14
|
+
const out = createOutput(flags);
|
|
15
|
+
out.startSpinner('Fetching links...');
|
|
16
|
+
try {
|
|
17
|
+
const client = await getClientAsync();
|
|
18
|
+
const linkList = await client.documents.getLinks(vaultId, docPath);
|
|
19
|
+
out.stopSpinner();
|
|
20
|
+
out.list(linkList.map(link => ({
|
|
21
|
+
targetPath: link.targetPath,
|
|
22
|
+
linkText: link.linkText,
|
|
23
|
+
resolved: link.isResolved ? 'Yes' : 'No',
|
|
24
|
+
})), {
|
|
25
|
+
emptyMessage: 'No forward links found.',
|
|
26
|
+
columns: [
|
|
27
|
+
{ key: 'targetPath', header: 'Target' },
|
|
28
|
+
{ key: 'linkText', header: 'Link Text' },
|
|
29
|
+
{ key: 'resolved', header: 'Resolved' },
|
|
30
|
+
],
|
|
31
|
+
textFn: (link) => {
|
|
32
|
+
const resolved = link.resolved === 'Yes' ? chalk.green('✓') : chalk.red('✗');
|
|
33
|
+
return ` ${resolved} [[${String(link.linkText)}]] → ${String(link.targetPath)}`;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
handleError(out, err, 'Failed to fetch links');
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// lsvault links backlinks <vaultId> <path>
|
|
42
|
+
addGlobalFlags(links.command('backlinks')
|
|
43
|
+
.description('List backlinks pointing to a document')
|
|
44
|
+
.argument('<vaultId>', 'Vault ID')
|
|
45
|
+
.argument('<path>', 'Document path'))
|
|
46
|
+
.action(async (vaultId, docPath, _opts) => {
|
|
47
|
+
const flags = resolveFlags(_opts);
|
|
48
|
+
const out = createOutput(flags);
|
|
49
|
+
out.startSpinner('Fetching backlinks...');
|
|
50
|
+
try {
|
|
51
|
+
const client = await getClientAsync();
|
|
52
|
+
const backlinks = await client.documents.getBacklinks(vaultId, docPath);
|
|
53
|
+
out.stopSpinner();
|
|
54
|
+
out.list(backlinks.map(bl => ({
|
|
55
|
+
source: bl.sourceDocument.title || bl.sourceDocument.path,
|
|
56
|
+
linkText: bl.linkText,
|
|
57
|
+
context: bl.contextSnippet || '',
|
|
58
|
+
})), {
|
|
59
|
+
emptyMessage: 'No backlinks found.',
|
|
60
|
+
columns: [
|
|
61
|
+
{ key: 'source', header: 'Source' },
|
|
62
|
+
{ key: 'linkText', header: 'Link Text' },
|
|
63
|
+
{ key: 'context', header: 'Context' },
|
|
64
|
+
],
|
|
65
|
+
textFn: (bl) => {
|
|
66
|
+
const lines = [chalk.cyan(` ${String(bl.source)}`)];
|
|
67
|
+
lines.push(` Link: [[${String(bl.linkText)}]]`);
|
|
68
|
+
if (bl.context)
|
|
69
|
+
lines.push(` Context: ...${String(bl.context)}...`);
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
handleError(out, err, 'Failed to fetch backlinks');
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
// lsvault links graph <vaultId>
|
|
79
|
+
addGlobalFlags(links.command('graph')
|
|
80
|
+
.description('Get the link graph for a vault')
|
|
81
|
+
.argument('<vaultId>', 'Vault ID'))
|
|
82
|
+
.action(async (vaultId, _opts) => {
|
|
83
|
+
const flags = resolveFlags(_opts);
|
|
84
|
+
const out = createOutput(flags);
|
|
85
|
+
out.startSpinner('Fetching link graph...');
|
|
86
|
+
try {
|
|
87
|
+
const client = await getClientAsync();
|
|
88
|
+
const graph = await client.vaults.getGraph(vaultId);
|
|
89
|
+
out.stopSpinner();
|
|
90
|
+
// For graph, output as JSON structure
|
|
91
|
+
process.stdout.write(JSON.stringify({ nodes: graph.nodes, edges: graph.edges }) + '\n');
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
handleError(out, err, 'Failed to fetch link graph');
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
// lsvault links broken <vaultId>
|
|
98
|
+
addGlobalFlags(links.command('broken')
|
|
99
|
+
.description('List unresolved (broken) links in a vault')
|
|
100
|
+
.argument('<vaultId>', 'Vault ID'))
|
|
101
|
+
.action(async (vaultId, _opts) => {
|
|
102
|
+
const flags = resolveFlags(_opts);
|
|
103
|
+
const out = createOutput(flags);
|
|
104
|
+
out.startSpinner('Fetching unresolved links...');
|
|
105
|
+
try {
|
|
106
|
+
const client = await getClientAsync();
|
|
107
|
+
const unresolved = await client.vaults.getUnresolvedLinks(vaultId);
|
|
108
|
+
out.stopSpinner();
|
|
109
|
+
if (unresolved.length === 0) {
|
|
110
|
+
out.success('No broken links found!');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Format as grouped output
|
|
114
|
+
for (const group of unresolved) {
|
|
115
|
+
process.stdout.write(chalk.red(` ✗ ${group.targetPath}`) + '\n');
|
|
116
|
+
for (const ref of group.references) {
|
|
117
|
+
process.stdout.write(` ← ${ref.sourcePath} (${chalk.dim(ref.linkText)})` + '\n');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
process.stdout.write(`\n ${chalk.yellow(`${unresolved.length} broken link target(s) found`)}` + '\n');
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
handleError(out, err, 'Failed to fetch unresolved links');
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getClientAsync } from '../client.js';
|
|
4
|
+
export function registerMfaCommands(program) {
|
|
5
|
+
const mfa = program.command('mfa').description('Multi-factor authentication management');
|
|
6
|
+
mfa.command('status')
|
|
7
|
+
.description('Show MFA status and configured methods')
|
|
8
|
+
.action(async () => {
|
|
9
|
+
const spinner = ora('Fetching MFA status...').start();
|
|
10
|
+
try {
|
|
11
|
+
const client = await getClientAsync();
|
|
12
|
+
const status = await client.mfa.getStatus();
|
|
13
|
+
spinner.stop();
|
|
14
|
+
console.log(chalk.bold('MFA Status'));
|
|
15
|
+
console.log(` Enabled: ${status.mfaEnabled ? chalk.green('Yes') : chalk.dim('No')}`);
|
|
16
|
+
console.log(` TOTP Configured: ${status.totpConfigured ? chalk.green('Yes') : chalk.dim('No')}`);
|
|
17
|
+
console.log(` Passkeys Registered: ${status.passkeyCount > 0 ? chalk.cyan(status.passkeyCount) : chalk.dim('0')}`);
|
|
18
|
+
console.log(` Backup Codes Left: ${status.backupCodesRemaining > 0 ? chalk.cyan(status.backupCodesRemaining) : chalk.yellow('0')}`);
|
|
19
|
+
if (status.passkeys.length > 0) {
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(chalk.bold('Registered Passkeys:'));
|
|
22
|
+
for (const passkey of status.passkeys) {
|
|
23
|
+
const lastUsed = passkey.lastUsedAt
|
|
24
|
+
? new Date(passkey.lastUsedAt).toLocaleDateString()
|
|
25
|
+
: chalk.dim('never');
|
|
26
|
+
console.log(` - ${chalk.cyan(passkey.name)} (last used: ${lastUsed})`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
spinner.fail('Failed to fetch MFA status');
|
|
32
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
mfa.command('setup-totp')
|
|
36
|
+
.description('Set up TOTP authenticator app (Google Authenticator, Authy, etc.)')
|
|
37
|
+
.action(async () => {
|
|
38
|
+
const spinner = ora('Generating TOTP secret...').start();
|
|
39
|
+
try {
|
|
40
|
+
const client = await getClientAsync();
|
|
41
|
+
const setup = await client.mfa.setupTotp();
|
|
42
|
+
spinner.stop();
|
|
43
|
+
console.log(chalk.bold('TOTP Setup'));
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log(`Secret: ${chalk.cyan(setup.secret)}`);
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log('Add this URI to your authenticator app:');
|
|
48
|
+
console.log(chalk.dim(setup.otpauthUri));
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(chalk.yellow('Note: QR codes cannot be displayed in the terminal.'));
|
|
51
|
+
console.log(chalk.yellow(' Copy the URI above to any authenticator app that supports otpauth:// URIs.'));
|
|
52
|
+
console.log('');
|
|
53
|
+
// Prompt for verification code
|
|
54
|
+
const code = await promptMfaCode();
|
|
55
|
+
if (!code) {
|
|
56
|
+
console.log(chalk.yellow('Setup cancelled.'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const verifySpinner = ora('Verifying code and enabling TOTP...').start();
|
|
60
|
+
const result = await client.mfa.verifyTotp(code);
|
|
61
|
+
verifySpinner.succeed('TOTP enabled successfully!');
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log(chalk.bold.yellow('IMPORTANT: Save these backup codes securely!'));
|
|
64
|
+
console.log(chalk.dim('You can use them to access your account if you lose your authenticator device.'));
|
|
65
|
+
console.log('');
|
|
66
|
+
// Display backup codes in a grid (2 columns)
|
|
67
|
+
const codes = result.backupCodes;
|
|
68
|
+
for (let i = 0; i < codes.length; i += 2) {
|
|
69
|
+
const left = codes[i] || '';
|
|
70
|
+
const right = codes[i + 1] || '';
|
|
71
|
+
console.log(` ${chalk.cyan(left.padEnd(20))} ${chalk.cyan(right)}`);
|
|
72
|
+
}
|
|
73
|
+
console.log('');
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
spinner.fail('TOTP setup failed');
|
|
77
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
mfa.command('disable-totp')
|
|
81
|
+
.description('Disable TOTP authentication (requires password)')
|
|
82
|
+
.action(async () => {
|
|
83
|
+
const password = await promptPassword();
|
|
84
|
+
if (!password) {
|
|
85
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const spinner = ora('Disabling TOTP...').start();
|
|
89
|
+
try {
|
|
90
|
+
const client = await getClientAsync();
|
|
91
|
+
const result = await client.mfa.disableTotp(password);
|
|
92
|
+
spinner.succeed(result.message);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
spinner.fail('Failed to disable TOTP');
|
|
96
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
mfa.command('backup-codes')
|
|
100
|
+
.description('Show remaining backup code count or regenerate codes')
|
|
101
|
+
.option('--regenerate', 'Generate new backup codes (requires password, invalidates old codes)')
|
|
102
|
+
.action(async (opts) => {
|
|
103
|
+
if (opts.regenerate) {
|
|
104
|
+
// Regenerate backup codes
|
|
105
|
+
const password = await promptPassword();
|
|
106
|
+
if (!password) {
|
|
107
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const spinner = ora('Regenerating backup codes...').start();
|
|
111
|
+
try {
|
|
112
|
+
const client = await getClientAsync();
|
|
113
|
+
const result = await client.mfa.regenerateBackupCodes(password);
|
|
114
|
+
spinner.succeed('Backup codes regenerated!');
|
|
115
|
+
console.log('');
|
|
116
|
+
console.log(chalk.bold.yellow('IMPORTANT: Save these new backup codes securely!'));
|
|
117
|
+
console.log(chalk.dim('All previous backup codes have been invalidated.'));
|
|
118
|
+
console.log('');
|
|
119
|
+
// Display backup codes in a grid (2 columns)
|
|
120
|
+
const codes = result.backupCodes;
|
|
121
|
+
for (let i = 0; i < codes.length; i += 2) {
|
|
122
|
+
const left = codes[i] || '';
|
|
123
|
+
const right = codes[i + 1] || '';
|
|
124
|
+
console.log(` ${chalk.cyan(left.padEnd(20))} ${chalk.cyan(right)}`);
|
|
125
|
+
}
|
|
126
|
+
console.log('');
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
spinner.fail('Failed to regenerate backup codes');
|
|
130
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Show backup code count
|
|
135
|
+
const spinner = ora('Fetching backup code count...').start();
|
|
136
|
+
try {
|
|
137
|
+
const client = await getClientAsync();
|
|
138
|
+
const status = await client.mfa.getStatus();
|
|
139
|
+
spinner.stop();
|
|
140
|
+
console.log(chalk.bold('Backup Codes'));
|
|
141
|
+
console.log(` Remaining: ${status.backupCodesRemaining > 0 ? chalk.cyan(status.backupCodesRemaining) : chalk.yellow('0')}`);
|
|
142
|
+
if (status.backupCodesRemaining === 0) {
|
|
143
|
+
console.log('');
|
|
144
|
+
console.log(chalk.yellow('You have no backup codes remaining.'));
|
|
145
|
+
console.log(chalk.yellow('Run `lsvault mfa backup-codes --regenerate` to generate new codes.'));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
spinner.fail('Failed to fetch backup code count');
|
|
150
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Prompt for a password from stdin (non-echoing).
|
|
157
|
+
* Returns the password or null if stdin is not a TTY.
|
|
158
|
+
*/
|
|
159
|
+
async function promptPassword() {
|
|
160
|
+
if (!process.stdin.isTTY) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const readline = await import('node:readline');
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
const rl = readline.createInterface({
|
|
166
|
+
input: process.stdin,
|
|
167
|
+
output: process.stderr,
|
|
168
|
+
terminal: true,
|
|
169
|
+
});
|
|
170
|
+
process.stderr.write('Password: ');
|
|
171
|
+
process.stdin.setRawMode?.(true);
|
|
172
|
+
let password = '';
|
|
173
|
+
const onData = (chunk) => {
|
|
174
|
+
const char = chunk.toString('utf-8');
|
|
175
|
+
if (char === '\n' || char === '\r' || char === '\u0004') {
|
|
176
|
+
process.stderr.write('\n');
|
|
177
|
+
process.stdin.setRawMode?.(false);
|
|
178
|
+
process.stdin.removeListener('data', onData);
|
|
179
|
+
rl.close();
|
|
180
|
+
resolve(password);
|
|
181
|
+
}
|
|
182
|
+
else if (char === '\u0003') {
|
|
183
|
+
// Ctrl+C
|
|
184
|
+
process.stderr.write('\n');
|
|
185
|
+
process.stdin.setRawMode?.(false);
|
|
186
|
+
process.stdin.removeListener('data', onData);
|
|
187
|
+
rl.close();
|
|
188
|
+
resolve(null);
|
|
189
|
+
}
|
|
190
|
+
else if (char === '\u007F' || char === '\b') {
|
|
191
|
+
// Backspace
|
|
192
|
+
if (password.length > 0) {
|
|
193
|
+
password = password.slice(0, -1);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
password += char;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
process.stdin.on('data', onData);
|
|
201
|
+
process.stdin.resume();
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Prompt for an MFA code from stdin (6 digits, echoed for visibility).
|
|
206
|
+
* Returns the code or null if stdin is not a TTY.
|
|
207
|
+
*/
|
|
208
|
+
async function promptMfaCode() {
|
|
209
|
+
if (!process.stdin.isTTY) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const readline = await import('node:readline');
|
|
213
|
+
return new Promise((resolve) => {
|
|
214
|
+
const rl = readline.createInterface({
|
|
215
|
+
input: process.stdin,
|
|
216
|
+
output: process.stderr,
|
|
217
|
+
terminal: true,
|
|
218
|
+
});
|
|
219
|
+
rl.question('Enter 6-digit code from authenticator app: ', (answer) => {
|
|
220
|
+
rl.close();
|
|
221
|
+
resolve(answer.trim() || null);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
package/dist/commands/publish.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getClientAsync } from '../client.js';
|
|
2
2
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
3
3
|
import { createOutput, handleError } from '../utils/output.js';
|
|
4
4
|
import chalk from 'chalk';
|
|
@@ -12,7 +12,7 @@ export function registerPublishCommands(program) {
|
|
|
12
12
|
const out = createOutput(flags);
|
|
13
13
|
out.startSpinner('Fetching published documents...');
|
|
14
14
|
try {
|
|
15
|
-
const client =
|
|
15
|
+
const client = await getClientAsync();
|
|
16
16
|
const docs = await client.publish.listMine(vaultId);
|
|
17
17
|
out.stopSpinner();
|
|
18
18
|
out.list(docs.map(doc => ({
|
|
@@ -60,7 +60,7 @@ export function registerPublishCommands(program) {
|
|
|
60
60
|
const out = createOutput(flags);
|
|
61
61
|
out.startSpinner('Publishing document...');
|
|
62
62
|
try {
|
|
63
|
-
const client =
|
|
63
|
+
const client = await getClientAsync();
|
|
64
64
|
const params = {
|
|
65
65
|
slug: String(_opts.slug),
|
|
66
66
|
};
|
|
@@ -96,7 +96,7 @@ export function registerPublishCommands(program) {
|
|
|
96
96
|
const out = createOutput(flags);
|
|
97
97
|
out.startSpinner('Updating published document...');
|
|
98
98
|
try {
|
|
99
|
-
const client =
|
|
99
|
+
const client = await getClientAsync();
|
|
100
100
|
const params = {
|
|
101
101
|
slug: String(_opts.slug),
|
|
102
102
|
};
|
|
@@ -127,7 +127,7 @@ export function registerPublishCommands(program) {
|
|
|
127
127
|
const out = createOutput(flags);
|
|
128
128
|
out.startSpinner('Unpublishing document...');
|
|
129
129
|
try {
|
|
130
|
-
const client =
|
|
130
|
+
const client = await getClientAsync();
|
|
131
131
|
await client.publish.delete(vaultId, docPath);
|
|
132
132
|
out.success('Document unpublished successfully', { path: docPath, unpublished: true });
|
|
133
133
|
}
|
package/dist/commands/search.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
2
|
+
import { getClientAsync } from '../client.js';
|
|
3
3
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
4
|
import { createOutput, handleError } from '../utils/output.js';
|
|
5
5
|
export function registerSearchCommands(program) {
|
|
@@ -9,26 +9,31 @@ export function registerSearchCommands(program) {
|
|
|
9
9
|
.option('--vault <vaultId>', 'Limit search to a specific vault')
|
|
10
10
|
.option('--tags <tags>', 'Filter by tags (comma-separated)')
|
|
11
11
|
.option('--limit <n>', 'Maximum number of results', '20')
|
|
12
|
+
.option('--mode <mode>', 'Search mode: text, semantic, hybrid', 'text')
|
|
12
13
|
.addHelpText('after', `
|
|
13
14
|
EXAMPLES
|
|
14
15
|
lsvault search "meeting notes"
|
|
15
16
|
lsvault search "project plan" --vault abc123
|
|
16
|
-
lsvault search "typescript" --tags dev,code --limit 5
|
|
17
|
+
lsvault search "typescript" --tags dev,code --limit 5
|
|
18
|
+
lsvault search "machine learning" --mode semantic`))
|
|
17
19
|
.action(async (query, _opts) => {
|
|
18
20
|
const flags = resolveFlags(_opts);
|
|
19
21
|
const out = createOutput(flags);
|
|
20
22
|
out.startSpinner('Searching...');
|
|
21
23
|
try {
|
|
22
|
-
const client =
|
|
24
|
+
const client = await getClientAsync();
|
|
25
|
+
const mode = _opts.mode;
|
|
23
26
|
const response = await client.search.search({
|
|
24
27
|
q: query,
|
|
25
28
|
vault: _opts.vault,
|
|
26
29
|
tags: _opts.tags,
|
|
27
30
|
limit: parseInt(String(_opts.limit || '20'), 10),
|
|
31
|
+
mode: mode,
|
|
28
32
|
});
|
|
29
33
|
out.stopSpinner();
|
|
30
34
|
if (flags.output === 'text') {
|
|
31
|
-
|
|
35
|
+
const modeInfo = mode && mode !== 'text' ? `[${mode}] ` : '';
|
|
36
|
+
out.status(chalk.dim(`${modeInfo}${response.total} result(s) for "${response.query}":\n`));
|
|
32
37
|
}
|
|
33
38
|
out.list(response.results.map(r => ({
|
|
34
39
|
title: r.title || r.path,
|
package/dist/commands/shares.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
2
|
+
import { getClientAsync } from '../client.js';
|
|
3
3
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
4
|
import { createOutput, handleError } from '../utils/output.js';
|
|
5
5
|
export function registerShareCommands(program) {
|
|
@@ -13,7 +13,7 @@ export function registerShareCommands(program) {
|
|
|
13
13
|
const out = createOutput(flags);
|
|
14
14
|
out.startSpinner('Fetching share links...');
|
|
15
15
|
try {
|
|
16
|
-
const client =
|
|
16
|
+
const client = await getClientAsync();
|
|
17
17
|
const links = await client.shares.list(vaultId, docPath);
|
|
18
18
|
out.stopSpinner();
|
|
19
19
|
out.list(links.map(link => ({
|
|
@@ -63,7 +63,7 @@ export function registerShareCommands(program) {
|
|
|
63
63
|
const out = createOutput(flags);
|
|
64
64
|
out.startSpinner('Creating share link...');
|
|
65
65
|
try {
|
|
66
|
-
const client =
|
|
66
|
+
const client = await getClientAsync();
|
|
67
67
|
const params = {};
|
|
68
68
|
if (_opts.permission)
|
|
69
69
|
params.permission = String(_opts.permission);
|
|
@@ -110,7 +110,7 @@ export function registerShareCommands(program) {
|
|
|
110
110
|
const out = createOutput(flags);
|
|
111
111
|
out.startSpinner('Revoking share link...');
|
|
112
112
|
try {
|
|
113
|
-
const client =
|
|
113
|
+
const client = await getClientAsync();
|
|
114
114
|
await client.shares.revoke(vaultId, shareId);
|
|
115
115
|
out.success('Share link revoked successfully', { id: shareId, revoked: true });
|
|
116
116
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
2
|
+
import { getClientAsync } from '../client.js';
|
|
3
3
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
4
|
import { createOutput, handleError } from '../utils/output.js';
|
|
5
5
|
import { formatBytes } from '../utils/format.js';
|
|
@@ -12,7 +12,7 @@ export function registerSubscriptionCommands(program) {
|
|
|
12
12
|
const out = createOutput(flags);
|
|
13
13
|
out.startSpinner('Fetching subscription...');
|
|
14
14
|
try {
|
|
15
|
-
const client =
|
|
15
|
+
const client = await getClientAsync();
|
|
16
16
|
const data = await client.subscription.get();
|
|
17
17
|
out.stopSpinner();
|
|
18
18
|
if (flags.output === 'json') {
|
|
@@ -32,7 +32,7 @@ export function registerSubscriptionCommands(program) {
|
|
|
32
32
|
process.stdout.write(chalk.dim('Usage:') + '\n');
|
|
33
33
|
process.stdout.write(` Vaults: ${data.usage.vaultCount}\n`);
|
|
34
34
|
process.stdout.write(` Storage: ${formatBytes(data.usage.totalStorageBytes)}\n`);
|
|
35
|
-
process.stdout.write(` API calls
|
|
35
|
+
process.stdout.write(` API calls this month: ${data.usage.apiCallsThisMonth}\n`);
|
|
36
36
|
process.stdout.write(` AI tokens: ${data.usage.aiTokens}\n`);
|
|
37
37
|
process.stdout.write(` Hook executions: ${data.usage.hookExecutions}\n`);
|
|
38
38
|
process.stdout.write(` Webhook deliveries: ${data.usage.webhookDeliveries}\n`);
|
|
@@ -49,7 +49,7 @@ export function registerSubscriptionCommands(program) {
|
|
|
49
49
|
const out = createOutput(flags);
|
|
50
50
|
out.startSpinner('Fetching plans...');
|
|
51
51
|
try {
|
|
52
|
-
const client =
|
|
52
|
+
const client = await getClientAsync();
|
|
53
53
|
const plans = await client.subscription.listPlans();
|
|
54
54
|
out.stopSpinner();
|
|
55
55
|
out.list(plans.map(p => ({ name: p.name, tier: p.tier, ...p.limits })), {
|
|
@@ -85,7 +85,7 @@ EXAMPLES
|
|
|
85
85
|
const out = createOutput(flags);
|
|
86
86
|
out.startSpinner('Creating checkout session...');
|
|
87
87
|
try {
|
|
88
|
-
const client =
|
|
88
|
+
const client = await getClientAsync();
|
|
89
89
|
const session = await client.subscription.createCheckoutSession(tier, String(_opts.returnUrl));
|
|
90
90
|
out.success('Checkout session created', { url: session.url });
|
|
91
91
|
if (flags.output === 'text') {
|
|
@@ -104,7 +104,7 @@ EXAMPLES
|
|
|
104
104
|
const out = createOutput(flags);
|
|
105
105
|
out.startSpinner('Cancelling subscription...');
|
|
106
106
|
try {
|
|
107
|
-
const client =
|
|
107
|
+
const client = await getClientAsync();
|
|
108
108
|
await client.subscription.cancel(_opts.reason);
|
|
109
109
|
out.success('Subscription cancelled', { cancelled: true });
|
|
110
110
|
}
|
|
@@ -120,7 +120,7 @@ EXAMPLES
|
|
|
120
120
|
const out = createOutput(flags);
|
|
121
121
|
out.startSpinner('Creating portal session...');
|
|
122
122
|
try {
|
|
123
|
-
const client =
|
|
123
|
+
const client = await getClientAsync();
|
|
124
124
|
const portal = await client.subscription.createPortalSession(String(_opts.returnUrl));
|
|
125
125
|
out.success('Portal session created', { url: portal.url });
|
|
126
126
|
if (flags.output === 'text') {
|
|
@@ -138,7 +138,7 @@ EXAMPLES
|
|
|
138
138
|
const out = createOutput(flags);
|
|
139
139
|
out.startSpinner('Fetching invoices...');
|
|
140
140
|
try {
|
|
141
|
-
const client =
|
|
141
|
+
const client = await getClientAsync();
|
|
142
142
|
const invoices = await client.subscription.listInvoices();
|
|
143
143
|
out.stopSpinner();
|
|
144
144
|
out.list(invoices.map(inv => ({
|