@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.
- package/changelog.json +12 -0
- package/dist/heart/auth/auth-flow.js +3 -0
- package/dist/heart/config.js +4 -0
- package/dist/repertoire/api-client.js +12 -15
- package/dist/repertoire/credential-access.js +449 -0
- package/dist/repertoire/guardrails.js +12 -12
- package/dist/repertoire/tools-base.js +2 -2
- package/dist/repertoire/tools-credential.js +184 -0
- package/dist/repertoire/travel-api-client.js +5 -15
- package/dist/senses/bluebubbles/index.js +20 -27
- package/package.json +1 -1
- package/skills/browser-navigation.md +3 -3
- package/skills/travel-planning.md +23 -28
- package/dist/repertoire/bitwarden-client.js +0 -290
- package/dist/repertoire/tools-vault.js +0 -185
|
@@ -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
|
|
22
|
+
const credential_access_1 = require("./credential-access");
|
|
24
23
|
// --- OpenWeatherMap ---
|
|
25
|
-
/**
|
|
26
|
-
|
|
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
|
|
38
|
-
return
|
|
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
|
-
//
|
|
1071
|
-
|
|
1072
|
-
|
|
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
|
|
1075
|
-
(
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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
|
|
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
|
@@ -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 `
|
|
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
|
|
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
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
|
|
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
|
-
- `
|
|
109
|
-
- `
|
|
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
|