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/handlers.js CHANGED
@@ -7,9 +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');
10
16
  const { createProjectStarter } = require('./project-scaffolder');
17
+ const {
18
+ loginHandler,
19
+ logoutHandler,
20
+ whoamiHandler,
21
+ tokenHandler,
22
+ } = require('./auth/handlers');
11
23
 
12
24
  function createHandlers(version, logo) {
25
+ function invalidArgument(message) {
26
+ const error = new Error(message);
27
+ error.code = 'EINVAL';
28
+ return error;
29
+ }
30
+
13
31
  return {
14
32
  local: async ({ options }) => {
15
33
  const isJson = options.json;
@@ -19,7 +37,13 @@ function createHandlers(version, logo) {
19
37
  if (options.stop) {
20
38
  try {
21
39
  const LocalServer = require('./local-server.js');
22
- 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
+ });
23
47
  server.stop();
24
48
  return;
25
49
  } catch (error) {
@@ -32,6 +56,9 @@ function createHandlers(version, logo) {
32
56
  const LocalServer = require('./local-server.js');
33
57
  const server = new LocalServer({
34
58
  port: options.port,
59
+ runtimeDir: options.runtimeDir,
60
+ runtimeSource: options.runtimeSource,
61
+ dataSource: options.dataSource,
35
62
  json: isJson,
36
63
  detach: options.detach,
37
64
  fresh: options.fresh,
@@ -48,23 +75,31 @@ function createHandlers(version, logo) {
48
75
  }
49
76
 
50
77
  // Set up shutdown handler
51
- process.on('SIGINT', async () => {
78
+ const shutdown = async () => {
52
79
  await server.shutdown();
53
80
  process.exit(0);
54
- });
81
+ };
82
+ process.on('SIGINT', shutdown);
83
+ process.on('SIGTERM', shutdown);
55
84
  } catch (error) {
56
85
  handleError(error, { port: options.port, command: 'local' }, { json: isJson });
57
86
  }
58
87
  },
59
88
  quickstart: async ({ options }) => {
60
- const instructions = getQuickStartInstructions(options.port);
89
+ const instructions = await getQuickStartInstructions(options.port);
61
90
  if (options.json) {
62
91
  console.log(JSON.stringify(instructions, null, 2));
63
92
  } else {
64
93
  console.log(chalk.yellow('\n🍠 SPAPS Quick Start Instructions\n'));
65
- console.log('1. Install SDK: npm install spaps-sdk');
66
- console.log('2. Create test file with the code above');
67
- 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
+ });
68
103
  console.log('\nFor JSON output: npx spaps quickstart --json');
69
104
  }
70
105
  },
@@ -82,6 +117,12 @@ function createHandlers(version, logo) {
82
117
  console.log(chalk.green('\n✅ SPAPS server is running!\n'));
83
118
  console.log(' URL:', chalk.cyan(status.url));
84
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
+ }
85
126
  console.log();
86
127
  }
87
128
  }
@@ -115,14 +156,15 @@ function createHandlers(version, logo) {
115
156
  console.log(chalk.cyan(' 3. Start coding!'));
116
157
  }
117
158
  },
118
- create: ({ options }) => {
159
+ create: async ({ options }) => {
119
160
  const isJson = options.json;
120
161
 
121
162
  try {
122
- const result = createProjectStarter({
163
+ const result = await createProjectStarter({
123
164
  name: options.name,
124
165
  template: options.template,
125
166
  dir: options.dir,
167
+ port: options.port,
126
168
  force: options.force,
127
169
  version,
128
170
  });
@@ -134,6 +176,7 @@ function createHandlers(version, logo) {
134
176
 
135
177
  console.log(chalk.green(`\n✨ Created ${result.project_name} (${result.template})`));
136
178
  console.log(chalk.cyan(` ${result.target_dir}`));
179
+ console.log(chalk.gray(` provisioning: ${result.provisioning.status}`));
137
180
 
138
181
  if (result.files_created.length > 0) {
139
182
  console.log(chalk.green('\nFiles created:'));
@@ -149,6 +192,13 @@ function createHandlers(version, logo) {
149
192
  });
150
193
  }
151
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
+
152
202
  console.log(chalk.green('\nNext steps:'));
153
203
  result.next_steps.forEach((step, index) => {
154
204
  console.log(chalk.cyan(` ${index + 1}. ${step}`));
@@ -206,12 +256,22 @@ function createHandlers(version, logo) {
206
256
  }
207
257
  },
208
258
  tools: async ({ options }) => {
209
- 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
+ });
210
264
  if (options.json) {
211
265
  console.log(JSON.stringify(spec, null, 2));
212
266
  } else {
213
267
  console.log(chalk.yellow('\n🍠 SPAPS AI Tool Spec (OpenAI-style)\n'));
214
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
+ }
215
275
  console.log('Tools:');
216
276
  spec.tools.forEach((t, i) => {
217
277
  console.log(chalk.green(` ${i + 1}. ${t.name}`), '-', t.description);
@@ -220,9 +280,140 @@ function createHandlers(version, logo) {
220
280
  console.log('\nTip: npx spaps tools --json > spaps-tools.json');
221
281
  }
222
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
+ },
223
410
  doctor: async ({ options }) => {
224
411
  await runDoctor({ port: options.port || DEFAULT_PORT, stripe: options.stripe || null, json: options.json });
225
- }
412
+ },
413
+ login: loginHandler,
414
+ logout: logoutHandler,
415
+ whoami: whoamiHandler,
416
+ token: tokenHandler,
226
417
  };
227
418
  }
228
419
 
@@ -455,6 +455,8 @@ function showQuickHelp() {
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
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
+ };