spaps 0.7.3 → 0.7.5

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.
Files changed (48) hide show
  1. package/AI_TOOLS.json +10 -11
  2. package/README.md +216 -36
  3. package/assets/local-runtime/Dockerfile +28 -0
  4. package/assets/local-runtime/alembic/env.py +101 -0
  5. package/assets/local-runtime/alembic/path_bootstrap.py +71 -0
  6. package/assets/local-runtime/alembic/versions/000000000001_baseline_consolidated_schema.py +1076 -0
  7. package/assets/local-runtime/alembic/versions/000000000002_fix_column_types_to_match_prod.py +83 -0
  8. package/assets/local-runtime/alembic/versions/000000000003_fix_email_template_key_uniqueness.py +49 -0
  9. package/assets/local-runtime/alembic/versions/000000000004_add_hold_duration_minutes_to_dayrate_config.py +30 -0
  10. package/assets/local-runtime/alembic/versions/000000000005_resource_scoped_entitlements.py +77 -0
  11. package/assets/local-runtime/alembic/versions/000000000006_cfo_rbac_add_is_admin.py +37 -0
  12. package/assets/local-runtime/alembic/versions/000000000007_agent_approvals.py +158 -0
  13. package/assets/local-runtime/alembic/versions/000000000008_add_company_id_to_cfo_connections.py +35 -0
  14. package/assets/local-runtime/alembic/versions/000000000009_tx_signing.py +62 -0
  15. package/assets/local-runtime/alembic/versions/000000000010_affiliate_referrals.py +235 -0
  16. package/assets/local-runtime/alembic/versions/000000000011_checkin_call_booking.py +137 -0
  17. package/assets/local-runtime/alembic/versions/000000000012_subscription_application_scoping.py +55 -0
  18. package/assets/local-runtime/alembic/versions/000000000013_refresh_token_anomaly_context.py +61 -0
  19. package/assets/local-runtime/alembic/versions/000000000014_buildooor_dayrate_hire_schedule.py +39 -0
  20. package/assets/local-runtime/alembic/versions/000000000015_support_telemetry_platform.py +112 -0
  21. package/assets/local-runtime/alembic/versions/000000000016_issue_reporting_platform.py +54 -0
  22. package/assets/local-runtime/alembic/versions/000000000017_issue_reporting_platform_import_tracking.py +44 -0
  23. package/assets/local-runtime/alembic/versions/000000000018_authorization_policy_engine.py +76 -0
  24. package/assets/local-runtime/alembic.ini +47 -0
  25. package/assets/local-runtime/docker-compose.yml +61 -0
  26. package/assets/local-runtime/manifest.json +8 -0
  27. package/assets/local-runtime/scripts/container-entrypoint.sh +13 -0
  28. package/assets/local-runtime/scripts/fetch-prod-db.sh +112 -0
  29. package/assets/local-runtime/scripts/run-migrations.sh +96 -0
  30. package/package.json +2 -1
  31. package/src/ai-helper.js +176 -234
  32. package/src/ai-tool-spec.js +52 -20
  33. package/src/auth/api-key.js +119 -0
  34. package/src/auth/client-id.js +136 -0
  35. package/src/auth/client.js +169 -0
  36. package/src/auth/credentials.js +110 -0
  37. package/src/auth/device-flow.js +159 -0
  38. package/src/auth/env.js +57 -0
  39. package/src/auth/handlers.js +462 -0
  40. package/src/auth/http.js +74 -0
  41. package/src/cli-dispatcher.js +134 -21
  42. package/src/docs-system.js +7 -7
  43. package/src/fixture-kernel.js +1143 -0
  44. package/src/handlers.js +202 -11
  45. package/src/help-system.js +2 -0
  46. package/src/local-runtime.js +258 -0
  47. package/src/local-server.js +597 -199
  48. package/src/project-scaffolder.js +185 -45
package/src/ai-helper.js CHANGED
@@ -1,273 +1,215 @@
1
- /**
2
- * SPAPS AI Agent Helper
3
- * Provides AI-friendly outputs and quick commands
4
- */
5
-
6
- const chalk = require('chalk');
7
-
8
1
  const { DEFAULT_PORT } = require('./config');
2
+ const { getServerRuntime, readSelfServicePassword } = require('./local-runtime');
3
+
4
+ function buildProvisioningCommand(template, port) {
5
+ return `SELF_SERVICE_PASSWORD=your-password npx spaps create demo-${template === 'node' ? 'api' : 'app'} --template ${template} --port ${port}`;
6
+ }
9
7
 
10
- function getQuickStartInstructions(port = DEFAULT_PORT) {
8
+ function buildLocalModeInstructions(runtime) {
11
9
  return {
12
10
  success: true,
11
+ server: {
12
+ running: true,
13
+ url: runtime.url,
14
+ docs: runtime.docs,
15
+ local_mode_active: true,
16
+ environment: runtime.local_mode.environment,
17
+ spaps_local_mode_env: runtime.local_mode.spaps_local_mode_env,
18
+ },
19
+ auth: {
20
+ local_mode: true,
21
+ api_key_required: false,
22
+ mode_source: '/health/local-mode',
23
+ test_users: runtime.local_mode.test_users,
24
+ test_application: runtime.local_mode.test_application,
25
+ hints: runtime.local_mode.hints,
26
+ },
27
+ summary: [
28
+ 'Install the SDK with `npm install spaps-sdk`.',
29
+ `Point the client at ${runtime.url}.`,
30
+ 'Use the local-mode hints from `/health/local-mode` if you need a specific test persona.',
31
+ ],
13
32
  instructions: {
14
- step1: {
15
- description: "Install SDK",
16
- command: "npm install spaps-sdk",
17
- verify: "npm list spaps-sdk"
33
+ install_sdk: {
34
+ description: 'Install the SDK',
35
+ command: 'npm install spaps-sdk',
18
36
  },
19
- step2: {
20
- description: "Create test file",
21
- filename: "test-spaps.js",
22
- content: `const { SweetPotatoSDK } = require('spaps-sdk');
23
-
24
- async function test() {
25
- const sdk = new SweetPotatoSDK({ apiUrl: 'http://localhost:${port}' });
37
+ create_client: {
38
+ description: 'Create a client for local mode',
39
+ filename: 'test-spaps.js',
40
+ content: `const { SPAPSClient } = require('spaps-sdk');
26
41
 
27
- // Test email/password auth (local mode accepts any credentials)
28
- const auth = await sdk.auth.signInWithPassword({ email: 'test@example.com', password: 'password' });
29
- console.log('✅ Login successful:', auth.user.email);
42
+ const spaps = new SPAPSClient({ apiUrl: '${runtime.url}' });
30
43
 
31
- // Test authenticated request
32
- const user = await sdk.auth.getCurrentUser();
33
- console.log('✅ Got user:', user.email);
34
-
35
- return { success: true, user };
44
+ async function main() {
45
+ const status = await fetch('${runtime.url}/health/local-mode').then((res) => res.json());
46
+ console.log(status.data);
36
47
  }
37
48
 
38
- test().then(console.log).catch(console.error);`
49
+ main().catch(console.error);
50
+ `,
51
+ },
52
+ verify_server: {
53
+ description: 'Confirm local mode status',
54
+ command: `curl -s ${runtime.url}/health/local-mode | jq '.'`,
39
55
  },
40
- step3: {
41
- description: "Run test",
42
- command: "node test-spaps.js",
43
- expected_output: {
44
- success: true,
45
- user: {
46
- id: "00000000-0000-0000-0000-000000000002",
47
- email: "test@example.com"
48
- }
49
- }
50
- }
51
56
  },
52
- endpoints: [
53
- {
54
- method: "POST",
55
- path: "/api/auth/login",
56
- body: { email: "string", password: "string" },
57
- response: { access_token: "string", refresh_token: "string", user: "object" }
57
+ };
58
+ }
59
+
60
+ function buildProvisionedModeInstructions(runtime, port) {
61
+ const hasPassword = Boolean(readSelfServicePassword());
62
+
63
+ return {
64
+ success: true,
65
+ server: {
66
+ running: true,
67
+ url: runtime.url,
68
+ docs: runtime.docs,
69
+ local_mode_active: false,
70
+ environment: runtime.local_mode.environment,
71
+ spaps_local_mode_env: runtime.local_mode.spaps_local_mode_env,
72
+ },
73
+ auth: {
74
+ local_mode: false,
75
+ api_key_required: true,
76
+ mode_source: '/health/local-mode',
77
+ header: 'X-API-Key',
78
+ env: 'SPAPS_API_KEY',
79
+ self_service_password_env: 'SELF_SERVICE_PASSWORD',
80
+ },
81
+ summary: [
82
+ 'Install the SDK with `npm install spaps-sdk`.',
83
+ 'Provision a local application before testing authenticated flows.',
84
+ `Send \`X-API-Key\` when /health/local-mode reports \`local_mode_active: false\`.`,
85
+ ],
86
+ instructions: {
87
+ install_sdk: {
88
+ description: 'Install the SDK',
89
+ command: 'npm install spaps-sdk',
58
90
  },
59
- {
60
- method: "POST",
61
- path: "/api/auth/register",
62
- body: { email: "string", password: "string" },
63
- response: { access_token: "string", refresh_token: "string", user: "object" }
91
+ provision_app: {
92
+ description: hasPassword
93
+ ? 'Provision a local browser app'
94
+ : 'Set SELF_SERVICE_PASSWORD, then provision a local browser app',
95
+ command: buildProvisioningCommand('react', port),
64
96
  },
65
- {
66
- method: "GET",
67
- path: "/api/auth/user",
68
- headers: { Authorization: "Bearer TOKEN" },
69
- response: { id: "string", email: "string", role: "string" }
97
+ create_client: {
98
+ description: 'Create a client that uses a provisioned key',
99
+ filename: 'test-spaps.js',
100
+ content: `const { createServerClient } = require('spaps-sdk');
101
+
102
+ const apiUrl = process.env.SPAPS_API_URL || '${runtime.url}';
103
+ const apiKey = process.env.SPAPS_API_KEY;
104
+
105
+ if (!apiKey) {
106
+ throw new Error('Set SPAPS_API_KEY before testing against a non-local-mode server.');
107
+ }
108
+
109
+ const spaps = createServerClient(apiKey, { apiUrl });
110
+
111
+ async function main() {
112
+ const result = await spaps.auth.signInWithPassword({
113
+ email: 'user@example.com',
114
+ password: 'correct-horse-battery-staple',
115
+ });
116
+ console.log(result.user.id);
117
+ }
118
+
119
+ main().catch(console.error);
120
+ `,
70
121
  },
71
- {
72
- method: "POST",
73
- path: "/api/stripe/checkout-sessions",
74
- body: { price_id: "string", success_url: "string", cancel_url: "string" },
75
- response: { id: "string", url: "string", status: "string" }
122
+ verify_server: {
123
+ description: 'Confirm whether the server is in local mode',
124
+ command: `curl -s ${runtime.url}/health/local-mode | jq '.'`,
76
125
  },
77
- {
78
- method: "GET",
79
- path: "/api/usage/balance",
80
- headers: { Authorization: "Bearer TOKEN" },
81
- response: { balance: "number", currency: "string" }
82
- }
83
- ],
84
- test_commands: {
85
- health_check: `curl http://localhost:${port}/health`,
86
- login: `curl -X POST http://localhost:${port}/api/auth/login -H "Content-Type: application/json" -d '{"email":"test@example.com","password":"password"}'`,
87
- with_sdk: `node -e "const {SweetPotatoSDK}=require('spaps-sdk');const sdk=new SweetPotatoSDK({apiUrl:'http://localhost:${port}'});sdk.auth.signInWithPassword({email:'test@example.com',password:'password'}).then(r=>console.log(JSON.stringify(r.user))).catch(console.error)"`
88
- }
126
+ },
89
127
  };
90
128
  }
91
129
 
92
- function getServerStatus(port = DEFAULT_PORT) {
93
- const http = require('http');
94
-
95
- return new Promise((resolve) => {
96
- const options = {
97
- hostname: 'localhost',
98
- port: port,
99
- path: '/health',
100
- method: 'GET',
101
- timeout: 1000
102
- };
103
-
104
- const req = http.request(options, (res) => {
105
- let data = '';
106
- res.on('data', chunk => data += chunk);
107
- res.on('end', () => {
108
- try {
109
- const parsed = JSON.parse(data);
110
- resolve({
111
- running: true,
112
- port: port,
113
- health: parsed,
114
- url: `http://localhost:${port}`,
115
- docs: `http://localhost:${port}/docs`
116
- });
117
- } catch {
118
- resolve({ running: true, port: port, error: 'Invalid response' });
119
- }
120
- });
121
- });
122
-
123
- req.on('error', () => {
124
- resolve({
125
- running: false,
126
- port: port,
127
- message: 'Server not running',
128
- start_command: `npx spaps local --port ${port}`
129
- });
130
- });
131
-
132
- req.on('timeout', () => {
133
- req.destroy();
134
- resolve({
135
- running: false,
136
- port: port,
137
- message: 'Server timeout',
138
- start_command: `npx spaps local --port ${port}`
139
- });
140
- });
141
-
142
- req.end();
143
- });
144
- }
130
+ async function getQuickStartInstructions(port = DEFAULT_PORT) {
131
+ const runtime = await getServerRuntime({ port });
145
132
 
146
- async function runQuickTest(port = 3301) {
147
- const results = [];
148
-
149
- // Check server
150
- const status = await getServerStatus(port);
151
- results.push({
152
- test: 'server_status',
153
- success: status.running,
154
- details: status
155
- });
156
-
157
- if (!status.running) {
133
+ if (!runtime.running) {
158
134
  return {
159
135
  success: false,
160
- message: 'Server not running',
161
- fix: `npx spaps local --port ${port}`,
162
- results
136
+ server: runtime,
137
+ summary: [
138
+ `Start the local server with \`npx spaps local --port ${port}\`.`,
139
+ 'Provision a starter application after the server is healthy.',
140
+ ],
141
+ instructions: {
142
+ start_server: {
143
+ description: 'Start the local server',
144
+ command: `npx spaps local --port ${port}`,
145
+ },
146
+ },
163
147
  };
164
148
  }
165
-
166
- // Try HTTP request
167
- try {
168
- const http = require('http');
169
- const loginResult = await new Promise((resolve, reject) => {
170
- const postData = JSON.stringify({
171
- email: 'test@example.com',
172
- password: 'password'
173
- });
174
-
175
- const options = {
176
- hostname: 'localhost',
177
- port: port,
178
- path: '/api/auth/login',
179
- method: 'POST',
180
- headers: {
181
- 'Content-Type': 'application/json',
182
- 'Content-Length': Buffer.byteLength(postData)
183
- }
184
- };
185
-
186
- const req = http.request(options, (res) => {
187
- let data = '';
188
- res.on('data', chunk => data += chunk);
189
- res.on('end', () => {
190
- try {
191
- resolve(JSON.parse(data));
192
- } catch {
193
- reject(new Error('Invalid JSON response'));
194
- }
195
- });
196
- });
197
-
198
- req.on('error', reject);
199
- req.write(postData);
200
- req.end();
201
- });
202
-
203
- results.push({
204
- test: 'login_endpoint',
205
- success: true,
206
- response: loginResult
207
- });
208
- } catch (error) {
209
- results.push({
210
- test: 'login_endpoint',
149
+
150
+ if (runtime.local_mode.active) {
151
+ return buildLocalModeInstructions(runtime);
152
+ }
153
+
154
+ return buildProvisionedModeInstructions(runtime, port);
155
+ }
156
+
157
+ async function getServerStatus(port = DEFAULT_PORT) {
158
+ return getServerRuntime({ port });
159
+ }
160
+
161
+ async function runQuickTest(port = DEFAULT_PORT) {
162
+ const runtime = await getServerRuntime({ port });
163
+ const results = [
164
+ {
165
+ test: 'server_status',
166
+ success: runtime.running,
167
+ details: runtime,
168
+ },
169
+ ];
170
+
171
+ if (!runtime.running) {
172
+ return {
211
173
  success: false,
212
- error: error.message
213
- });
174
+ summary: '0/1 tests passed',
175
+ results,
176
+ next_steps: [`Start the server with: npx spaps local --port ${port}`],
177
+ };
214
178
  }
215
-
216
- // Check SDK availability
217
- try {
218
- require.resolve('spaps-sdk');
219
- results.push({
220
- test: 'sdk_installed',
221
- success: true,
222
- message: 'spaps-sdk is installed'
223
- });
224
-
225
- // Try SDK login
226
- try {
227
- const { SPAPSClient } = require('spaps-sdk');
228
- const spaps = new SPAPSClient({ apiUrl: `http://localhost:${port}` });
229
- const { data } = await spaps.login('test@example.com', 'password');
230
-
231
- results.push({
232
- test: 'sdk_login',
233
- success: true,
234
- user: data.user
235
- });
236
- } catch (error) {
237
- results.push({
238
- test: 'sdk_login',
239
- success: false,
240
- error: error.message
241
- });
242
- }
243
- } catch {
179
+
180
+ results.push({
181
+ test: 'local_mode_contract',
182
+ success: runtime.local_mode.known,
183
+ details: runtime.local_mode,
184
+ fix: runtime.local_mode.known ? null : `curl -s ${runtime.url}/health/local-mode`,
185
+ });
186
+
187
+ if (!runtime.local_mode.active) {
188
+ const hasKey = Boolean(process.env.SPAPS_API_KEY);
244
189
  results.push({
245
- test: 'sdk_installed',
246
- success: false,
247
- message: 'spaps-sdk not installed',
248
- fix: 'npm install spaps-sdk'
190
+ test: 'api_key_wiring',
191
+ success: hasKey,
192
+ message: hasKey
193
+ ? 'SPAPS_API_KEY is set for non-local-mode testing'
194
+ : 'SPAPS_API_KEY is required when local mode is disabled',
195
+ fix: hasKey ? null : buildProvisioningCommand('node', port),
249
196
  });
250
197
  }
251
-
252
- const allSuccess = results.every(r => r.success);
253
-
198
+
199
+ const allSuccess = results.every((result) => result.success);
200
+
254
201
  return {
255
202
  success: allSuccess,
256
- summary: `${results.filter(r => r.success).length}/${results.length} tests passed`,
203
+ summary: `${results.filter((result) => result.success).length}/${results.length} tests passed`,
257
204
  results,
258
- next_steps: allSuccess ? [
259
- 'Server is running and SDK is working',
260
- 'You can now use SPAPS in your application',
261
- 'See docs at http://localhost:' + port + '/docs'
262
- ] : [
263
- 'Fix the failing tests above',
264
- 'Run: npx spaps test --json to retry'
265
- ]
205
+ next_steps: allSuccess
206
+ ? [`See docs at ${runtime.docs}`]
207
+ : ['Fix the failing checks above and re-run: npx spaps test --json'],
266
208
  };
267
209
  }
268
210
 
269
211
  module.exports = {
270
212
  getQuickStartInstructions,
271
213
  getServerStatus,
272
- runQuickTest
214
+ runQuickTest,
273
215
  };
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * AI Tool Spec generator for SPAPS
3
3
  * - Produces OpenAI-style function schemas for common SPAPS actions
4
- * - Keeps defaults safe for local development (no API key required)
4
+ * - Derives auth expectations from the running local server when available
5
5
  */
6
6
 
7
7
  const { DEFAULT_PORT } = require('./config');
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
+ const { getServerRuntime } = require('./local-runtime');
10
11
 
11
12
  function tryLoadManifest() {
12
13
  const candidates = [
@@ -41,24 +42,54 @@ function tryLoadOpenAPI() {
41
42
  return null;
42
43
  }
43
44
 
44
- function buildOpenAIToolSpec({ port = DEFAULT_PORT } = {}) {
45
+ function buildAuthSection(runtime) {
46
+ if (!runtime.running) {
47
+ return {
48
+ local_mode: false,
49
+ mode_source: '/health/local-mode',
50
+ api_key_required: true,
51
+ header: 'X-API-Key',
52
+ env: 'SPAPS_API_KEY',
53
+ status: 'server_unreachable',
54
+ };
55
+ }
56
+
57
+ return {
58
+ local_mode: Boolean(runtime.local_mode.active),
59
+ mode_source: '/health/local-mode',
60
+ api_key_required: !runtime.local_mode.active,
61
+ header: 'X-API-Key',
62
+ env: 'SPAPS_API_KEY',
63
+ environment: runtime.local_mode.environment,
64
+ spaps_local_mode_env: runtime.local_mode.spaps_local_mode_env,
65
+ ...(runtime.local_mode.active
66
+ ? {
67
+ test_users: runtime.local_mode.test_users,
68
+ test_application: runtime.local_mode.test_application,
69
+ hints: runtime.local_mode.hints,
70
+ }
71
+ : {
72
+ self_service_password_env: 'SELF_SERVICE_PASSWORD',
73
+ }),
74
+ };
75
+ }
76
+
77
+ async function buildOpenAIToolSpec({ port = DEFAULT_PORT, version = '0.0.0' } = {}) {
45
78
  const baseUrl = `http://localhost:${port}`;
79
+ const runtime = await getServerRuntime({ port });
80
+ const auth = buildAuthSection(runtime);
46
81
  const spec = {
47
82
  name: 'spaps',
48
- version: '0.1.0',
49
- description: 'Auth + payments via SPAPS (local by default). Start with: npx spaps local',
83
+ version,
84
+ description: 'Auth + payments via SPAPS. Resolve local auth requirements from /health/local-mode.',
50
85
  base_url: baseUrl,
51
- auth: {
52
- local_mode: true,
53
- production: {
54
- header: 'X-API-Key',
55
- env: 'SPAPS_API_KEY'
56
- }
57
- },
86
+ auth,
58
87
  tools: [
59
88
  {
60
89
  name: 'login',
61
- description: 'Login with email/password. Local mode accepts any values.',
90
+ description: auth.local_mode
91
+ ? 'Authenticate a local test user. API key validation is bypassed while local mode is active.'
92
+ : 'Authenticate a user with email/password for a provisioned application. Send X-API-Key when local mode is disabled.',
62
93
  method: 'POST',
63
94
  path: '/api/auth/login',
64
95
  parameters: {
@@ -72,7 +103,9 @@ function buildOpenAIToolSpec({ port = DEFAULT_PORT } = {}) {
72
103
  },
73
104
  {
74
105
  name: 'register',
75
- description: 'Register a new user with email/password.',
106
+ description: auth.local_mode
107
+ ? 'Register a user while local mode is active.'
108
+ : 'Register a new user with email/password for a provisioned application.',
76
109
  method: 'POST',
77
110
  path: '/api/auth/register',
78
111
  parameters: {
@@ -86,7 +119,7 @@ function buildOpenAIToolSpec({ port = DEFAULT_PORT } = {}) {
86
119
  },
87
120
  {
88
121
  name: 'get_current_user',
89
- description: 'Get the currently authenticated user. Uses bearer token from previous login.',
122
+ description: 'Get the currently authenticated user. Uses the bearer token from a previous login.',
90
123
  method: 'GET',
91
124
  path: '/api/auth/user',
92
125
  parameters: {
@@ -98,7 +131,7 @@ function buildOpenAIToolSpec({ port = DEFAULT_PORT } = {}) {
98
131
  },
99
132
  {
100
133
  name: 'create_checkout_session',
101
- description: 'Create a Stripe Checkout session. In local mode uses Stripe test or mock based on USE_REAL_STRIPE.',
134
+ description: 'Create a Stripe Checkout session against the configured SPAPS server.',
102
135
  method: 'POST',
103
136
  path: '/api/stripe/checkout-sessions',
104
137
  parameters: {
@@ -116,7 +149,7 @@ function buildOpenAIToolSpec({ port = DEFAULT_PORT } = {}) {
116
149
  },
117
150
  {
118
151
  name: 'list_products',
119
- description: 'List products (Stripe-backed or local).',
152
+ description: 'List products exposed by the SPAPS server.',
120
153
  method: 'GET',
121
154
  path: '/api/stripe/products',
122
155
  parameters: {
@@ -129,7 +162,7 @@ function buildOpenAIToolSpec({ port = DEFAULT_PORT } = {}) {
129
162
  },
130
163
  {
131
164
  name: 'request_magic_link',
132
- description: 'Send a magic link for passwordless login (local mode simulates delivery).',
165
+ description: 'Send a magic link for passwordless login.',
133
166
  method: 'POST',
134
167
  path: '/api/auth/magic-link',
135
168
  parameters: {
@@ -254,7 +287,6 @@ function buildOpenAIToolSpec({ port = DEFAULT_PORT } = {}) {
254
287
  }
255
288
  }
256
289
  if (Object.keys(responses).length) {
257
- // Merge with default errors for completeness
258
290
  const merged = { ...defaultErrors, ...responses };
259
291
  t.responses = merged;
260
292
  }
@@ -287,11 +319,11 @@ function buildOpenAIToolSpec({ port = DEFAULT_PORT } = {}) {
287
319
  return spec;
288
320
  }
289
321
 
290
- function buildToolSpec({ format = 'openai', port = DEFAULT_PORT } = {}) {
322
+ async function buildToolSpec({ format = 'openai', port = DEFAULT_PORT, version = '0.0.0' } = {}) {
291
323
  switch (format) {
292
324
  case 'openai':
293
325
  default:
294
- return buildOpenAIToolSpec({ port });
326
+ return buildOpenAIToolSpec({ port, version });
295
327
  }
296
328
  }
297
329