@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.
- package/README.md +1 -1
- package/changelog.json +16 -0
- package/dist/heart/daemon/agent-config-check.js +14 -1
- package/dist/heart/daemon/agentic-repair.js +2 -0
- package/dist/heart/daemon/cli-exec.js +38 -13
- package/dist/heart/daemon/connect-bay.js +118 -251
- package/dist/heart/daemon/human-command-screens.js +112 -34
- package/dist/heart/daemon/interactive-repair.js +96 -9
- package/dist/heart/daemon/terminal-ui.js +200 -0
- package/dist/heart/provider-credentials.js +20 -4
- package/dist/repertoire/bitwarden-store.js +66 -25
- package/package.json +1 -1
|
@@ -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
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
692
|
-
|
|
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");
|