@ouro.bot/cli 0.1.0-alpha.363 → 0.1.0-alpha.365

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.
Files changed (44) hide show
  1. package/README.md +16 -7
  2. package/changelog.json +17 -0
  3. package/dist/heart/auth/auth-flow.js +25 -110
  4. package/dist/heart/config.js +69 -55
  5. package/dist/heart/core.js +83 -33
  6. package/dist/heart/daemon/agent-config-check.js +41 -238
  7. package/dist/heart/daemon/agentic-repair.js +1 -1
  8. package/dist/heart/daemon/cli-defaults.js +15 -68
  9. package/dist/heart/daemon/cli-exec.js +334 -102
  10. package/dist/heart/daemon/cli-parse.js +71 -0
  11. package/dist/heart/daemon/daemon-cli.js +1 -2
  12. package/dist/heart/daemon/daemon-entry.js +1 -3
  13. package/dist/heart/daemon/doctor.js +9 -29
  14. package/dist/heart/daemon/provider-discovery.js +32 -59
  15. package/dist/heart/hatch/hatch-flow.js +9 -12
  16. package/dist/heart/hatch/specialist-prompt.js +1 -1
  17. package/dist/heart/hatch/specialist-tools.js +21 -1
  18. package/dist/heart/migrate-config.js +15 -42
  19. package/dist/heart/provider-binding-resolver.js +6 -7
  20. package/dist/heart/provider-credentials.js +379 -0
  21. package/dist/heart/provider-failover.js +3 -11
  22. package/dist/heart/provider-ping.js +13 -3
  23. package/dist/heart/provider-state.js +8 -0
  24. package/dist/heart/provider-visibility.js +3 -6
  25. package/dist/heart/providers/anthropic-token.js +15 -47
  26. package/dist/heart/providers/anthropic.js +4 -9
  27. package/dist/heart/providers/azure.js +3 -3
  28. package/dist/heart/providers/github-copilot.js +2 -2
  29. package/dist/heart/providers/minimax-vlm.js +2 -2
  30. package/dist/heart/providers/minimax.js +1 -1
  31. package/dist/heart/providers/openai-codex.js +4 -9
  32. package/dist/heart/versioning/ouro-path-installer.js +10 -5
  33. package/dist/mind/prompt.js +1 -1
  34. package/dist/repertoire/bitwarden-store.js +63 -17
  35. package/dist/repertoire/bundle-templates.js +2 -2
  36. package/dist/repertoire/credential-access.js +47 -467
  37. package/dist/repertoire/tools-attachments.js +5 -4
  38. package/dist/repertoire/tools-vault.js +10 -80
  39. package/dist/repertoire/vault-unlock.js +359 -0
  40. package/dist/senses/bluebubbles/client.js +39 -4
  41. package/dist/senses/pipeline.js +0 -1
  42. package/package.json +1 -1
  43. package/skills/configure-dev-tools.md +10 -0
  44. package/dist/heart/provider-credential-pool.js +0 -395
@@ -85,8 +85,8 @@ async function minimaxVlmDescribe(params) {
85
85
  if (!params.apiKey) {
86
86
  // We deliberately do NOT emit _start for param-validation errors — there's
87
87
  // no meaningful "started a request" to pair with. Only the _error fires.
88
- emitError(params, "minimax VLM: API key is empty — re-run credential setup or add a minimax key to secrets.json");
89
- throw new Error("minimax VLM: API key is empty — re-run credential setup or add a minimax key to secrets.json");
88
+ emitError(params, "minimax VLM: API key is empty — run `ouro auth --agent <agent> --provider minimax`");
89
+ throw new Error("minimax VLM: API key is empty — run `ouro auth --agent <agent> --provider minimax`");
90
90
  }
91
91
  if (!params.prompt) {
92
92
  emitError(params, "minimax VLM: missing prompt — supply a targeted question (e.g. 'what's the flight number in the bottom-right?') and retry");
@@ -29,7 +29,7 @@ function createMinimaxProviderRuntime(model, minimaxConfig = (0, config_1.getMin
29
29
  meta: { provider: "minimax" },
30
30
  });
31
31
  if (!minimaxConfig.apiKey) {
32
- throw new Error("provider 'minimax' is selected in agent.json but providers.minimax.apiKey is missing in secrets.json.");
32
+ throw new Error("provider 'minimax' is selected but minimax.apiKey is missing in the agent vault. Run `ouro auth --agent <agent> --provider minimax`.");
33
33
  }
34
34
  // Registry consulted; MiniMax models return empty defaults (no capabilities to derive)
35
35
  (0, model_capabilities_1.getModelCapabilities)(model);
@@ -20,9 +20,6 @@ const OPENAI_CODEX_AUTH_FAILURE_MARKERS = [
20
20
  "invalid bearer token",
21
21
  ];
22
22
  const OPENAI_CODEX_BACKEND_BASE_URL = "https://chatgpt.com/backend-api/codex";
23
- function getOpenAICodexSecretsPathForGuidance() {
24
- return (0, identity_1.getAgentSecretsPath)();
25
- }
26
23
  function getOpenAICodexAgentNameForGuidance() {
27
24
  return (0, identity_1.getAgentName)();
28
25
  }
@@ -30,11 +27,9 @@ function getOpenAICodexOAuthInstructions() {
30
27
  const agentName = getOpenAICodexAgentNameForGuidance();
31
28
  return [
32
29
  "Fix:",
33
- ` 1. Run \`ouro auth --agent ${agentName}\``,
34
- ` 2. Open ${getOpenAICodexSecretsPathForGuidance()}`,
35
- " 3. Confirm providers.openai-codex.oauthAccessToken is set",
36
- " 4. This provider uses chatgpt.com/backend-api/codex/responses (not api.openai.com/responses).",
37
- " 5. After reauth, retry the failed ouro command or reconnect this session.",
30
+ ` 1. Run \`ouro auth --agent ${agentName} --provider openai-codex\``,
31
+ " 2. This provider uses chatgpt.com/backend-api/codex/responses (not api.openai.com/responses).",
32
+ " 3. After reauth, retry the failed ouro command or reconnect this session.",
38
33
  ].join("\n");
39
34
  }
40
35
  function getOpenAICodexReauthGuidance(reason) {
@@ -95,7 +90,7 @@ function createOpenAICodexProviderRuntime(model, codexConfig = (0, config_1.getO
95
90
  meta: { provider: "openai-codex" },
96
91
  });
97
92
  if (!codexConfig.oauthAccessToken) {
98
- throw new Error(getOpenAICodexReauthGuidance("provider 'openai-codex' is selected in agent.json but providers.openai-codex.oauthAccessToken is missing in secrets.json."));
93
+ throw new Error(getOpenAICodexReauthGuidance("provider 'openai-codex' is selected but openai-codex.oauthAccessToken is missing in the agent vault."));
99
94
  }
100
95
  const token = codexConfig.oauthAccessToken.trim();
101
96
  if (!token) {
@@ -63,16 +63,21 @@ function writeWrapperScript(scriptPath, mkdirSync, writeFileSync, chmodSync) {
63
63
  writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
64
64
  chmodSync(scriptPath, 0o755);
65
65
  }
66
- function detectShellProfile(homeDir, shell) {
66
+ function detectShellProfile(homeDir, shell, platform) {
67
67
  if (!shell)
68
68
  return null;
69
69
  const base = path.basename(shell);
70
70
  if (base === "zsh")
71
71
  return path.join(homeDir, ".zshrc");
72
72
  if (base === "bash") {
73
- // macOS uses .bash_profile, Linux uses .bashrc
74
- const profilePath = path.join(homeDir, ".bash_profile");
75
- return profilePath;
73
+ // macOS uses .bash_profile; Linux/WSL uses .bashrc (the default
74
+ // interactive shell config on Debian/Ubuntu). Writing to .bash_profile
75
+ // on Linux often has no effect because non-login shells skip it.
76
+ /* v8 ignore next -- ?? fallback: callers always pass platform from deps @preserve */
77
+ const effectivePlatform = platform ?? process.platform;
78
+ return effectivePlatform === "darwin"
79
+ ? path.join(homeDir, ".bash_profile")
80
+ : path.join(homeDir, ".bashrc");
76
81
  }
77
82
  if (base === "fish")
78
83
  return path.join(homeDir, ".config", "fish", "config.fish");
@@ -250,7 +255,7 @@ function installOuroCommand(deps = {}) {
250
255
  const pathReady = isBinDirInPath(binDir, envPath);
251
256
  let shellProfileUpdated = null;
252
257
  if (!pathReady) {
253
- const profilePath = detectShellProfile(homeDir, shell);
258
+ const profilePath = detectShellProfile(homeDir, shell, platform);
254
259
  if (profilePath) {
255
260
  try {
256
261
  let existing = "";
@@ -382,7 +382,7 @@ function runtimeInfoSection(channel, options) {
382
382
  lines.push(`process type: ${processTypeLabel(channel)}`);
383
383
  lines.push(`daemon: ${daemonStatus(options?.daemonRunning)}`);
384
384
  lines.push(`mcp serve: i can expose my tools to dev tools via \`ouro mcp-serve\`. see the configure-dev-tools skill for setup.`);
385
- lines.push(`harness docs: the harness repo has docs/ and skills/ with guides for setup, operations, and capabilities. docs/ does NOT ship in the npm package — in production, fetch from https://github.com/ouroborosbot/ouroboros/tree/main/docs instead. in dev mode, read from ${sourceRoot}/docs/. when someone asks about setup, installation, cross-machine cloning, deployment, testing, auth, or how i work — consult the docs before guessing.`);
385
+ lines.push(`harness docs: the harness repo has docs/ and skills/ with guides for setup, operations, and capabilities. docs/ does NOT ship in the npm package — in production, fetch from https://github.com/ouroborosbot/ouroboros/tree/main/docs instead. in dev mode, read from ${sourceRoot}/docs/. when someone asks about setup, installation, cross-machine cloning, deployment, testing, auth, or how i work — consult the docs first. NEVER guess about how the harness works. if the docs don't answer the question, investigate in code. if i discover the docs are stale or missing coverage, open a PR to fix them — stale docs cause the same damage as wrong answers.`);
386
386
  if (channel === "cli") {
387
387
  lines.push("i introduce myself on boot with a fun random greeting.");
388
388
  }
@@ -2,24 +2,59 @@
2
2
  /**
3
3
  * Bitwarden CLI credential store — wraps `bw` CLI for the agent's own vault.
4
4
  *
5
- * Unlike AacCredentialStore (which accesses someone else's vault via approval),
6
- * this store authenticates directly as the agent using its own master password.
5
+ * This store authenticates directly as the agent using its own master password.
7
6
  * The agent owns the vault, so no human-in-the-loop is needed.
8
7
  *
9
8
  * Requires the `bw` CLI to be installed. Session tokens are cached process-local.
10
9
  */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
11
43
  Object.defineProperty(exports, "__esModule", { value: true });
12
44
  exports.BitwardenCredentialStore = void 0;
13
45
  const node_child_process_1 = require("node:child_process");
46
+ const fs = __importStar(require("node:fs"));
14
47
  const runtime_1 = require("../nerves/runtime");
15
48
  const bw_installer_1 = require("./bw-installer");
16
49
  // ---------------------------------------------------------------------------
17
50
  // bw CLI wrapper
18
51
  // ---------------------------------------------------------------------------
19
- function execBw(args, sessionToken) {
20
- const env = sessionToken
21
- ? { ...process.env, BW_SESSION: sessionToken }
22
- : process.env;
52
+ function execBw(args, sessionToken, appDataDir) {
53
+ const env = {
54
+ ...process.env,
55
+ ...(sessionToken ? { BW_SESSION: sessionToken } : {}),
56
+ ...(appDataDir ? { BITWARDENCLI_APPDATA_DIR: appDataDir } : {}),
57
+ };
23
58
  return new Promise((resolve, reject) => {
24
59
  (0, node_child_process_1.execFile)("bw", args, { timeout: 30_000, env }, (err, stdout) => {
25
60
  if (err) {
@@ -62,11 +97,13 @@ class BitwardenCredentialStore {
62
97
  serverUrl;
63
98
  email;
64
99
  masterPassword;
100
+ appDataDir;
65
101
  sessionToken = null;
66
- constructor(serverUrl, email, masterPassword) {
102
+ constructor(serverUrl, email, masterPassword, options = {}) {
67
103
  this.serverUrl = serverUrl;
68
104
  this.email = email;
69
105
  this.masterPassword = masterPassword;
106
+ this.appDataDir = options.appDataDir;
70
107
  }
71
108
  isReady() {
72
109
  return true;
@@ -79,6 +116,9 @@ class BitwardenCredentialStore {
79
116
  async login() {
80
117
  // Ensure bw CLI is installed before any bw commands
81
118
  await (0, bw_installer_1.ensureBwCli)();
119
+ if (this.appDataDir) {
120
+ fs.mkdirSync(this.appDataDir, { recursive: true, mode: 0o700 });
121
+ }
82
122
  let lastError;
83
123
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
84
124
  try {
@@ -112,7 +152,7 @@ class BitwardenCredentialStore {
112
152
  // Check current status
113
153
  let status = {};
114
154
  try {
115
- const raw = await execBw(["status"]);
155
+ const raw = await execBw(["status"], undefined, this.appDataDir);
116
156
  status = JSON.parse(raw);
117
157
  }
118
158
  catch (err) {
@@ -125,7 +165,7 @@ class BitwardenCredentialStore {
125
165
  // Configure server URL if needed (only works when logged out)
126
166
  if (status.status === "unauthenticated" || !status.serverUrl) {
127
167
  try {
128
- await execBw(["config", "server", this.serverUrl]);
168
+ await execBw(["config", "server", this.serverUrl], undefined, this.appDataDir);
129
169
  }
130
170
  catch {
131
171
  // "Logout required" means already logged in — that's fine, skip config
@@ -133,12 +173,12 @@ class BitwardenCredentialStore {
133
173
  }
134
174
  if (status.status === "locked") {
135
175
  // Already logged in, just needs unlock
136
- const unlockOutput = await execBw(["unlock", this.masterPassword, "--raw"]);
176
+ const unlockOutput = await execBw(["unlock", this.masterPassword, "--raw"], undefined, this.appDataDir);
137
177
  this.sessionToken = unlockOutput.trim();
138
178
  }
139
179
  else if (status.status === "unauthenticated" || !status.status) {
140
180
  // Not logged in — full login
141
- const loginOutput = await execBw(["login", this.email, this.masterPassword, "--raw"]);
181
+ const loginOutput = await execBw(["login", this.email, this.masterPassword, "--raw"], undefined, this.appDataDir);
142
182
  try {
143
183
  const parsed = JSON.parse(loginOutput);
144
184
  this.sessionToken = parsed.access_token ?? loginOutput.trim();
@@ -149,7 +189,7 @@ class BitwardenCredentialStore {
149
189
  }
150
190
  else {
151
191
  // Status is "unlocked" — already good, just need the session token
152
- const unlockOutput = await execBw(["unlock", this.masterPassword, "--raw"]);
192
+ const unlockOutput = await execBw(["unlock", this.masterPassword, "--raw"], undefined, this.appDataDir);
153
193
  this.sessionToken = unlockOutput.trim();
154
194
  }
155
195
  }
@@ -221,8 +261,9 @@ class BitwardenCredentialStore {
221
261
  meta: { domain, backend: "bitwarden" },
222
262
  });
223
263
  const session = await this.ensureSession();
224
- // Create a new login item
264
+ const existing = await this.findItemByDomain(domain, session);
225
265
  const item = {
266
+ ...(existing ?? {}),
226
267
  type: 1, // Login type
227
268
  name: domain,
228
269
  login: {
@@ -233,7 +274,12 @@ class BitwardenCredentialStore {
233
274
  notes: data.notes ?? null,
234
275
  };
235
276
  const encoded = Buffer.from(JSON.stringify(item)).toString("base64");
236
- await execBw(["create", "item", encoded], session);
277
+ if (existing) {
278
+ await execBw(["edit", "item", existing.id, encoded], session, this.appDataDir);
279
+ }
280
+ else {
281
+ await execBw(["create", "item", encoded], session, this.appDataDir);
282
+ }
237
283
  (0, runtime_1.emitNervesEvent)({
238
284
  event: "repertoire.bw_credential_store_end",
239
285
  component: "repertoire",
@@ -250,7 +296,7 @@ class BitwardenCredentialStore {
250
296
  });
251
297
  const session = await this.ensureSession();
252
298
  try {
253
- const stdout = await execBw(["list", "items"], session);
299
+ const stdout = await execBw(["list", "items"], session, this.appDataDir);
254
300
  const items = JSON.parse(stdout);
255
301
  const results = items.map((item) => ({
256
302
  domain: item.name,
@@ -294,7 +340,7 @@ class BitwardenCredentialStore {
294
340
  });
295
341
  return false;
296
342
  }
297
- await execBw(["delete", "item", item.id], session);
343
+ await execBw(["delete", "item", item.id], session, this.appDataDir);
298
344
  (0, runtime_1.emitNervesEvent)({
299
345
  event: "repertoire.bw_credential_delete_end",
300
346
  component: "repertoire",
@@ -306,7 +352,7 @@ class BitwardenCredentialStore {
306
352
  // --- Private ---
307
353
  async findItemByDomain(domain, session) {
308
354
  try {
309
- const stdout = await execBw(["list", "items", "--search", domain], session);
355
+ const stdout = await execBw(["list", "items", "--search", domain], session, this.appDataDir);
310
356
  const items = JSON.parse(stdout);
311
357
  // Find exact match by name
312
358
  return items.find((item) => item.name === domain) ?? items[0] ?? null;
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * - Runtime state (sessions, logs, runtime files) — stale data with no
10
10
  * value for review or history.
11
- * - Credentials — real secrets live in `~/.agentsecrets`, but defense
11
+ * - Credentials — real secrets live in the agent vault, but defense
12
12
  * in depth in case anything leaks into the bundle.
13
13
  * - Editor / OS noise (.DS_Store, .idea/, etc.).
14
14
  * - Build artifacts (rare in bundles, but possible).
@@ -32,7 +32,7 @@ exports.PII_BUNDLE_DIRECTORIES = exports.BUNDLE_GITIGNORE_TEMPLATE = void 0;
32
32
  exports.BUNDLE_GITIGNORE_TEMPLATE = `# Runtime state — sessions, logs, runtime files, never tracked
33
33
  state/
34
34
 
35
- # Credentials — never tracked. Real secrets live in ~/.agentsecrets, but
35
+ # Credentials — never tracked. Real secrets live in the agent vault, but
36
36
  # defense in depth in case anything leaks into the bundle.
37
37
  .env
38
38
  .env.*