@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/sync.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import {
|
|
4
|
+
import { getClientAsync } from '../client.js';
|
|
5
5
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
6
6
|
import { createOutput, handleError } from '../utils/output.js';
|
|
7
7
|
import { formatUptime } from '../utils/format.js';
|
|
@@ -30,7 +30,7 @@ export function registerSyncCommands(program) {
|
|
|
30
30
|
const out = createOutput(flags);
|
|
31
31
|
out.startSpinner('Initializing sync...');
|
|
32
32
|
try {
|
|
33
|
-
const client =
|
|
33
|
+
const client = await getClientAsync();
|
|
34
34
|
const vault = await client.vaults.get(vaultId);
|
|
35
35
|
const absPath = path.resolve(localPath);
|
|
36
36
|
const mode = _opts.mode ?? 'sync';
|
|
@@ -144,7 +144,7 @@ export function registerSyncCommands(program) {
|
|
|
144
144
|
process.exitCode = 1;
|
|
145
145
|
return;
|
|
146
146
|
}
|
|
147
|
-
const client =
|
|
147
|
+
const client = await getClientAsync();
|
|
148
148
|
const ignorePatterns = resolveIgnorePatterns(config.ignore, config.localPath);
|
|
149
149
|
const lastState = loadSyncState(config.id);
|
|
150
150
|
out.startSpinner('Scanning local files...');
|
|
@@ -220,7 +220,7 @@ export function registerSyncCommands(program) {
|
|
|
220
220
|
process.exitCode = 1;
|
|
221
221
|
return;
|
|
222
222
|
}
|
|
223
|
-
const client =
|
|
223
|
+
const client = await getClientAsync();
|
|
224
224
|
const ignorePatterns = resolveIgnorePatterns(config.ignore, config.localPath);
|
|
225
225
|
const lastState = loadSyncState(config.id);
|
|
226
226
|
out.startSpinner('Scanning local files...');
|
|
@@ -296,7 +296,7 @@ export function registerSyncCommands(program) {
|
|
|
296
296
|
process.exitCode = 1;
|
|
297
297
|
return;
|
|
298
298
|
}
|
|
299
|
-
const client =
|
|
299
|
+
const client = await getClientAsync();
|
|
300
300
|
const ignorePatterns = resolveIgnorePatterns(config.ignore, config.localPath);
|
|
301
301
|
const lastState = loadSyncState(config.id);
|
|
302
302
|
out.startSpinner('Scanning...');
|
|
@@ -373,7 +373,7 @@ export function registerSyncCommands(program) {
|
|
|
373
373
|
process.exitCode = 1;
|
|
374
374
|
return;
|
|
375
375
|
}
|
|
376
|
-
const client =
|
|
376
|
+
const client = await getClientAsync();
|
|
377
377
|
const ignorePatterns = resolveIgnorePatterns(config.ignore, config.localPath);
|
|
378
378
|
const pollInterval = parseInt(String(_opts.pollInterval ?? '30000'), 10);
|
|
379
379
|
out.status(`Watching sync ${chalk.cyan(syncId.slice(0, 8))}...`);
|
|
@@ -447,7 +447,7 @@ export function registerSyncCommands(program) {
|
|
|
447
447
|
process.exitCode = 1;
|
|
448
448
|
return;
|
|
449
449
|
}
|
|
450
|
-
const client =
|
|
450
|
+
const client = await getClientAsync();
|
|
451
451
|
const localFile = path.join(config.localPath, docPath);
|
|
452
452
|
const state = loadSyncState(config.id);
|
|
453
453
|
if (useVersion === 'local') {
|
|
@@ -501,8 +501,11 @@ export function registerSyncCommands(program) {
|
|
|
501
501
|
const out = createOutput(flags);
|
|
502
502
|
try {
|
|
503
503
|
const logFile = _opts.logFile;
|
|
504
|
-
const pid = startDaemon(logFile);
|
|
504
|
+
const { pid, lingerWarning } = startDaemon(logFile);
|
|
505
505
|
out.success('Daemon started', { pid, status: 'running' });
|
|
506
|
+
if (lingerWarning) {
|
|
507
|
+
out.warn(`Warning: ${lingerWarning}`);
|
|
508
|
+
}
|
|
506
509
|
}
|
|
507
510
|
catch (err) {
|
|
508
511
|
handleError(out, err, 'Failed to start daemon');
|
package/dist/commands/teams.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 registerTeamCommands(program) {
|
|
@@ -12,7 +12,7 @@ export function registerTeamCommands(program) {
|
|
|
12
12
|
const out = createOutput(flags);
|
|
13
13
|
out.startSpinner('Fetching teams...');
|
|
14
14
|
try {
|
|
15
|
-
const client =
|
|
15
|
+
const client = await getClientAsync();
|
|
16
16
|
const teamList = await client.teams.list();
|
|
17
17
|
out.stopSpinner();
|
|
18
18
|
out.list(teamList.map(t => ({ name: t.name, id: t.id, description: t.description || 'No description' })), {
|
|
@@ -37,7 +37,7 @@ export function registerTeamCommands(program) {
|
|
|
37
37
|
const out = createOutput(flags);
|
|
38
38
|
out.startSpinner('Fetching team...');
|
|
39
39
|
try {
|
|
40
|
-
const client =
|
|
40
|
+
const client = await getClientAsync();
|
|
41
41
|
const team = await client.teams.get(teamId);
|
|
42
42
|
out.stopSpinner();
|
|
43
43
|
out.record({
|
|
@@ -66,7 +66,7 @@ EXAMPLES
|
|
|
66
66
|
const out = createOutput(flags);
|
|
67
67
|
out.startSpinner('Creating team...');
|
|
68
68
|
try {
|
|
69
|
-
const client =
|
|
69
|
+
const client = await getClientAsync();
|
|
70
70
|
const team = await client.teams.create({ name, description: _opts.description });
|
|
71
71
|
out.success(`Team created: ${chalk.cyan(team.name)} (${team.id})`, { id: team.id, name: team.name });
|
|
72
72
|
}
|
|
@@ -84,7 +84,7 @@ EXAMPLES
|
|
|
84
84
|
const out = createOutput(flags);
|
|
85
85
|
out.startSpinner('Updating team...');
|
|
86
86
|
try {
|
|
87
|
-
const client =
|
|
87
|
+
const client = await getClientAsync();
|
|
88
88
|
const team = await client.teams.update(teamId, {
|
|
89
89
|
name: _opts.name,
|
|
90
90
|
description: _opts.description,
|
|
@@ -103,7 +103,7 @@ EXAMPLES
|
|
|
103
103
|
const out = createOutput(flags);
|
|
104
104
|
out.startSpinner('Deleting team...');
|
|
105
105
|
try {
|
|
106
|
-
const client =
|
|
106
|
+
const client = await getClientAsync();
|
|
107
107
|
await client.teams.delete(teamId);
|
|
108
108
|
out.success('Team deleted.', { id: teamId, deleted: true });
|
|
109
109
|
}
|
|
@@ -121,7 +121,7 @@ EXAMPLES
|
|
|
121
121
|
const out = createOutput(flags);
|
|
122
122
|
out.startSpinner('Fetching members...');
|
|
123
123
|
try {
|
|
124
|
-
const client =
|
|
124
|
+
const client = await getClientAsync();
|
|
125
125
|
const memberList = await client.teams.listMembers(teamId);
|
|
126
126
|
out.stopSpinner();
|
|
127
127
|
out.list(memberList.map(m => ({
|
|
@@ -154,7 +154,7 @@ EXAMPLES
|
|
|
154
154
|
const role = String(_opts.role);
|
|
155
155
|
out.startSpinner('Updating member role...');
|
|
156
156
|
try {
|
|
157
|
-
const client =
|
|
157
|
+
const client = await getClientAsync();
|
|
158
158
|
const member = await client.teams.updateMemberRole(teamId, userId, role);
|
|
159
159
|
out.success(`Role updated to ${chalk.magenta(member.role)} for ${member.user.email}`, {
|
|
160
160
|
userId,
|
|
@@ -175,7 +175,7 @@ EXAMPLES
|
|
|
175
175
|
const out = createOutput(flags);
|
|
176
176
|
out.startSpinner('Removing member...');
|
|
177
177
|
try {
|
|
178
|
-
const client =
|
|
178
|
+
const client = await getClientAsync();
|
|
179
179
|
await client.teams.removeMember(teamId, userId);
|
|
180
180
|
out.success('Member removed.', { teamId, userId, removed: true });
|
|
181
181
|
}
|
|
@@ -191,7 +191,7 @@ EXAMPLES
|
|
|
191
191
|
const out = createOutput(flags);
|
|
192
192
|
out.startSpinner('Leaving team...');
|
|
193
193
|
try {
|
|
194
|
-
const client =
|
|
194
|
+
const client = await getClientAsync();
|
|
195
195
|
await client.teams.leave(teamId);
|
|
196
196
|
out.success('Left the team.', { teamId, left: true });
|
|
197
197
|
}
|
|
@@ -209,7 +209,7 @@ EXAMPLES
|
|
|
209
209
|
const out = createOutput(flags);
|
|
210
210
|
out.startSpinner('Fetching invitations...');
|
|
211
211
|
try {
|
|
212
|
-
const client =
|
|
212
|
+
const client = await getClientAsync();
|
|
213
213
|
const invitationList = await client.teams.listInvitations(teamId);
|
|
214
214
|
out.stopSpinner();
|
|
215
215
|
out.list(invitationList.map(inv => ({
|
|
@@ -242,7 +242,7 @@ EXAMPLES
|
|
|
242
242
|
const role = String(_opts.role);
|
|
243
243
|
out.startSpinner('Sending invitation...');
|
|
244
244
|
try {
|
|
245
|
-
const client =
|
|
245
|
+
const client = await getClientAsync();
|
|
246
246
|
const invitation = await client.teams.inviteMember(teamId, email, role);
|
|
247
247
|
out.success(`Invited ${chalk.cyan(invitation.email)} as ${chalk.magenta(invitation.role)}`, {
|
|
248
248
|
id: invitation.id,
|
|
@@ -263,7 +263,7 @@ EXAMPLES
|
|
|
263
263
|
const out = createOutput(flags);
|
|
264
264
|
out.startSpinner('Revoking invitation...');
|
|
265
265
|
try {
|
|
266
|
-
const client =
|
|
266
|
+
const client = await getClientAsync();
|
|
267
267
|
await client.teams.revokeInvitation(teamId, invitationId);
|
|
268
268
|
out.success('Invitation revoked.', { id: invitationId, revoked: true });
|
|
269
269
|
}
|
|
@@ -281,7 +281,7 @@ EXAMPLES
|
|
|
281
281
|
const out = createOutput(flags);
|
|
282
282
|
out.startSpinner('Fetching team vaults...');
|
|
283
283
|
try {
|
|
284
|
-
const client =
|
|
284
|
+
const client = await getClientAsync();
|
|
285
285
|
const vaultList = await client.teams.listVaults(teamId);
|
|
286
286
|
out.stopSpinner();
|
|
287
287
|
out.list(vaultList.map(v => ({ name: String(v.name), slug: String(v.slug), description: String(v.description) || 'No description' })), {
|
|
@@ -308,7 +308,7 @@ EXAMPLES
|
|
|
308
308
|
const out = createOutput(flags);
|
|
309
309
|
out.startSpinner('Creating team vault...');
|
|
310
310
|
try {
|
|
311
|
-
const client =
|
|
311
|
+
const client = await getClientAsync();
|
|
312
312
|
const vault = await client.teams.createVault(teamId, { name, description: _opts.description });
|
|
313
313
|
out.success(`Team vault created: ${chalk.cyan(String(vault.name))}`, {
|
|
314
314
|
name: String(vault.name),
|
package/dist/commands/user.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 } from '../utils/format.js';
|
|
@@ -12,7 +12,7 @@ export function registerUserCommands(program) {
|
|
|
12
12
|
const out = createOutput(flags);
|
|
13
13
|
out.startSpinner('Fetching storage usage...');
|
|
14
14
|
try {
|
|
15
|
-
const client =
|
|
15
|
+
const client = await getClientAsync();
|
|
16
16
|
const storage = await client.user.getStorage();
|
|
17
17
|
out.stopSpinner();
|
|
18
18
|
if (flags.output === 'json') {
|
package/dist/commands/vaults.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 { generateVaultKey } from '@lifestreamdynamics/vault-sdk';
|
|
@@ -13,7 +13,7 @@ export function registerVaultCommands(program) {
|
|
|
13
13
|
const out = createOutput(flags);
|
|
14
14
|
out.startSpinner('Fetching vaults...');
|
|
15
15
|
try {
|
|
16
|
-
const client =
|
|
16
|
+
const client = await getClientAsync();
|
|
17
17
|
const vaultList = await client.vaults.list();
|
|
18
18
|
out.stopSpinner();
|
|
19
19
|
out.list(vaultList.map(v => ({ name: v.name, slug: v.slug, encrypted: v.encryptionEnabled ? 'yes' : 'no', description: v.description || 'No description', id: v.id })), {
|
|
@@ -43,7 +43,7 @@ export function registerVaultCommands(program) {
|
|
|
43
43
|
const out = createOutput(flags);
|
|
44
44
|
out.startSpinner('Fetching vault...');
|
|
45
45
|
try {
|
|
46
|
-
const client =
|
|
46
|
+
const client = await getClientAsync();
|
|
47
47
|
const vault = await client.vaults.get(vaultId);
|
|
48
48
|
out.stopSpinner();
|
|
49
49
|
out.record({
|
|
@@ -75,7 +75,7 @@ EXAMPLES
|
|
|
75
75
|
const out = createOutput(flags);
|
|
76
76
|
out.startSpinner('Creating vault...');
|
|
77
77
|
try {
|
|
78
|
-
const client =
|
|
78
|
+
const client = await getClientAsync();
|
|
79
79
|
const isEncrypted = _opts.encrypted === true;
|
|
80
80
|
const vault = await client.vaults.create({
|
|
81
81
|
name,
|
|
@@ -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 registerVersionCommands(program) {
|
|
@@ -16,7 +16,7 @@ EXAMPLES
|
|
|
16
16
|
const out = createOutput(flags);
|
|
17
17
|
out.startSpinner('Fetching versions...');
|
|
18
18
|
try {
|
|
19
|
-
const client =
|
|
19
|
+
const client = await getClientAsync();
|
|
20
20
|
const versionList = await client.documents.listVersions(vaultId, docPath);
|
|
21
21
|
out.stopSpinner();
|
|
22
22
|
out.list(versionList.map(v => ({
|
|
@@ -65,7 +65,7 @@ EXAMPLES
|
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
try {
|
|
68
|
-
const client =
|
|
68
|
+
const client = await getClientAsync();
|
|
69
69
|
const version = await client.documents.getVersion(vaultId, docPath, versionNum);
|
|
70
70
|
if (version.content === null) {
|
|
71
71
|
out.error('Version content is no longer available (expired or pruned)');
|
|
@@ -99,7 +99,7 @@ EXAMPLES
|
|
|
99
99
|
}
|
|
100
100
|
out.startSpinner('Computing diff...');
|
|
101
101
|
try {
|
|
102
|
-
const client =
|
|
102
|
+
const client = await getClientAsync();
|
|
103
103
|
const diff = await client.documents.diffVersions(vaultId, docPath, from, to);
|
|
104
104
|
out.stopSpinner();
|
|
105
105
|
if (flags.output === 'json') {
|
|
@@ -143,7 +143,7 @@ EXAMPLES
|
|
|
143
143
|
}
|
|
144
144
|
out.startSpinner(`Restoring to version ${versionNum}...`);
|
|
145
145
|
try {
|
|
146
|
-
const client =
|
|
146
|
+
const client = await getClientAsync();
|
|
147
147
|
const doc = await client.documents.restoreVersion(vaultId, docPath, versionNum);
|
|
148
148
|
out.success(`Restored ${chalk.cyan(docPath)} to version ${versionNum}`, {
|
|
149
149
|
path: doc.path,
|
|
@@ -173,7 +173,7 @@ EXAMPLES
|
|
|
173
173
|
}
|
|
174
174
|
out.startSpinner(`Pinning version ${versionNum}...`);
|
|
175
175
|
try {
|
|
176
|
-
const client =
|
|
176
|
+
const client = await getClientAsync();
|
|
177
177
|
await client.documents.pinVersion(vaultId, docPath, versionNum);
|
|
178
178
|
out.success(`Pinned version ${versionNum} of ${chalk.cyan(docPath)}`, {
|
|
179
179
|
path: docPath,
|
|
@@ -204,7 +204,7 @@ EXAMPLES
|
|
|
204
204
|
}
|
|
205
205
|
out.startSpinner(`Unpinning version ${versionNum}...`);
|
|
206
206
|
try {
|
|
207
|
-
const client =
|
|
207
|
+
const client = await getClientAsync();
|
|
208
208
|
await client.documents.unpinVersion(vaultId, docPath, versionNum);
|
|
209
209
|
out.success(`Unpinned version ${versionNum} of ${chalk.cyan(docPath)}`, {
|
|
210
210
|
path: docPath,
|
|
@@ -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 registerWebhookCommands(program) {
|
|
@@ -12,7 +12,7 @@ export function registerWebhookCommands(program) {
|
|
|
12
12
|
const out = createOutput(flags);
|
|
13
13
|
out.startSpinner('Fetching webhooks...');
|
|
14
14
|
try {
|
|
15
|
-
const client =
|
|
15
|
+
const client = await getClientAsync();
|
|
16
16
|
const webhookList = await client.webhooks.list(vaultId);
|
|
17
17
|
out.stopSpinner();
|
|
18
18
|
out.list(webhookList.map(wh => ({
|
|
@@ -50,7 +50,7 @@ export function registerWebhookCommands(program) {
|
|
|
50
50
|
const out = createOutput(flags);
|
|
51
51
|
out.startSpinner('Creating webhook...');
|
|
52
52
|
try {
|
|
53
|
-
const client =
|
|
53
|
+
const client = await getClientAsync();
|
|
54
54
|
const params = {
|
|
55
55
|
url,
|
|
56
56
|
events: String(_opts.events || 'create,update,delete').split(',').map((e) => e.trim()),
|
|
@@ -95,7 +95,7 @@ export function registerWebhookCommands(program) {
|
|
|
95
95
|
}
|
|
96
96
|
out.startSpinner('Updating webhook...');
|
|
97
97
|
try {
|
|
98
|
-
const client =
|
|
98
|
+
const client = await getClientAsync();
|
|
99
99
|
const params = {};
|
|
100
100
|
if (_opts.url)
|
|
101
101
|
params.url = String(_opts.url);
|
|
@@ -125,7 +125,7 @@ export function registerWebhookCommands(program) {
|
|
|
125
125
|
const out = createOutput(flags);
|
|
126
126
|
out.startSpinner('Deleting webhook...');
|
|
127
127
|
try {
|
|
128
|
-
const client =
|
|
128
|
+
const client = await getClientAsync();
|
|
129
129
|
await client.webhooks.delete(vaultId, webhookId);
|
|
130
130
|
out.success('Webhook deleted successfully', { id: webhookId, deleted: true });
|
|
131
131
|
}
|
|
@@ -142,7 +142,7 @@ export function registerWebhookCommands(program) {
|
|
|
142
142
|
const out = createOutput(flags);
|
|
143
143
|
out.startSpinner('Fetching deliveries...');
|
|
144
144
|
try {
|
|
145
|
-
const client =
|
|
145
|
+
const client = await getClientAsync();
|
|
146
146
|
const deliveries = await client.webhooks.listDeliveries(vaultId, webhookId);
|
|
147
147
|
out.stopSpinner();
|
|
148
148
|
out.list(deliveries.map(d => ({
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import os from 'node:os';
|
|
|
4
4
|
import { createCredentialManager } from './lib/credential-manager.js';
|
|
5
5
|
const CONFIG_DIR = path.join(os.homedir(), '.lsvault');
|
|
6
6
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
export const DEFAULT_API_URL = 'https://vault.lifestreamdynamics.com';
|
|
7
8
|
// Singleton credential manager
|
|
8
9
|
let _credentialManager;
|
|
9
10
|
export function getCredentialManager() {
|
|
@@ -25,7 +26,7 @@ export function setCredentialManager(cm) {
|
|
|
25
26
|
*/
|
|
26
27
|
export function loadConfig() {
|
|
27
28
|
const config = {
|
|
28
|
-
apiUrl: process.env.LSVAULT_API_URL ||
|
|
29
|
+
apiUrl: process.env.LSVAULT_API_URL || DEFAULT_API_URL,
|
|
29
30
|
};
|
|
30
31
|
if (process.env.LSVAULT_API_KEY) {
|
|
31
32
|
config.apiKey = process.env.LSVAULT_API_KEY;
|
|
@@ -48,7 +49,7 @@ export async function loadConfigAsync() {
|
|
|
48
49
|
const cm = getCredentialManager();
|
|
49
50
|
const secureCreds = await cm.getCredentials();
|
|
50
51
|
const config = {
|
|
51
|
-
apiUrl: secureCreds.apiUrl || process.env.LSVAULT_API_URL ||
|
|
52
|
+
apiUrl: secureCreds.apiUrl || process.env.LSVAULT_API_URL || DEFAULT_API_URL,
|
|
52
53
|
};
|
|
53
54
|
// Load JWT tokens if available
|
|
54
55
|
if (secureCreds.accessToken) {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { registerAuthCommands } from './commands/auth.js';
|
|
4
|
+
import { registerMfaCommands } from './commands/mfa.js';
|
|
4
5
|
import { registerVaultCommands } from './commands/vaults.js';
|
|
5
6
|
import { registerDocCommands } from './commands/docs.js';
|
|
6
7
|
import { registerSearchCommands } from './commands/search.js';
|
|
@@ -18,6 +19,8 @@ import { registerWebhookCommands } from './commands/webhooks.js';
|
|
|
18
19
|
import { registerConfigCommands } from './commands/config.js';
|
|
19
20
|
import { registerSyncCommands } from './commands/sync.js';
|
|
20
21
|
import { registerVersionCommands } from './commands/versions.js';
|
|
22
|
+
import { registerLinkCommands } from './commands/links.js';
|
|
23
|
+
import { registerCalendarCommands } from './commands/calendar.js';
|
|
21
24
|
const program = new Command();
|
|
22
25
|
program
|
|
23
26
|
.name('lsvault')
|
|
@@ -43,6 +46,7 @@ LEARN MORE
|
|
|
43
46
|
lsvault <command> --help Show help for a command
|
|
44
47
|
lsvault <command> <subcommand> --help Show help for a subcommand`);
|
|
45
48
|
registerAuthCommands(program);
|
|
49
|
+
registerMfaCommands(program);
|
|
46
50
|
registerVaultCommands(program);
|
|
47
51
|
registerDocCommands(program);
|
|
48
52
|
registerSearchCommands(program);
|
|
@@ -60,4 +64,6 @@ registerWebhookCommands(program);
|
|
|
60
64
|
registerConfigCommands(program);
|
|
61
65
|
registerSyncCommands(program);
|
|
62
66
|
registerVersionCommands(program);
|
|
67
|
+
registerLinkCommands(program);
|
|
68
|
+
registerCalendarCommands(program);
|
|
63
69
|
program.parse();
|
package/dist/lib/keychain.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const SERVICE_NAME = 'lifestream-vault-cli';
|
|
2
2
|
const ACCOUNT_API_KEY = 'api-key';
|
|
3
3
|
const ACCOUNT_API_URL = 'api-url';
|
|
4
|
+
const ACCOUNT_ACCESS_TOKEN = 'access-token';
|
|
5
|
+
const ACCOUNT_REFRESH_TOKEN = 'refresh-token';
|
|
4
6
|
/**
|
|
5
7
|
* Dynamically loads keytar. Returns null if unavailable (not installed or
|
|
6
8
|
* native module fails to load, e.g. missing libsecret on Linux).
|
|
@@ -48,6 +50,12 @@ export function createKeychainBackend() {
|
|
|
48
50
|
const apiUrl = await kt.getPassword(SERVICE_NAME, ACCOUNT_API_URL);
|
|
49
51
|
if (apiUrl)
|
|
50
52
|
result.apiUrl = apiUrl;
|
|
53
|
+
const accessToken = await kt.getPassword(SERVICE_NAME, ACCOUNT_ACCESS_TOKEN);
|
|
54
|
+
if (accessToken)
|
|
55
|
+
result.accessToken = accessToken;
|
|
56
|
+
const refreshToken = await kt.getPassword(SERVICE_NAME, ACCOUNT_REFRESH_TOKEN);
|
|
57
|
+
if (refreshToken)
|
|
58
|
+
result.refreshToken = refreshToken;
|
|
51
59
|
}
|
|
52
60
|
catch {
|
|
53
61
|
// Keychain access failed silently
|
|
@@ -64,6 +72,12 @@ export function createKeychainBackend() {
|
|
|
64
72
|
if (config.apiUrl) {
|
|
65
73
|
await kt.setPassword(SERVICE_NAME, ACCOUNT_API_URL, config.apiUrl);
|
|
66
74
|
}
|
|
75
|
+
if (config.accessToken) {
|
|
76
|
+
await kt.setPassword(SERVICE_NAME, ACCOUNT_ACCESS_TOKEN, config.accessToken);
|
|
77
|
+
}
|
|
78
|
+
if (config.refreshToken) {
|
|
79
|
+
await kt.setPassword(SERVICE_NAME, ACCOUNT_REFRESH_TOKEN, config.refreshToken);
|
|
80
|
+
}
|
|
67
81
|
},
|
|
68
82
|
async clearCredentials() {
|
|
69
83
|
const kt = await getKeytar();
|
|
@@ -77,6 +91,14 @@ export function createKeychainBackend() {
|
|
|
77
91
|
await kt.deletePassword(SERVICE_NAME, ACCOUNT_API_URL);
|
|
78
92
|
}
|
|
79
93
|
catch { /* ignore */ }
|
|
94
|
+
try {
|
|
95
|
+
await kt.deletePassword(SERVICE_NAME, ACCOUNT_ACCESS_TOKEN);
|
|
96
|
+
}
|
|
97
|
+
catch { /* ignore */ }
|
|
98
|
+
try {
|
|
99
|
+
await kt.deletePassword(SERVICE_NAME, ACCOUNT_REFRESH_TOKEN);
|
|
100
|
+
}
|
|
101
|
+
catch { /* ignore */ }
|
|
80
102
|
},
|
|
81
103
|
};
|
|
82
104
|
}
|
|
@@ -10,6 +10,8 @@ import { createRemotePoller } from './remote-poller.js';
|
|
|
10
10
|
import { removePid } from './daemon.js';
|
|
11
11
|
import { loadConfig } from '../config.js';
|
|
12
12
|
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
|
|
13
|
+
import { scanLocalFiles, scanRemoteFiles, computePushDiff, computePullDiff, executePush, executePull } from './engine.js';
|
|
14
|
+
import { loadSyncState } from './state.js';
|
|
13
15
|
const managed = [];
|
|
14
16
|
function log(msg) {
|
|
15
17
|
const ts = new Date().toISOString();
|
|
@@ -35,6 +37,66 @@ async function start() {
|
|
|
35
37
|
}
|
|
36
38
|
log(`Found ${configs.length} auto-sync configuration(s)`);
|
|
37
39
|
const client = createClient();
|
|
40
|
+
// Startup reconciliation: catch changes made while daemon was stopped
|
|
41
|
+
for (const config of configs) {
|
|
42
|
+
try {
|
|
43
|
+
log(`Reconciling ${config.id.slice(0, 8)} (${config.mode} mode)...`);
|
|
44
|
+
const ignorePatterns = resolveIgnorePatterns(config.ignore, config.localPath);
|
|
45
|
+
const lastState = loadSyncState(config.id);
|
|
46
|
+
const localFiles = scanLocalFiles(config.localPath, ignorePatterns);
|
|
47
|
+
const remoteFiles = await scanRemoteFiles(client, config.vaultId, ignorePatterns);
|
|
48
|
+
let pushed = 0;
|
|
49
|
+
let pulled = 0;
|
|
50
|
+
let deleted = 0;
|
|
51
|
+
if (config.mode === 'push' || config.mode === 'sync') {
|
|
52
|
+
const pushDiff = computePushDiff(localFiles, remoteFiles, lastState);
|
|
53
|
+
const pushOps = pushDiff.uploads.length + pushDiff.deletes.length;
|
|
54
|
+
if (pushOps > 0) {
|
|
55
|
+
const result = await executePush(client, config, pushDiff);
|
|
56
|
+
pushed = result.filesUploaded;
|
|
57
|
+
deleted += result.filesDeleted;
|
|
58
|
+
if (result.errors.length > 0) {
|
|
59
|
+
for (const err of result.errors) {
|
|
60
|
+
log(` Push error: ${err.path}: ${err.error}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (config.mode === 'pull' || config.mode === 'sync') {
|
|
66
|
+
const pullDiff = computePullDiff(localFiles, remoteFiles, lastState);
|
|
67
|
+
const pullOps = pullDiff.downloads.length + pullDiff.deletes.length;
|
|
68
|
+
if (pullOps > 0) {
|
|
69
|
+
const result = await executePull(client, config, pullDiff);
|
|
70
|
+
pulled = result.filesDownloaded;
|
|
71
|
+
deleted += result.filesDeleted;
|
|
72
|
+
if (result.errors.length > 0) {
|
|
73
|
+
for (const err of result.errors) {
|
|
74
|
+
log(` Pull error: ${err.path}: ${err.error}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const total = pushed + pulled + deleted;
|
|
80
|
+
if (total > 0) {
|
|
81
|
+
const parts = [];
|
|
82
|
+
if (pushed > 0)
|
|
83
|
+
parts.push(`${pushed} uploaded`);
|
|
84
|
+
if (pulled > 0)
|
|
85
|
+
parts.push(`${pulled} downloaded`);
|
|
86
|
+
if (deleted > 0)
|
|
87
|
+
parts.push(`${deleted} deleted`);
|
|
88
|
+
log(`Reconciled ${config.id.slice(0, 8)}: ${parts.join(', ')}`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
log(`Reconciled ${config.id.slice(0, 8)}: up to date`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
96
|
+
log(`Reconciliation failed for ${config.id.slice(0, 8)}: ${msg}`);
|
|
97
|
+
// Continue — still start the watcher even if reconciliation fails
|
|
98
|
+
}
|
|
99
|
+
}
|
|
38
100
|
for (const config of configs) {
|
|
39
101
|
try {
|
|
40
102
|
const ignorePatterns = resolveIgnorePatterns(config.ignore, config.localPath);
|
package/dist/sync/daemon.d.ts
CHANGED
|
@@ -36,9 +36,17 @@ export declare function rotateLogIfNeeded(logFile?: string): void;
|
|
|
36
36
|
* Start the daemon as a detached child process.
|
|
37
37
|
* Returns the PID of the spawned process.
|
|
38
38
|
*/
|
|
39
|
-
export declare function startDaemon(logFile?: string):
|
|
39
|
+
export declare function startDaemon(logFile?: string): {
|
|
40
|
+
pid: number;
|
|
41
|
+
lingerWarning?: string;
|
|
42
|
+
};
|
|
40
43
|
/**
|
|
41
44
|
* Stop the running daemon.
|
|
42
45
|
*/
|
|
43
46
|
export declare function stopDaemon(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Check if systemd linger is enabled for the current user (Linux only).
|
|
49
|
+
* When linger is disabled, user services stop when the SSH session ends.
|
|
50
|
+
*/
|
|
51
|
+
export declare function checkLingerStatus(): 'enabled' | 'disabled' | 'unknown';
|
|
44
52
|
export { DAEMON_DIR, PID_FILE, LOG_FILE };
|
package/dist/sync/daemon.js
CHANGED
|
@@ -150,7 +150,12 @@ export function startDaemon(logFile) {
|
|
|
150
150
|
writePid(child.pid);
|
|
151
151
|
child.unref();
|
|
152
152
|
fs.closeSync(logFd);
|
|
153
|
-
|
|
153
|
+
const result = { pid: child.pid };
|
|
154
|
+
const lingerStatus = checkLingerStatus();
|
|
155
|
+
if (lingerStatus === 'disabled') {
|
|
156
|
+
result.lingerWarning = 'systemd lingering is not enabled for your user. The daemon will stop when you log out. To fix:\n sudo loginctl enable-linger $(whoami)';
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
154
159
|
}
|
|
155
160
|
/**
|
|
156
161
|
* Stop the running daemon.
|
|
@@ -171,4 +176,20 @@ export function stopDaemon() {
|
|
|
171
176
|
return false;
|
|
172
177
|
}
|
|
173
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if systemd linger is enabled for the current user (Linux only).
|
|
181
|
+
* When linger is disabled, user services stop when the SSH session ends.
|
|
182
|
+
*/
|
|
183
|
+
export function checkLingerStatus() {
|
|
184
|
+
if (process.platform !== 'linux')
|
|
185
|
+
return 'unknown';
|
|
186
|
+
try {
|
|
187
|
+
const username = os.userInfo().username;
|
|
188
|
+
const lingerFile = `/var/lib/systemd/linger/${username}`;
|
|
189
|
+
return fs.existsSync(lingerFile) ? 'enabled' : 'disabled';
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return 'unknown';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
174
195
|
export { DAEMON_DIR, PID_FILE, LOG_FILE };
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifestreamdynamics/vault-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Command-line interface for Lifestream Vault",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=20"
|
|
7
|
+
},
|
|
5
8
|
"type": "module",
|
|
6
9
|
"bin": {
|
|
7
10
|
"lsvault": "./dist/index.js"
|
|
@@ -41,7 +44,7 @@
|
|
|
41
44
|
"prepublishOnly": "npm run build && npm test"
|
|
42
45
|
},
|
|
43
46
|
"dependencies": {
|
|
44
|
-
"@lifestreamdynamics/vault-sdk": "^1.
|
|
47
|
+
"@lifestreamdynamics/vault-sdk": "^1.1.0",
|
|
45
48
|
"chalk": "^5.4.0",
|
|
46
49
|
"chokidar": "^4.0.3",
|
|
47
50
|
"commander": "^13.0.0",
|
|
@@ -53,10 +56,10 @@
|
|
|
53
56
|
},
|
|
54
57
|
"devDependencies": {
|
|
55
58
|
"@types/node": "^22.0.0",
|
|
56
|
-
"@vitest/coverage-v8": "^
|
|
57
|
-
"@vitest/ui": "^
|
|
59
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
60
|
+
"@vitest/ui": "^4.0.18",
|
|
58
61
|
"tsx": "^4.19.0",
|
|
59
62
|
"typescript": "^5.7.0",
|
|
60
|
-
"vitest": "^
|
|
63
|
+
"vitest": "^4.0.18"
|
|
61
64
|
}
|
|
62
65
|
}
|