@ouro.bot/cli 0.1.0-alpha.436 → 0.1.0-alpha.437
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/changelog.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.437",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Provider-credential refresh now reads the known `providers/<provider>` vault items directly instead of starting with a full `bw list items` scan, so startup, repair, and connect-bay readiness stop paying whole-vault latency just to inspect the handful of provider records Ouro already names exactly.",
|
|
8
|
+
"Structured Ouro vault items now treat an exact-item `not found` from Bitwarden as a real miss instead of falling back into a fuzzy filtered search, which removes the slow retrying scan path for absent `providers/*` and `runtime/*` records without changing the safety fallback for malformed direct responses.",
|
|
9
|
+
"This closes the remaining real-world `connect` slowdown found after the live-ping work: local dogfood dropped the branch connect-bay readiness run from 127 seconds in the shipped path to 18 seconds after the direct provider-read fix, with new coverage locking the direct-read contract in place."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"version": "0.1.0-alpha.436",
|
|
6
14
|
"changes": [
|
|
@@ -96,6 +96,11 @@ function isPresentCredentialValue(value) {
|
|
|
96
96
|
return value.trim().length > 0;
|
|
97
97
|
return Number.isFinite(value) && value !== 0;
|
|
98
98
|
}
|
|
99
|
+
function isMissingProviderCredentialError(message, itemName) {
|
|
100
|
+
const normalized = message.toLowerCase();
|
|
101
|
+
return normalized.includes(itemName.toLowerCase())
|
|
102
|
+
&& (normalized.includes("no credential found") || normalized.includes("missing") || normalized.includes("not found"));
|
|
103
|
+
}
|
|
99
104
|
function copyKnownFields(source, fields) {
|
|
100
105
|
const result = {};
|
|
101
106
|
for (const field of fields) {
|
|
@@ -225,17 +230,21 @@ async function refreshProviderCredentialPool(agentName, options = {}) {
|
|
|
225
230
|
try {
|
|
226
231
|
const store = (0, credential_access_1.getCredentialStore)(agentName);
|
|
227
232
|
options.onProgress?.(`reading vault items for ${agentName}...`);
|
|
228
|
-
const items = await store.list();
|
|
229
233
|
const providers = {};
|
|
230
234
|
let updatedAt = new Date(0).toISOString();
|
|
231
|
-
for (const
|
|
232
|
-
|
|
233
|
-
continue;
|
|
234
|
-
const provider = item.domain.slice(VAULT_ITEM_PREFIX.length);
|
|
235
|
-
if (!isAgentProvider(provider))
|
|
236
|
-
continue;
|
|
235
|
+
for (const provider of VALID_PROVIDERS) {
|
|
236
|
+
const itemName = providerCredentialItemName(provider);
|
|
237
237
|
options.onProgress?.(`reading ${provider} credentials...`);
|
|
238
|
-
|
|
238
|
+
let raw;
|
|
239
|
+
try {
|
|
240
|
+
raw = await store.getRawSecret(itemName, "password");
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
244
|
+
if (isMissingProviderCredentialError(message, itemName))
|
|
245
|
+
continue;
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
239
248
|
const payload = validateProviderCredentialPayload(JSON.parse(raw), provider);
|
|
240
249
|
const record = recordFromPayload(payload);
|
|
241
250
|
providers[provider] = record;
|
|
@@ -134,11 +134,13 @@ 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
|
+
}
|
|
137
141
|
function isBwDirectLookupFallbackError(err) {
|
|
138
142
|
const message = err.message.toLowerCase();
|
|
139
|
-
return (message.includes("
|
|
140
|
-
message.includes("bw cli error: item not found") ||
|
|
141
|
-
message.includes("invalid json from bw get item") ||
|
|
143
|
+
return (message.includes("invalid json from bw get item") ||
|
|
142
144
|
message.includes("invalid item from bw get item"));
|
|
143
145
|
}
|
|
144
146
|
// ---------------------------------------------------------------------------
|
|
@@ -246,14 +248,14 @@ async function withBwLock(appDataDir, fn) {
|
|
|
246
248
|
}
|
|
247
249
|
}
|
|
248
250
|
}
|
|
249
|
-
function execBw(args, sessionToken, appDataDir, stdin) {
|
|
251
|
+
function execBw(args, sessionToken, appDataDir, stdin, bwBinaryPath = "bw") {
|
|
250
252
|
const env = {
|
|
251
253
|
...process.env,
|
|
252
254
|
...(sessionToken ? { BW_SESSION: sessionToken } : {}),
|
|
253
255
|
...(appDataDir ? { BITWARDENCLI_APPDATA_DIR: appDataDir } : {}),
|
|
254
256
|
};
|
|
255
257
|
const runCommand = () => new Promise((resolve, reject) => {
|
|
256
|
-
const child = (0, node_child_process_1.execFile)(
|
|
258
|
+
const child = (0, node_child_process_1.execFile)(bwBinaryPath, args, { timeout: 30_000, env }, (err, stdout, stderr) => {
|
|
257
259
|
if (err) {
|
|
258
260
|
if (isBwNotInstalled(err)) {
|
|
259
261
|
reject(new Error("bw CLI not found. Install from https://bitwarden.com/help/cli/"));
|
|
@@ -274,7 +276,7 @@ function execBw(args, sessionToken, appDataDir, stdin) {
|
|
|
274
276
|
function isBwNotInstalled(err) {
|
|
275
277
|
const msg = err.message.toLowerCase();
|
|
276
278
|
const code = err.code;
|
|
277
|
-
return code === "ENOENT" ||
|
|
279
|
+
return code === "ENOENT" || /\bspawn\b.*\benoent\b/.test(msg) || msg.includes("command not found");
|
|
278
280
|
}
|
|
279
281
|
/** Check if the error is transient (network/timeout) and worth retrying. */
|
|
280
282
|
function isTransientError(err) {
|
|
@@ -385,6 +387,7 @@ class BitwardenCredentialStore {
|
|
|
385
387
|
masterPassword;
|
|
386
388
|
appDataDir;
|
|
387
389
|
sessionToken = null;
|
|
390
|
+
bwBinaryPath = "bw";
|
|
388
391
|
constructor(serverUrl, email, masterPassword, options = {}) {
|
|
389
392
|
this.serverUrl = serverUrl;
|
|
390
393
|
this.email = email;
|
|
@@ -394,6 +397,9 @@ class BitwardenCredentialStore {
|
|
|
394
397
|
isReady() {
|
|
395
398
|
return true;
|
|
396
399
|
}
|
|
400
|
+
execBw(args, sessionToken, stdin) {
|
|
401
|
+
return execBw(args, sessionToken, this.appDataDir, stdin, this.bwBinaryPath);
|
|
402
|
+
}
|
|
397
403
|
/**
|
|
398
404
|
* Ensure the bw CLI is authenticated and unlocked.
|
|
399
405
|
* Handles three states: logged out → login, locked → unlock, already unlocked → no-op.
|
|
@@ -401,7 +407,7 @@ class BitwardenCredentialStore {
|
|
|
401
407
|
*/
|
|
402
408
|
async login() {
|
|
403
409
|
// Ensure bw CLI is installed before any bw commands
|
|
404
|
-
await (0, bw_installer_1.ensureBwCli)();
|
|
410
|
+
this.bwBinaryPath = await (0, bw_installer_1.ensureBwCli)();
|
|
405
411
|
if (this.appDataDir) {
|
|
406
412
|
fs.mkdirSync(this.appDataDir, { recursive: true, mode: 0o700 });
|
|
407
413
|
}
|
|
@@ -438,7 +444,7 @@ class BitwardenCredentialStore {
|
|
|
438
444
|
// Check current status
|
|
439
445
|
let status = {};
|
|
440
446
|
try {
|
|
441
|
-
const raw = await execBw(["status"]
|
|
447
|
+
const raw = await this.execBw(["status"]);
|
|
442
448
|
status = JSON.parse(raw);
|
|
443
449
|
}
|
|
444
450
|
catch (err) {
|
|
@@ -451,7 +457,7 @@ class BitwardenCredentialStore {
|
|
|
451
457
|
// Configure server URL if needed (only works when logged out)
|
|
452
458
|
if (status.status === "unauthenticated" || !status.serverUrl) {
|
|
453
459
|
try {
|
|
454
|
-
await execBw(["config", "server", this.serverUrl]
|
|
460
|
+
await this.execBw(["config", "server", this.serverUrl]);
|
|
455
461
|
}
|
|
456
462
|
catch (error) {
|
|
457
463
|
const err = error;
|
|
@@ -463,12 +469,12 @@ class BitwardenCredentialStore {
|
|
|
463
469
|
}
|
|
464
470
|
if (status.status === "locked") {
|
|
465
471
|
// Already logged in, just needs unlock
|
|
466
|
-
const unlockOutput = await execBw(["unlock", this.masterPassword, "--raw"]
|
|
472
|
+
const unlockOutput = await this.execBw(["unlock", this.masterPassword, "--raw"]);
|
|
467
473
|
this.sessionToken = unlockOutput.trim();
|
|
468
474
|
}
|
|
469
475
|
else if (status.status === "unauthenticated" || !status.status) {
|
|
470
476
|
// Not logged in — full login
|
|
471
|
-
const loginOutput = await execBw(["login", this.email, this.masterPassword, "--raw"]
|
|
477
|
+
const loginOutput = await this.execBw(["login", this.email, this.masterPassword, "--raw"]);
|
|
472
478
|
try {
|
|
473
479
|
const parsed = JSON.parse(loginOutput);
|
|
474
480
|
this.sessionToken = parsed.access_token ?? loginOutput.trim();
|
|
@@ -479,12 +485,12 @@ class BitwardenCredentialStore {
|
|
|
479
485
|
}
|
|
480
486
|
else {
|
|
481
487
|
// Status is "unlocked" — already good, just need the session token
|
|
482
|
-
const unlockOutput = await execBw(["unlock", this.masterPassword, "--raw"]
|
|
488
|
+
const unlockOutput = await this.execBw(["unlock", this.masterPassword, "--raw"]);
|
|
483
489
|
this.sessionToken = unlockOutput.trim();
|
|
484
490
|
}
|
|
485
491
|
// Sync vault data after obtaining a fresh session token
|
|
486
492
|
/* v8 ignore next -- defensive: loginAttempt always sets sessionToken before sync @preserve */
|
|
487
|
-
await execBw(["sync"], this.sessionToken ?? undefined
|
|
493
|
+
await this.execBw(["sync"], this.sessionToken ?? undefined);
|
|
488
494
|
}
|
|
489
495
|
async ensureSession() {
|
|
490
496
|
if (!this.sessionToken) {
|
|
@@ -610,12 +616,12 @@ class BitwardenCredentialStore {
|
|
|
610
616
|
const encoded = Buffer.from(JSON.stringify(item)).toString("base64");
|
|
611
617
|
let savedItem;
|
|
612
618
|
if (existing) {
|
|
613
|
-
const stdout = await execBw(["edit", "item", existing.id], session,
|
|
619
|
+
const stdout = await this.execBw(["edit", "item", existing.id], session, encoded);
|
|
614
620
|
const savedItemId = parseBwItemId(stdout) ?? existing.id;
|
|
615
621
|
savedItem = await this.findItemById(savedItemId, session);
|
|
616
622
|
}
|
|
617
623
|
else {
|
|
618
|
-
const stdout = await execBw(["create", "item"], session,
|
|
624
|
+
const stdout = await this.execBw(["create", "item"], session, encoded);
|
|
619
625
|
const savedItemId = parseBwItemId(stdout);
|
|
620
626
|
savedItem = savedItemId
|
|
621
627
|
? await this.findItemById(savedItemId, session)
|
|
@@ -637,7 +643,7 @@ class BitwardenCredentialStore {
|
|
|
637
643
|
message: "listing bw credentials",
|
|
638
644
|
meta: { backend: "bitwarden" },
|
|
639
645
|
});
|
|
640
|
-
const stdout = await this.withTransientRetry(() => this.withSessionRetry((session) => execBw(["list", "items"], session
|
|
646
|
+
const stdout = await this.withTransientRetry(() => this.withSessionRetry((session) => this.execBw(["list", "items"], session)));
|
|
641
647
|
const items = parseBwItems(stdout, "bw list items");
|
|
642
648
|
const results = items.map((item) => ({
|
|
643
649
|
domain: item.name,
|
|
@@ -670,7 +676,7 @@ class BitwardenCredentialStore {
|
|
|
670
676
|
});
|
|
671
677
|
return false;
|
|
672
678
|
}
|
|
673
|
-
await this.withSessionRetry((session) => execBw(["delete", "item", item.id], session
|
|
679
|
+
await this.withSessionRetry((session) => this.execBw(["delete", "item", item.id], session));
|
|
674
680
|
(0, runtime_1.emitNervesEvent)({
|
|
675
681
|
event: "repertoire.bw_credential_delete_end",
|
|
676
682
|
component: "repertoire",
|
|
@@ -683,24 +689,26 @@ class BitwardenCredentialStore {
|
|
|
683
689
|
async findItemByDomain(domain, session) {
|
|
684
690
|
if (shouldPreferExactItemLookup(domain)) {
|
|
685
691
|
try {
|
|
686
|
-
const stdout = await execBw(["get", "item", domain], session
|
|
692
|
+
const stdout = await this.execBw(["get", "item", domain], session);
|
|
687
693
|
const item = parseBwItem(stdout, "bw get item");
|
|
688
694
|
if (item.name === domain)
|
|
689
695
|
return item;
|
|
690
696
|
}
|
|
691
697
|
catch (error) {
|
|
692
698
|
const err = error;
|
|
699
|
+
if (isBwDirectLookupMissingError(err))
|
|
700
|
+
return null;
|
|
693
701
|
if (!isBwDirectLookupFallbackError(err))
|
|
694
702
|
throw err;
|
|
695
703
|
}
|
|
696
704
|
}
|
|
697
|
-
const stdout = await execBw(["list", "items", "--search", domain], session
|
|
705
|
+
const stdout = await this.execBw(["list", "items", "--search", domain], session);
|
|
698
706
|
const items = parseBwItems(stdout, "bw list items --search");
|
|
699
707
|
// Find exact match by name
|
|
700
708
|
return items.find((item) => item.name === domain) ?? null;
|
|
701
709
|
}
|
|
702
710
|
async findItemById(id, session) {
|
|
703
|
-
const stdout = await execBw(["get", "item", id], session
|
|
711
|
+
const stdout = await this.execBw(["get", "item", id], session);
|
|
704
712
|
return parseBwItem(stdout, "bw get item");
|
|
705
713
|
}
|
|
706
714
|
assertStoredCredentialMatches(domain, data, item) {
|
|
@@ -5,12 +5,50 @@
|
|
|
5
5
|
* Mirrors the whisper-cpp pattern in senses/bluebubbles/media.ts:
|
|
6
6
|
* check PATH first, install via npm if missing, emit nerves event.
|
|
7
7
|
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
8
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.findExecutableOnPath = findExecutableOnPath;
|
|
43
|
+
exports.findExecutableViaNpmPrefix = findExecutableViaNpmPrefix;
|
|
9
44
|
exports.ensureBwCli = ensureBwCli;
|
|
10
45
|
const node_child_process_1 = require("node:child_process");
|
|
46
|
+
const fs = __importStar(require("node:fs"));
|
|
47
|
+
const path = __importStar(require("node:path"));
|
|
11
48
|
const runtime_1 = require("../nerves/runtime");
|
|
12
49
|
const INSTALL_TIMEOUT_MS = 120_000;
|
|
13
50
|
const WHICH_TIMEOUT_MS = 5_000;
|
|
51
|
+
const DEFAULT_WINDOWS_PATHEXT = ".EXE;.CMD;.BAT;.COM";
|
|
14
52
|
function execFileAsync(cmd, args, timeout) {
|
|
15
53
|
return new Promise((resolve, reject) => {
|
|
16
54
|
(0, node_child_process_1.execFile)(cmd, args, { timeout }, (err, stdout) => {
|
|
@@ -22,20 +60,88 @@ function execFileAsync(cmd, args, timeout) {
|
|
|
22
60
|
});
|
|
23
61
|
});
|
|
24
62
|
}
|
|
63
|
+
function stripWrappingQuotes(value) {
|
|
64
|
+
const trimmed = value.trim();
|
|
65
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
|
|
66
|
+
return trimmed.slice(1, -1);
|
|
67
|
+
}
|
|
68
|
+
return trimmed;
|
|
69
|
+
}
|
|
70
|
+
function isExecutableFile(targetPath, platform) {
|
|
71
|
+
try {
|
|
72
|
+
fs.accessSync(targetPath, platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function executableNames(command, platform, pathExt) {
|
|
80
|
+
if (platform !== "win32")
|
|
81
|
+
return [command];
|
|
82
|
+
if (path.extname(command))
|
|
83
|
+
return [command];
|
|
84
|
+
const extensions = pathExt
|
|
85
|
+
.split(";")
|
|
86
|
+
.map((entry) => entry.trim())
|
|
87
|
+
.filter((entry) => entry.length > 0);
|
|
88
|
+
return extensions.length === 0
|
|
89
|
+
? [command]
|
|
90
|
+
: extensions.map((extension) => (extension.startsWith(".") ? `${command}${extension}` : `${command}.${extension}`));
|
|
91
|
+
}
|
|
92
|
+
function findExecutableInDirectory(command, directory, platform, pathExt) {
|
|
93
|
+
const cleanDirectory = stripWrappingQuotes(directory);
|
|
94
|
+
if (!cleanDirectory)
|
|
95
|
+
return null;
|
|
96
|
+
for (const candidateName of executableNames(command, platform, pathExt)) {
|
|
97
|
+
const candidatePath = path.isAbsolute(candidateName)
|
|
98
|
+
? candidateName
|
|
99
|
+
: path.join(cleanDirectory, candidateName);
|
|
100
|
+
if (isExecutableFile(candidatePath, platform)) {
|
|
101
|
+
return candidatePath;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
function findExecutableOnPath(command, envPath = process.env.PATH ?? "", platform = process.platform, pathExt = process.env.PATHEXT ?? DEFAULT_WINDOWS_PATHEXT) {
|
|
107
|
+
if (path.isAbsolute(command) || command.includes(path.sep)) {
|
|
108
|
+
return isExecutableFile(command, platform) ? command : null;
|
|
109
|
+
}
|
|
110
|
+
for (const directory of envPath.split(path.delimiter)) {
|
|
111
|
+
const found = findExecutableInDirectory(command, directory, platform, pathExt);
|
|
112
|
+
if (found)
|
|
113
|
+
return found;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
async function findExecutableViaNpmPrefix(command, platform = process.platform, pathExt = process.env.PATHEXT ?? DEFAULT_WINDOWS_PATHEXT) {
|
|
118
|
+
try {
|
|
119
|
+
const prefix = stripWrappingQuotes((await execFileAsync("npm", ["prefix", "-g"], WHICH_TIMEOUT_MS)).trim());
|
|
120
|
+
if (!prefix)
|
|
121
|
+
return null;
|
|
122
|
+
const searchDirs = platform === "win32"
|
|
123
|
+
? [prefix, path.join(prefix, "bin")]
|
|
124
|
+
: [path.join(prefix, "bin"), prefix];
|
|
125
|
+
for (const directory of searchDirs) {
|
|
126
|
+
const found = findExecutableInDirectory(command, directory, platform, pathExt);
|
|
127
|
+
if (found)
|
|
128
|
+
return found;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Prefix lookup is only a post-install fallback.
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
25
136
|
/**
|
|
26
137
|
* Ensure the `bw` CLI is available, installing it via npm if needed.
|
|
27
138
|
* Returns the path to the `bw` binary.
|
|
28
139
|
*/
|
|
29
140
|
async function ensureBwCli() {
|
|
30
141
|
// 1. Check if bw is already in PATH
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return existing;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// Not found — fall through to install
|
|
142
|
+
const existing = findExecutableOnPath("bw");
|
|
143
|
+
if (existing) {
|
|
144
|
+
return existing;
|
|
39
145
|
}
|
|
40
146
|
// 2. Install via npm
|
|
41
147
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -60,20 +166,15 @@ async function ensureBwCli() {
|
|
|
60
166
|
throw new Error(`failed to install bw CLI via npm: ${reason}`);
|
|
61
167
|
}
|
|
62
168
|
// 3. Verify installation and return path
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return installed;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
// Fall through to error
|
|
169
|
+
const installed = findExecutableOnPath("bw") ?? await findExecutableViaNpmPrefix("bw");
|
|
170
|
+
if (installed) {
|
|
171
|
+
(0, runtime_1.emitNervesEvent)({
|
|
172
|
+
event: "repertoire.bw_cli_install_end",
|
|
173
|
+
component: "repertoire",
|
|
174
|
+
message: "bw CLI installed successfully",
|
|
175
|
+
meta: { path: installed },
|
|
176
|
+
});
|
|
177
|
+
return installed;
|
|
77
178
|
}
|
|
78
|
-
throw new Error("bw CLI installed via npm but binary not found in PATH");
|
|
179
|
+
throw new Error("bw CLI installed via npm but binary not found in PATH or npm global bin");
|
|
79
180
|
}
|