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
@@ -28,10 +28,11 @@ function defineProgram({ handlers = {}, dryRun = false, version = '0.0.0', logo
28
28
  // For commands with args, it passes (arg1, arg2, ..., options, command)
29
29
  const cmd = args[args.length - 1];
30
30
  const options = args[args.length - 2] || {};
31
+ const positionals = args.slice(0, -2);
31
32
  const parentJson = program.opts().json;
32
33
  const isJson = Boolean(options.json || parentJson);
33
34
 
34
- const intent = { name, options: { ...shape(options, cmd, isJson) } };
35
+ const intent = { name, options: { ...shape(options, cmd, isJson, positionals) } };
35
36
  intents.push(intent);
36
37
 
37
38
  if (dryRun) return intent;
@@ -48,34 +49,26 @@ function defineProgram({ handlers = {}, dryRun = false, version = '0.0.0', logo
48
49
  .command('local [subcommand]')
49
50
  .description('Start local SPAPS server via Docker Compose (subcommand: stop)')
50
51
  .option('-p, --port <port>', 'Port to check (default: 3301)', String(DEFAULT_PORT))
52
+ .option('--runtime-dir <path>', 'Portable runtime directory (defaults to ~/.cache/spaps/local-<port>)')
53
+ .option('--runtime-source <source>', 'Runtime source: auto|repo|bundle', 'auto')
54
+ .option(
55
+ '--data-source <source>',
56
+ 'Base data source: empty|prod-cache|prod-fresh (use --from-backup for an explicit dump file)',
57
+ 'empty'
58
+ )
51
59
  .option('-d, --detach', 'Run in background (don\'t tail logs)', false)
52
60
  .option('--fresh', 'Fresh start: tear down and rebuild from scratch', false)
53
61
  .option('--from-backup <path>', 'Load from Supabase backup file', null)
54
62
  .option('-o, --open', 'Open browser automatically', false)
55
63
  .option('--json', 'Output in JSON format')
56
64
  .action(
57
- makeAction('local', (subcommandOrOpts, cmdOrOpts, isJsonOrCmd) => {
58
- // Handle both 'local' and 'local stop' cases
59
- let subcommand = null;
60
- let opts = null;
61
- let cmd = null;
62
- let isJson = false;
63
-
64
- if (typeof subcommandOrOpts === 'string') {
65
- // 'local stop' case
66
- subcommand = subcommandOrOpts;
67
- opts = cmdOrOpts;
68
- cmd = isJsonOrCmd;
69
- isJson = Boolean(opts.json || (cmd && cmd.parent && cmd.parent.opts().json));
70
- } else {
71
- // 'local' case (no subcommand)
72
- opts = subcommandOrOpts;
73
- cmd = cmdOrOpts;
74
- isJson = Boolean(opts.json || (cmd && cmd.parent && cmd.parent.opts().json));
75
- }
76
-
65
+ makeAction('local', (opts, _cmd, isJson, positionals) => {
66
+ const subcommand = typeof positionals[0] === 'string' ? positionals[0] : null;
77
67
  const out = {
78
68
  port: Number(opts.port) || 3301,
69
+ runtimeDir: opts.runtimeDir || null,
70
+ runtimeSource: String(opts.runtimeSource || 'auto'),
71
+ dataSource: String(opts.dataSource || 'empty'),
79
72
  open: Boolean(opts.open),
80
73
  detach: Boolean(opts.detach),
81
74
  fresh: Boolean(opts.fresh),
@@ -141,6 +134,7 @@ function defineProgram({ handlers = {}, dryRun = false, version = '0.0.0', logo
141
134
  .description('Create a starter project directory wired for SPAPS')
142
135
  .option('-t, --template <template>', 'Starter template: nextjs|react|node|vanilla')
143
136
  .option('--dir <dir>', 'Target directory (defaults to ./<name>)')
137
+ .option('-p, --port <port>', 'Local SPAPS port to provision against', String(DEFAULT_PORT))
144
138
  .option('-f, --force', 'Allow writing into a non-empty directory', false)
145
139
  .option('--json', 'Output in JSON format')
146
140
  .action(
@@ -148,6 +142,7 @@ function defineProgram({ handlers = {}, dryRun = false, version = '0.0.0', logo
148
142
  name: cmd.args[0],
149
143
  template: opts.template || null,
150
144
  dir: opts.dir || null,
145
+ port: Number(opts.port) || DEFAULT_PORT,
151
146
  force: Boolean(opts.force),
152
147
  json: isJson,
153
148
  }))
@@ -221,6 +216,38 @@ function defineProgram({ handlers = {}, dryRun = false, version = '0.0.0', logo
221
216
  }
222
217
  }
223
218
 
219
+ // spaps fixtures
220
+ const cmdFixtures = program
221
+ .command('fixtures <subcommand>')
222
+ .description('Manage repo-local .spaps auth fixtures (init|apply|reset|storage-state)')
223
+ .option('--dir <dir>', 'Target repo directory (defaults to current working directory)')
224
+ .option('-p, --port <port>', 'Port to inspect for SPAPS runtime hints', String(DEFAULT_PORT))
225
+ .option('--base-url <url>', 'Browser app base URL for generated storage-state files')
226
+ .option('--persona <persona>', 'Persona code to target for storage-state export')
227
+ .option('-f, --format <format>', 'Artifact format (playwright)', 'playwright')
228
+ .option('--force', 'Overwrite fixture files during init', false)
229
+ .option('--json', 'Output in JSON format')
230
+ .action(
231
+ makeAction('fixtures', (opts, cmd, isJson) => {
232
+ return {
233
+ subcommand: cmd.args[0],
234
+ dir: opts.dir || null,
235
+ port: Number(opts.port) || DEFAULT_PORT,
236
+ baseUrl: opts.baseUrl || null,
237
+ persona: opts.persona || null,
238
+ format: String(opts.format || 'playwright'),
239
+ force: Boolean(opts.force),
240
+ json: isJson,
241
+ };
242
+ })
243
+ );
244
+ if (dryRun) {
245
+ cmdFixtures.allowUnknownOption(true);
246
+ if (typeof cmdFixtures.allowExcessArguments === 'function') {
247
+ cmdFixtures.allowExcessArguments(true);
248
+ }
249
+ }
250
+
224
251
  // spaps doctor
225
252
  const cmdDoctor = program
226
253
  .command('doctor')
@@ -238,6 +265,92 @@ function defineProgram({ handlers = {}, dryRun = false, version = '0.0.0', logo
238
265
  }
239
266
  }
240
267
 
268
+ // spaps login
269
+ const cmdLogin = program
270
+ .command('login')
271
+ .description('Authenticate with a SPAPS server (RFC 8628 device flow)')
272
+ .option('-p, --port <port>', 'Port (default: 3301)', String(DEFAULT_PORT))
273
+ .option('--server-url <url>', 'Full server URL (overrides --port and SPAPS_API_URL)')
274
+ .option('--client-id <id>', 'Application slug to authorize as')
275
+ .option('--json', 'Output in JSON format')
276
+ .action(
277
+ makeAction('login', (opts, _cmd, isJson) => ({
278
+ port: Number(opts.port) || DEFAULT_PORT,
279
+ serverUrl: opts.serverUrl || null,
280
+ clientId: opts.clientId || null,
281
+ json: isJson,
282
+ }))
283
+ );
284
+ if (dryRun) {
285
+ cmdLogin.allowUnknownOption(true);
286
+ if (typeof cmdLogin.allowExcessArguments === 'function') {
287
+ cmdLogin.allowExcessArguments(true);
288
+ }
289
+ }
290
+
291
+ // spaps logout
292
+ const cmdLogout = program
293
+ .command('logout')
294
+ .description('Revoke and clear stored SPAPS credentials')
295
+ .option('-p, --port <port>', 'Port (default: 3301)', String(DEFAULT_PORT))
296
+ .option('--server-url <url>', 'Full server URL (overrides --port and SPAPS_API_URL)')
297
+ .option('--json', 'Output in JSON format')
298
+ .action(
299
+ makeAction('logout', (opts, _cmd, isJson) => ({
300
+ port: Number(opts.port) || DEFAULT_PORT,
301
+ serverUrl: opts.serverUrl || null,
302
+ json: isJson,
303
+ }))
304
+ );
305
+ if (dryRun) {
306
+ cmdLogout.allowUnknownOption(true);
307
+ if (typeof cmdLogout.allowExcessArguments === 'function') {
308
+ cmdLogout.allowExcessArguments(true);
309
+ }
310
+ }
311
+
312
+ // spaps whoami
313
+ const cmdWhoami = program
314
+ .command('whoami')
315
+ .description('Show the currently authenticated user')
316
+ .option('-p, --port <port>', 'Port (default: 3301)', String(DEFAULT_PORT))
317
+ .option('--server-url <url>', 'Full server URL (overrides --port and SPAPS_API_URL)')
318
+ .option('--json', 'Output in JSON format')
319
+ .action(
320
+ makeAction('whoami', (opts, _cmd, isJson) => ({
321
+ port: Number(opts.port) || DEFAULT_PORT,
322
+ serverUrl: opts.serverUrl || null,
323
+ json: isJson,
324
+ }))
325
+ );
326
+ if (dryRun) {
327
+ cmdWhoami.allowUnknownOption(true);
328
+ if (typeof cmdWhoami.allowExcessArguments === 'function') {
329
+ cmdWhoami.allowExcessArguments(true);
330
+ }
331
+ }
332
+
333
+ // spaps token (print access token for piping to curl or env vars)
334
+ const cmdToken = program
335
+ .command('token')
336
+ .description('Print the current access token (for piping to tools like curl)')
337
+ .option('-p, --port <port>', 'Port (default: 3301)', String(DEFAULT_PORT))
338
+ .option('--server-url <url>', 'Full server URL (overrides --port and SPAPS_API_URL)')
339
+ .option('--json', 'Output JSON with metadata instead of bare token')
340
+ .action(
341
+ makeAction('token', (opts, _cmd, isJson) => ({
342
+ port: Number(opts.port) || DEFAULT_PORT,
343
+ serverUrl: opts.serverUrl || null,
344
+ json: isJson,
345
+ }))
346
+ );
347
+ if (dryRun) {
348
+ cmdToken.allowUnknownOption(true);
349
+ if (typeof cmdToken.allowExcessArguments === 'function') {
350
+ cmdToken.allowExcessArguments(true);
351
+ }
352
+ }
353
+
241
354
  return { program, getIntents: () => intents };
242
355
  }
243
356
 
@@ -22,13 +22,13 @@ ${chalk.green('Basic Usage:')}
22
22
  ${chalk.gray('// CommonJS')}
23
23
  const { SweetPotatoSDK } = require('spaps-sdk')
24
24
 
25
- ${chalk.gray('// Create client (auto-detects local mode)')}
25
+ ${chalk.gray('// Create client for local or provisioned SPAPS')}
26
26
  const sdk = new SweetPotatoSDK({
27
27
  apiUrl: process.env.SPAPS_API_URL || 'http://localhost:3301',
28
- apiKey: process.env.SPAPS_API_KEY, ${chalk.gray('// Not required in local mode')}
28
+ apiKey: process.env.SPAPS_API_KEY, ${chalk.gray('// Required unless /health/local-mode says otherwise')}
29
29
  })
30
30
 
31
- ${chalk.gray('// Sign in with email/password (local mode accepts any credentials)')}
31
+ ${chalk.gray('// Sign in with email/password')}
32
32
  const auth = await sdk.auth.signInWithPassword({ email: 'user@example.com', password: 'password' })
33
33
  console.log('User:', auth.user)
34
34
  `
@@ -108,18 +108,18 @@ ${chalk.green('Environment Variables:')}
108
108
  ${chalk.green('Configuration Options:')}
109
109
  const sdk = new SweetPotatoSDK({
110
110
  apiUrl: process.env.SPAPS_API_URL || 'http://localhost:3301',
111
- apiKey: process.env.SPAPS_API_KEY, ${chalk.gray('// Omit in local dev')}
111
+ apiKey: process.env.SPAPS_API_KEY, ${chalk.gray('// Omit only when /health/local-mode reports local_mode_active: true')}
112
112
  })
113
113
 
114
114
  ${chalk.green('Local Mode Detection:')}
115
- ${chalk.gray('// The SDK automatically detects local mode when:')}
115
+ ${chalk.gray('// Localhost URLs still need the running server to advertise local mode:')}
116
116
  - URL contains 'localhost'
117
117
  - URL contains '127.0.0.1'
118
118
  - No API URL is provided
119
119
 
120
- ${chalk.gray('// Check if in local mode')}
120
+ ${chalk.gray('// Verify against the server before assuming no key is required')}
121
121
  if (sdk.isLocalMode) {
122
- console.log('Running in local mode - no API key needed!')
122
+ console.log('Check /health/local-mode before skipping API keys.')
123
123
  }
124
124
  `
125
125
  },