@ouro.bot/cli 0.1.0-alpha.445 → 0.1.0-alpha.447

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.
@@ -134,15 +134,6 @@ function isBwConfigLogoutRequired(err) {
134
134
  function shouldPreferExactItemLookup(domain) {
135
135
  return domain.includes("/");
136
136
  }
137
- function isBwDirectLookupMissingError(err) {
138
- const message = err.message.toLowerCase();
139
- return message.includes("bw cli error: not found") || message.includes("bw cli error: item not found");
140
- }
141
- function isBwDirectLookupFallbackError(err) {
142
- const message = err.message.toLowerCase();
143
- return (message.includes("invalid json from bw get item") ||
144
- message.includes("invalid item from bw get item"));
145
- }
146
137
  // ---------------------------------------------------------------------------
147
138
  // Cross-process bw CLI lock
148
139
  // ---------------------------------------------------------------------------
@@ -159,6 +150,9 @@ function isBwDirectLookupFallbackError(err) {
159
150
  const BW_LOCK_FILENAME = ".ouro-bw.lock";
160
151
  const BW_LOCK_TIMEOUT_MS = 30_000;
161
152
  const BW_LOCK_POLL_MS = 100;
153
+ const BW_DATA_FILENAME = "data.json";
154
+ const BW_SYNC_MARKER_FILENAME = ".ouro-last-sync";
155
+ const BW_SYNC_FRESH_MS = 60_000;
162
156
  /** In-process async mutex keyed by appDataDir. */
163
157
  const inProcessLocks = new Map();
164
158
  function isPidAlive(pid) {
@@ -388,6 +382,7 @@ class BitwardenCredentialStore {
388
382
  appDataDir;
389
383
  sessionToken = null;
390
384
  bwBinaryPath = "bw";
385
+ structuredItemCache = null;
391
386
  constructor(serverUrl, email, masterPassword, options = {}) {
392
387
  this.serverUrl = serverUrl;
393
388
  this.email = email;
@@ -488,9 +483,19 @@ class BitwardenCredentialStore {
488
483
  const unlockOutput = await this.execBw(["unlock", this.masterPassword, "--raw"]);
489
484
  this.sessionToken = unlockOutput.trim();
490
485
  }
491
- // Sync vault data after obtaining a fresh session token
492
- /* v8 ignore next -- defensive: loginAttempt always sets sessionToken before sync @preserve */
493
- await this.execBw(["sync"], this.sessionToken ?? undefined);
486
+ if (this.shouldSyncVaultAfterSession(status)) {
487
+ /* v8 ignore next -- defensive: loginAttempt always sets sessionToken before sync @preserve */
488
+ await this.execBw(["sync"], this.sessionToken ?? undefined);
489
+ this.writeSyncMarker();
490
+ }
491
+ else {
492
+ (0, runtime_1.emitNervesEvent)({
493
+ event: "repertoire.bw_sync_skipped",
494
+ component: "repertoire",
495
+ message: "skipping bw sync because local vault cache is still fresh",
496
+ meta: { email: this.email, serverUrl: this.serverUrl, freshnessWindowMs: BW_SYNC_FRESH_MS },
497
+ });
498
+ }
494
499
  }
495
500
  async ensureSession() {
496
501
  if (!this.sessionToken) {
@@ -512,6 +517,7 @@ class BitwardenCredentialStore {
512
517
  throw err;
513
518
  }
514
519
  this.sessionToken = null;
520
+ this.structuredItemCache = null;
515
521
  attemptedFreshSession = true;
516
522
  }
517
523
  }
@@ -614,6 +620,7 @@ class BitwardenCredentialStore {
614
620
  notes: data.notes ?? null,
615
621
  };
616
622
  const encoded = Buffer.from(JSON.stringify(item)).toString("base64");
623
+ this.structuredItemCache = null;
617
624
  let savedItem;
618
625
  if (existing) {
619
626
  const stdout = await this.execBw(["edit", "item", existing.id], session, encoded);
@@ -629,6 +636,7 @@ class BitwardenCredentialStore {
629
636
  }
630
637
  this.assertStoredCredentialMatches(domain, data, savedItem);
631
638
  });
639
+ this.structuredItemCache = null;
632
640
  (0, runtime_1.emitNervesEvent)({
633
641
  event: "repertoire.bw_credential_store_end",
634
642
  component: "repertoire",
@@ -677,6 +685,7 @@ class BitwardenCredentialStore {
677
685
  return false;
678
686
  }
679
687
  await this.withSessionRetry((session) => this.execBw(["delete", "item", item.id], session));
688
+ this.structuredItemCache = null;
680
689
  (0, runtime_1.emitNervesEvent)({
681
690
  event: "repertoire.bw_credential_delete_end",
682
691
  component: "repertoire",
@@ -688,25 +697,57 @@ class BitwardenCredentialStore {
688
697
  // --- Private ---
689
698
  async findItemByDomain(domain, session) {
690
699
  if (shouldPreferExactItemLookup(domain)) {
691
- try {
692
- const stdout = await this.execBw(["get", "item", domain], session);
693
- const item = parseBwItem(stdout, "bw get item");
694
- if (item.name === domain)
695
- return item;
696
- }
697
- catch (error) {
698
- const err = error;
699
- if (isBwDirectLookupMissingError(err))
700
- return null;
701
- if (!isBwDirectLookupFallbackError(err))
702
- throw err;
703
- }
700
+ const items = await this.readStructuredItemCache(session);
701
+ return items.get(domain) ?? null;
704
702
  }
705
703
  const stdout = await this.execBw(["list", "items", "--search", domain], session);
706
704
  const items = parseBwItems(stdout, "bw list items --search");
707
705
  // Find exact match by name
708
706
  return items.find((item) => item.name === domain) ?? null;
709
707
  }
708
+ shouldSyncVaultAfterSession(status) {
709
+ if (status.status === "unauthenticated" || !status.status)
710
+ return true;
711
+ if (!this.appDataDir)
712
+ return true;
713
+ const freshnessTimestamp = this.latestLocalSyncTimestamp();
714
+ if (freshnessTimestamp !== null) {
715
+ return Date.now() - freshnessTimestamp > BW_SYNC_FRESH_MS;
716
+ }
717
+ return true;
718
+ }
719
+ latestLocalSyncTimestamp() {
720
+ const files = [BW_SYNC_MARKER_FILENAME, BW_DATA_FILENAME];
721
+ let latest = null;
722
+ for (const name of files) {
723
+ try {
724
+ const mtimeMs = fs.statSync(path.join(this.appDataDir, name)).mtimeMs;
725
+ latest = latest === null ? mtimeMs : Math.max(latest, mtimeMs);
726
+ }
727
+ catch {
728
+ // Missing freshness file is fine; use any other available timestamp.
729
+ }
730
+ }
731
+ return latest;
732
+ }
733
+ writeSyncMarker() {
734
+ if (!this.appDataDir)
735
+ return;
736
+ try {
737
+ fs.writeFileSync(path.join(this.appDataDir, BW_SYNC_MARKER_FILENAME), `${Date.now()}\n`, { mode: 0o600 });
738
+ }
739
+ catch {
740
+ // If the marker cannot be written, fall back to syncing next time.
741
+ }
742
+ }
743
+ async readStructuredItemCache(session) {
744
+ if (this.structuredItemCache)
745
+ return this.structuredItemCache;
746
+ const stdout = await this.execBw(["list", "items"], session);
747
+ const items = parseBwItems(stdout, "bw list items");
748
+ this.structuredItemCache = new Map(items.map((item) => [item.name, item]));
749
+ return this.structuredItemCache;
750
+ }
710
751
  async findItemById(id, session) {
711
752
  const stdout = await this.execBw(["get", "item", id], session);
712
753
  return parseBwItem(stdout, "bw get item");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.445",
3
+ "version": "0.1.0-alpha.447",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",