@ouro.bot/cli 0.1.0-alpha.236 → 0.1.0-alpha.238
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 +5 -3
- package/dist/heart/config-registry.js +26 -27
- package/dist/heart/core.js +0 -17
- package/dist/heart/daemon/cli-render.js +129 -46
- package/dist/heart/daemon/daemon.js +4 -0
- package/dist/heart/daemon/socket-client.js +1 -0
- package/dist/nerves/coverage/file-completeness.js +2 -0
- package/dist/repertoire/ado-semantic.js +0 -5
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- 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-config.js +6 -87
- package/dist/repertoire/tools-flight.js +224 -0
- package/dist/repertoire/tools-github.js +0 -1
- package/dist/repertoire/tools-stripe.js +180 -0
- package/dist/repertoire/tools-teams.js +0 -3
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/tools.js +1 -15
- package/dist/repertoire/user-profile.js +118 -0
- package/dist/senses/inner-dialog.js +0 -1
- package/dist/senses/teams.js +0 -48
- 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
|
@@ -118,7 +118,7 @@ exports.configToolDefinitions = [
|
|
|
118
118
|
type: "function",
|
|
119
119
|
function: {
|
|
120
120
|
name: "update_config",
|
|
121
|
-
description: "Update
|
|
121
|
+
description: "Update an agent configuration value. Managed keys (version, enabled) cannot be modified.",
|
|
122
122
|
parameters: {
|
|
123
123
|
type: "object",
|
|
124
124
|
properties: {
|
|
@@ -152,15 +152,10 @@ exports.configToolDefinitions = [
|
|
|
152
152
|
return `Error: validation failed for "${a.path}": ${validationError}`;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
//
|
|
156
|
-
if (entry.tier ===
|
|
157
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_refused", message: `refused
|
|
158
|
-
return `
|
|
159
|
-
}
|
|
160
|
-
// Tier 2: refuse — must use propose_config
|
|
161
|
-
if (entry.tier === 2) {
|
|
162
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_refused", message: `refused T2 change to ${a.path} — use propose_config`, meta: { path: a.path, tier: 2 } });
|
|
163
|
-
return `Refused: "${a.path}" is a Tier 2 (proposal) key that requires operator approval. Use propose_config instead of update_config to propose this change.`;
|
|
155
|
+
// Managed: refuse
|
|
156
|
+
if (entry.tier === "managed") {
|
|
157
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_refused", message: `refused managed change to ${a.path}`, meta: { path: a.path, tier: "managed" } });
|
|
158
|
+
return `This key is managed by the harness and cannot be modified directly.`;
|
|
164
159
|
}
|
|
165
160
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
166
161
|
const configPath = path.join(agentRoot, "agent.json");
|
|
@@ -172,7 +167,7 @@ exports.configToolDefinitions = [
|
|
|
172
167
|
/* v8 ignore next -- defensive: agent.json read failure in update_config @preserve */
|
|
173
168
|
return `Error: failed to read agent.json at ${configPath}`;
|
|
174
169
|
}
|
|
175
|
-
// Apply
|
|
170
|
+
// Apply change immediately
|
|
176
171
|
const parts = entry.path.split(".");
|
|
177
172
|
let target = rawConfig;
|
|
178
173
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
@@ -187,80 +182,4 @@ exports.configToolDefinitions = [
|
|
|
187
182
|
return `Success: "${a.path}" updated to ${JSON.stringify(parsedValue)}. Change applied immediately.`;
|
|
188
183
|
},
|
|
189
184
|
},
|
|
190
|
-
{
|
|
191
|
-
tool: {
|
|
192
|
-
type: "function",
|
|
193
|
-
function: {
|
|
194
|
-
name: "propose_config",
|
|
195
|
-
description: "Propose a change to a Tier 2 (proposal) agent configuration value. Requires operator approval before the change is applied. For Tier 1 self-service keys, use update_config instead.",
|
|
196
|
-
parameters: {
|
|
197
|
-
type: "object",
|
|
198
|
-
properties: {
|
|
199
|
-
path: { type: "string", description: "Config key in dot-notation (e.g., 'context.maxTokens', 'humanFacing.model')" },
|
|
200
|
-
value: { type: "string", description: "New value as JSON (e.g., '120000', '\"azure\"', '{\"enabled\": true}')" },
|
|
201
|
-
},
|
|
202
|
-
required: ["path", "value"],
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
confirmationRequired: true,
|
|
207
|
-
confirmationAlwaysRequired: true,
|
|
208
|
-
handler: (a) => {
|
|
209
|
-
const entry = (0, config_registry_1.getRegistryEntry)(a.path);
|
|
210
|
-
if (!entry) {
|
|
211
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_error", message: `unknown config path: ${a.path}`, meta: { path: a.path } });
|
|
212
|
-
return `Error: unknown config path "${a.path}". Use read_config to see available paths.`;
|
|
213
|
-
}
|
|
214
|
-
let parsedValue;
|
|
215
|
-
try {
|
|
216
|
-
parsedValue = JSON.parse(a.value);
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_error", message: `invalid JSON value for ${a.path}`, meta: { path: a.path, value: a.value } });
|
|
220
|
-
return `Error: invalid JSON value. Provide value as valid JSON (e.g., 120000, "azure", {"enabled": true}).`;
|
|
221
|
-
}
|
|
222
|
-
// Validate value against registry entry's validator
|
|
223
|
-
/* v8 ignore next -- all current entries have validators; guard kept for future entries without one @preserve */
|
|
224
|
-
if (entry.validate) {
|
|
225
|
-
const validationError = entry.validate(parsedValue);
|
|
226
|
-
if (validationError) {
|
|
227
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_error", message: `validation failed for ${a.path}: ${validationError}`, meta: { path: a.path, value: a.value, validationError } });
|
|
228
|
-
return `Error: validation failed for "${a.path}": ${validationError}`;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Tier 3: refuse
|
|
232
|
-
if (entry.tier === 3) {
|
|
233
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_refused", message: `refused T3 change to ${a.path}`, meta: { path: a.path, tier: 3 } });
|
|
234
|
-
return `Refused: "${a.path}" is an operator-only (Tier 3) key. ${entry.description} Only the operator can change this value directly in agent.json.`;
|
|
235
|
-
}
|
|
236
|
-
// Tier 1: refuse — use update_config for self-service keys
|
|
237
|
-
if (entry.tier === 1) {
|
|
238
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_refused", message: `refused T1 change to ${a.path} — use update_config`, meta: { path: a.path, tier: 1 } });
|
|
239
|
-
return `Refused: "${a.path}" is a Tier 1 (self-service) key. Use update_config instead — no operator approval needed.`;
|
|
240
|
-
}
|
|
241
|
-
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
242
|
-
const configPath = path.join(agentRoot, "agent.json");
|
|
243
|
-
let rawConfig;
|
|
244
|
-
try {
|
|
245
|
-
rawConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
246
|
-
}
|
|
247
|
-
catch {
|
|
248
|
-
/* v8 ignore next -- defensive: agent.json read failure in propose_config @preserve */
|
|
249
|
-
return `Error: failed to read agent.json at ${configPath}`;
|
|
250
|
-
}
|
|
251
|
-
// Apply T2 change (operator confirmation already obtained via confirmationAlwaysRequired)
|
|
252
|
-
const parts = entry.path.split(".");
|
|
253
|
-
let target = rawConfig;
|
|
254
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
255
|
-
if (!(parts[i] in target) || typeof target[parts[i]] !== "object" || target[parts[i]] === null) {
|
|
256
|
-
target[parts[i]] = {};
|
|
257
|
-
}
|
|
258
|
-
target = target[parts[i]];
|
|
259
|
-
}
|
|
260
|
-
target[parts[parts.length - 1]] = parsedValue;
|
|
261
|
-
fs.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n", "utf-8");
|
|
262
|
-
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.propose_config_applied", message: `applied approved config change to ${a.path}`, meta: { path: a.path, tier: entry.tier, value: parsedValue } });
|
|
263
|
-
return `Success: "${a.path}" updated to ${JSON.stringify(parsedValue)}. Change approved and applied.`;
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
185
|
];
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.flightToolDefinitions = void 0;
|
|
4
|
+
exports.resetDuffelClient = resetDuffelClient;
|
|
5
|
+
const duffel_client_1 = require("./duffel-client");
|
|
6
|
+
const user_profile_1 = require("./user-profile");
|
|
7
|
+
const credential_access_1 = require("./credential-access");
|
|
8
|
+
const runtime_1 = require("../nerves/runtime");
|
|
9
|
+
// Lazy-initialized Duffel client singleton
|
|
10
|
+
let _duffelClient = null;
|
|
11
|
+
async function getDuffelClient() {
|
|
12
|
+
if (!_duffelClient) {
|
|
13
|
+
_duffelClient = await (0, duffel_client_1.createDuffelClient)();
|
|
14
|
+
}
|
|
15
|
+
return _duffelClient;
|
|
16
|
+
}
|
|
17
|
+
function requireFamilyContext(ctx) {
|
|
18
|
+
if (!ctx?.context?.friend?.id) {
|
|
19
|
+
return "no friend context — cannot access flight tools.";
|
|
20
|
+
}
|
|
21
|
+
if (ctx.context.friend.trustLevel !== "family") {
|
|
22
|
+
return "booking and cancellation require family trust level.";
|
|
23
|
+
}
|
|
24
|
+
return { friendId: ctx.context.friend.id };
|
|
25
|
+
}
|
|
26
|
+
function requireFriendContext(ctx) {
|
|
27
|
+
if (!ctx?.context?.friend?.id) {
|
|
28
|
+
return "no friend context — cannot search flights.";
|
|
29
|
+
}
|
|
30
|
+
return { friendId: ctx.context.friend.id };
|
|
31
|
+
}
|
|
32
|
+
exports.flightToolDefinitions = [
|
|
33
|
+
{
|
|
34
|
+
tool: {
|
|
35
|
+
type: "function",
|
|
36
|
+
function: {
|
|
37
|
+
name: "flight_search",
|
|
38
|
+
description: "Search for flights between two airports. Returns available offers with prices.",
|
|
39
|
+
parameters: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
origin: { type: "string", description: "Origin airport IATA code (e.g. 'SFO')" },
|
|
43
|
+
destination: { type: "string", description: "Destination airport IATA code (e.g. 'JFK')" },
|
|
44
|
+
departure_date: { type: "string", description: "Departure date (YYYY-MM-DD)" },
|
|
45
|
+
return_date: { type: "string", description: "Return date for round trips (optional)" },
|
|
46
|
+
passengers: { type: "string", description: "Number of adult passengers (default '1')" },
|
|
47
|
+
cabin_class: { type: "string", description: "Cabin class: economy, premium_economy, business, first" },
|
|
48
|
+
},
|
|
49
|
+
required: ["origin", "destination", "departure_date"],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
handler: async (args, ctx) => {
|
|
54
|
+
(0, runtime_1.emitNervesEvent)({
|
|
55
|
+
component: "repertoire",
|
|
56
|
+
event: "repertoire.tool_flight_search",
|
|
57
|
+
message: "flight_search invoked",
|
|
58
|
+
meta: { tool: "flight_search", origin: args.origin, destination: args.destination },
|
|
59
|
+
});
|
|
60
|
+
const guard = requireFriendContext(ctx);
|
|
61
|
+
if (typeof guard === "string")
|
|
62
|
+
return guard;
|
|
63
|
+
try {
|
|
64
|
+
const client = await getDuffelClient();
|
|
65
|
+
const passengerCount = parseInt(args.passengers || "1", 10);
|
|
66
|
+
const passengers = Array.from({ length: passengerCount }, () => ({ type: "adult" }));
|
|
67
|
+
const offers = await client.searchFlights({
|
|
68
|
+
origin: args.origin,
|
|
69
|
+
destination: args.destination,
|
|
70
|
+
departureDate: args.departure_date,
|
|
71
|
+
returnDate: args.return_date,
|
|
72
|
+
passengers,
|
|
73
|
+
cabinClass: args.cabin_class,
|
|
74
|
+
});
|
|
75
|
+
if (offers.length === 0) {
|
|
76
|
+
return "no flights found for those criteria.";
|
|
77
|
+
}
|
|
78
|
+
return JSON.stringify(offers, null, 2);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
/* v8 ignore next -- defensive @preserve */
|
|
82
|
+
return `flight search error: ${err instanceof Error ? err.message : String(err)}`;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
summaryKeys: ["origin", "destination", "departure_date"],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
tool: {
|
|
89
|
+
type: "function",
|
|
90
|
+
function: {
|
|
91
|
+
name: "flight_hold",
|
|
92
|
+
description: "Hold a flight offer for a short period before committing to book. Not all airlines support holds.",
|
|
93
|
+
parameters: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
offer_id: { type: "string", description: "The Duffel offer ID to hold" },
|
|
97
|
+
},
|
|
98
|
+
required: ["offer_id"],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
handler: async (args, ctx) => {
|
|
103
|
+
(0, runtime_1.emitNervesEvent)({
|
|
104
|
+
component: "repertoire",
|
|
105
|
+
event: "repertoire.tool_flight_hold",
|
|
106
|
+
message: "flight_hold invoked",
|
|
107
|
+
meta: { tool: "flight_hold", offerId: args.offer_id },
|
|
108
|
+
});
|
|
109
|
+
const guard = requireFamilyContext(ctx);
|
|
110
|
+
if (typeof guard === "string")
|
|
111
|
+
return guard;
|
|
112
|
+
// Hold functionality would call Duffel's offer hold API.
|
|
113
|
+
// For pre-build, we return a structured acknowledgment.
|
|
114
|
+
return JSON.stringify({
|
|
115
|
+
status: "hold_requested",
|
|
116
|
+
offerId: args.offer_id,
|
|
117
|
+
message: "Hold requested. Confirm or cancel before the hold expires.",
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
summaryKeys: ["offer_id"],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
tool: {
|
|
124
|
+
type: "function",
|
|
125
|
+
function: {
|
|
126
|
+
name: "flight_book",
|
|
127
|
+
description: "Book a flight. Pulls passenger name/DOB/passport from the user's profile. Creates a virtual card, books the flight, then deactivates the card. Requires family trust level.",
|
|
128
|
+
parameters: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
offer_id: { type: "string", description: "The Duffel offer ID to book" },
|
|
132
|
+
amount: { type: "string", description: "Expected total amount in dollars" },
|
|
133
|
+
currency: { type: "string", description: "Currency code (e.g. 'usd')" },
|
|
134
|
+
},
|
|
135
|
+
required: ["offer_id", "amount", "currency"],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
handler: async (args, ctx) => {
|
|
140
|
+
(0, runtime_1.emitNervesEvent)({
|
|
141
|
+
component: "repertoire",
|
|
142
|
+
event: "repertoire.tool_flight_book",
|
|
143
|
+
message: "flight_book invoked",
|
|
144
|
+
meta: { tool: "flight_book", offerId: args.offer_id },
|
|
145
|
+
});
|
|
146
|
+
const guard = requireFamilyContext(ctx);
|
|
147
|
+
if (typeof guard === "string")
|
|
148
|
+
return guard;
|
|
149
|
+
try {
|
|
150
|
+
const store = (0, credential_access_1.getCredentialStore)();
|
|
151
|
+
// Get passenger data from profile
|
|
152
|
+
const legalName = await (0, user_profile_1.getUserProfileField)(guard.friendId, "legalName", store);
|
|
153
|
+
if (!legalName) {
|
|
154
|
+
return "passenger profile not found — please store your profile first using user_profile_store.";
|
|
155
|
+
}
|
|
156
|
+
const dateOfBirth = await (0, user_profile_1.getUserProfileField)(guard.friendId, "dateOfBirth", store);
|
|
157
|
+
const passport = await (0, user_profile_1.getUserProfileField)(guard.friendId, "passport", store);
|
|
158
|
+
const client = await getDuffelClient();
|
|
159
|
+
const result = await client.createOrder({
|
|
160
|
+
offerId: args.offer_id,
|
|
161
|
+
passengers: [{
|
|
162
|
+
type: "adult",
|
|
163
|
+
givenName: legalName.first,
|
|
164
|
+
familyName: legalName.last,
|
|
165
|
+
/* v8 ignore next -- reason @preserve */
|
|
166
|
+
dateOfBirth: dateOfBirth ?? "1990-01-01",
|
|
167
|
+
passportNumber: passport?.number,
|
|
168
|
+
passportCountry: passport?.country,
|
|
169
|
+
passportExpiry: passport?.expiry,
|
|
170
|
+
}],
|
|
171
|
+
amount: parseFloat(args.amount),
|
|
172
|
+
currency: args.currency,
|
|
173
|
+
});
|
|
174
|
+
return JSON.stringify(result, null, 2);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
/* v8 ignore next -- defensive @preserve */
|
|
178
|
+
return `booking error: ${err instanceof Error ? err.message : String(err)}`;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
summaryKeys: ["offer_id", "amount"],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
tool: {
|
|
185
|
+
type: "function",
|
|
186
|
+
function: {
|
|
187
|
+
name: "flight_cancel",
|
|
188
|
+
description: "Cancel a flight booking. Not all bookings are cancellable. Requires family trust level.",
|
|
189
|
+
parameters: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
order_id: { type: "string", description: "The Duffel order ID to cancel" },
|
|
193
|
+
},
|
|
194
|
+
required: ["order_id"],
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
handler: async (args, ctx) => {
|
|
199
|
+
(0, runtime_1.emitNervesEvent)({
|
|
200
|
+
component: "repertoire",
|
|
201
|
+
event: "repertoire.tool_flight_cancel",
|
|
202
|
+
message: "flight_cancel invoked",
|
|
203
|
+
meta: { tool: "flight_cancel", orderId: args.order_id },
|
|
204
|
+
});
|
|
205
|
+
const guard = requireFamilyContext(ctx);
|
|
206
|
+
if (typeof guard === "string")
|
|
207
|
+
return guard;
|
|
208
|
+
try {
|
|
209
|
+
const client = await getDuffelClient();
|
|
210
|
+
const result = await client.cancelOrder(args.order_id);
|
|
211
|
+
return JSON.stringify(result, null, 2);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
/* v8 ignore next -- defensive @preserve */
|
|
215
|
+
return `cancellation error: ${err instanceof Error ? err.message : String(err)}`;
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
summaryKeys: ["order_id"],
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
/** Reset the Duffel client singleton (for testing). */
|
|
222
|
+
function resetDuffelClient() {
|
|
223
|
+
_duffelClient = null;
|
|
224
|
+
}
|
|
@@ -42,7 +42,6 @@ exports.githubToolDefinitions = [
|
|
|
42
42
|
return (0, github_client_1.githubRequest)(ctx.githubToken, "POST", `/repos/${owner}/${repo}/issues`, JSON.stringify(payload));
|
|
43
43
|
},
|
|
44
44
|
integration: "github",
|
|
45
|
-
confirmationRequired: true,
|
|
46
45
|
summaryKeys: ["title"],
|
|
47
46
|
},
|
|
48
47
|
];
|
|
@@ -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
|
+
}
|
|
@@ -64,7 +64,6 @@ exports.teamsToolDefinitions = [
|
|
|
64
64
|
return (0, graph_client_1.graphRequest)(ctx.graphToken, args.method, args.path, args.body);
|
|
65
65
|
},
|
|
66
66
|
integration: "graph",
|
|
67
|
-
confirmationRequired: true,
|
|
68
67
|
summaryKeys: ["method", "path"],
|
|
69
68
|
},
|
|
70
69
|
// -- Generic ADO tools --
|
|
@@ -126,7 +125,6 @@ exports.teamsToolDefinitions = [
|
|
|
126
125
|
return (0, ado_client_1.adoRequest)(ctx.adoToken, args.method, args.organization, args.path, args.body, args.host);
|
|
127
126
|
},
|
|
128
127
|
integration: "ado",
|
|
129
|
-
confirmationRequired: true,
|
|
130
128
|
summaryKeys: ["method", "organization", "path"],
|
|
131
129
|
},
|
|
132
130
|
// -- Convenience aliases (backward compat) --
|
|
@@ -237,7 +235,6 @@ exports.teamsToolDefinitions = [
|
|
|
237
235
|
}
|
|
238
236
|
},
|
|
239
237
|
/* v8 ignore stop */
|
|
240
|
-
confirmationRequired: true,
|
|
241
238
|
summaryKeys: ["user_name", "user_id"],
|
|
242
239
|
},
|
|
243
240
|
// -- Documentation tools --
|