spine-framework 0.3.17 → 0.3.18

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 (269) hide show
  1. package/.framework/cli/commands/create-app.ts +1 -1
  2. package/.framework/cli/commands/dev.ts +215 -33
  3. package/.framework/cli/index.ts +37 -75
  4. package/.framework/functions/_shared/app-manifest.ts +8 -15
  5. package/.framework/functions/_shared/db.ts +11 -15
  6. package/.framework/functions/_shared/index.ts +7 -16
  7. package/.framework/functions/_shared/principal.ts +4 -62
  8. package/.framework/functions/_shared/resolve-ids.ts +6 -6
  9. package/.framework/functions/_shared/webhook-registry.ts +43 -1
  10. package/.framework/functions/admin-funnel.ts +104 -0
  11. package/.framework/functions/admin-install-funnel.ts +225 -0
  12. package/.framework/functions/admin-signals.ts +241 -0
  13. package/.framework/functions/integration-routes.ts +4 -8
  14. package/.framework/migrations/001_schema.sql +406 -0
  15. package/.framework/migrations/002_rls_and_functions.sql +710 -0
  16. package/.framework/migrations/003_system_data.sql +520 -0
  17. package/.framework/migrations/004_enhancements.sql +43 -0
  18. package/.framework/public/_redirects +3 -0
  19. package/.framework/public/spine-logo.jpg +0 -0
  20. package/.framework/public/spine-logo.svg +3 -0
  21. package/{scripts → .framework/scripts}/assemble-frontend.sh +8 -5
  22. package/{scripts → .framework/scripts}/assemble-functions.sh +4 -47
  23. package/{scripts → .framework/scripts}/assemble.sh +2 -8
  24. package/{scripts → .framework/scripts}/boundary-check.sh +5 -9
  25. package/{scripts → .framework/scripts}/check-core-integrity.sh +0 -0
  26. package/.framework/seeds/link_types/account_cli_instances.json +9 -0
  27. package/.framework/seeds/types/funnel_signal_use.json +98 -0
  28. package/.framework/seeds/types/spine_instance.json +109 -0
  29. package/.framework/src/App.tsx +11 -21
  30. package/.framework/src/components/CustomAppLoader.tsx +2 -19
  31. package/.framework/src/components/ui/accordion.tsx +1 -1
  32. package/.framework/src/components/ui/alert-dialog.tsx +1 -1
  33. package/.framework/src/components/ui/aspect-ratio.tsx +1 -1
  34. package/.framework/src/components/ui/avatar.tsx +1 -1
  35. package/.framework/src/components/ui/badge.tsx +2 -2
  36. package/.framework/src/components/ui/breadcrumb.tsx +2 -2
  37. package/.framework/src/components/ui/button-group.tsx +2 -2
  38. package/.framework/src/components/ui/button.tsx +2 -2
  39. package/.framework/src/components/ui/chart.tsx +6 -1
  40. package/.framework/src/components/ui/checkbox.tsx +1 -1
  41. package/.framework/src/components/ui/collapsible.tsx +1 -1
  42. package/.framework/src/components/ui/context-menu.tsx +1 -1
  43. package/.framework/src/components/ui/dialog.tsx +1 -1
  44. package/.framework/src/components/ui/direction.tsx +17 -1
  45. package/.framework/src/components/ui/dropdown-menu.tsx +1 -1
  46. package/.framework/src/components/ui/hover-card.tsx +1 -1
  47. package/.framework/src/components/ui/item.tsx +2 -2
  48. package/.framework/src/components/ui/label.tsx +1 -1
  49. package/.framework/src/components/ui/menubar.tsx +1 -1
  50. package/.framework/src/components/ui/navigation-menu.tsx +1 -1
  51. package/.framework/src/components/ui/popover.tsx +1 -1
  52. package/.framework/src/components/ui/progress.tsx +1 -1
  53. package/.framework/src/components/ui/radio-group.tsx +1 -1
  54. package/.framework/src/components/ui/scroll-area.tsx +1 -1
  55. package/.framework/src/components/ui/select.tsx +1 -1
  56. package/.framework/src/components/ui/separator.tsx +1 -1
  57. package/.framework/src/components/ui/sheet.tsx +1 -1
  58. package/.framework/src/components/ui/sidebar.tsx +6 -6
  59. package/.framework/src/components/ui/slider.tsx +1 -1
  60. package/.framework/src/components/ui/switch.tsx +1 -1
  61. package/.framework/src/components/ui/tabs.tsx +1 -1
  62. package/.framework/src/components/ui/toggle-group.tsx +1 -1
  63. package/.framework/src/components/ui/toggle.tsx +1 -1
  64. package/.framework/src/components/ui/tooltip.tsx +1 -1
  65. package/.framework/src/pages/auth/LoginPage.tsx +10 -9
  66. package/LICENSE.md +63 -93
  67. package/README.md +254 -129
  68. package/custom/.devin/skills/install-app/SKILL.md +39 -0
  69. package/custom/.devin/workflows/app-install.yaml +83 -0
  70. package/custom/README.md +79 -0
  71. package/docs/APPS.md +232 -0
  72. package/docs/ARCHITECTURE.md +246 -0
  73. package/docs/INSTALL.md +155 -0
  74. package/docs/README.md +170 -0
  75. package/docs/design-schema-spec.md +1837 -0
  76. package/docs/dev-guides/01-create-an-app.md +183 -0
  77. package/docs/dev-guides/02-create-a-function.md +178 -0
  78. package/docs/dev-guides/03-create-a-component.md +178 -0
  79. package/docs/dev-guides/04-webhook-handlers.md +129 -0
  80. package/docs/dev-guides/05-testing.md +168 -0
  81. package/docs/dev-guides/README.md +40 -0
  82. package/docs/npm-packages-plan.md +288 -0
  83. package/docs/spine-core--api-reference.md +822 -0
  84. package/package.json +78 -150
  85. package/.framework/cli/bin.cjs +0 -14
  86. package/.framework/cli/commands/claim.ts +0 -430
  87. package/.framework/cli/commands/init.ts +0 -219
  88. package/.framework/cli/commands/install-app.ts +0 -685
  89. package/.framework/cli/commands/migrate.ts +0 -178
  90. package/.framework/cli/commands/publish.ts +0 -191
  91. package/.framework/cli/commands/status.ts +0 -268
  92. package/.framework/cli/commands/uninstall-app.ts +0 -166
  93. package/.framework/cli/commands/update-app.ts +0 -397
  94. package/.framework/cli/welcome.cjs +0 -45
  95. package/.framework/config/signal-client.ts +0 -147
  96. package/.framework/config/signals.ts +0 -70
  97. package/.framework/docs/STABILITY.md +0 -52
  98. package/.framework/docs/examples/custom-app/README.md +0 -77
  99. package/.framework/docs/examples/custom-function/README.md +0 -27
  100. package/.framework/docs/examples/custom-function/handler.ts +0 -48
  101. package/.framework/docs/examples/custom-webhook/README.md +0 -68
  102. package/.framework/functions/handler-registry.ts +0 -22
  103. package/.framework/functions/signal.ts +0 -168
  104. package/.framework/migrations/000_foundation.sql +0 -1302
  105. package/.framework/migrations/001_seed.sql +0 -163
  106. package/.framework/migrations/002_seed_constraints.sql +0 -19
  107. package/.framework/migrations/003_auth_user_trigger.sql +0 -67
  108. package/.framework/migrations/004_app_version_tracking.sql +0 -8
  109. package/.framework/migrations/005_pipelines_slug_stages.sql +0 -21
  110. package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
  111. package/CHANGELOG.md +0 -71
  112. package/STRUCTURE.md +0 -150
  113. package/bin/spine-framework.cjs +0 -62
  114. package/bin/welcome.cjs +0 -58
  115. package/bin/ws-shim.ts +0 -10
  116. package/config/components.json +0 -25
  117. package/config/deno.lock +0 -108
  118. package/config/package-lock.json +0 -17183
  119. package/config/postcss.config.cjs +0 -10
  120. package/config/tailwind.config.cjs +0 -78
  121. package/config/tsconfig.build.json +0 -32
  122. package/config/tsconfig.cli.json +0 -18
  123. package/config/tsconfig.json +0 -41
  124. package/config/tsconfig.node.json +0 -17
  125. package/config/tsconfig.node.tsbuildinfo +0 -1
  126. package/config/tsconfig.tsbuildinfo +0 -1
  127. package/config/typedoc.json +0 -16
  128. package/config/vite.config.d.ts +0 -2
  129. package/config/vite.config.ts +0 -71
  130. package/dist/cli/commands/agents.d.ts +0 -39
  131. package/dist/cli/commands/agents.d.ts.map +0 -1
  132. package/dist/cli/commands/auth.d.ts +0 -36
  133. package/dist/cli/commands/auth.d.ts.map +0 -1
  134. package/dist/cli/commands/claim.d.ts +0 -38
  135. package/dist/cli/commands/claim.d.ts.map +0 -1
  136. package/dist/cli/commands/create-app.d.ts +0 -23
  137. package/dist/cli/commands/create-app.d.ts.map +0 -1
  138. package/dist/cli/commands/dev.d.ts +0 -24
  139. package/dist/cli/commands/dev.d.ts.map +0 -1
  140. package/dist/cli/commands/doctor.d.ts +0 -42
  141. package/dist/cli/commands/doctor.d.ts.map +0 -1
  142. package/dist/cli/commands/generate.d.ts +0 -36
  143. package/dist/cli/commands/generate.d.ts.map +0 -1
  144. package/dist/cli/commands/init.d.ts +0 -20
  145. package/dist/cli/commands/init.d.ts.map +0 -1
  146. package/dist/cli/commands/install-app.d.ts +0 -30
  147. package/dist/cli/commands/install-app.d.ts.map +0 -1
  148. package/dist/cli/commands/items.d.ts +0 -45
  149. package/dist/cli/commands/items.d.ts.map +0 -1
  150. package/dist/cli/commands/migrate.d.ts +0 -21
  151. package/dist/cli/commands/migrate.d.ts.map +0 -1
  152. package/dist/cli/commands/migrations.d.ts +0 -41
  153. package/dist/cli/commands/migrations.d.ts.map +0 -1
  154. package/dist/cli/commands/pipelines.d.ts +0 -40
  155. package/dist/cli/commands/pipelines.d.ts.map +0 -1
  156. package/dist/cli/commands/publish.d.ts +0 -23
  157. package/dist/cli/commands/publish.d.ts.map +0 -1
  158. package/dist/cli/commands/status.d.ts +0 -23
  159. package/dist/cli/commands/status.d.ts.map +0 -1
  160. package/dist/cli/commands/system.d.ts +0 -29
  161. package/dist/cli/commands/system.d.ts.map +0 -1
  162. package/dist/cli/commands/test.d.ts +0 -46
  163. package/dist/cli/commands/test.d.ts.map +0 -1
  164. package/dist/cli/commands/uninstall-app.d.ts +0 -23
  165. package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
  166. package/dist/cli/commands/update-app.d.ts +0 -24
  167. package/dist/cli/commands/update-app.d.ts.map +0 -1
  168. package/dist/cli/context.d.ts +0 -88
  169. package/dist/cli/context.d.ts.map +0 -1
  170. package/dist/cli/env-loader.d.ts +0 -14
  171. package/dist/cli/env-loader.d.ts.map +0 -1
  172. package/dist/cli/index.d.ts +0 -44
  173. package/dist/cli/index.d.ts.map +0 -1
  174. package/dist/config/signals.d.ts +0 -82
  175. package/dist/config/signals.d.ts.map +0 -1
  176. package/dist/functions/_shared/agent-runner.d.ts +0 -156
  177. package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
  178. package/dist/functions/_shared/app-manifest.d.ts +0 -68
  179. package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
  180. package/dist/functions/_shared/audit.d.ts +0 -91
  181. package/dist/functions/_shared/audit.d.ts.map +0 -1
  182. package/dist/functions/_shared/db.d.ts +0 -125
  183. package/dist/functions/_shared/db.d.ts.map +0 -1
  184. package/dist/functions/_shared/index.d.ts +0 -299
  185. package/dist/functions/_shared/index.d.ts.map +0 -1
  186. package/dist/functions/_shared/middleware.d.ts +0 -315
  187. package/dist/functions/_shared/middleware.d.ts.map +0 -1
  188. package/dist/functions/_shared/permissions.d.ts +0 -626
  189. package/dist/functions/_shared/permissions.d.ts.map +0 -1
  190. package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
  191. package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
  192. package/dist/functions/_shared/principal.d.ts +0 -284
  193. package/dist/functions/_shared/principal.d.ts.map +0 -1
  194. package/dist/functions/_shared/resolve-ids.d.ts +0 -10
  195. package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
  196. package/dist/functions/_shared/schema-utils.d.ts +0 -181
  197. package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
  198. package/dist/functions/_shared/testing.d.ts +0 -172
  199. package/dist/functions/_shared/testing.d.ts.map +0 -1
  200. package/dist/functions/_shared/trigger-engine.d.ts +0 -140
  201. package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
  202. package/dist/functions/_shared/webhook-registration.d.ts +0 -81
  203. package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
  204. package/dist/functions/_shared/webhook-registry.d.ts +0 -41
  205. package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
  206. package/dist/functions/account-nodes.d.ts +0 -48
  207. package/dist/functions/account-nodes.d.ts.map +0 -1
  208. package/dist/functions/admin-data.d.ts +0 -178
  209. package/dist/functions/admin-data.d.ts.map +0 -1
  210. package/dist/functions/ai-agents.d.ts +0 -125
  211. package/dist/functions/ai-agents.d.ts.map +0 -1
  212. package/dist/functions/api-keys.d.ts +0 -140
  213. package/dist/functions/api-keys.d.ts.map +0 -1
  214. package/dist/functions/apps.d.ts +0 -163
  215. package/dist/functions/apps.d.ts.map +0 -1
  216. package/dist/functions/auth.d.ts +0 -74
  217. package/dist/functions/auth.d.ts.map +0 -1
  218. package/dist/functions/debug-auth.d.ts +0 -33
  219. package/dist/functions/debug-auth.d.ts.map +0 -1
  220. package/dist/functions/embeddings.d.ts +0 -205
  221. package/dist/functions/embeddings.d.ts.map +0 -1
  222. package/dist/functions/handler-registry.d.ts +0 -22
  223. package/dist/functions/handler-registry.d.ts.map +0 -1
  224. package/dist/functions/integration-routes.d.ts +0 -45
  225. package/dist/functions/integration-routes.d.ts.map +0 -1
  226. package/dist/functions/integrations.d.ts +0 -124
  227. package/dist/functions/integrations.d.ts.map +0 -1
  228. package/dist/functions/item-progress.d.ts +0 -41
  229. package/dist/functions/item-progress.d.ts.map +0 -1
  230. package/dist/functions/logs.d.ts +0 -162
  231. package/dist/functions/logs.d.ts.map +0 -1
  232. package/dist/functions/observability.d.ts +0 -123
  233. package/dist/functions/observability.d.ts.map +0 -1
  234. package/dist/functions/pipeline-executions.d.ts +0 -190
  235. package/dist/functions/pipeline-executions.d.ts.map +0 -1
  236. package/dist/functions/pipelines.d.ts +0 -171
  237. package/dist/functions/pipelines.d.ts.map +0 -1
  238. package/dist/functions/prompt-configs.d.ts +0 -125
  239. package/dist/functions/prompt-configs.d.ts.map +0 -1
  240. package/dist/functions/roles.d.ts +0 -118
  241. package/dist/functions/roles.d.ts.map +0 -1
  242. package/dist/functions/signal.d.ts +0 -73
  243. package/dist/functions/signal.d.ts.map +0 -1
  244. package/dist/functions/system-cron.d.ts +0 -65
  245. package/dist/functions/system-cron.d.ts.map +0 -1
  246. package/dist/functions/system.d.ts +0 -29
  247. package/dist/functions/system.d.ts.map +0 -1
  248. package/dist/functions/tests.d.ts +0 -28
  249. package/dist/functions/tests.d.ts.map +0 -1
  250. package/dist/functions/timers.d.ts +0 -139
  251. package/dist/functions/timers.d.ts.map +0 -1
  252. package/dist/functions/triggers.d.ts +0 -203
  253. package/dist/functions/triggers.d.ts.map +0 -1
  254. package/dist/functions/types.d.ts +0 -151
  255. package/dist/functions/types.d.ts.map +0 -1
  256. package/dist/src/types/types.d.ts +0 -364
  257. package/dist/src/types/types.d.ts.map +0 -1
  258. package/index.html +0 -13
  259. package/netlify.toml +0 -36
  260. package/package-project.json +0 -71
  261. package/scripts/ingest-chunks.cjs +0 -202
  262. package/scripts/kb-chunk-parser.cjs +0 -312
  263. package/scripts/kb-chunk-parser.ts +0 -330
  264. package/scripts/load-test-app-install.ts +0 -484
  265. package/scripts/netlify-dev-wrapper.sh +0 -22
  266. package/vitest.config.ts +0 -45
  267. /package/{scripts → .framework/scripts}/app-install-cli.ts +0 -0
  268. /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
  269. /package/{scripts → .framework/scripts}/verify-integrity.sh +0 -0
@@ -28,7 +28,7 @@ import { adminDb } from '../../functions/_shared/index.ts'
28
28
 
29
29
  const __filename = fileURLToPath(import.meta.url)
30
30
  const __dirname = dirname(__filename)
31
- const PROJECT_ROOT = process.cwd()
31
+ const PROJECT_ROOT = resolve(__dirname, '../../../')
32
32
 
33
33
  function toTitleCase(slug: string): string {
34
34
  return slug.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
@@ -5,81 +5,177 @@
5
5
  * @layer cli
6
6
  * @stability stable
7
7
  *
8
- * `spine-framework dev` command — assemble and start the Netlify dev server.
8
+ * `spine dev` command — unified development server for Spine.
9
+ * Handles assembly, integrity verification, and Vite dev server startup.
10
+ *
11
+ * **Commands:**
12
+ * | Subcommand | Description |
13
+ * |-------------------|-------------------------------------------------------|
14
+ * | `dev` | Start dev server with assembly and integrity check |
15
+ * | `dev --no-verify` | Skip integrity verification (faster) |
16
+ * | `dev --port 3002` | Use custom port (default: 3001) |
17
+ * | `dev --watch` | Watch v2-custom for changes and auto-reassemble |
18
+ * | `dev --no-watch` | Disable file watching (manual rebuild only) |
9
19
  *
10
20
  * **What happens:**
11
- * 1. Runs `npm run assemble` to merge framework + custom apps
12
- * 2. Starts `netlify dev` (which runs Vite + Netlify Functions)
21
+ * 1. Runs `assemble:v2` to merge v2-core + v2-custom
22
+ * 2. Runs `verify` to check core integrity (unless --no-verify)
23
+ * 3. Starts Vite dev server on port 3001 (or --port)
24
+ * 4. Watches v2-custom/ for changes (--watch by default)
25
+ * 5. Fast incremental reassembly on custom code changes
13
26
  *
14
27
  * **Usage:**
15
28
  * ```bash
16
- * spine-framework dev
17
- * spine-framework dev --no-assemble # skip assembly step
29
+ * spine dev
30
+ * spine dev --port 3002
31
+ * spine dev --no-verify
18
32
  * ```
19
33
  *
20
- * @seeAlso scripts/assemble.sh (assembly script)
21
- * @seeAlso netlify.toml (Netlify dev configuration)
34
+ * @seeAlso scripts/assemble-v2.sh (assembly script)
35
+ * @seeAlso scripts/verify-integrity.sh (verification script)
36
+ * @seeAlso vite.config.ts (Vite configuration)
22
37
  */
23
38
 
24
39
  import type { Command } from 'commander'
25
- import { spawn } from 'child_process'
40
+ import { spawn, execSync } from 'child_process'
26
41
  import { resolve, dirname } from 'path'
27
42
  import { fileURLToPath } from 'url'
28
- import { existsSync } from 'fs'
43
+ import { watch, existsSync, statSync, readdirSync } from 'fs'
29
44
 
30
45
  const __filename = fileURLToPath(import.meta.url)
31
46
  const __dirname = dirname(__filename)
32
- const PROJECT_ROOT = process.cwd()
47
+ const PROJECT_ROOT = resolve(__dirname, '../../../')
33
48
 
34
49
  // ─── COMMAND REGISTRATION ──────────────────────────────────────────────────
35
50
 
36
51
  export function registerDevCommands(program: Command) {
37
52
  program
38
53
  .command('dev')
39
- .description('Assemble and start the Netlify dev server')
40
- .option('--no-assemble', 'Skip the assembly step')
54
+ .description('Start Spine development server')
55
+ .option('--no-verify', 'Skip integrity verification')
56
+ .option('--port <port>', 'Dev server port', '3001')
57
+ .option('--host', 'Expose to network', false)
58
+ .option('--no-watch', 'Disable file watching')
59
+ .option('--watch-delay <ms>', 'Debounce delay for reassembly', '500')
41
60
  .action(async (opts) => {
42
61
  console.log('\n🚀 Starting Spine dev server...\n')
43
62
 
44
63
  try {
45
64
  // Step 1: Assembly
46
- if (opts.assemble !== false) {
47
- console.log('📦 Step 1: Assembling...')
48
- const assemblyExitCode = await runCommand('npm', ['run', 'assemble'], PROJECT_ROOT)
65
+ console.log('📦 Step 1: Assembling v2-core + v2-custom...')
66
+ const assemblyExitCode = await runCommand(
67
+ 'bash',
68
+ [resolve(PROJECT_ROOT, 'scripts/assemble-v2.sh')],
69
+ PROJECT_ROOT
70
+ )
71
+
72
+ if (assemblyExitCode !== 0) {
73
+ console.error('\n❌ Assembly failed. Please check the errors above.')
74
+ process.exit(1)
75
+ }
76
+ console.log('✓ Assembly complete\n')
49
77
 
50
- if (assemblyExitCode !== 0) {
51
- console.error('\n❌ Assembly failed. Please check the errors above.')
52
- process.exit(1)
78
+ // Step 2: Integrity verification (unless skipped)
79
+ if (opts.verify !== false) {
80
+ console.log('🔒 Step 2: Verifying core integrity...')
81
+ const verifyExitCode = await runCommand(
82
+ 'bash',
83
+ [resolve(PROJECT_ROOT, 'scripts/verify-integrity.sh')],
84
+ PROJECT_ROOT
85
+ )
86
+
87
+ if (verifyExitCode !== 0) {
88
+ console.warn('\n⚠️ Integrity check failed. Continuing anyway (--no-verify to skip)\n')
89
+ } else {
90
+ console.log('✓ Integrity verified\n')
53
91
  }
54
- console.log('✓ Assembly complete\n')
55
92
  } else {
56
- console.log('⏭️ Step 1: Skipping assembly (--no-assemble)\n')
93
+ console.log('⏭️ Step 2: Skipping integrity verification (--no-verify)\n')
57
94
  }
58
95
 
59
- // Step 2: Start netlify dev
60
- console.log('🌐 Step 2: Starting netlify dev...')
96
+ // Step 3: Start Vite
97
+ console.log(`🌐 Step 3: Starting Vite dev server on port ${opts.port}...`)
61
98
  console.log(' (Press Ctrl+C to stop)\n')
62
99
 
63
- const netlifyBin = resolve(PROJECT_ROOT, 'node_modules/.bin/netlify')
64
- const cmd = existsSync(netlifyBin) ? netlifyBin : 'netlify'
100
+ const vite = spawn(
101
+ resolve(PROJECT_ROOT, 'node_modules/.bin/vite'),
102
+ [
103
+ '--config', resolve(PROJECT_ROOT, 'vite.config.ts'),
104
+ '--port', opts.port,
105
+ ...(opts.host ? ['--host'] : [])
106
+ ],
107
+ {
108
+ cwd: PROJECT_ROOT,
109
+ stdio: 'inherit',
110
+ env: { ...process.env, SPINE_DEV: 'true' }
111
+ }
112
+ )
113
+
114
+ // Step 4: Watch v2-custom for changes (unless disabled)
115
+ let watcher: ReturnType<typeof watch> | null = null
116
+ if (opts.watch !== false) {
117
+ const watchDelay = parseInt(opts.watchDelay) || 500
118
+ const customDir = resolve(PROJECT_ROOT, 'v2-custom')
65
119
 
66
- const netlify = spawn(cmd, ['dev'], {
67
- cwd: PROJECT_ROOT,
68
- stdio: 'inherit',
69
- env: { ...process.env }
70
- })
120
+ if (existsSync(customDir)) {
121
+ console.log(`👁️ Step 4: Watching v2-custom/ for changes...`)
122
+ console.log(` (Changes trigger fast reassembly after ${watchDelay}ms debounce)\n`)
123
+
124
+ let reassemblyTimeout: NodeJS.Timeout | null = null
125
+
126
+ watcher = watch(customDir, { recursive: true }, (eventType, filename) => {
127
+ // Ignore node_modules, .git, and temp files
128
+ if (!filename || filename.includes('node_modules') || filename.startsWith('.')) {
129
+ return
130
+ }
131
+
132
+ console.log(`\n📝 Change detected in v2-custom/${filename}`)
71
133
 
72
- netlify.on('exit', (code) => {
134
+ // Debounce reassembly
135
+ if (reassemblyTimeout) {
136
+ clearTimeout(reassemblyTimeout)
137
+ }
138
+
139
+ reassemblyTimeout = setTimeout(async () => {
140
+ console.log('🔧 Running fast reassembly...')
141
+ try {
142
+ const start = Date.now()
143
+ await fastReassemble(PROJECT_ROOT)
144
+ const duration = Date.now() - start
145
+ console.log(`✓ Reassembly complete (${duration}ms)\n`)
146
+ } catch (err: any) {
147
+ console.error(`✗ Reassembly failed: ${err.message}\n`)
148
+ }
149
+ }, watchDelay)
150
+ })
151
+ } else {
152
+ console.log('⏭️ Step 4: v2-custom/ not found, skipping watch mode\n')
153
+ }
154
+ } else {
155
+ console.log('⏭️ Step 4: File watching disabled (--no-watch)\n')
156
+ }
157
+
158
+ vite.on('exit', (code) => {
159
+ if (watcher) {
160
+ watcher.close()
161
+ }
73
162
  process.exit(code || 0)
74
163
  })
75
164
 
165
+ // Handle Ctrl+C gracefully
76
166
  process.on('SIGINT', () => {
77
- console.log('\n\n🛑 Shutting down...')
78
- netlify.kill('SIGTERM')
167
+ console.log('\n\n🛑 Shutting down dev server...')
168
+ if (watcher) {
169
+ watcher.close()
170
+ }
171
+ vite.kill('SIGTERM')
79
172
  })
80
173
 
81
174
  process.on('SIGTERM', () => {
82
- netlify.kill('SIGTERM')
175
+ if (watcher) {
176
+ watcher.close()
177
+ }
178
+ vite.kill('SIGTERM')
83
179
  })
84
180
 
85
181
  } catch (err: any) {
@@ -111,3 +207,89 @@ function runCommand(command: string, args: string[], cwd: string): Promise<numbe
111
207
  })
112
208
  })
113
209
  }
210
+
211
+ /**
212
+ * Fast incremental reassembly of v2-custom files only.
213
+ * Uses rsync if available, falls back to selective copy.
214
+ */
215
+ async function fastReassemble(projectRoot: string): Promise<void> {
216
+ const customDir = resolve(projectRoot, 'v2-custom')
217
+ const srcDest = resolve(projectRoot, 'src/v2-assembled')
218
+ const funcsDest = resolve(projectRoot, 'functions')
219
+
220
+ // Check if rsync is available
221
+ let hasRsync = false
222
+ try {
223
+ execSync('which rsync', { stdio: 'pipe' })
224
+ hasRsync = true
225
+ } catch {
226
+ hasRsync = false
227
+ }
228
+
229
+ // Reassemble custom frontend files
230
+ const customSrcDir = resolve(customDir, 'src')
231
+ if (existsSync(customSrcDir)) {
232
+ const destDir = srcDest
233
+
234
+ if (hasRsync) {
235
+ // Use rsync for efficient incremental sync
236
+ execSync(
237
+ `rsync -av --delete "${customSrcDir}/" "${destDir}/"`,
238
+ { stdio: 'pipe', cwd: projectRoot }
239
+ )
240
+ } else {
241
+ // Fallback: copy files that have changed
242
+ copyChangedFiles(customSrcDir, destDir)
243
+ }
244
+ }
245
+
246
+ // Reassemble custom function files
247
+ const customFuncsDir = resolve(customDir, 'functions')
248
+ if (existsSync(customFuncsDir)) {
249
+ if (hasRsync) {
250
+ execSync(
251
+ `rsync -av --delete "${customFuncsDir}/" "${funcsDest}/"`,
252
+ { stdio: 'pipe', cwd: projectRoot }
253
+ )
254
+ } else {
255
+ copyChangedFiles(customFuncsDir, funcsDest)
256
+ }
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Copy only files that have changed (mtime comparison).
262
+ * Fallback when rsync is not available.
263
+ */
264
+ function copyChangedFiles(src: string, dest: string): void {
265
+ const files = readdirSync(src, { withFileTypes: true })
266
+
267
+ for (const entry of files) {
268
+ const srcPath = resolve(src, entry.name)
269
+ const destPath = resolve(dest, entry.name)
270
+
271
+ if (entry.isDirectory()) {
272
+ if (!existsSync(destPath)) {
273
+ // Create directory
274
+ const { mkdirSync } = require('fs')
275
+ mkdirSync(destPath, { recursive: true })
276
+ }
277
+ // Recurse
278
+ copyChangedFiles(srcPath, destPath)
279
+ } else {
280
+ // Check if file needs copying
281
+ const srcStat = statSync(srcPath)
282
+ let needsCopy = true
283
+
284
+ if (existsSync(destPath)) {
285
+ const destStat = statSync(destPath)
286
+ needsCopy = srcStat.mtime > destStat.mtime
287
+ }
288
+
289
+ if (needsCopy) {
290
+ const { copyFileSync } = require('fs')
291
+ copyFileSync(srcPath, destPath)
292
+ }
293
+ }
294
+ }
295
+ }
@@ -19,13 +19,6 @@
19
19
  * | items | commands/items.ts |
20
20
  * | agents | commands/agents.ts |
21
21
  * | migrations | commands/migrations.ts |
22
- * | init | commands/init.ts |
23
- * | install-app | commands/install-app.ts |
24
- * | update-app | commands/update-app.ts |
25
- * | publish | commands/publish.ts |
26
- * | status | commands/status.ts |
27
- * | claim | commands/claim.ts |
28
- * | uninstall | commands/uninstall-app.ts |
29
22
  *
30
23
  * **Usage:**
31
24
  * ```bash
@@ -42,6 +35,7 @@
42
35
  * @seeAlso functions/_shared/index.ts (core functions exposed to CLI)
43
36
  */
44
37
 
38
+ import './env-loader.ts'
45
39
  import { Command } from 'commander'
46
40
 
47
41
  const program = new Command()
@@ -52,75 +46,43 @@ program
52
46
  .version('2.0.0')
53
47
  .option('--account <id>', 'Override the account ID for this command')
54
48
 
55
- // Commands that do NOT need DB/env — safe to run on a fresh install
56
- const { registerInitCommands } = await import('./commands/init.ts')
57
- const { registerMigrateCommands } = await import('./commands/migrate.ts')
58
- registerInitCommands(program)
59
- registerMigrateCommands(program)
49
+ const [
50
+ { registerAuthCommands },
51
+ { registerPipelineCommands },
52
+ { registerItemCommands },
53
+ { registerAgentCommands },
54
+ { registerMigrationCommands },
55
+ { registerDoctorCommands },
56
+ { registerDevCommands },
57
+ { registerTestCommands },
58
+ { registerSystemCommands },
59
+ { registerGenerateCommands },
60
+ { registerCreateAppCommands },
61
+ ] = await Promise.all([
62
+ import('./commands/auth.ts'),
63
+ import('./commands/pipelines.ts'),
64
+ import('./commands/items.ts'),
65
+ import('./commands/agents.ts'),
66
+ import('./commands/migrations.ts'),
67
+ import('./commands/doctor.ts'),
68
+ import('./commands/dev.ts'),
69
+ import('./commands/test.ts'),
70
+ import('./commands/system.ts'),
71
+ import('./commands/generate.ts'),
72
+ import('./commands/create-app.ts'),
73
+ ])
60
74
 
61
- // All other commands require SUPABASE_URL to be set — only load if not init/migrate
62
- const subcommand = process.argv[2]
63
- const noDbCommands = ['init', 'migrate', '--help', '-h', '--version', '-V', undefined]
64
-
65
- if (!noDbCommands.includes(subcommand)) {
66
- await import('./env-loader.ts')
67
-
68
- const [
69
- { registerAuthCommands },
70
- { registerPipelineCommands },
71
- { registerItemCommands },
72
- { registerAgentCommands },
73
- { registerMigrationCommands },
74
- { registerDoctorCommands },
75
- { registerDevCommands },
76
- { registerTestCommands },
77
- { registerSystemCommands },
78
- { registerGenerateCommands },
79
- { registerCreateAppCommands },
80
- { registerInstallAppCommands },
81
- { registerUpdateAppCommands },
82
- { registerPublishCommands },
83
- { registerStatusCommands },
84
- { registerClaimCommands },
85
- { registerUninstallAppCommands },
86
- ] = await Promise.all([
87
- import('./commands/auth.ts'),
88
- import('./commands/pipelines.ts'),
89
- import('./commands/items.ts'),
90
- import('./commands/agents.ts'),
91
- import('./commands/migrations.ts'),
92
- import('./commands/doctor.ts'),
93
- import('./commands/dev.ts'),
94
- import('./commands/test.ts'),
95
- import('./commands/system.ts'),
96
- import('./commands/generate.ts'),
97
- import('./commands/create-app.ts'),
98
- import('./commands/install-app.ts'),
99
- import('./commands/update-app.ts'),
100
- import('./commands/publish.ts'),
101
- import('./commands/status.ts'),
102
- import('./commands/claim.ts'),
103
- import('./commands/uninstall-app.ts'),
104
- ])
105
-
106
- registerAuthCommands(program)
107
- registerPipelineCommands(program)
108
- registerItemCommands(program)
109
- registerAgentCommands(program)
110
- registerMigrationCommands(program)
111
- registerDoctorCommands(program)
112
- registerDevCommands(program)
113
- registerTestCommands(program)
114
- registerSystemCommands(program)
115
- registerGenerateCommands(program)
116
- registerCreateAppCommands(program)
117
- registerInstallAppCommands(program)
118
- registerUpdateAppCommands(program)
119
- registerPublishCommands(program)
120
- registerStatusCommands(program)
121
- registerClaimCommands(program)
122
- registerUninstallAppCommands(program)
123
- }
75
+ registerAuthCommands(program)
76
+ registerPipelineCommands(program)
77
+ registerItemCommands(program)
78
+ registerAgentCommands(program)
79
+ registerMigrationCommands(program)
80
+ registerDoctorCommands(program)
81
+ registerDevCommands(program)
82
+ registerTestCommands(program)
83
+ registerSystemCommands(program)
84
+ registerGenerateCommands(program)
85
+ registerCreateAppCommands(program)
124
86
 
125
87
  program.parseAsync(process.argv).catch((err) => {
126
88
  console.error('Error:', err.message)
@@ -16,8 +16,8 @@
16
16
  * @seeAlso 015_simplify_apps_table.sql (database structure)
17
17
  */
18
18
 
19
- import { readFileSync, readdirSync, existsSync } from 'fs'
20
- import { resolve, join } from 'path'
19
+ import { readFileSync } from 'fs'
20
+ import { resolve } from 'path'
21
21
 
22
22
  export interface AppManifest {
23
23
  name: string
@@ -168,17 +168,10 @@ export function mergeWithManifest(dbRecord: any): any {
168
168
  * @returns Array of discovered app slugs and their paths
169
169
  */
170
170
  export function discoverManifests(): Array<{slug: string, path: string}> {
171
- const appsDir = resolve(process.cwd(), 'custom/apps')
172
- if (!existsSync(appsDir)) return []
173
-
174
- const results: Array<{slug: string, path: string}> = []
175
- for (const entry of readdirSync(appsDir, { withFileTypes: true })) {
176
- if (!entry.isDirectory()) continue
177
- if (entry.name === 'lib' || entry.name.startsWith('.')) continue
178
- const manifestPath = join('custom/apps', entry.name, 'manifest.json')
179
- if (existsSync(resolve(process.cwd(), manifestPath))) {
180
- results.push({ slug: entry.name, path: manifestPath })
181
- }
182
- }
183
- return results
171
+ // In production, this would scan the filesystem
172
+ // For now, return known manifests based on the plan
173
+ return [
174
+ { slug: 'cortex', path: 'custom/apps/cortex/manifest.json' },
175
+ { slug: 'customer-portal', path: 'custom/apps/customer-portal/manifest.json' }
176
+ ]
184
177
  }
@@ -15,14 +15,14 @@
15
15
  */
16
16
 
17
17
  import { createClient } from '@supabase/supabase-js'
18
- import WebSocket from 'ws'
19
-
20
- // WebSocket constructor for Supabase Realtime in Node.js environment
21
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
- const RealtimeWebSocket = WebSocket as any
23
18
 
24
19
  // ─── ENVIRONMENT RESOLUTION ──────────────────────────────────────────────────
25
20
 
21
+ const _env = (globalThis as any).process?.env || {}
22
+ const supabaseUrl: string = _env.SUPABASE_URL!
23
+ const supabaseServiceKey: string = _env.SUPABASE_SERVICE_ROLE_KEY!
24
+ const supabaseAnonKey: string = _env.SUPABASE_ANON_KEY!
25
+
26
26
  /**
27
27
  * Active database schema name, read from `DB_SCHEMA` env var.
28
28
  *
@@ -35,10 +35,6 @@ const RealtimeWebSocket = WebSocket as any
35
35
  * @sideEffects none
36
36
  * @calledBy adminDb, getUserDb (applied at client construction time)
37
37
  */
38
- const _env = (globalThis as any).process?.env || {}
39
- const supabaseUrl: string = _env.SUPABASE_URL || 'https://placeholder.supabase.co'
40
- const supabaseServiceKey: string = _env.SUPABASE_SERVICE_ROLE_KEY || ''
41
- const supabaseAnonKey: string = _env.SUPABASE_ANON_KEY || ''
42
38
  const dbSchema: string = _env.DB_SCHEMA || 'public'
43
39
 
44
40
  // ─── CLIENTS ─────────────────────────────────────────────────────────────────
@@ -78,9 +74,9 @@ const dbSchema: string = _env.DB_SCHEMA || 'public'
78
74
  * ```
79
75
  */
80
76
  export const adminDb = createClient(supabaseUrl, supabaseServiceKey, {
81
- db: { schema: dbSchema },
82
- auth: { autoRefreshToken: false, persistSession: false },
83
- realtime: { transport: RealtimeWebSocket }
77
+ db: {
78
+ schema: dbSchema
79
+ }
84
80
  })
85
81
 
86
82
  // ─── CHUNK_START: SHARED_DB_GET_USER_DB ──────────────────────────────────────────────
@@ -99,9 +95,9 @@ export const adminDb = createClient(supabaseUrl, supabaseServiceKey, {
99
95
  */
100
96
  export function getUserDb(jwt: string) {
101
97
  return createClient(supabaseUrl, supabaseAnonKey, {
102
- db: { schema: dbSchema },
103
- auth: { autoRefreshToken: false, persistSession: false },
104
- realtime: { transport: RealtimeWebSocket }, // WebSocket for Node.js 20+
98
+ db: {
99
+ schema: dbSchema
100
+ },
105
101
  global: {
106
102
  headers: {
107
103
  Authorization: `Bearer ${jwt}`
@@ -285,21 +285,21 @@ export { emitAudit } from './audit'
285
285
  // ============================================
286
286
 
287
287
  /**
288
- * lookupHandler — look up webhook handler metadata by name.
288
+ * resolveHandler — dynamically load a webhook handler by name.
289
289
  *
290
- * Queries the webhook_handlers table for handler registration info.
291
- * Actual handler loading is done via HANDLER_REGISTRY static imports.
290
+ * Used by integration-routes.ts to resolve handlers at runtime
291
+ * without static imports, enabling custom handlers to self-register.
292
292
  *
293
293
  * @param handlerName — The handler identifier from webhook_handlers table
294
- * @returns Handler metadata or null if not found
294
+ * @returns Handler function or null if not found
295
295
  *
296
296
  * @example
297
297
  * ```ts
298
- * const meta = await lookupHandler('cortex-webhook')
299
- * if (meta) console.log(meta.functionName)
298
+ * const handler = await resolveHandler('cortex-webhook')
299
+ * if (handler) await handler(event, context)
300
300
  * ```
301
301
  */
302
- export { lookupHandler } from './webhook-registry'
302
+ export { resolveHandler, lookupHandler, loadHandler } from './webhook-registry'
303
303
 
304
304
  /**
305
305
  * registerWebhookHandler — self-register a custom webhook handler.
@@ -380,12 +380,3 @@ export {
380
380
  expectErrorResponse
381
381
  } from './testing'
382
382
  export type { TestContext, TestPrincipal, TestLogger } from './testing'
383
-
384
- export {
385
- resolveTypeIds,
386
- resolveTypeId,
387
- resolveLinkTypeIds,
388
- resolveAccountId,
389
- resolveAgentId,
390
- resolvePromptConfigId
391
- } from './resolve-ids'
@@ -32,11 +32,6 @@
32
32
 
33
33
  import { getUserDb, adminDb } from './db'
34
34
  import { createClient } from '@supabase/supabase-js'
35
- import WebSocket from 'ws'
36
-
37
- // WebSocket constructor for Supabase Realtime in Node.js environment
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- const RealtimeWebSocket = WebSocket as any
40
35
 
41
36
  // ─── AUTH CLIENT ─────────────────────────────────────────────────────────────
42
37
 
@@ -51,8 +46,7 @@ const supabase = createClient(
51
46
  auth: {
52
47
  autoRefreshToken: false,
53
48
  persistSession: false
54
- },
55
- realtime: { transport: RealtimeWebSocket } // WebSocket for Node.js 20+
49
+ }
56
50
  }
57
51
  )
58
52
 
@@ -501,7 +495,7 @@ async function resolveHumanPrincipal(token: string, event: any): Promise<Princip
501
495
  type: 'human',
502
496
  accountId: person.account_id || null,
503
497
  roles: roleSlugs,
504
- displayName: person.full_name || person.email,
498
+ displayName: person.display_name || person.email,
505
499
  email: person.email,
506
500
  provenance: {
507
501
  sourceType: 'jwt',
@@ -567,60 +561,8 @@ async function resolveInternalPersonId(authUserId: string, email?: string): Prom
567
561
 
568
562
  if (byAuthId) return byAuthId.id
569
563
 
570
- // Not found — auto-create the person (handles cases where auth user pre-existed DB reset
571
- // or trigger fired before seed data was in place)
572
- const { data: account } = await adminDb
573
- .from('accounts')
574
- .select('id')
575
- .eq('slug', 'spine-system')
576
- .single()
577
-
578
- const { data: type } = await adminDb
579
- .from('types')
580
- .select('id')
581
- .eq('kind', 'person')
582
- .eq('slug', 'person')
583
- .single()
584
-
585
- if (!account || !type) {
586
- throw new Error('Cannot auto-create person: spine-system account or person type not found in DB')
587
- }
588
-
589
- // First person ever gets system_admin role
590
- const { count } = await adminDb
591
- .from('people')
592
- .select('id', { count: 'exact', head: true })
593
-
594
- let roleId: string | null = null
595
- if ((count ?? 0) === 0) {
596
- const { data: adminRole } = await adminDb
597
- .from('roles')
598
- .select('id')
599
- .eq('slug', 'system_admin')
600
- .single()
601
- roleId = adminRole?.id ?? null
602
- }
603
-
604
- const { data: newPerson, error: createError } = await adminDb
605
- .from('people')
606
- .upsert({
607
- id: authUserId,
608
- auth_uid: authUserId,
609
- email: email ?? '',
610
- full_name: email ? email.split('@')[0] : 'Unknown',
611
- account_id: account.id,
612
- type_id: type.id,
613
- role_id: roleId,
614
- is_active: true
615
- }, { onConflict: 'id' })
616
- .select('id')
617
- .single()
618
-
619
- if (createError || !newPerson) {
620
- throw new Error('Failed to auto-create person for auth user ' + authUserId + ': ' + createError?.message)
621
- }
622
-
623
- return newPerson.id
564
+ // Not found - return the auth ID as fallback
565
+ return authUserId
624
566
  }
625
567
 
626
568
  // ─── HELPER FUNCTIONS ────────────────────────────────────────────────────────