@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/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A powerful command-line interface for Lifestream Vault - the multi-user Markdown document storage service with WebDAV sync, search, and collaboration features.
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
|
-
[](https://www.npmjs.com/package/@lifestreamdynamics/vault-cli)
|
|
7
7
|
|
|
8
8
|
## 📖 Table of Contents
|
|
9
9
|
|
|
@@ -20,6 +20,7 @@ A powerful command-line interface for Lifestream Vault - the multi-user Markdown
|
|
|
20
20
|
- [Team Commands](#team-commands)
|
|
21
21
|
- [Sharing & Publishing](#sharing--publishing)
|
|
22
22
|
- [Hooks & Webhooks](#hooks--webhooks)
|
|
23
|
+
- [Links & Backlinks](#links--backlinks)
|
|
23
24
|
- [Admin Commands](#admin-commands)
|
|
24
25
|
- [Sync & Watch Mode](#-sync--watch-mode)
|
|
25
26
|
- [Configuration](#️-configuration)
|
|
@@ -49,14 +50,14 @@ A powerful command-line interface for Lifestream Vault - the multi-user Markdown
|
|
|
49
50
|
- **🛡️ Admin Tools** - User management, system stats, and audit logs (admin only)
|
|
50
51
|
- **⚙️ Flexible Configuration** - Multiple profiles, environment variable support, and configurable sync behavior
|
|
51
52
|
- **🌐 Offline Support** - Work offline and sync when reconnected
|
|
52
|
-
- **📦 TypeScript SDK** - Built on `@
|
|
53
|
+
- **📦 TypeScript SDK** - Built on `@lifestreamdynamics/vault-sdk` with full type safety
|
|
53
54
|
|
|
54
55
|
## 📦 Installation
|
|
55
56
|
|
|
56
57
|
### Global Installation (Recommended)
|
|
57
58
|
|
|
58
59
|
```bash
|
|
59
|
-
npm install -g @
|
|
60
|
+
npm install -g @lifestreamdynamics/vault-cli
|
|
60
61
|
```
|
|
61
62
|
|
|
62
63
|
After installation, the `lsvault` command will be available globally:
|
|
@@ -68,7 +69,7 @@ lsvault --help
|
|
|
68
69
|
### Local Installation (Project-Specific)
|
|
69
70
|
|
|
70
71
|
```bash
|
|
71
|
-
npm install @
|
|
72
|
+
npm install @lifestreamdynamics/vault-cli
|
|
72
73
|
|
|
73
74
|
# Run with npx
|
|
74
75
|
npx lsvault --help
|
|
@@ -98,7 +99,7 @@ lsvault auth login --api-key lsv_k_your_api_key_here
|
|
|
98
99
|
lsvault auth login --email user@example.com
|
|
99
100
|
|
|
100
101
|
# Set a custom API URL (optional)
|
|
101
|
-
lsvault auth login --api-key lsv_k_your_key --api-url https://vault.
|
|
102
|
+
lsvault auth login --api-key lsv_k_your_key --api-url https://vault.lifestreamdynamics.com
|
|
102
103
|
```
|
|
103
104
|
|
|
104
105
|
### 2. List Your Vaults
|
|
@@ -372,6 +373,41 @@ lsvault webhooks create \
|
|
|
372
373
|
--secret webhook_secret_key
|
|
373
374
|
```
|
|
374
375
|
|
|
376
|
+
### Calendar
|
|
377
|
+
|
|
378
|
+
| Command | Description |
|
|
379
|
+
|---------|-------------|
|
|
380
|
+
| `lsvault calendar view <vaultId>` | Browse calendar views and activity heatmap |
|
|
381
|
+
| `lsvault calendar due <vaultId>` | List documents by due date |
|
|
382
|
+
| `lsvault calendar events <vaultId>` | List calendar events |
|
|
383
|
+
| `lsvault calendar create-event <vaultId>` | Create a calendar event |
|
|
384
|
+
| `lsvault calendar update-event <vaultId> <eventId>` | Update a calendar event |
|
|
385
|
+
| `lsvault calendar delete-event <vaultId> <eventId>` | Delete a calendar event |
|
|
386
|
+
|
|
387
|
+
### Links & Backlinks
|
|
388
|
+
|
|
389
|
+
| Command | Description |
|
|
390
|
+
|---------|-------------|
|
|
391
|
+
| `lsvault links list <vaultId> <path>` | List forward links from a document |
|
|
392
|
+
| `lsvault links backlinks <vaultId> <path>` | List backlinks pointing to a document |
|
|
393
|
+
| `lsvault links graph <vaultId>` | Get the link graph for a vault |
|
|
394
|
+
| `lsvault links broken <vaultId>` | List unresolved (broken) links in a vault |
|
|
395
|
+
|
|
396
|
+
**Example:**
|
|
397
|
+
```bash
|
|
398
|
+
# List forward links from a document
|
|
399
|
+
lsvault links list vault_abc123 notes/index.md
|
|
400
|
+
|
|
401
|
+
# Find all documents linking to a specific document
|
|
402
|
+
lsvault links backlinks vault_abc123 notes/important.md
|
|
403
|
+
|
|
404
|
+
# Get the full link graph for visualization
|
|
405
|
+
lsvault links graph vault_abc123 --output json > graph.json
|
|
406
|
+
|
|
407
|
+
# Find broken links
|
|
408
|
+
lsvault links broken vault_abc123
|
|
409
|
+
```
|
|
410
|
+
|
|
375
411
|
### Admin Commands
|
|
376
412
|
|
|
377
413
|
**Note:** Admin commands require admin role.
|
|
@@ -474,12 +510,14 @@ The CLI stores configuration in `~/.lsvault/config.json`:
|
|
|
474
510
|
|
|
475
511
|
```json
|
|
476
512
|
{
|
|
477
|
-
"apiUrl": "
|
|
513
|
+
"apiUrl": "https://vault.lifestreamdynamics.com"
|
|
478
514
|
}
|
|
479
515
|
```
|
|
480
516
|
|
|
481
517
|
**Note:** Credentials are stored securely in the system keychain (or encrypted file fallback), not in the plaintext config file.
|
|
482
518
|
|
|
519
|
+
> **Self-hosting?** Replace `https://vault.lifestreamdynamics.com` with your server's URL, or set the `LSVAULT_API_URL` environment variable.
|
|
520
|
+
|
|
483
521
|
### Configuration Profiles
|
|
484
522
|
|
|
485
523
|
Manage multiple configurations with profiles:
|
|
@@ -489,13 +527,13 @@ Manage multiple configurations with profiles:
|
|
|
489
527
|
lsvault config profiles
|
|
490
528
|
|
|
491
529
|
# Create a profile
|
|
492
|
-
lsvault config create-profile production --api-url https://vault.
|
|
530
|
+
lsvault config create-profile production --api-url https://vault.lifestreamdynamics.com
|
|
493
531
|
|
|
494
532
|
# Switch profiles
|
|
495
533
|
lsvault config use production
|
|
496
534
|
|
|
497
535
|
# Set config values
|
|
498
|
-
lsvault config set apiUrl https://vault.
|
|
536
|
+
lsvault config set apiUrl https://vault.lifestreamdynamics.com
|
|
499
537
|
|
|
500
538
|
# Get config values
|
|
501
539
|
lsvault config get apiUrl
|
|
@@ -522,14 +560,14 @@ Sync configurations are stored per vault in `~/.lsvault/sync/`:
|
|
|
522
560
|
|
|
523
561
|
| Variable | Description | Default |
|
|
524
562
|
|----------|-------------|---------|
|
|
525
|
-
| `LSVAULT_API_URL` | API server base URL | `
|
|
563
|
+
| `LSVAULT_API_URL` | API server base URL | `https://vault.lifestreamdynamics.com` |
|
|
526
564
|
| `LSVAULT_API_KEY` | API key for authentication | - |
|
|
527
565
|
| `LSVAULT_CONFIG_DIR` | Configuration directory | `~/.lsvault` |
|
|
528
566
|
| `LSVAULT_PROFILE` | Active configuration profile | `default` |
|
|
529
567
|
|
|
530
568
|
**Example:**
|
|
531
569
|
```bash
|
|
532
|
-
export LSVAULT_API_URL=https://vault.
|
|
570
|
+
export LSVAULT_API_URL=https://vault.lifestreamdynamics.com
|
|
533
571
|
export LSVAULT_API_KEY=lsv_k_your_key_here
|
|
534
572
|
lsvault vaults list
|
|
535
573
|
```
|
|
@@ -718,7 +756,7 @@ lsvault auth migrate
|
|
|
718
756
|
1. Verify API URL is correct: `lsvault config get apiUrl`
|
|
719
757
|
2. Test connectivity: `curl <API_URL>/api/v1/health`
|
|
720
758
|
3. Check firewall/proxy settings
|
|
721
|
-
4. Use custom API URL: `lsvault auth login --api-url https://vault.
|
|
759
|
+
4. Use custom API URL: `lsvault auth login --api-url https://vault.lifestreamdynamics.com`
|
|
722
760
|
|
|
723
761
|
### Output Format Issues
|
|
724
762
|
|
|
@@ -733,9 +771,9 @@ lsvault docs get vault_abc123 /path.md --quiet > output.md
|
|
|
733
771
|
|
|
734
772
|
## 🔗 Related Packages
|
|
735
773
|
|
|
736
|
-
- **[@
|
|
737
|
-
- **[@
|
|
738
|
-
- **[@
|
|
774
|
+
- **[@lifestreamdynamics/vault-sdk](https://npmjs.com/package/@lifestreamdynamics/vault-sdk)** - TypeScript SDK for Lifestream Vault API
|
|
775
|
+
- **[@lifestreamdynamics/vault-api](https://github.com/lifestreamdynamics/lifestream-vault)** - Backend API server
|
|
776
|
+
- **[@lifestreamdynamics/vault-web](https://github.com/lifestreamdynamics/lifestream-vault)** - Web frontend
|
|
739
777
|
|
|
740
778
|
## 📄 Documentation
|
|
741
779
|
|
package/dist/commands/admin.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
|
import { formatBytes, formatUptime } from '../utils/format.js';
|
|
@@ -12,7 +12,7 @@ export function registerAdminCommands(program) {
|
|
|
12
12
|
const out = createOutput(flags);
|
|
13
13
|
out.startSpinner('Fetching system stats...');
|
|
14
14
|
try {
|
|
15
|
-
const client =
|
|
15
|
+
const client = await getClientAsync();
|
|
16
16
|
const data = await client.admin.getStats();
|
|
17
17
|
out.stopSpinner();
|
|
18
18
|
out.record({
|
|
@@ -41,7 +41,7 @@ export function registerAdminCommands(program) {
|
|
|
41
41
|
const out = createOutput(flags);
|
|
42
42
|
out.startSpinner('Fetching timeseries data...');
|
|
43
43
|
try {
|
|
44
|
-
const client =
|
|
44
|
+
const client = await getClientAsync();
|
|
45
45
|
const data = await client.admin.getTimeseries(String(_opts.metric), String(_opts.period));
|
|
46
46
|
out.stopSpinner();
|
|
47
47
|
if (flags.output === 'text') {
|
|
@@ -79,7 +79,7 @@ export function registerAdminCommands(program) {
|
|
|
79
79
|
const out = createOutput(flags);
|
|
80
80
|
out.startSpinner('Fetching users...');
|
|
81
81
|
try {
|
|
82
|
-
const client =
|
|
82
|
+
const client = await getClientAsync();
|
|
83
83
|
const result = await client.admin.listUsers({
|
|
84
84
|
page: _opts.page,
|
|
85
85
|
limit: _opts.limit,
|
|
@@ -125,7 +125,7 @@ export function registerAdminCommands(program) {
|
|
|
125
125
|
const out = createOutput(flags);
|
|
126
126
|
out.startSpinner('Fetching user...');
|
|
127
127
|
try {
|
|
128
|
-
const client =
|
|
128
|
+
const client = await getClientAsync();
|
|
129
129
|
const user = await client.admin.getUser(userId);
|
|
130
130
|
out.stopSpinner();
|
|
131
131
|
out.record({
|
|
@@ -169,7 +169,7 @@ export function registerAdminCommands(program) {
|
|
|
169
169
|
}
|
|
170
170
|
out.startSpinner('Updating user...');
|
|
171
171
|
try {
|
|
172
|
-
const client =
|
|
172
|
+
const client = await getClientAsync();
|
|
173
173
|
const updated = await client.admin.updateUser(userId, params);
|
|
174
174
|
out.success(`User updated: ${chalk.cyan(updated.email)} -- ${chalk.magenta(updated.role)} -- ${updated.isActive ? chalk.green('active') : chalk.red('inactive')}`, {
|
|
175
175
|
email: updated.email,
|
|
@@ -190,7 +190,7 @@ export function registerAdminCommands(program) {
|
|
|
190
190
|
const out = createOutput(flags);
|
|
191
191
|
out.startSpinner('Fetching activity...');
|
|
192
192
|
try {
|
|
193
|
-
const client =
|
|
193
|
+
const client = await getClientAsync();
|
|
194
194
|
const activity = await client.admin.getActivity(_opts.limit);
|
|
195
195
|
out.stopSpinner();
|
|
196
196
|
out.list(activity.map(a => ({
|
|
@@ -224,7 +224,7 @@ export function registerAdminCommands(program) {
|
|
|
224
224
|
const out = createOutput(flags);
|
|
225
225
|
out.startSpinner('Fetching subscription summary...');
|
|
226
226
|
try {
|
|
227
|
-
const client =
|
|
227
|
+
const client = await getClientAsync();
|
|
228
228
|
const summary = await client.admin.getSubscriptionSummary();
|
|
229
229
|
out.stopSpinner();
|
|
230
230
|
out.record({
|
|
@@ -246,7 +246,7 @@ export function registerAdminCommands(program) {
|
|
|
246
246
|
const out = createOutput(flags);
|
|
247
247
|
out.startSpinner('Checking system health...');
|
|
248
248
|
try {
|
|
249
|
-
const client =
|
|
249
|
+
const client = await getClientAsync();
|
|
250
250
|
const health = await client.admin.getHealth();
|
|
251
251
|
out.stopSpinner();
|
|
252
252
|
out.record({
|
package/dist/commands/auth.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
|
|
4
4
|
import { loadConfigAsync, getCredentialManager } from '../config.js';
|
|
5
|
-
import {
|
|
5
|
+
import { getClientAsync } from '../client.js';
|
|
6
6
|
import { migrateCredentials, hasPlaintextCredentials, checkAndPromptMigration } from '../lib/migration.js';
|
|
7
7
|
export function registerAuthCommands(program) {
|
|
8
8
|
const auth = program.command('auth').description('Authentication and credential management');
|
|
@@ -11,11 +11,13 @@ export function registerAuthCommands(program) {
|
|
|
11
11
|
.option('--api-key <key>', 'API key (lsv_k_... prefix)')
|
|
12
12
|
.option('--email <email>', 'Email address for password login')
|
|
13
13
|
.option('--password <password>', 'Password (prompts interactively if omitted)')
|
|
14
|
-
.option('--
|
|
14
|
+
.option('--mfa-code <code>', 'MFA code (TOTP or backup code) if account has MFA enabled')
|
|
15
|
+
.option('--api-url <url>', 'API server URL (default: https://vault.lifestreamdynamics.com)')
|
|
15
16
|
.addHelpText('after', `
|
|
16
17
|
EXAMPLES
|
|
17
18
|
lsvault auth login --api-key lsv_k_abc123
|
|
18
19
|
lsvault auth login --email user@example.com
|
|
20
|
+
lsvault auth login --email user@example.com --mfa-code 123456
|
|
19
21
|
lsvault auth login --email user@example.com --api-url https://api.example.com`)
|
|
20
22
|
.action(async (opts) => {
|
|
21
23
|
const cm = getCredentialManager();
|
|
@@ -42,7 +44,20 @@ EXAMPLES
|
|
|
42
44
|
const apiUrl = opts.apiUrl ?? config.apiUrl;
|
|
43
45
|
const spinner = ora('Authenticating...').start();
|
|
44
46
|
try {
|
|
45
|
-
const { tokens, refreshToken } = await LifestreamVaultClient.login(apiUrl, opts.email, password
|
|
47
|
+
const { tokens, refreshToken } = await LifestreamVaultClient.login(apiUrl, opts.email, password, {}, {
|
|
48
|
+
mfaCode: opts.mfaCode,
|
|
49
|
+
onMfaRequired: async (challenge) => {
|
|
50
|
+
spinner.stop();
|
|
51
|
+
console.log(chalk.yellow('MFA required for this account.'));
|
|
52
|
+
console.log(`Available methods: ${challenge.methods.join(', ')}`);
|
|
53
|
+
const code = await promptMfaCode();
|
|
54
|
+
if (!code) {
|
|
55
|
+
throw new Error('MFA code is required');
|
|
56
|
+
}
|
|
57
|
+
spinner.start('Verifying MFA code...');
|
|
58
|
+
return { method: 'totp', code };
|
|
59
|
+
},
|
|
60
|
+
});
|
|
46
61
|
// Save tokens to secure storage
|
|
47
62
|
await cm.saveCredentials({
|
|
48
63
|
accessToken: tokens.accessToken,
|
|
@@ -179,7 +194,7 @@ EXAMPLES
|
|
|
179
194
|
if (config.apiKey || config.accessToken) {
|
|
180
195
|
const spinner = ora('Fetching user info...').start();
|
|
181
196
|
try {
|
|
182
|
-
const client =
|
|
197
|
+
const client = await getClientAsync();
|
|
183
198
|
const user = await client.user.me();
|
|
184
199
|
spinner.stop();
|
|
185
200
|
console.log(`User: ${chalk.cyan(user.email)}`);
|
|
@@ -245,6 +260,57 @@ async function promptPassword() {
|
|
|
245
260
|
process.stdin.resume();
|
|
246
261
|
});
|
|
247
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Prompt for an MFA code from stdin (6 digits, non-echoing).
|
|
265
|
+
* Returns the code or null if stdin is not a TTY.
|
|
266
|
+
*/
|
|
267
|
+
async function promptMfaCode() {
|
|
268
|
+
// In non-interactive mode, cannot prompt
|
|
269
|
+
if (!process.stdin.isTTY) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const readline = await import('node:readline');
|
|
273
|
+
return new Promise((resolve) => {
|
|
274
|
+
const rl = readline.createInterface({
|
|
275
|
+
input: process.stdin,
|
|
276
|
+
output: process.stderr,
|
|
277
|
+
terminal: true,
|
|
278
|
+
});
|
|
279
|
+
// Disable echoing
|
|
280
|
+
process.stderr.write('MFA code: ');
|
|
281
|
+
process.stdin.setRawMode?.(true);
|
|
282
|
+
let code = '';
|
|
283
|
+
const onData = (chunk) => {
|
|
284
|
+
const char = chunk.toString('utf-8');
|
|
285
|
+
if (char === '\n' || char === '\r' || char === '\u0004') {
|
|
286
|
+
process.stderr.write('\n');
|
|
287
|
+
process.stdin.setRawMode?.(false);
|
|
288
|
+
process.stdin.removeListener('data', onData);
|
|
289
|
+
rl.close();
|
|
290
|
+
resolve(code);
|
|
291
|
+
}
|
|
292
|
+
else if (char === '\u0003') {
|
|
293
|
+
// Ctrl+C
|
|
294
|
+
process.stderr.write('\n');
|
|
295
|
+
process.stdin.setRawMode?.(false);
|
|
296
|
+
process.stdin.removeListener('data', onData);
|
|
297
|
+
rl.close();
|
|
298
|
+
resolve(null);
|
|
299
|
+
}
|
|
300
|
+
else if (char === '\u007F' || char === '\b') {
|
|
301
|
+
// Backspace
|
|
302
|
+
if (code.length > 0) {
|
|
303
|
+
code = code.slice(0, -1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
code += char;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
process.stdin.on('data', onData);
|
|
311
|
+
process.stdin.resume();
|
|
312
|
+
});
|
|
313
|
+
}
|
|
248
314
|
function formatMethod(method) {
|
|
249
315
|
switch (method) {
|
|
250
316
|
case 'keychain': return chalk.green('OS Keychain');
|
|
@@ -0,0 +1,167 @@
|
|
|
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 registerCalendarCommands(program) {
|
|
6
|
+
const calendar = program.command('calendar').description('Document calendar and due date management');
|
|
7
|
+
// calendar view
|
|
8
|
+
addGlobalFlags(calendar.command('view')
|
|
9
|
+
.description('View calendar activity for a vault')
|
|
10
|
+
.argument('<vaultId>', 'Vault ID')
|
|
11
|
+
.option('--start <date>', 'Start date (YYYY-MM-DD)', getDefaultStart())
|
|
12
|
+
.option('--end <date>', 'End date (YYYY-MM-DD)', getDefaultEnd()))
|
|
13
|
+
.action(async (vaultId, _opts) => {
|
|
14
|
+
const flags = resolveFlags(_opts);
|
|
15
|
+
const out = createOutput(flags);
|
|
16
|
+
out.startSpinner('Loading calendar...');
|
|
17
|
+
try {
|
|
18
|
+
const client = await getClientAsync();
|
|
19
|
+
const response = await client.calendar.getActivity(vaultId, {
|
|
20
|
+
start: _opts.start,
|
|
21
|
+
end: _opts.end,
|
|
22
|
+
});
|
|
23
|
+
out.stopSpinner();
|
|
24
|
+
if (flags.output === 'text') {
|
|
25
|
+
out.status(chalk.dim(`Activity from ${response.start} to ${response.end}:\n`));
|
|
26
|
+
}
|
|
27
|
+
out.list(response.days.map(d => ({
|
|
28
|
+
date: d.date,
|
|
29
|
+
created: String(d.created),
|
|
30
|
+
updated: String(d.updated),
|
|
31
|
+
deleted: String(d.deleted),
|
|
32
|
+
total: String(d.total),
|
|
33
|
+
})), {
|
|
34
|
+
emptyMessage: 'No activity in this period.',
|
|
35
|
+
columns: [
|
|
36
|
+
{ key: 'date', header: 'Date' },
|
|
37
|
+
{ key: 'created', header: 'Created' },
|
|
38
|
+
{ key: 'updated', header: 'Updated' },
|
|
39
|
+
{ key: 'deleted', header: 'Deleted' },
|
|
40
|
+
{ key: 'total', header: 'Total' },
|
|
41
|
+
],
|
|
42
|
+
textFn: (d) => {
|
|
43
|
+
const bar = '█'.repeat(Math.min(Number(d.total), 20));
|
|
44
|
+
return `${chalk.dim(String(d.date))} ${chalk.green(bar)} ${chalk.bold(String(d.total))}`;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
handleError(out, err, 'Calendar view failed');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
// calendar due
|
|
53
|
+
addGlobalFlags(calendar.command('due')
|
|
54
|
+
.description('List documents with due dates')
|
|
55
|
+
.argument('<vaultId>', 'Vault ID')
|
|
56
|
+
.option('--status <status>', 'Filter: overdue, upcoming, all', 'all'))
|
|
57
|
+
.action(async (vaultId, _opts) => {
|
|
58
|
+
const flags = resolveFlags(_opts);
|
|
59
|
+
const out = createOutput(flags);
|
|
60
|
+
out.startSpinner('Loading due dates...');
|
|
61
|
+
try {
|
|
62
|
+
const client = await getClientAsync();
|
|
63
|
+
const docs = await client.calendar.getDueDates(vaultId, {
|
|
64
|
+
status: _opts.status,
|
|
65
|
+
});
|
|
66
|
+
out.stopSpinner();
|
|
67
|
+
out.list(docs.map(d => ({
|
|
68
|
+
title: d.title || d.path,
|
|
69
|
+
path: d.path,
|
|
70
|
+
dueAt: d.dueAt,
|
|
71
|
+
priority: d.priority || '-',
|
|
72
|
+
status: d.overdue ? 'OVERDUE' : d.completed ? 'Done' : 'Pending',
|
|
73
|
+
})), {
|
|
74
|
+
emptyMessage: 'No documents with due dates.',
|
|
75
|
+
columns: [
|
|
76
|
+
{ key: 'title', header: 'Title' },
|
|
77
|
+
{ key: 'dueAt', header: 'Due' },
|
|
78
|
+
{ key: 'priority', header: 'Priority' },
|
|
79
|
+
{ key: 'status', header: 'Status' },
|
|
80
|
+
],
|
|
81
|
+
textFn: (d) => {
|
|
82
|
+
const statusColor = d.status === 'OVERDUE' ? chalk.red : d.status === 'Done' ? chalk.green : chalk.yellow;
|
|
83
|
+
return `${chalk.cyan(String(d.title))} — due ${chalk.dim(String(d.dueAt))} ${statusColor(String(d.status))}`;
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
handleError(out, err, 'Due dates failed');
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
// calendar set-due
|
|
92
|
+
addGlobalFlags(calendar.command('set-due')
|
|
93
|
+
.description('Set due date on a document')
|
|
94
|
+
.argument('<vaultId>', 'Vault ID')
|
|
95
|
+
.argument('<path>', 'Document path')
|
|
96
|
+
.requiredOption('--date <date>', 'Due date (YYYY-MM-DD or "clear")')
|
|
97
|
+
.option('--priority <priority>', 'Priority (low/medium/high)')
|
|
98
|
+
.option('--recurrence <recurrence>', 'Recurrence (daily/weekly/monthly/yearly)'))
|
|
99
|
+
.action(async (vaultId, path, _opts) => {
|
|
100
|
+
const flags = resolveFlags(_opts);
|
|
101
|
+
const out = createOutput(flags);
|
|
102
|
+
out.startSpinner('Setting due date...');
|
|
103
|
+
try {
|
|
104
|
+
const client = await getClientAsync();
|
|
105
|
+
const dateStr = _opts.date;
|
|
106
|
+
await client.calendar.setDocumentDue(vaultId, path, {
|
|
107
|
+
dueAt: dateStr === 'clear' ? null : new Date(dateStr).toISOString(),
|
|
108
|
+
priority: _opts.priority || null,
|
|
109
|
+
recurrence: _opts.recurrence || null,
|
|
110
|
+
});
|
|
111
|
+
out.stopSpinner();
|
|
112
|
+
out.status(dateStr === 'clear'
|
|
113
|
+
? chalk.green(`Due date cleared for ${path}`)
|
|
114
|
+
: chalk.green(`Due date set to ${dateStr} for ${path}`));
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
handleError(out, err, 'Set due date failed');
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// calendar events
|
|
121
|
+
addGlobalFlags(calendar.command('events')
|
|
122
|
+
.description('List calendar events')
|
|
123
|
+
.argument('<vaultId>', 'Vault ID')
|
|
124
|
+
.option('--start <date>', 'Start date')
|
|
125
|
+
.option('--end <date>', 'End date'))
|
|
126
|
+
.action(async (vaultId, _opts) => {
|
|
127
|
+
const flags = resolveFlags(_opts);
|
|
128
|
+
const out = createOutput(flags);
|
|
129
|
+
out.startSpinner('Loading events...');
|
|
130
|
+
try {
|
|
131
|
+
const client = await getClientAsync();
|
|
132
|
+
const events = await client.calendar.listEvents(vaultId, {
|
|
133
|
+
start: _opts.start,
|
|
134
|
+
end: _opts.end,
|
|
135
|
+
});
|
|
136
|
+
out.stopSpinner();
|
|
137
|
+
out.list(events.map(e => ({
|
|
138
|
+
title: e.title,
|
|
139
|
+
startDate: e.startDate,
|
|
140
|
+
priority: e.priority || '-',
|
|
141
|
+
completed: e.completed ? '✓' : '-',
|
|
142
|
+
})), {
|
|
143
|
+
emptyMessage: 'No calendar events.',
|
|
144
|
+
columns: [
|
|
145
|
+
{ key: 'title', header: 'Title' },
|
|
146
|
+
{ key: 'startDate', header: 'Date' },
|
|
147
|
+
{ key: 'priority', header: 'Priority' },
|
|
148
|
+
{ key: 'completed', header: 'Done' },
|
|
149
|
+
],
|
|
150
|
+
textFn: (e) => `${chalk.cyan(String(e.title))} — ${chalk.dim(String(e.startDate))}`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
handleError(out, err, 'Calendar events failed');
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function getDefaultStart() {
|
|
159
|
+
const d = new Date();
|
|
160
|
+
d.setDate(1);
|
|
161
|
+
return d.toISOString().split('T')[0];
|
|
162
|
+
}
|
|
163
|
+
function getDefaultEnd() {
|
|
164
|
+
const d = new Date();
|
|
165
|
+
d.setMonth(d.getMonth() + 1, 0);
|
|
166
|
+
return d.toISOString().split('T')[0];
|
|
167
|
+
}
|
|
@@ -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 registerConnectorCommands(program) {
|
|
@@ -12,7 +12,7 @@ export function registerConnectorCommands(program) {
|
|
|
12
12
|
const out = createOutput(flags);
|
|
13
13
|
out.startSpinner('Fetching connectors...');
|
|
14
14
|
try {
|
|
15
|
-
const client =
|
|
15
|
+
const client = await getClientAsync();
|
|
16
16
|
const connectorList = await client.connectors.list(_opts.vault);
|
|
17
17
|
out.stopSpinner();
|
|
18
18
|
out.list(connectorList.map(c => ({
|
|
@@ -48,7 +48,7 @@ export function registerConnectorCommands(program) {
|
|
|
48
48
|
const out = createOutput(flags);
|
|
49
49
|
out.startSpinner('Fetching connector...');
|
|
50
50
|
try {
|
|
51
|
-
const client =
|
|
51
|
+
const client = await getClientAsync();
|
|
52
52
|
const c = await client.connectors.get(connectorId);
|
|
53
53
|
out.stopSpinner();
|
|
54
54
|
out.record({
|
|
@@ -81,7 +81,7 @@ export function registerConnectorCommands(program) {
|
|
|
81
81
|
const out = createOutput(flags);
|
|
82
82
|
out.startSpinner('Creating connector...');
|
|
83
83
|
try {
|
|
84
|
-
const client =
|
|
84
|
+
const client = await getClientAsync();
|
|
85
85
|
const connector = await client.connectors.create({
|
|
86
86
|
provider: provider,
|
|
87
87
|
name,
|
|
@@ -109,7 +109,7 @@ export function registerConnectorCommands(program) {
|
|
|
109
109
|
const out = createOutput(flags);
|
|
110
110
|
out.startSpinner('Updating connector...');
|
|
111
111
|
try {
|
|
112
|
-
const client =
|
|
112
|
+
const client = await getClientAsync();
|
|
113
113
|
const params = {};
|
|
114
114
|
if (_opts.name)
|
|
115
115
|
params.name = _opts.name;
|
|
@@ -133,7 +133,7 @@ export function registerConnectorCommands(program) {
|
|
|
133
133
|
const out = createOutput(flags);
|
|
134
134
|
out.startSpinner('Deleting connector...');
|
|
135
135
|
try {
|
|
136
|
-
const client =
|
|
136
|
+
const client = await getClientAsync();
|
|
137
137
|
await client.connectors.delete(connectorId);
|
|
138
138
|
out.success('Connector deleted.', { id: connectorId, deleted: true });
|
|
139
139
|
}
|
|
@@ -149,7 +149,7 @@ export function registerConnectorCommands(program) {
|
|
|
149
149
|
const out = createOutput(flags);
|
|
150
150
|
out.startSpinner('Testing connection...');
|
|
151
151
|
try {
|
|
152
|
-
const client =
|
|
152
|
+
const client = await getClientAsync();
|
|
153
153
|
const result = await client.connectors.test(connectorId);
|
|
154
154
|
if (result.success) {
|
|
155
155
|
out.success('Connection test passed.', { success: true });
|
|
@@ -174,7 +174,7 @@ export function registerConnectorCommands(program) {
|
|
|
174
174
|
const out = createOutput(flags);
|
|
175
175
|
out.startSpinner('Triggering sync...');
|
|
176
176
|
try {
|
|
177
|
-
const client =
|
|
177
|
+
const client = await getClientAsync();
|
|
178
178
|
const result = await client.connectors.sync(connectorId);
|
|
179
179
|
out.success(result.message, { message: result.message });
|
|
180
180
|
}
|
|
@@ -190,7 +190,7 @@ export function registerConnectorCommands(program) {
|
|
|
190
190
|
const out = createOutput(flags);
|
|
191
191
|
out.startSpinner('Fetching sync logs...');
|
|
192
192
|
try {
|
|
193
|
-
const client =
|
|
193
|
+
const client = await getClientAsync();
|
|
194
194
|
const logs = await client.connectors.logs(connectorId);
|
|
195
195
|
out.stopSpinner();
|
|
196
196
|
out.list(logs.map(log => ({
|
package/dist/commands/docs.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
|
import { createCredentialManager } from '../lib/credential-manager.js';
|
|
@@ -18,7 +18,7 @@ EXAMPLES
|
|
|
18
18
|
const out = createOutput(flags);
|
|
19
19
|
out.startSpinner('Fetching documents...');
|
|
20
20
|
try {
|
|
21
|
-
const client =
|
|
21
|
+
const client = await getClientAsync();
|
|
22
22
|
const documents = await client.documents.list(vaultId, _opts.dir);
|
|
23
23
|
out.stopSpinner();
|
|
24
24
|
out.list(documents.map(doc => ({
|
|
@@ -62,7 +62,7 @@ EXAMPLES
|
|
|
62
62
|
const flags = resolveFlags(_opts);
|
|
63
63
|
const out = createOutput(flags);
|
|
64
64
|
try {
|
|
65
|
-
const client =
|
|
65
|
+
const client = await getClientAsync();
|
|
66
66
|
const result = await client.documents.get(vaultId, docPath);
|
|
67
67
|
// Auto-decrypt if the document is encrypted
|
|
68
68
|
if (result.document.encrypted && !_opts.meta) {
|
|
@@ -119,7 +119,7 @@ EXAMPLES
|
|
|
119
119
|
process.stdin.on('end', () => resolve(data));
|
|
120
120
|
});
|
|
121
121
|
out.startSpinner('Uploading document...');
|
|
122
|
-
const client =
|
|
122
|
+
const client = await getClientAsync();
|
|
123
123
|
// Check if vault is encrypted and auto-encrypt
|
|
124
124
|
const vault = await client.vaults.get(vaultId);
|
|
125
125
|
let doc;
|
|
@@ -157,7 +157,7 @@ EXAMPLES
|
|
|
157
157
|
const out = createOutput(flags);
|
|
158
158
|
out.startSpinner('Deleting document...');
|
|
159
159
|
try {
|
|
160
|
-
const client =
|
|
160
|
+
const client = await getClientAsync();
|
|
161
161
|
await client.documents.delete(vaultId, docPath);
|
|
162
162
|
out.success(`Deleted: ${chalk.cyan(docPath)}`, { path: docPath, deleted: true });
|
|
163
163
|
}
|
|
@@ -180,7 +180,7 @@ EXAMPLES
|
|
|
180
180
|
const out = createOutput(flags);
|
|
181
181
|
out.startSpinner('Moving document...');
|
|
182
182
|
try {
|
|
183
|
-
const client =
|
|
183
|
+
const client = await getClientAsync();
|
|
184
184
|
const result = await client.documents.move(vaultId, source, dest, _opts.overwrite);
|
|
185
185
|
out.success(`Moved: ${chalk.cyan(result.source)} -> ${chalk.cyan(result.destination)}`, {
|
|
186
186
|
source: result.source,
|