@lifestreamdynamics/vault-cli 1.3.3 → 1.3.5

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.
@@ -24,7 +24,17 @@ export function registerSyncCommands(program) {
24
24
  .option('--on-conflict <strategy>', 'Conflict strategy: newer, local, remote, ask (default: newer)')
25
25
  .option('--ignore <patterns...>', 'Glob patterns to ignore')
26
26
  .option('--interval <interval>', 'Auto-sync interval (e.g., 5m, 1h)')
27
- .option('--auto-sync', 'Enable auto-sync'))
27
+ .option('--auto-sync', 'Enable auto-sync')
28
+ .addHelpText('after', `
29
+ Examples:
30
+ lsvault sync init <vaultId> ~/my-vault
31
+ lsvault sync init <vaultId> ~/mirror --mode pull --on-conflict remote
32
+ lsvault sync init <vaultId> ~/docs --mode push --on-conflict local --auto-sync
33
+
34
+ Sync modes:
35
+ pull Download remote changes only (ideal for cron/automation)
36
+ push Upload local changes only (ideal for CI pipelines)
37
+ sync Bidirectional with conflict detection (default)`))
28
38
  .action(async (vaultId, localPath, _opts) => {
29
39
  const flags = resolveFlags(_opts);
30
40
  const out = createOutput(flags);
@@ -158,11 +168,19 @@ export function registerSyncCommands(program) {
158
168
  out.debug(`Found ${Object.keys(remoteFiles).length} remote files`);
159
169
  out.startSpinner('Computing diff...');
160
170
  const diff = computePullDiff(localFiles, remoteFiles, lastState);
171
+ const unchanged = Object.keys(remoteFiles).length - diff.downloads.length;
161
172
  const totalOps = diff.downloads.length + diff.deletes.length;
162
173
  if (totalOps === 0) {
163
174
  out.succeedSpinner('Everything is up to date');
164
175
  if (flags.output === 'json') {
165
- out.record({ status: 'up-to-date', changes: 0 });
176
+ out.record({
177
+ status: 'up-to-date',
178
+ downloaded: 0,
179
+ deleted: 0,
180
+ unchanged: Object.keys(remoteFiles).length,
181
+ bytesTransferred: 0,
182
+ errors: 0,
183
+ });
166
184
  }
167
185
  return;
168
186
  }
@@ -175,6 +193,7 @@ export function registerSyncCommands(program) {
175
193
  dryRun: true,
176
194
  downloads: diff.downloads.length,
177
195
  deletes: diff.deletes.length,
196
+ unchanged,
178
197
  totalBytes: diff.totalBytes,
179
198
  });
180
199
  }
@@ -201,6 +220,7 @@ export function registerSyncCommands(program) {
201
220
  out.success('', {
202
221
  downloaded: result.filesDownloaded,
203
222
  deleted: result.filesDeleted,
223
+ unchanged,
204
224
  bytesTransferred: result.bytesTransferred,
205
225
  errors: result.errors.length,
206
226
  });
@@ -234,11 +254,19 @@ export function registerSyncCommands(program) {
234
254
  out.debug(`Found ${Object.keys(remoteFiles).length} remote files`);
235
255
  out.startSpinner('Computing diff...');
236
256
  const diff = computePushDiff(localFiles, remoteFiles, lastState);
257
+ const unchanged = Object.keys(localFiles).length - diff.uploads.length;
237
258
  const totalOps = diff.uploads.length + diff.deletes.length;
238
259
  if (totalOps === 0) {
239
260
  out.succeedSpinner('Everything is up to date');
240
261
  if (flags.output === 'json') {
241
- out.record({ status: 'up-to-date', changes: 0 });
262
+ out.record({
263
+ status: 'up-to-date',
264
+ uploaded: 0,
265
+ deleted: 0,
266
+ unchanged: Object.keys(localFiles).length,
267
+ bytesTransferred: 0,
268
+ errors: 0,
269
+ });
242
270
  }
243
271
  return;
244
272
  }
@@ -251,6 +279,7 @@ export function registerSyncCommands(program) {
251
279
  dryRun: true,
252
280
  uploads: diff.uploads.length,
253
281
  deletes: diff.deletes.length,
282
+ unchanged,
254
283
  totalBytes: diff.totalBytes,
255
284
  });
256
285
  }
@@ -277,6 +306,7 @@ export function registerSyncCommands(program) {
277
306
  out.success('', {
278
307
  uploaded: result.filesUploaded,
279
308
  deleted: result.filesDeleted,
309
+ unchanged,
280
310
  bytesTransferred: result.bytesTransferred,
281
311
  errors: result.errors.length,
282
312
  });
@@ -69,8 +69,14 @@ export function createCredentialManager(options = {}) {
69
69
  async saveCredentials(config) {
70
70
  // Prefer keychain if available
71
71
  if (await keychain.isAvailable()) {
72
- await keychain.saveCredentials(config);
73
- return;
72
+ try {
73
+ await keychain.saveCredentials(config);
74
+ return;
75
+ }
76
+ catch {
77
+ // Keychain reported available but failed to save — fall through
78
+ // to encrypted config silently.
79
+ }
74
80
  }
75
81
  // Fall back to encrypted config
76
82
  encryptedConfig.saveCredentials(config, passphrase);
@@ -9,7 +9,19 @@ const ACCOUNT_REFRESH_TOKEN = 'refresh-token';
9
9
  */
10
10
  async function loadKeytar() {
11
11
  try {
12
- return await import('keytar');
12
+ const mod = await import('keytar');
13
+ // CJS-in-ESM interop: keytar is a native CJS addon, so dynamic import
14
+ // wraps it as { default: { getPassword, setPassword, ... } }.
15
+ const resolved = (mod.default && typeof mod.default.getPassword === 'function')
16
+ ? mod.default
17
+ : mod;
18
+ // Validate that the resolved module has the methods we need
19
+ if (typeof resolved.getPassword !== 'function' ||
20
+ typeof resolved.setPassword !== 'function' ||
21
+ typeof resolved.deletePassword !== 'function') {
22
+ return null;
23
+ }
24
+ return resolved;
13
25
  }
14
26
  catch {
15
27
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifestreamdynamics/vault-cli",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "Command-line interface for Lifestream Vault",
5
5
  "engines": {
6
6
  "node": ">=20"