@ouro.bot/cli 0.1.0-alpha.238 → 0.1.0-alpha.239
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 +7 -0
- package/dist/heart/hatch/hatch-flow.js +1 -1
- package/dist/mind/friends/resolver.js +36 -0
- package/dist/repertoire/bitwarden-store.js +3 -0
- package/dist/repertoire/bw-installer.js +79 -0
- package/dist/repertoire/credential-access.js +38 -3
- package/dist/senses/cli.js +1 -2
- package/dist/senses/shared-turn.js +1 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
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.239",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix(friends): stable local CLI identity \u2014 dropped hostname from external ID (was `username@hostname`, now just `username`). macOS hostname instability (`Mac` vs `Aris-MacBook-Pro.local`) was creating duplicate friend records with separate sessions and trust levels.",
|
|
8
|
+
"feat(friends): migration fallback \u2014 FriendResolver now searches for old `username@*` format IDs when exact match fails, linking new stable ID to existing friend record."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
4
11
|
{
|
|
5
12
|
"version": "0.1.0-alpha.238",
|
|
6
13
|
"changes": [
|
|
@@ -91,7 +91,7 @@ function writeFriendImprint(bundleRoot, humanName, now) {
|
|
|
91
91
|
fs.mkdirSync(friendsDir, { recursive: true });
|
|
92
92
|
const nowIso = now.toISOString();
|
|
93
93
|
const id = `friend-${(0, config_1.slugify)(humanName) || "friend"}`;
|
|
94
|
-
const localExternalId =
|
|
94
|
+
const localExternalId = os.userInfo().username;
|
|
95
95
|
const record = {
|
|
96
96
|
id,
|
|
97
97
|
name: humanName,
|
|
@@ -31,6 +31,42 @@ class FriendResolver {
|
|
|
31
31
|
}
|
|
32
32
|
if (existing)
|
|
33
33
|
return existing;
|
|
34
|
+
// Migration: local provider previously used "${username}@${hostname}" format.
|
|
35
|
+
// If no exact match, try finding a friend with old-format external ID.
|
|
36
|
+
/* v8 ignore start -- one-time migration: hostname→username format, tested manually @preserve */
|
|
37
|
+
if (this.params.provider === "local" && !this.params.externalId.includes("@")) {
|
|
38
|
+
try {
|
|
39
|
+
const all = typeof this.store.listAll === "function" ? await this.store.listAll() : [];
|
|
40
|
+
const migrationMatch = all.find((f) => f.externalIds.some((eid) => eid.provider === "local" && eid.externalId.startsWith(this.params.externalId + "@")));
|
|
41
|
+
if (migrationMatch) {
|
|
42
|
+
// Link the new external ID format to the existing friend
|
|
43
|
+
const now = new Date().toISOString();
|
|
44
|
+
migrationMatch.externalIds.push({
|
|
45
|
+
provider: this.params.provider,
|
|
46
|
+
externalId: this.params.externalId,
|
|
47
|
+
linkedAt: now,
|
|
48
|
+
});
|
|
49
|
+
migrationMatch.updatedAt = now;
|
|
50
|
+
try {
|
|
51
|
+
await this.store.put(migrationMatch.id, migrationMatch);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// best-effort persist
|
|
55
|
+
}
|
|
56
|
+
(0, runtime_1.emitNervesEvent)({
|
|
57
|
+
component: "friends",
|
|
58
|
+
event: "friends.local_id_migrated",
|
|
59
|
+
message: `migrated local friend identity from hostname format to username-only`,
|
|
60
|
+
meta: { friendId: migrationMatch.id, newExternalId: this.params.externalId },
|
|
61
|
+
});
|
|
62
|
+
return migrationMatch;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// fall through to create new
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/* v8 ignore stop */
|
|
34
70
|
// First encounter -- create new FriendRecord
|
|
35
71
|
const now = new Date().toISOString();
|
|
36
72
|
const externalId = {
|
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.BitwardenCredentialStore = void 0;
|
|
13
13
|
const node_child_process_1 = require("node:child_process");
|
|
14
14
|
const runtime_1 = require("../nerves/runtime");
|
|
15
|
+
const bw_installer_1 = require("./bw-installer");
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// bw CLI wrapper
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -76,6 +77,8 @@ class BitwardenCredentialStore {
|
|
|
76
77
|
* Retries transient failures (network/timeout) up to MAX_RETRIES with exponential backoff.
|
|
77
78
|
*/
|
|
78
79
|
async login() {
|
|
80
|
+
// Ensure bw CLI is installed before any bw commands
|
|
81
|
+
await (0, bw_installer_1.ensureBwCli)();
|
|
79
82
|
let lastError;
|
|
80
83
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
81
84
|
try {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Lazy bw CLI installer — auto-installs the Bitwarden CLI when not present.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the whisper-cpp pattern in senses/bluebubbles/media.ts:
|
|
6
|
+
* check PATH first, install via npm if missing, emit nerves event.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ensureBwCli = ensureBwCli;
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const runtime_1 = require("../nerves/runtime");
|
|
12
|
+
const INSTALL_TIMEOUT_MS = 120_000;
|
|
13
|
+
const WHICH_TIMEOUT_MS = 5_000;
|
|
14
|
+
function execFileAsync(cmd, args, timeout) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
(0, node_child_process_1.execFile)(cmd, args, { timeout }, (err, stdout) => {
|
|
17
|
+
if (err) {
|
|
18
|
+
reject(err);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
resolve(stdout);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Ensure the `bw` CLI is available, installing it via npm if needed.
|
|
27
|
+
* Returns the path to the `bw` binary.
|
|
28
|
+
*/
|
|
29
|
+
async function ensureBwCli() {
|
|
30
|
+
// 1. Check if bw is already in PATH
|
|
31
|
+
try {
|
|
32
|
+
const existing = (await execFileAsync("which", ["bw"], WHICH_TIMEOUT_MS)).trim();
|
|
33
|
+
if (existing) {
|
|
34
|
+
return existing;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Not found — fall through to install
|
|
39
|
+
}
|
|
40
|
+
// 2. Install via npm
|
|
41
|
+
(0, runtime_1.emitNervesEvent)({
|
|
42
|
+
event: "repertoire.bw_cli_install_start",
|
|
43
|
+
component: "repertoire",
|
|
44
|
+
message: "bw CLI not found, installing via npm",
|
|
45
|
+
meta: {},
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
await execFileAsync("npm", ["install", "-g", "@bitwarden/cli"], INSTALL_TIMEOUT_MS);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
/* v8 ignore next -- execFileCb always throws Error instances @preserve */
|
|
52
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
53
|
+
(0, runtime_1.emitNervesEvent)({
|
|
54
|
+
level: "error",
|
|
55
|
+
event: "repertoire.bw_cli_install_fail",
|
|
56
|
+
component: "repertoire",
|
|
57
|
+
message: "failed to install bw CLI via npm",
|
|
58
|
+
meta: { reason },
|
|
59
|
+
});
|
|
60
|
+
throw new Error(`failed to install bw CLI via npm: ${reason}`);
|
|
61
|
+
}
|
|
62
|
+
// 3. Verify installation and return path
|
|
63
|
+
try {
|
|
64
|
+
const installed = (await execFileAsync("which", ["bw"], WHICH_TIMEOUT_MS)).trim();
|
|
65
|
+
if (installed) {
|
|
66
|
+
(0, runtime_1.emitNervesEvent)({
|
|
67
|
+
event: "repertoire.bw_cli_install_end",
|
|
68
|
+
component: "repertoire",
|
|
69
|
+
message: "bw CLI installed successfully",
|
|
70
|
+
meta: { path: installed },
|
|
71
|
+
});
|
|
72
|
+
return installed;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Fall through to error
|
|
77
|
+
}
|
|
78
|
+
throw new Error("bw CLI installed via npm but binary not found in PATH");
|
|
79
|
+
}
|
|
@@ -45,6 +45,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
46
|
exports.AacCredentialStore = exports.BuiltInCredentialStore = void 0;
|
|
47
47
|
exports.domainToSlug = domainToSlug;
|
|
48
|
+
exports.ensureVaultDefaults = ensureVaultDefaults;
|
|
48
49
|
exports.getCredentialStore = getCredentialStore;
|
|
49
50
|
exports.resetCredentialStore = resetCredentialStore;
|
|
50
51
|
const crypto = __importStar(require("node:crypto"));
|
|
@@ -415,6 +416,37 @@ exports.AacCredentialStore = AacCredentialStore;
|
|
|
415
416
|
// Singleton + auto-detection
|
|
416
417
|
// ---------------------------------------------------------------------------
|
|
417
418
|
let _store = null;
|
|
419
|
+
/**
|
|
420
|
+
* Auto-add vault defaults to agent.json when no vault section exists.
|
|
421
|
+
* Returns the vault config (either existing or freshly written defaults).
|
|
422
|
+
*/
|
|
423
|
+
function ensureVaultDefaults(agentName, agentRoot, existingVault) {
|
|
424
|
+
if (existingVault) {
|
|
425
|
+
return existingVault;
|
|
426
|
+
}
|
|
427
|
+
const configPath = path.join(agentRoot, "agent.json");
|
|
428
|
+
let raw;
|
|
429
|
+
try {
|
|
430
|
+
raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
// agent.json unreadable — cannot auto-configure
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
const defaults = {
|
|
437
|
+
email: `${agentName}@ouro.bot`,
|
|
438
|
+
serverUrl: "https://vault.ouro.bot",
|
|
439
|
+
};
|
|
440
|
+
raw.vault = defaults;
|
|
441
|
+
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
|
|
442
|
+
(0, runtime_1.emitNervesEvent)({
|
|
443
|
+
event: "repertoire.vault_defaults_auto_configured",
|
|
444
|
+
component: "repertoire",
|
|
445
|
+
message: "auto-configured vault defaults in agent.json",
|
|
446
|
+
meta: { agentName, email: defaults.email, serverUrl: defaults.serverUrl },
|
|
447
|
+
});
|
|
448
|
+
return defaults;
|
|
449
|
+
}
|
|
418
450
|
/**
|
|
419
451
|
* Get the credential store singleton.
|
|
420
452
|
*
|
|
@@ -433,16 +465,19 @@ function getCredentialStore() {
|
|
|
433
465
|
// Dynamic import to avoid circular dependency at module level
|
|
434
466
|
const identity = require("../heart/identity");
|
|
435
467
|
agentName = identity.getAgentName();
|
|
468
|
+
const agentRoot = identity.getAgentRoot(agentName);
|
|
436
469
|
// Try to load vault config from agent.json + secrets.json
|
|
437
470
|
const config = identity.loadAgentConfig?.();
|
|
438
|
-
|
|
471
|
+
// Auto-configure vault defaults if missing
|
|
472
|
+
const vaultSection = ensureVaultDefaults(agentName, agentRoot, config?.vault);
|
|
473
|
+
const vaultConfig = identity.resolveVaultConfig?.(agentName, vaultSection);
|
|
439
474
|
const secretsPath = identity.getAgentSecretsPath?.(agentName);
|
|
440
475
|
let vaultSecrets;
|
|
441
476
|
/* v8 ignore start -- requires real agent secrets.json + vault config + bw CLI @preserve */
|
|
442
477
|
if (secretsPath) {
|
|
443
478
|
try {
|
|
444
|
-
const
|
|
445
|
-
const secrets = JSON.parse(
|
|
479
|
+
const fsSync = require("fs");
|
|
480
|
+
const secrets = JSON.parse(fsSync.readFileSync(secretsPath, "utf-8"));
|
|
446
481
|
vaultSecrets = secrets.vault;
|
|
447
482
|
}
|
|
448
483
|
catch {
|
package/dist/senses/cli.js
CHANGED
|
@@ -883,8 +883,7 @@ async function main(agentName, options) {
|
|
|
883
883
|
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
884
884
|
const friendStore = new store_file_1.FileFriendStore(friendsPath);
|
|
885
885
|
const username = os.userInfo().username;
|
|
886
|
-
const
|
|
887
|
-
const localExternalId = `${username}@${hostname}`;
|
|
886
|
+
const localExternalId = username;
|
|
888
887
|
const resolver = new resolver_1.FriendResolver(friendStore, {
|
|
889
888
|
provider: "local",
|
|
890
889
|
externalId: localExternalId,
|
|
@@ -99,8 +99,7 @@ async function runSenseTurn(options) {
|
|
|
99
99
|
else {
|
|
100
100
|
// Treat as local user identity (username@hostname pattern)
|
|
101
101
|
const username = os.userInfo().username;
|
|
102
|
-
|
|
103
|
-
resolverParams = { provider: "local", externalId: `${username}@${hostname}`, displayName: username, channel };
|
|
102
|
+
resolverParams = { provider: "local", externalId: username, displayName: username, channel };
|
|
104
103
|
}
|
|
105
104
|
const resolver = new resolver_1.FriendResolver(friendStore, resolverParams);
|
|
106
105
|
// Initialize MCP manager so MCP tools appear as first-class tools in the agent's tool list
|