spaps 0.7.2 → 0.7.4

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 (49) hide show
  1. package/AI_TOOLS.json +10 -11
  2. package/README.md +267 -110
  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 +5 -4
  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 +155 -24
  42. package/src/docs-system.js +7 -7
  43. package/src/error-handler.js +42 -0
  44. package/src/fixture-kernel.js +1143 -0
  45. package/src/handlers.js +252 -15
  46. package/src/help-system.js +3 -1
  47. package/src/local-runtime.js +258 -0
  48. package/src/local-server.js +597 -199
  49. package/src/project-scaffolder.js +441 -0
package/src/handlers.js CHANGED
@@ -7,8 +7,27 @@ const { showInteractiveDocs, showQuickReference, searchDocs } = require('./docs-
7
7
  const { getQuickStartInstructions, getServerStatus, runQuickTest } = require('./ai-helper');
8
8
  const { buildToolSpec } = require('./ai-tool-spec');
9
9
  const { runDoctor } = require('./doctor');
10
+ const {
11
+ applyFixtures,
12
+ exportStorageState,
13
+ initFixtureKernel,
14
+ resetFixtures,
15
+ } = require('./fixture-kernel');
16
+ const { createProjectStarter } = require('./project-scaffolder');
17
+ const {
18
+ loginHandler,
19
+ logoutHandler,
20
+ whoamiHandler,
21
+ tokenHandler,
22
+ } = require('./auth/handlers');
10
23
 
11
24
  function createHandlers(version, logo) {
25
+ function invalidArgument(message) {
26
+ const error = new Error(message);
27
+ error.code = 'EINVAL';
28
+ return error;
29
+ }
30
+
12
31
  return {
13
32
  local: async ({ options }) => {
14
33
  const isJson = options.json;
@@ -18,7 +37,13 @@ function createHandlers(version, logo) {
18
37
  if (options.stop) {
19
38
  try {
20
39
  const LocalServer = require('./local-server.js');
21
- const server = new LocalServer({ json: isJson });
40
+ const server = new LocalServer({
41
+ port: options.port,
42
+ runtimeDir: options.runtimeDir,
43
+ runtimeSource: options.runtimeSource,
44
+ dataSource: options.dataSource,
45
+ json: isJson,
46
+ });
22
47
  server.stop();
23
48
  return;
24
49
  } catch (error) {
@@ -31,6 +56,9 @@ function createHandlers(version, logo) {
31
56
  const LocalServer = require('./local-server.js');
32
57
  const server = new LocalServer({
33
58
  port: options.port,
59
+ runtimeDir: options.runtimeDir,
60
+ runtimeSource: options.runtimeSource,
61
+ dataSource: options.dataSource,
34
62
  json: isJson,
35
63
  detach: options.detach,
36
64
  fresh: options.fresh,
@@ -47,23 +75,31 @@ function createHandlers(version, logo) {
47
75
  }
48
76
 
49
77
  // Set up shutdown handler
50
- process.on('SIGINT', async () => {
78
+ const shutdown = async () => {
51
79
  await server.shutdown();
52
80
  process.exit(0);
53
- });
81
+ };
82
+ process.on('SIGINT', shutdown);
83
+ process.on('SIGTERM', shutdown);
54
84
  } catch (error) {
55
85
  handleError(error, { port: options.port, command: 'local' }, { json: isJson });
56
86
  }
57
87
  },
58
88
  quickstart: async ({ options }) => {
59
- const instructions = getQuickStartInstructions(options.port);
89
+ const instructions = await getQuickStartInstructions(options.port);
60
90
  if (options.json) {
61
91
  console.log(JSON.stringify(instructions, null, 2));
62
92
  } else {
63
93
  console.log(chalk.yellow('\n🍠 SPAPS Quick Start Instructions\n'));
64
- console.log('1. Install SDK: npm install spaps-sdk');
65
- console.log('2. Create test file with the code above');
66
- console.log('3. Run: node test-spaps.js');
94
+ const modeSummary = instructions.server.running
95
+ ? instructions.auth.local_mode
96
+ ? 'Mode: local mode active'
97
+ : 'Mode: provisioned application required'
98
+ : 'Mode: server unreachable';
99
+ console.log(modeSummary);
100
+ instructions.summary.forEach((line, index) => {
101
+ console.log(`${index + 1}. ${line}`);
102
+ });
67
103
  console.log('\nFor JSON output: npx spaps quickstart --json');
68
104
  }
69
105
  },
@@ -81,6 +117,12 @@ function createHandlers(version, logo) {
81
117
  console.log(chalk.green('\n✅ SPAPS server is running!\n'));
82
118
  console.log(' URL:', chalk.cyan(status.url));
83
119
  console.log(' Docs:', chalk.cyan(status.docs));
120
+ if (status.local_mode?.known) {
121
+ console.log(
122
+ ' Mode:',
123
+ chalk.cyan(status.local_mode.active ? 'local mode active' : 'application key required')
124
+ );
125
+ }
84
126
  console.log();
85
127
  }
86
128
  }
@@ -114,12 +156,66 @@ function createHandlers(version, logo) {
114
156
  console.log(chalk.cyan(' 3. Start coding!'));
115
157
  }
116
158
  },
117
- create: () => {
118
- console.log(chalk.yellow('🍠 SPAPS'));
119
- console.log(chalk.yellow(`🚧 'spaps create' coming in v0.3.0!`));
120
- console.log();
121
- console.log('For now, check out our examples:');
122
- console.log(chalk.cyan(' https://github.com/yourusername/sweet-potato/tree/main/examples'));
159
+ create: async ({ options }) => {
160
+ const isJson = options.json;
161
+
162
+ try {
163
+ const result = await createProjectStarter({
164
+ name: options.name,
165
+ template: options.template,
166
+ dir: options.dir,
167
+ port: options.port,
168
+ force: options.force,
169
+ version,
170
+ });
171
+
172
+ if (isJson) {
173
+ console.log(JSON.stringify(result, null, 2));
174
+ return;
175
+ }
176
+
177
+ console.log(chalk.green(`\n✨ Created ${result.project_name} (${result.template})`));
178
+ console.log(chalk.cyan(` ${result.target_dir}`));
179
+ console.log(chalk.gray(` provisioning: ${result.provisioning.status}`));
180
+
181
+ if (result.files_created.length > 0) {
182
+ console.log(chalk.green('\nFiles created:'));
183
+ result.files_created.forEach((file) => {
184
+ console.log(chalk.gray(` • ${file}`));
185
+ });
186
+ }
187
+
188
+ if (result.files_overwritten.length > 0) {
189
+ console.log(chalk.yellow('\nFiles overwritten:'));
190
+ result.files_overwritten.forEach((file) => {
191
+ console.log(chalk.gray(` • ${file}`));
192
+ });
193
+ }
194
+
195
+ if (result.warnings.length > 0) {
196
+ console.log(chalk.yellow('\nWarnings:'));
197
+ result.warnings.forEach((warning) => {
198
+ console.log(chalk.gray(` • ${warning}`));
199
+ });
200
+ }
201
+
202
+ console.log(chalk.green('\nNext steps:'));
203
+ result.next_steps.forEach((step, index) => {
204
+ console.log(chalk.cyan(` ${index + 1}. ${step}`));
205
+ });
206
+ console.log();
207
+ } catch (error) {
208
+ handleError(
209
+ error,
210
+ {
211
+ command: 'create',
212
+ name: options.name,
213
+ template: options.template,
214
+ dir: options.dir,
215
+ },
216
+ { json: isJson }
217
+ );
218
+ }
123
219
  },
124
220
  types: () => {
125
221
  console.log(chalk.yellow('🍠 SPAPS'));
@@ -160,12 +256,22 @@ function createHandlers(version, logo) {
160
256
  }
161
257
  },
162
258
  tools: async ({ options }) => {
163
- const spec = buildToolSpec({ format: options.format || 'openai', port: options.port });
259
+ const spec = await buildToolSpec({
260
+ format: options.format || 'openai',
261
+ port: options.port,
262
+ version,
263
+ });
164
264
  if (options.json) {
165
265
  console.log(JSON.stringify(spec, null, 2));
166
266
  } else {
167
267
  console.log(chalk.yellow('\n🍠 SPAPS AI Tool Spec (OpenAI-style)\n'));
168
268
  console.log('Base URL:', spec.base_url);
269
+ if (spec.auth && typeof spec.auth.local_mode === 'boolean') {
270
+ console.log(
271
+ 'Auth:',
272
+ spec.auth.local_mode ? 'local mode active' : 'application key required'
273
+ );
274
+ }
169
275
  console.log('Tools:');
170
276
  spec.tools.forEach((t, i) => {
171
277
  console.log(chalk.green(` ${i + 1}. ${t.name}`), '-', t.description);
@@ -174,9 +280,140 @@ function createHandlers(version, logo) {
174
280
  console.log('\nTip: npx spaps tools --json > spaps-tools.json');
175
281
  }
176
282
  },
283
+ fixtures: async ({ options }) => {
284
+ const isJson = options.json;
285
+
286
+ try {
287
+ if (options.format && options.format !== 'playwright') {
288
+ throw invalidArgument(`Unsupported fixture format "${options.format}". Only "playwright" is currently supported.`);
289
+ }
290
+
291
+ let result;
292
+ switch (options.subcommand) {
293
+ case 'init':
294
+ result = await initFixtureKernel({
295
+ dir: options.dir,
296
+ port: options.port,
297
+ baseUrl: options.baseUrl,
298
+ version,
299
+ force: options.force,
300
+ });
301
+ break;
302
+ case 'apply':
303
+ result = await applyFixtures({
304
+ dir: options.dir,
305
+ port: options.port,
306
+ baseUrl: options.baseUrl,
307
+ version,
308
+ });
309
+ break;
310
+ case 'reset':
311
+ result = await resetFixtures({
312
+ dir: options.dir,
313
+ port: options.port,
314
+ baseUrl: options.baseUrl,
315
+ version,
316
+ });
317
+ break;
318
+ case 'storage-state':
319
+ if (!options.persona) {
320
+ throw invalidArgument('The fixtures storage-state command requires --persona.');
321
+ }
322
+ result = await exportStorageState({
323
+ dir: options.dir,
324
+ port: options.port,
325
+ baseUrl: options.baseUrl,
326
+ version,
327
+ persona: options.persona,
328
+ });
329
+ break;
330
+ default:
331
+ throw invalidArgument(
332
+ `Unsupported fixtures subcommand "${options.subcommand}". Use init, apply, reset, or storage-state.`
333
+ );
334
+ }
335
+
336
+ if (isJson) {
337
+ console.log(JSON.stringify(result, null, 2));
338
+ return;
339
+ }
340
+
341
+ console.log(chalk.green(`\n✨ .spaps fixtures ${result.subcommand} complete`));
342
+ console.log(chalk.cyan(` ${result.fixture_dir}`));
343
+
344
+ if (Array.isArray(result.files_created) && result.files_created.length > 0) {
345
+ console.log(chalk.green('\nFiles created:'));
346
+ result.files_created.forEach((file) => {
347
+ console.log(chalk.gray(` • ${file}`));
348
+ });
349
+ }
350
+
351
+ if (Array.isArray(result.files_overwritten) && result.files_overwritten.length > 0) {
352
+ console.log(chalk.yellow('\nFiles overwritten:'));
353
+ result.files_overwritten.forEach((file) => {
354
+ console.log(chalk.gray(` • ${file}`));
355
+ });
356
+ }
357
+
358
+ if (Array.isArray(result.removed) && result.removed.length > 0) {
359
+ console.log(chalk.yellow('\nRemoved stale artifacts:'));
360
+ result.removed.forEach((file) => {
361
+ console.log(chalk.gray(` • ${file}`));
362
+ });
363
+ }
364
+
365
+ if (result.generated?.personas?.length) {
366
+ console.log(chalk.green('\nGenerated personas:'));
367
+ result.generated.personas.forEach((entry) => {
368
+ console.log(chalk.gray(` • ${entry.persona}`));
369
+ console.log(chalk.gray(` storageState: ${entry.storage_state_path}`));
370
+ console.log(chalk.gray(` headers: ${entry.headers_path}`));
371
+ });
372
+ }
373
+
374
+ if (result.generated?.bridge?.script_path) {
375
+ console.log(chalk.green('\nFrontend bridge:'));
376
+ console.log(chalk.gray(` script: ${result.generated.bridge.script_path}`));
377
+ console.log(chalk.gray(` include: <script src="${result.generated.bridge.public_url}"></script>`));
378
+ }
379
+
380
+ if (result.storage_state_path) {
381
+ console.log(chalk.green('\nStorage state:'));
382
+ console.log(chalk.gray(` ${result.storage_state_path}`));
383
+ console.log(chalk.gray(` headers: ${result.headers_path}`));
384
+ }
385
+
386
+ if (result.bridge?.script_path) {
387
+ console.log(chalk.green('\nFrontend bridge:'));
388
+ console.log(chalk.gray(` script: ${result.bridge.script_path}`));
389
+ console.log(chalk.gray(` include: <script src="${result.bridge.public_url}"></script>`));
390
+ }
391
+
392
+ console.log(chalk.green('\nNext steps:'));
393
+ (result.next_steps || []).forEach((step, index) => {
394
+ console.log(chalk.cyan(` ${index + 1}. ${step}`));
395
+ });
396
+ console.log();
397
+ } catch (error) {
398
+ handleError(
399
+ error,
400
+ {
401
+ command: 'fixtures',
402
+ subcommand: options.subcommand,
403
+ dir: options.dir,
404
+ persona: options.persona,
405
+ },
406
+ { json: isJson }
407
+ );
408
+ }
409
+ },
177
410
  doctor: async ({ options }) => {
178
411
  await runDoctor({ port: options.port || DEFAULT_PORT, stripe: options.stripe || null, json: options.json });
179
- }
412
+ },
413
+ login: loginHandler,
414
+ logout: logoutHandler,
415
+ whoami: whoamiHandler,
416
+ token: tokenHandler,
180
417
  };
181
418
  }
182
419
 
@@ -454,7 +454,9 @@ function showQuickHelp() {
454
454
  console.log(chalk.green('Common Commands:'));
455
455
  console.log(' npx spaps local ' + chalk.gray('# Start local server'));
456
456
  console.log(' npx spaps init ' + chalk.gray('# Initialize in project'));
457
- console.log(' npx spaps create <name> ' + chalk.gray('# Create new project (v0.3.0)'));
457
+ console.log(' npx spaps create <name> ' + chalk.gray('# Scaffold a SPAPS starter project'));
458
+ console.log(' npx spaps fixtures apply ' + chalk.gray('# Generate repo-local auth fixtures'));
459
+ console.log(' npx spaps fixtures storage-state ' + chalk.gray('# Export one persona artifact'));
458
460
  console.log(' npx spaps types ' + chalk.gray('# Generate types (v0.4.0)'));
459
461
  console.log();
460
462
 
@@ -0,0 +1,258 @@
1
+ const axios = require('axios');
2
+
3
+ const { DEFAULT_PORT } = require('./config');
4
+
5
+ const REQUEST_TIMEOUT_MS = 1200;
6
+
7
+ function buildBaseUrl(port = DEFAULT_PORT) {
8
+ return `http://localhost:${port}`;
9
+ }
10
+
11
+ function buildDocsUrl(port = DEFAULT_PORT) {
12
+ return `${buildBaseUrl(port)}/docs`;
13
+ }
14
+
15
+ function unwrapEnvelope(payload) {
16
+ if (
17
+ payload &&
18
+ typeof payload === 'object' &&
19
+ payload.success === true &&
20
+ Object.prototype.hasOwnProperty.call(payload, 'data')
21
+ ) {
22
+ return payload.data;
23
+ }
24
+
25
+ return payload;
26
+ }
27
+
28
+ function extractError(payload, status) {
29
+ if (payload && typeof payload === 'object') {
30
+ if (payload.error && typeof payload.error === 'object') {
31
+ return {
32
+ code: payload.error.code || `HTTP_${status}`,
33
+ message: payload.error.message || `Request failed with status ${status}`,
34
+ };
35
+ }
36
+
37
+ return {
38
+ code: payload.code || payload.error || `HTTP_${status}`,
39
+ message: payload.message || payload.detail || `Request failed with status ${status}`,
40
+ };
41
+ }
42
+
43
+ return {
44
+ code: `HTTP_${status}`,
45
+ message: `Request failed with status ${status}`,
46
+ };
47
+ }
48
+
49
+ async function requestJson({ method, url, data = null, headers = {}, timeoutMs = REQUEST_TIMEOUT_MS }) {
50
+ try {
51
+ const response = await axios({
52
+ method,
53
+ url,
54
+ data,
55
+ headers,
56
+ timeout: timeoutMs,
57
+ validateStatus: () => true,
58
+ });
59
+
60
+ const ok = response.status >= 200 && response.status < 300;
61
+
62
+ return {
63
+ ok,
64
+ status: response.status,
65
+ raw: response.data,
66
+ data: unwrapEnvelope(response.data),
67
+ error: ok ? null : extractError(response.data, response.status),
68
+ network_error: false,
69
+ };
70
+ } catch (error) {
71
+ return {
72
+ ok: false,
73
+ status: null,
74
+ raw: null,
75
+ data: null,
76
+ error: {
77
+ code: error.code || 'REQUEST_FAILED',
78
+ message: error.message || 'Request failed',
79
+ },
80
+ network_error: true,
81
+ };
82
+ }
83
+ }
84
+
85
+ async function getServerRuntime({ port = DEFAULT_PORT, timeoutMs = REQUEST_TIMEOUT_MS } = {}) {
86
+ const url = buildBaseUrl(port);
87
+ const docs = buildDocsUrl(port);
88
+
89
+ const health = await requestJson({
90
+ method: 'GET',
91
+ url: `${url}/health`,
92
+ timeoutMs,
93
+ });
94
+
95
+ if (!health.ok) {
96
+ return {
97
+ running: false,
98
+ port,
99
+ url,
100
+ docs,
101
+ message: health.network_error ? 'Server not running' : 'Health endpoint returned an error',
102
+ start_command: `npx spaps local --port ${port}`,
103
+ error: health.error,
104
+ };
105
+ }
106
+
107
+ const localMode = await requestJson({
108
+ method: 'GET',
109
+ url: `${url}/health/local-mode`,
110
+ timeoutMs,
111
+ });
112
+
113
+ const localModeData = localMode.ok && localMode.data && typeof localMode.data === 'object'
114
+ ? localMode.data
115
+ : null;
116
+
117
+ return {
118
+ running: true,
119
+ port,
120
+ url,
121
+ docs,
122
+ health: health.data || health.raw,
123
+ local_mode: {
124
+ known: localMode.ok,
125
+ active: localModeData ? Boolean(localModeData.local_mode_active) : null,
126
+ environment: localModeData?.environment || null,
127
+ spaps_local_mode_env:
128
+ typeof localModeData?.spaps_local_mode_env === 'boolean'
129
+ ? localModeData.spaps_local_mode_env
130
+ : null,
131
+ test_users: Array.isArray(localModeData?.test_users) ? localModeData.test_users : [],
132
+ test_application: localModeData?.test_application || null,
133
+ hints: localModeData?.hints || {},
134
+ raw: localModeData,
135
+ },
136
+ };
137
+ }
138
+
139
+ function readSelfServicePassword() {
140
+ return process.env.SELF_SERVICE_PASSWORD || process.env.SELF_SERVICE_ADMIN_PASSWORD || '';
141
+ }
142
+
143
+ async function provisionStarterApplication({
144
+ port = DEFAULT_PORT,
145
+ name,
146
+ slug,
147
+ blueprintKey,
148
+ allowedOrigins = [],
149
+ }) {
150
+ const runtime = await getServerRuntime({ port });
151
+
152
+ if (!runtime.running) {
153
+ return {
154
+ status: 'scaffold_only',
155
+ reason: 'server_unreachable',
156
+ runtime,
157
+ warnings: [
158
+ `Local server was unreachable at ${runtime.url}. Starter files were created without provisioning.`,
159
+ ],
160
+ };
161
+ }
162
+
163
+ if (runtime.local_mode.active) {
164
+ return {
165
+ status: 'local_mode',
166
+ reason: null,
167
+ runtime,
168
+ application: {
169
+ id: runtime.local_mode.test_application?.id || null,
170
+ slug: runtime.local_mode.test_application?.slug || slug,
171
+ },
172
+ warnings: [
173
+ `Local mode is active on ${runtime.url}. Starter files were created without provisioning because API key validation is bypassed.`,
174
+ ],
175
+ };
176
+ }
177
+
178
+ const password = readSelfServicePassword();
179
+ if (!password) {
180
+ return {
181
+ status: 'scaffold_only',
182
+ reason: 'self_service_password_missing',
183
+ runtime,
184
+ warnings: [
185
+ `The server at ${runtime.url} requires a provisioned application key. Set SELF_SERVICE_PASSWORD and re-run this command to provision one automatically.`,
186
+ ],
187
+ };
188
+ }
189
+
190
+ const auth = await requestJson({
191
+ method: 'POST',
192
+ url: `${runtime.url}/api/self-service/auth`,
193
+ data: { password },
194
+ });
195
+
196
+ if (!auth.ok || !auth.data?.token) {
197
+ return {
198
+ status: 'scaffold_only',
199
+ reason: 'self_service_auth_failed',
200
+ runtime,
201
+ warnings: [
202
+ `Self-service authentication failed: ${auth.error?.message || 'no token returned'}. Starter files were created without provisioning.`,
203
+ ],
204
+ error: auth.error,
205
+ };
206
+ }
207
+
208
+ const creation = await requestJson({
209
+ method: 'POST',
210
+ url: `${runtime.url}/api/self-service/applications`,
211
+ headers: {
212
+ Authorization: `Bearer ${auth.data.token}`,
213
+ },
214
+ data: {
215
+ name,
216
+ slug,
217
+ blueprint_key: blueprintKey,
218
+ allowed_origins: allowedOrigins,
219
+ },
220
+ });
221
+
222
+ if (!creation.ok || !creation.data?.application) {
223
+ return {
224
+ status: 'scaffold_only',
225
+ reason: 'application_provision_failed',
226
+ runtime,
227
+ warnings: [
228
+ `Application provisioning failed: ${creation.error?.message || 'unexpected response'}. Starter files were created without provisioning.`,
229
+ ],
230
+ error: creation.error,
231
+ };
232
+ }
233
+
234
+ return {
235
+ status: 'provisioned',
236
+ reason: null,
237
+ runtime,
238
+ application: {
239
+ id: creation.data.application.id,
240
+ slug: creation.data.application.slug,
241
+ },
242
+ keys: {
243
+ publishable: creation.data.publishable_key || null,
244
+ secret: creation.data.secret_key || creation.data.api_key || null,
245
+ },
246
+ warnings: [],
247
+ };
248
+ }
249
+
250
+ module.exports = {
251
+ buildBaseUrl,
252
+ buildDocsUrl,
253
+ getServerRuntime,
254
+ provisionStarterApplication,
255
+ readSelfServicePassword,
256
+ requestJson,
257
+ unwrapEnvelope,
258
+ };