@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.
@@ -118,7 +118,7 @@ exports.configToolDefinitions = [
118
118
  type: "function",
119
119
  function: {
120
120
  name: "update_config",
121
- description: "Update a Tier 1 (self-service) agent configuration value immediately. For Tier 2 keys that require operator approval, use propose_config instead. Tier 3 (operator-only) keys are refused.",
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
- // Tier 3: refuse
156
- if (entry.tier === 3) {
157
- (0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.update_config_refused", message: `refused T3 change to ${a.path}`, meta: { path: a.path, tier: 3 } });
158
- return `Refused: "${a.path}" is an operator-only (Tier 3) key. ${entry.description} Only the operator can change this value directly in agent.json.`;
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 T1 change immediately
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 --