@ouro.bot/cli 0.1.0-alpha.226 → 0.1.0-alpha.228

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.
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.credentialToolDefinitions = void 0;
4
+ const credential_access_1 = require("./credential-access");
5
+ const runtime_1 = require("../nerves/runtime");
6
+ exports.credentialToolDefinitions = [
7
+ {
8
+ tool: {
9
+ type: "function",
10
+ function: {
11
+ name: "credential_get",
12
+ description: "Get credential metadata for a domain. Returns username, notes, and creation date. Never returns passwords — the credential gateway handles secret injection internally.",
13
+ parameters: {
14
+ type: "object",
15
+ properties: {
16
+ domain: {
17
+ type: "string",
18
+ description: "Domain to look up (e.g. 'airbnb.com', 'api.openweathermap.org')",
19
+ },
20
+ },
21
+ required: ["domain"],
22
+ },
23
+ },
24
+ },
25
+ handler: async (args) => {
26
+ (0, runtime_1.emitNervesEvent)({
27
+ component: "repertoire",
28
+ event: "repertoire.credential_tool_call",
29
+ message: "credential_get invoked",
30
+ meta: { tool: "credential_get", domain: args.domain },
31
+ });
32
+ try {
33
+ const store = (0, credential_access_1.getCredentialStore)();
34
+ const meta = await store.get(args.domain);
35
+ if (!meta) {
36
+ return `No credential found for "${args.domain}".`;
37
+ }
38
+ return JSON.stringify(meta, null, 2);
39
+ }
40
+ catch (err) {
41
+ /* v8 ignore next -- defensive: store.get wraps errors @preserve */
42
+ return `Credential error: ${err instanceof Error ? err.message : String(err)}`;
43
+ }
44
+ },
45
+ summaryKeys: ["domain"],
46
+ },
47
+ {
48
+ tool: {
49
+ type: "function",
50
+ function: {
51
+ name: "credential_store",
52
+ description: "Store credentials the agent acquired (e.g. during sign-up). The password is accepted in args because the model generated it. Stored passwords are never returned later — only metadata is visible.",
53
+ parameters: {
54
+ type: "object",
55
+ properties: {
56
+ domain: {
57
+ type: "string",
58
+ description: "Domain these credentials are for (e.g. 'airbnb.com')",
59
+ },
60
+ username: {
61
+ type: "string",
62
+ description: "Username or email for the account",
63
+ },
64
+ password: {
65
+ type: "string",
66
+ description: "Password for the account",
67
+ },
68
+ notes: {
69
+ type: "string",
70
+ description: "Optional notes about this credential",
71
+ },
72
+ },
73
+ required: ["domain", "username", "password"],
74
+ },
75
+ },
76
+ },
77
+ handler: async (args) => {
78
+ (0, runtime_1.emitNervesEvent)({
79
+ component: "repertoire",
80
+ event: "repertoire.credential_tool_call",
81
+ message: "credential_store invoked",
82
+ meta: { tool: "credential_store", domain: args.domain },
83
+ });
84
+ try {
85
+ const store = (0, credential_access_1.getCredentialStore)();
86
+ await store.store(args.domain, {
87
+ username: args.username,
88
+ password: args.password,
89
+ notes: args.notes,
90
+ });
91
+ return `Credentials stored for "${args.domain}".`;
92
+ }
93
+ catch (err) {
94
+ /* v8 ignore next -- defensive: store.store wraps errors @preserve */
95
+ return `Credential store error: ${err instanceof Error ? err.message : String(err)}`;
96
+ }
97
+ },
98
+ confirmationRequired: true,
99
+ summaryKeys: ["domain"],
100
+ },
101
+ {
102
+ tool: {
103
+ type: "function",
104
+ function: {
105
+ name: "credential_list",
106
+ description: "List stored credential domains. Returns metadata only (domain, username, notes, creation date). Never returns passwords.",
107
+ parameters: {
108
+ type: "object",
109
+ properties: {
110
+ search: {
111
+ type: "string",
112
+ description: "Optional search filter to match against domain names",
113
+ },
114
+ },
115
+ },
116
+ },
117
+ },
118
+ handler: async (args) => {
119
+ (0, runtime_1.emitNervesEvent)({
120
+ component: "repertoire",
121
+ event: "repertoire.credential_tool_call",
122
+ message: "credential_list invoked",
123
+ meta: { tool: "credential_list" },
124
+ });
125
+ try {
126
+ const store = (0, credential_access_1.getCredentialStore)();
127
+ let items = await store.list();
128
+ // Client-side search filter
129
+ if (args.search) {
130
+ const term = args.search.toLowerCase();
131
+ items = items.filter((item) => item.domain.toLowerCase().includes(term) ||
132
+ (item.username && item.username.toLowerCase().includes(term)));
133
+ }
134
+ return JSON.stringify(items, null, 2);
135
+ }
136
+ catch (err) {
137
+ /* v8 ignore next -- defensive: store.list wraps errors @preserve */
138
+ return `Credential list error: ${err instanceof Error ? err.message : String(err)}`;
139
+ }
140
+ },
141
+ summaryKeys: ["search"],
142
+ },
143
+ {
144
+ tool: {
145
+ type: "function",
146
+ function: {
147
+ name: "credential_delete",
148
+ description: "Delete stored credentials for a domain.",
149
+ parameters: {
150
+ type: "object",
151
+ properties: {
152
+ domain: {
153
+ type: "string",
154
+ description: "Domain whose credentials should be deleted",
155
+ },
156
+ },
157
+ required: ["domain"],
158
+ },
159
+ },
160
+ },
161
+ handler: async (args) => {
162
+ (0, runtime_1.emitNervesEvent)({
163
+ component: "repertoire",
164
+ event: "repertoire.credential_tool_call",
165
+ message: "credential_delete invoked",
166
+ meta: { tool: "credential_delete", domain: args.domain },
167
+ });
168
+ try {
169
+ const store = (0, credential_access_1.getCredentialStore)();
170
+ const deleted = await store.delete(args.domain);
171
+ if (deleted) {
172
+ return `Credentials for "${args.domain}" deleted.`;
173
+ }
174
+ return `No credential found for "${args.domain}".`;
175
+ }
176
+ catch (err) {
177
+ /* v8 ignore next -- defensive: store.delete wraps errors @preserve */
178
+ return `Credential delete error: ${err instanceof Error ? err.message : String(err)}`;
179
+ }
180
+ },
181
+ confirmationRequired: true,
182
+ summaryKeys: ["domain"],
183
+ },
184
+ ];
@@ -9,7 +9,6 @@
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.TRAVEL_ADVISORY_RSS_URL = void 0;
12
- exports.setWeatherVaultConfig = setWeatherVaultConfig;
13
12
  exports.getWeather = getWeather;
14
13
  exports.getWeatherByCity = getWeatherByCity;
15
14
  exports.parseAdvisoryLevel = parseAdvisoryLevel;
@@ -20,22 +19,13 @@ exports.geocode = geocode;
20
19
  exports.reverseGeocode = reverseGeocode;
21
20
  exports.searchPOI = searchPOI;
22
21
  const runtime_1 = require("../nerves/runtime");
23
- const bitwarden_client_1 = require("./bitwarden-client");
22
+ const credential_access_1 = require("./credential-access");
24
23
  // --- OpenWeatherMap ---
25
- /**
26
- * Vault item ID and field name for OpenWeatherMap API key.
27
- * Override via setWeatherVaultConfig() for custom vault layouts.
28
- */
29
- let weatherVaultItemId = "openweathermap-api";
30
- let weatherVaultField = "apiKey";
31
- function setWeatherVaultConfig(itemId, field) {
32
- weatherVaultItemId = itemId;
33
- if (field)
34
- weatherVaultField = field;
35
- }
24
+ /** Domain used for weather API key credential retrieval. */
25
+ const WEATHER_CREDENTIAL_DOMAIN = "api.openweathermap.org";
36
26
  async function getWeatherApiKey() {
37
- const client = (0, bitwarden_client_1.getBitwardenClient)();
38
- return client.getRawSecret(weatherVaultItemId, weatherVaultField);
27
+ const store = (0, credential_access_1.getCredentialStore)();
28
+ return store.getRawSecret(WEATHER_CREDENTIAL_DOMAIN, "password");
39
29
  }
40
30
  async function getWeather(lat, lon) {
41
31
  return withNervesEvents("getWeather", { lat, lon }, async () => {
@@ -1067,44 +1067,37 @@ async function sendProactiveBlueBubblesMessageToSession(params, deps = {}) {
1067
1067
  catch {
1068
1068
  friend = null;
1069
1069
  }
1070
- // Fallback: if friendId is a name (not UUID), resolve via listAll
1071
- /* v8 ignore start -- name resolution fallback + diagnostics @preserve */
1072
- if (!friend && store.listAll) {
1070
+ // Direct filesystem fallback — store.get() with name resolution wasn't working in production
1071
+ // despite correct compiled code. Bypass the entire store abstraction.
1072
+ /* v8 ignore start -- direct filesystem name resolution @preserve */
1073
+ if (!friend) {
1073
1074
  try {
1074
- const all = await store.listAll();
1075
- (0, runtime_1.emitNervesEvent)({
1076
- component: "senses",
1077
- event: "senses.bluebubbles_proactive_name_resolve",
1078
- message: "attempting name-based friend resolution",
1079
- meta: {
1080
- friendId: params.friendId,
1081
- storeHasListAll: true,
1082
- friendCount: all.length,
1083
- friendNames: all.map((f) => f.name ?? "<no name>"),
1084
- },
1085
- });
1086
- friend = all.find((f) => f.name?.toLowerCase() === params.friendId.toLowerCase()) ?? null;
1075
+ const friendsDir = path.join((0, identity_1.getAgentRoot)(), "friends");
1076
+ const files = fs.readdirSync(friendsDir).filter((f) => f.endsWith(".json"));
1077
+ for (const file of files) {
1078
+ const raw = JSON.parse(fs.readFileSync(path.join(friendsDir, file), "utf-8"));
1079
+ if (raw.name?.toLowerCase() === params.friendId.toLowerCase()) {
1080
+ friend = raw;
1081
+ (0, runtime_1.emitNervesEvent)({
1082
+ component: "senses",
1083
+ event: "senses.bluebubbles_proactive_name_resolved",
1084
+ message: "resolved friend by name via direct filesystem scan",
1085
+ meta: { friendId: params.friendId, resolvedId: raw.id, name: raw.name },
1086
+ });
1087
+ break;
1088
+ }
1089
+ }
1087
1090
  }
1088
1091
  catch (err) {
1089
1092
  (0, runtime_1.emitNervesEvent)({
1090
1093
  level: "warn",
1091
1094
  component: "senses",
1092
1095
  event: "senses.bluebubbles_proactive_name_resolve_error",
1093
- message: "name resolution fallback failed",
1096
+ message: "direct filesystem name resolution failed",
1094
1097
  meta: { friendId: params.friendId, error: err instanceof Error ? err.message : String(err) },
1095
1098
  });
1096
- friend = null;
1097
1099
  }
1098
1100
  }
1099
- else if (!friend) {
1100
- (0, runtime_1.emitNervesEvent)({
1101
- level: "warn",
1102
- component: "senses",
1103
- event: "senses.bluebubbles_proactive_name_resolve_skip",
1104
- message: "name resolution skipped: store.listAll not available",
1105
- meta: { friendId: params.friendId, hasListAll: typeof store.listAll },
1106
- });
1107
- }
1108
1101
  /* v8 ignore stop */
1109
1102
  if (!friend) {
1110
1103
  (0, runtime_1.emitNervesEvent)({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.226",
3
+ "version": "0.1.0-alpha.228",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",
@@ -44,7 +44,7 @@ Follow this pattern for every page interaction:
44
44
  3. Use `browser_type` for username/password fields
45
45
  4. Use `browser_click` to submit
46
46
  5. Wait for redirect, then verify login succeeded via snapshot
47
- 6. NEVER hardcode credentials -- use `vault_get` to retrieve login info
47
+ 6. NEVER hardcode credentials -- use `credential_get` to retrieve login info
48
48
 
49
49
  ### Search Forms (Hotels, Flights, Rentals)
50
50
  1. Navigate to the search page
@@ -58,7 +58,7 @@ Follow this pattern for every page interaction:
58
58
  ### Booking Forms
59
59
  1. **ALWAYS confirm with the user before proceeding to payment**
60
60
  2. Fill traveler information
61
- 3. Use vault credentials for payment (via `vault_get` -- NEVER type raw card numbers)
61
+ 3. Use stored credentials for payment (via `credential_get` -- NEVER type raw card numbers)
62
62
  4. Screenshot the final review page for user confirmation
63
63
  5. Only click "Book" / "Confirm" after explicit user approval
64
64
 
@@ -98,7 +98,7 @@ The stealth configuration handles most fingerprinting automatically. Additionall
98
98
  - **CAPTCHAs**: Stop and ask the user to solve manually.
99
99
  - **Stale elements**: Re-snapshot the page and retry the interaction.
100
100
  - **Blocked/403**: The site may have detected automation. Wait 30 seconds and try with a different approach (e.g., direct URL instead of navigation).
101
- - **Session expired**: Re-login using stored vault credentials.
101
+ - **Session expired**: Re-login using stored stored credentials.
102
102
 
103
103
  ## Human Confirmation Gates
104
104
 
@@ -71,10 +71,25 @@ Present top 3-5 options comparing:
71
71
  - **Never proceed with a financial commitment without explicit approval**
72
72
 
73
73
  ### Credential Handling
74
- - Use `vault_get` to retrieve payment credentials (never expose raw values)
75
- - Use `vault_get` for loyalty program numbers
76
- - Use browser-navigation skill form patterns for entering credentials
77
- - The credential gateway ensures secrets never enter model context
74
+
75
+ Credentials are managed through the credential access layer, which stores
76
+ agent-owned secrets encrypted in the bundle vault. Raw passwords never
77
+ enter model context.
78
+
79
+ - Use `credential_get` to check what credentials exist for a domain (metadata only, never passwords)
80
+ - Use `credential_store` to save credentials the agent acquired (e.g., during sign-up for a service)
81
+ - The credential gateway automatically injects secrets into API requests via `getRawSecret()`
82
+ - Use browser-navigation skill form patterns for entering credentials during interactive sessions
83
+
84
+ **How credentials work:**
85
+ - Agent-owned credentials are AES-256-GCM encrypted in `~/AgentBundles/<agent>.ouro/vault/`
86
+ - The master key lives at `~/.agentsecrets/<agent>/vault.key` (auto-generated on first use)
87
+ - The agent can sign up for services and store its own credentials
88
+ - Stored passwords are never returned to the model — only metadata (domain, username, notes)
89
+
90
+ **Upgrade path:** Install `aac` (Bitwarden Agent Access CLI) for vault-backed credentials.
91
+ When `aac` is available, the harness can read credentials from Bitwarden directly,
92
+ enabling access to human-managed vault items without storing them locally.
78
93
 
79
94
  ### Post-Booking
80
95
  - Save confirmation details (confirmation number, dates, hotel name, airline, booking reference)
@@ -105,35 +120,15 @@ Track and reference these travel preferences:
105
120
  - `weather_lookup` - Current weather by city or coordinates
106
121
  - `travel_advisory` - US State Dept advisory by country code
107
122
  - `geocode_search` - Location/POI search with coordinates
108
- - `vault_get` - Retrieve credentials (payment, loyalty)
109
- - `vault_store` - Store new credentials (family trust required)
123
+ - `credential_get` - Check credential metadata for a domain (never returns passwords)
124
+ - `credential_store` - Store credentials the agent acquired (family trust, confirmation required)
125
+ - `credential_list` - List stored credential domains
126
+ - `credential_delete` - Delete stored credentials (family trust, confirmation required)
110
127
 
111
128
  ### MCP Tools (when configured)
112
129
  - Browser tools via `@playwright/mcp` - see `browser-navigation` skill
113
130
  - Duffel flight search (when MCP server available)
114
131
  - Expedia hotel search (when MCP server available)
115
- - Bitwarden vault management via `@bitwarden/mcp-server` (agent-facing interactive vault access)
116
-
117
- ### Bitwarden MCP Server
118
-
119
- For agent-facing vault interaction (browsing items, searching, reading fields), configure
120
- `@bitwarden/mcp-server` as an MCP server in agent.json:
121
-
122
- ```json
123
- {
124
- "mcpServers": {
125
- "bitwarden": {
126
- "command": "npx",
127
- "args": ["@bitwarden/mcp-server"]
128
- }
129
- }
130
- }
131
- ```
132
-
133
- This is distinct from the built-in `bw` CLI-based credential gateway, which handles
134
- harness-internal secret injection (e.g., API keys injected into tool requests via
135
- `getRawSecret()`). The MCP server is for the agent to interactively browse and manage
136
- vault items on behalf of the user.
137
132
 
138
133
  ### Human Confirmation Required For
139
134
  - Any booking or payment