@ouro.bot/cli 0.1.0-alpha.237 → 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 +8 -1
- package/dist/heart/hatch/hatch-flow.js +1 -1
- package/dist/mind/friends/resolver.js +36 -0
- package/dist/nerves/coverage/file-completeness.js +2 -0
- package/dist/repertoire/bitwarden-store.js +3 -0
- package/dist/repertoire/bw-installer.js +79 -0
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +38 -3
- package/dist/repertoire/duffel-client.js +185 -0
- package/dist/repertoire/guardrails.js +11 -2
- package/dist/repertoire/mcp-client.js +1 -0
- package/dist/repertoire/mcp-manager.js +53 -1
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tools-base.js +6 -0
- package/dist/repertoire/tools-flight.js +224 -0
- package/dist/repertoire/tools-stripe.js +180 -0
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/user-profile.js +118 -0
- package/dist/senses/cli.js +1 -2
- package/dist/senses/shared-turn.js +1 -2
- package/package.json +3 -2
- package/skills/agent-commerce.md +106 -0
- package/skills/commerce-setup-guide.md +116 -0
- package/skills/commerce-setup.md +84 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stripeToolDefinitions = void 0;
|
|
4
|
+
exports.resetStripeClient = resetStripeClient;
|
|
5
|
+
const stripe_client_1 = require("./stripe-client");
|
|
6
|
+
const runtime_1 = require("../nerves/runtime");
|
|
7
|
+
// Lazy-initialized Stripe client singleton
|
|
8
|
+
let _stripeClient = null;
|
|
9
|
+
async function getStripeClient() {
|
|
10
|
+
if (!_stripeClient) {
|
|
11
|
+
_stripeClient = await (0, stripe_client_1.createStripeClient)();
|
|
12
|
+
}
|
|
13
|
+
return _stripeClient;
|
|
14
|
+
}
|
|
15
|
+
function requireFamilyContext(ctx) {
|
|
16
|
+
if (!ctx?.context?.friend?.id) {
|
|
17
|
+
return "no friend context — cannot access payment tools.";
|
|
18
|
+
}
|
|
19
|
+
if (ctx.context.friend.trustLevel !== "family") {
|
|
20
|
+
return "payment tools require family trust level.";
|
|
21
|
+
}
|
|
22
|
+
return { friendId: ctx.context.friend.id };
|
|
23
|
+
}
|
|
24
|
+
exports.stripeToolDefinitions = [
|
|
25
|
+
{
|
|
26
|
+
tool: {
|
|
27
|
+
type: "function",
|
|
28
|
+
function: {
|
|
29
|
+
name: "stripe_create_card",
|
|
30
|
+
description: "Create a virtual card for a transaction. Returns card ID and last 4 digits (never the full card number). Requires family trust level.",
|
|
31
|
+
parameters: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
type: {
|
|
35
|
+
type: "string",
|
|
36
|
+
enum: ["single_use", "persistent"],
|
|
37
|
+
description: "Card type: single_use (one transaction) or persistent (recurring)",
|
|
38
|
+
},
|
|
39
|
+
spend_limit: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Maximum spend limit in dollars (e.g. '500')",
|
|
42
|
+
},
|
|
43
|
+
currency: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Currency code (e.g. 'usd')",
|
|
46
|
+
},
|
|
47
|
+
merchant_categories: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Comma-separated allowed merchant categories (optional)",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: ["type", "spend_limit", "currency"],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
handler: async (args, ctx) => {
|
|
57
|
+
(0, runtime_1.emitNervesEvent)({
|
|
58
|
+
component: "repertoire",
|
|
59
|
+
event: "repertoire.tool_stripe_create_card",
|
|
60
|
+
message: "stripe_create_card invoked",
|
|
61
|
+
meta: { tool: "stripe_create_card" },
|
|
62
|
+
});
|
|
63
|
+
const guard = requireFamilyContext(ctx);
|
|
64
|
+
if (typeof guard === "string")
|
|
65
|
+
return guard;
|
|
66
|
+
try {
|
|
67
|
+
const client = await getStripeClient();
|
|
68
|
+
const categories = args.merchant_categories
|
|
69
|
+
? args.merchant_categories.split(",").map((c) => c.trim())
|
|
70
|
+
: undefined;
|
|
71
|
+
const card = await client.createVirtualCard({
|
|
72
|
+
type: args.type,
|
|
73
|
+
spendLimit: parseFloat(args.spend_limit),
|
|
74
|
+
currency: args.currency,
|
|
75
|
+
merchantCategories: categories,
|
|
76
|
+
});
|
|
77
|
+
return JSON.stringify({
|
|
78
|
+
cardId: card.cardId,
|
|
79
|
+
last4: card.last4,
|
|
80
|
+
status: card.status,
|
|
81
|
+
spendLimit: args.spend_limit,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
/* v8 ignore next -- defensive: Stripe errors are always Error instances @preserve */
|
|
86
|
+
return `card creation error: ${err instanceof Error ? err.message : String(err)}`;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
summaryKeys: ["type", "spend_limit", "currency"],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
tool: {
|
|
93
|
+
type: "function",
|
|
94
|
+
function: {
|
|
95
|
+
name: "stripe_deactivate_card",
|
|
96
|
+
description: "Deactivate (cancel) a virtual card. Use after a transaction is complete or to revoke access. Requires family trust level.",
|
|
97
|
+
parameters: {
|
|
98
|
+
type: "object",
|
|
99
|
+
properties: {
|
|
100
|
+
card_id: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "The Stripe card ID to deactivate (e.g. 'ic_...')",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
required: ["card_id"],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
handler: async (args, ctx) => {
|
|
110
|
+
(0, runtime_1.emitNervesEvent)({
|
|
111
|
+
component: "repertoire",
|
|
112
|
+
event: "repertoire.tool_stripe_deactivate_card",
|
|
113
|
+
message: "stripe_deactivate_card invoked",
|
|
114
|
+
meta: { tool: "stripe_deactivate_card", cardId: args.card_id },
|
|
115
|
+
});
|
|
116
|
+
const guard = requireFamilyContext(ctx);
|
|
117
|
+
if (typeof guard === "string")
|
|
118
|
+
return guard;
|
|
119
|
+
try {
|
|
120
|
+
const client = await getStripeClient();
|
|
121
|
+
const card = await client.deactivateCard(args.card_id);
|
|
122
|
+
return JSON.stringify({
|
|
123
|
+
cardId: card.cardId,
|
|
124
|
+
last4: card.last4,
|
|
125
|
+
status: card.status,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
/* v8 ignore next -- defensive: Stripe errors are always Error instances @preserve */
|
|
130
|
+
return `card deactivation error: ${err instanceof Error ? err.message : String(err)}`;
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
summaryKeys: ["card_id"],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
tool: {
|
|
137
|
+
type: "function",
|
|
138
|
+
function: {
|
|
139
|
+
name: "stripe_list_cards",
|
|
140
|
+
description: "List all active virtual cards. Shows card IDs and last 4 digits only. Requires family trust level.",
|
|
141
|
+
parameters: {
|
|
142
|
+
type: "object",
|
|
143
|
+
properties: {},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
handler: async (_args, ctx) => {
|
|
148
|
+
(0, runtime_1.emitNervesEvent)({
|
|
149
|
+
component: "repertoire",
|
|
150
|
+
event: "repertoire.tool_stripe_list_cards",
|
|
151
|
+
message: "stripe_list_cards invoked",
|
|
152
|
+
meta: { tool: "stripe_list_cards" },
|
|
153
|
+
});
|
|
154
|
+
const guard = requireFamilyContext(ctx);
|
|
155
|
+
if (typeof guard === "string")
|
|
156
|
+
return guard;
|
|
157
|
+
try {
|
|
158
|
+
const client = await getStripeClient();
|
|
159
|
+
const cards = await client.listCards();
|
|
160
|
+
if (cards.length === 0) {
|
|
161
|
+
return "no active cards.";
|
|
162
|
+
}
|
|
163
|
+
return JSON.stringify(cards.map((c) => ({
|
|
164
|
+
cardId: c.cardId,
|
|
165
|
+
last4: c.last4,
|
|
166
|
+
status: c.status,
|
|
167
|
+
})));
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
/* v8 ignore next -- defensive: Stripe errors are always Error instances @preserve */
|
|
171
|
+
return `card listing error: ${err instanceof Error ? err.message : String(err)}`;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
summaryKeys: [],
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
/** Reset the Stripe client singleton (for testing). */
|
|
178
|
+
function resetStripeClient() {
|
|
179
|
+
_stripeClient = null;
|
|
180
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.userProfileToolDefinitions = void 0;
|
|
4
|
+
const user_profile_1 = require("./user-profile");
|
|
5
|
+
const credential_access_1 = require("./credential-access");
|
|
6
|
+
const runtime_1 = require("../nerves/runtime");
|
|
7
|
+
function requireFamilyContext(ctx) {
|
|
8
|
+
if (!ctx?.context?.friend?.id) {
|
|
9
|
+
return "no friend context — cannot access user profile.";
|
|
10
|
+
}
|
|
11
|
+
if (ctx.context.friend.trustLevel !== "family") {
|
|
12
|
+
return "user profile access requires family trust level.";
|
|
13
|
+
}
|
|
14
|
+
return { friendId: ctx.context.friend.id };
|
|
15
|
+
}
|
|
16
|
+
exports.userProfileToolDefinitions = [
|
|
17
|
+
{
|
|
18
|
+
tool: {
|
|
19
|
+
type: "function",
|
|
20
|
+
function: {
|
|
21
|
+
name: "user_profile_store",
|
|
22
|
+
description: "Store or update user profile fields (legal name, DOB, passport, etc.) in the agent's vault. Fields are merged with any existing profile. Requires family trust level.",
|
|
23
|
+
parameters: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
fields: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: 'JSON object with profile fields to store/update. Keys: legalName, dateOfBirth, gender, nationality, passport, driverLicense, email, phone, addresses, loyaltyPrograms, preferences, emergencyContact.',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
required: ["fields"],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
handler: async (args, ctx) => {
|
|
36
|
+
(0, runtime_1.emitNervesEvent)({
|
|
37
|
+
component: "repertoire",
|
|
38
|
+
event: "repertoire.tool_user_profile_store",
|
|
39
|
+
message: "user_profile_store invoked",
|
|
40
|
+
meta: { tool: "user_profile_store" },
|
|
41
|
+
});
|
|
42
|
+
const guard = requireFamilyContext(ctx);
|
|
43
|
+
if (typeof guard === "string")
|
|
44
|
+
return guard;
|
|
45
|
+
let fields;
|
|
46
|
+
try {
|
|
47
|
+
fields = JSON.parse(args.fields);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return "invalid JSON in fields parameter.";
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const store = (0, credential_access_1.getCredentialStore)();
|
|
54
|
+
await (0, user_profile_1.updateUserProfileFields)(guard.friendId, fields, store);
|
|
55
|
+
return `profile fields stored for ${guard.friendId}.`;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
/* v8 ignore next -- defensive: updateUserProfileFields errors are always Error instances @preserve */
|
|
59
|
+
return `failed to store profile: ${err instanceof Error ? err.message : String(err)}`;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
summaryKeys: ["fields"],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
tool: {
|
|
66
|
+
type: "function",
|
|
67
|
+
function: {
|
|
68
|
+
name: "user_profile_get",
|
|
69
|
+
description: "Retrieve a specific field from a user's profile. Only returns the requested field, never the full profile. Requires family trust level.",
|
|
70
|
+
parameters: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
field: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "The profile field to retrieve: legalName, dateOfBirth, gender, nationality, passport, driverLicense, email, phone, addresses, loyaltyPrograms, preferences, emergencyContact.",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: ["field"],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
handler: async (args, ctx) => {
|
|
83
|
+
(0, runtime_1.emitNervesEvent)({
|
|
84
|
+
component: "repertoire",
|
|
85
|
+
event: "repertoire.tool_user_profile_get",
|
|
86
|
+
message: "user_profile_get invoked",
|
|
87
|
+
meta: { tool: "user_profile_get", field: args.field },
|
|
88
|
+
});
|
|
89
|
+
const guard = requireFamilyContext(ctx);
|
|
90
|
+
if (typeof guard === "string")
|
|
91
|
+
return guard;
|
|
92
|
+
try {
|
|
93
|
+
const store = (0, credential_access_1.getCredentialStore)();
|
|
94
|
+
const value = await (0, user_profile_1.getUserProfileField)(guard.friendId, args.field, store);
|
|
95
|
+
if (value === undefined) {
|
|
96
|
+
return `field "${args.field}" is not set on the profile.`;
|
|
97
|
+
}
|
|
98
|
+
/* v8 ignore next -- platform-dependent v8 branch counting on ternary @preserve */
|
|
99
|
+
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
/* v8 ignore next -- defensive: getUserProfileField errors are always Error instances @preserve */
|
|
103
|
+
return `failed to get profile field: ${err instanceof Error ? err.message : String(err)}`;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
summaryKeys: ["field"],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
tool: {
|
|
110
|
+
type: "function",
|
|
111
|
+
function: {
|
|
112
|
+
name: "user_profile_delete",
|
|
113
|
+
description: "Delete a user's entire profile from the vault. This is irreversible. Requires family trust level.",
|
|
114
|
+
parameters: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
handler: async (_args, ctx) => {
|
|
121
|
+
(0, runtime_1.emitNervesEvent)({
|
|
122
|
+
component: "repertoire",
|
|
123
|
+
event: "repertoire.tool_user_profile_delete",
|
|
124
|
+
message: "user_profile_delete invoked",
|
|
125
|
+
meta: { tool: "user_profile_delete" },
|
|
126
|
+
});
|
|
127
|
+
const guard = requireFamilyContext(ctx);
|
|
128
|
+
if (typeof guard === "string")
|
|
129
|
+
return guard;
|
|
130
|
+
try {
|
|
131
|
+
const store = (0, credential_access_1.getCredentialStore)();
|
|
132
|
+
const deleted = await (0, user_profile_1.deleteUserProfile)(guard.friendId, store);
|
|
133
|
+
return deleted
|
|
134
|
+
? `profile deleted for ${guard.friendId}.`
|
|
135
|
+
: `no profile found for ${guard.friendId}.`;
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
/* v8 ignore next -- defensive: deleteUserProfile errors are always Error instances @preserve */
|
|
139
|
+
return `failed to delete profile: ${err instanceof Error ? err.message : String(err)}`;
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
summaryKeys: [],
|
|
143
|
+
},
|
|
144
|
+
];
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* User profile model and vault storage.
|
|
4
|
+
*
|
|
5
|
+
* Profiles are stored as secure notes in the agent's Vaultwarden vault,
|
|
6
|
+
* keyed by friend ID: `user-profile/{friendId}`.
|
|
7
|
+
*
|
|
8
|
+
* The storage layer uses the existing CredentialStore interface — the profile
|
|
9
|
+
* JSON is stored in the `password` field of a login item (the vault's most
|
|
10
|
+
* reliable field for arbitrary data). Field-level access ensures the full
|
|
11
|
+
* profile is never dumped to model context unnecessarily.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.storeUserProfile = storeUserProfile;
|
|
15
|
+
exports.getUserProfile = getUserProfile;
|
|
16
|
+
exports.getUserProfileField = getUserProfileField;
|
|
17
|
+
exports.deleteUserProfile = deleteUserProfile;
|
|
18
|
+
exports.updateUserProfileFields = updateUserProfileFields;
|
|
19
|
+
const runtime_1 = require("../nerves/runtime");
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Storage key
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
function profileKey(friendId) {
|
|
24
|
+
return `user-profile/${friendId}`;
|
|
25
|
+
}
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// CRUD operations
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* Store a complete user profile in the vault.
|
|
31
|
+
* Overwrites any existing profile for the given friend ID.
|
|
32
|
+
*/
|
|
33
|
+
async function storeUserProfile(friendId, profile, store) {
|
|
34
|
+
(0, runtime_1.emitNervesEvent)({
|
|
35
|
+
event: "repertoire.user_profile_store",
|
|
36
|
+
component: "repertoire",
|
|
37
|
+
message: `storing user profile for ${friendId}`,
|
|
38
|
+
meta: { friendId },
|
|
39
|
+
});
|
|
40
|
+
const key = profileKey(friendId);
|
|
41
|
+
/* v8 ignore start -- platform-dependent v8 branch counting on await @preserve */
|
|
42
|
+
await store.store(key, {
|
|
43
|
+
password: JSON.stringify(profile),
|
|
44
|
+
notes: "user-profile",
|
|
45
|
+
});
|
|
46
|
+
/* v8 ignore stop */
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Retrieve the full user profile for a friend.
|
|
50
|
+
* Returns null if no profile exists or if the stored data is invalid.
|
|
51
|
+
*/
|
|
52
|
+
async function getUserProfile(friendId, store) {
|
|
53
|
+
(0, runtime_1.emitNervesEvent)({
|
|
54
|
+
event: "repertoire.user_profile_get",
|
|
55
|
+
component: "repertoire",
|
|
56
|
+
message: `getting user profile for ${friendId}`,
|
|
57
|
+
meta: { friendId },
|
|
58
|
+
});
|
|
59
|
+
try {
|
|
60
|
+
const raw = await store.getRawSecret(profileKey(friendId), "password");
|
|
61
|
+
const parsed = JSON.parse(raw);
|
|
62
|
+
return parsed;
|
|
63
|
+
/* v8 ignore next 2 -- platform-dependent v8 branch counting on catch @preserve */
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Retrieve a specific field from a user profile.
|
|
71
|
+
* Returns undefined if the profile doesn't exist or the field is not set.
|
|
72
|
+
*/
|
|
73
|
+
async function getUserProfileField(friendId, field, store) {
|
|
74
|
+
const profile = await getUserProfile(friendId, store);
|
|
75
|
+
if (!profile)
|
|
76
|
+
return undefined;
|
|
77
|
+
return profile[field];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Delete a user profile from the vault.
|
|
81
|
+
* Returns true if the profile was deleted, false if it didn't exist.
|
|
82
|
+
*/
|
|
83
|
+
async function deleteUserProfile(friendId, store) {
|
|
84
|
+
(0, runtime_1.emitNervesEvent)({
|
|
85
|
+
event: "repertoire.user_profile_delete",
|
|
86
|
+
component: "repertoire",
|
|
87
|
+
message: `deleting user profile for ${friendId}`,
|
|
88
|
+
meta: { friendId },
|
|
89
|
+
});
|
|
90
|
+
return store.delete(profileKey(friendId));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Update specific fields on a user profile, merging with existing data.
|
|
94
|
+
* Creates the profile if it doesn't exist.
|
|
95
|
+
* Preferences are merged (not replaced) at the key level.
|
|
96
|
+
*/
|
|
97
|
+
async function updateUserProfileFields(friendId, fields, store) {
|
|
98
|
+
(0, runtime_1.emitNervesEvent)({
|
|
99
|
+
event: "repertoire.user_profile_update",
|
|
100
|
+
component: "repertoire",
|
|
101
|
+
message: `updating user profile fields for ${friendId}`,
|
|
102
|
+
meta: { friendId, fieldCount: Object.keys(fields).length },
|
|
103
|
+
});
|
|
104
|
+
const existing = await getUserProfile(friendId, store);
|
|
105
|
+
let merged;
|
|
106
|
+
if (existing) {
|
|
107
|
+
// Merge preferences at key level
|
|
108
|
+
const mergedPreferences = {
|
|
109
|
+
...existing.preferences,
|
|
110
|
+
...(fields.preferences ?? {}),
|
|
111
|
+
};
|
|
112
|
+
merged = { ...existing, ...fields, preferences: mergedPreferences };
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
merged = fields;
|
|
116
|
+
}
|
|
117
|
+
await storeUserProfile(friendId, merged, store);
|
|
118
|
+
}
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.239",
|
|
4
4
|
"main": "dist/heart/daemon/ouro-entry.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cli": "dist/heart/daemon/ouro-bot-entry.js",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"ink": "^3.2.0",
|
|
43
43
|
"openai": "^6.27.0",
|
|
44
44
|
"react": "^17.0.2",
|
|
45
|
-
"semver": "^7.7.4"
|
|
45
|
+
"semver": "^7.7.4",
|
|
46
|
+
"stripe": "^22.0.0"
|
|
46
47
|
},
|
|
47
48
|
"repository": {
|
|
48
49
|
"type": "git",
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Agent Commerce Skill
|
|
2
|
+
|
|
3
|
+
How to book, purchase, and pay for things on behalf of humans.
|
|
4
|
+
|
|
5
|
+
## Three Patterns
|
|
6
|
+
|
|
7
|
+
### Pattern A: API (Structured, Preferred)
|
|
8
|
+
|
|
9
|
+
For services with direct API access: Duffel flights, LiteAPI hotels.
|
|
10
|
+
|
|
11
|
+
1. Search using the API tool (`flight_search`, LiteAPI MCP)
|
|
12
|
+
2. Present options to the human with prices and details
|
|
13
|
+
3. Human approves a specific option and price
|
|
14
|
+
4. Book using the API tool with passenger data from `user_profile_get`
|
|
15
|
+
5. Create a single-use virtual card via `stripe_create_card`
|
|
16
|
+
6. Complete payment through the API
|
|
17
|
+
7. Deactivate the card via `stripe_deactivate_card`
|
|
18
|
+
8. Confirm booking to the human
|
|
19
|
+
|
|
20
|
+
**Key tools**: `flight_search`, `flight_book`, `flight_cancel`, `user_profile_get`, `user_profile_store`, `stripe_create_card`, `stripe_deactivate_card`, `stripe_list_cards`
|
|
21
|
+
|
|
22
|
+
### Pattern B: Browser (Best-Effort)
|
|
23
|
+
|
|
24
|
+
For sites without API access, use browser automation via Playwright MCP.
|
|
25
|
+
|
|
26
|
+
1. Navigate to the booking site
|
|
27
|
+
2. Search for the requested service
|
|
28
|
+
3. Fill forms using data from `user_profile_get`
|
|
29
|
+
4. Use a virtual card from `stripe_create_card` for payment
|
|
30
|
+
5. If blocked by anti-bot measures, fall back to Pattern C
|
|
31
|
+
6. Complete and confirm the booking
|
|
32
|
+
|
|
33
|
+
**Limitations**: Browser automation is fragile. Sites may block, layouts change, CAPTCHAs appear. Always have Pattern C as fallback.
|
|
34
|
+
|
|
35
|
+
### Pattern C: Link-Only (Primary for Hostile Sites)
|
|
36
|
+
|
|
37
|
+
For sites that block automation or require complex human interaction.
|
|
38
|
+
|
|
39
|
+
1. Research the best options using browser tools or API tools
|
|
40
|
+
2. Prepare a curated link with pre-filled parameters where possible
|
|
41
|
+
3. Send the link to the human with a summary of what to book
|
|
42
|
+
4. Human completes the booking in their own browser
|
|
43
|
+
|
|
44
|
+
**When to use**: Always use Pattern C as the primary approach for sites known to block automation (most airline direct sites, hotel chains, rental car sites). Pattern B is best-effort, not reliable.
|
|
45
|
+
|
|
46
|
+
## Payment autonomy Levels
|
|
47
|
+
|
|
48
|
+
- **Level 0**: No autonomous payments. Agent researches, human pays manually.
|
|
49
|
+
- **Level 1**: Agent creates virtual cards, human approves each transaction explicitly.
|
|
50
|
+
- **Level 2**: Agent can book pre-approved items (within budget, approved categories) without per-transaction approval.
|
|
51
|
+
- **Level 3**: Full delegation with spending limits. Agent manages a budget and books as needed.
|
|
52
|
+
|
|
53
|
+
Default is Level 1. Level changes require explicit human approval.
|
|
54
|
+
|
|
55
|
+
## Error Handling
|
|
56
|
+
|
|
57
|
+
### Price Change Guard
|
|
58
|
+
Before completing a booking, verify the final price matches the approved price within 5%. If the price changed more than 5%, stop and report to the human. Never pay a price the human didn't approve.
|
|
59
|
+
|
|
60
|
+
### Partial Failure Reporting
|
|
61
|
+
When booking involves multiple services (e.g., flight + hotel), each service may succeed or fail independently — this is a partial failure scenario. Report the status of each service separately. **Never auto-cancel a successful booking because a related booking failed.** Let the human decide.
|
|
62
|
+
|
|
63
|
+
Example: "Flight SFO-JFK booked (confirmation: ABC123). Hotel booking failed: no availability for those dates. Would you like me to search for alternative hotels?"
|
|
64
|
+
|
|
65
|
+
### Refund Flow
|
|
66
|
+
If a booking fails after card creation:
|
|
67
|
+
1. Deactivate the virtual card immediately
|
|
68
|
+
2. Report the failure to the human
|
|
69
|
+
3. If a charge was made, note it for the human to follow up with the provider
|
|
70
|
+
|
|
71
|
+
## CAPTCHA Handling
|
|
72
|
+
|
|
73
|
+
When a CAPTCHA appears during browser automation (Pattern B):
|
|
74
|
+
1. Take a screenshot and send it to the human
|
|
75
|
+
2. Explain what page you're on and what you were trying to do
|
|
76
|
+
3. Ask the human to solve the CAPTCHA in their own browser
|
|
77
|
+
4. Switch to Pattern C (link-only) for this transaction
|
|
78
|
+
|
|
79
|
+
Never attempt to solve CAPTCHAs programmatically.
|
|
80
|
+
|
|
81
|
+
## Card Number Isolation
|
|
82
|
+
|
|
83
|
+
Card numbers must NEVER appear in:
|
|
84
|
+
- Tool return values shown to the model
|
|
85
|
+
- Nerves events or logs
|
|
86
|
+
- Chat messages to the human
|
|
87
|
+
- Any stored state or memory
|
|
88
|
+
|
|
89
|
+
The only place card numbers exist is inside the Stripe client's internal payment flow functions, scoped to a single function call. The model only ever sees card IDs and last-4 digits.
|
|
90
|
+
|
|
91
|
+
## Profile Data Usage
|
|
92
|
+
|
|
93
|
+
Access profile data only when needed for the current transaction:
|
|
94
|
+
- `user_profile_get` to retrieve specific fields (never dump full profile)
|
|
95
|
+
- Passport data only for international bookings
|
|
96
|
+
- Loyalty program numbers only when booking with that program
|
|
97
|
+
- Emergency contact only when the booking service requires it
|
|
98
|
+
|
|
99
|
+
## Self-Test
|
|
100
|
+
|
|
101
|
+
Before first use, run the commerce self-test to verify all services are configured:
|
|
102
|
+
- Stripe: creates and deactivates a test virtual card
|
|
103
|
+
- Duffel: runs a test flight search
|
|
104
|
+
- LiteAPI: verifies API key in vault
|
|
105
|
+
|
|
106
|
+
Report results to the human with actionable next steps for any failures.
|