spine-framework 0.3.16 → 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 +3 -7
  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 -68
  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
@@ -1,430 +0,0 @@
1
- /// <reference types="node" />
2
- /**
3
- * @module cli/commands/claim
4
- * @audience installer
5
- * @layer cli
6
- * @stability stable
7
- *
8
- * `spine-framework claim` — Claim framework installation via Spine Portal.
9
- *
10
- * Engagement-based claim system that offers contextual prompts based on
11
- * user activity. Tracks engagement signals and triggers claim prompts
12
- * at value-realization moments.
13
- *
14
- * **Usage:**
15
- * ```bash
16
- * spine-framework claim # Interactive claim flow
17
- * spine-framework claim --status # Show claim status only
18
- * spine-framework claim --token <t> # Headless claim with token
19
- * ```
20
- */
21
-
22
- import type { Command } from 'commander'
23
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
24
- import { resolve } from 'path'
25
- import { createHash, randomBytes } from 'crypto'
26
- import * as readline from 'readline'
27
- import { signalConfig, claimTriggers, engagementSignals, type EngagementData, type ClaimPrompt, type EngagementSignalType } from '../../config/signals.ts'
28
-
29
- const PROJECT_ROOT = process.cwd()
30
- const SPINE_DIR = resolve(PROJECT_ROOT, '.spine')
31
- const SERIAL_FILE = resolve(SPINE_DIR, 'serial.json')
32
- const ENGAGEMENT_FILE = resolve(SPINE_DIR, 'engagement.json')
33
-
34
- // ─── SERIAL NUMBER MANAGEMENT ────────────────────────────────────────────────
35
-
36
- export interface SerialData {
37
- serial_number: string
38
- salt: string
39
- created_at: string
40
- framework_version: string
41
- account_id: string | null
42
- machine_fingerprint: string
43
- }
44
-
45
- function generateMachineFingerprint(): string {
46
- const hostname = process.env.HOSTNAME || process.env.COMPUTERNAME || 'unknown'
47
- const platform = process.platform
48
- const arch = process.arch
49
- const data = `${hostname}-${platform}-${arch}-${process.env.USER || 'unknown'}`
50
- return createHash('sha256').update(data).digest('hex').substring(0, 16)
51
- }
52
-
53
- function generateSerialNumber(version: string, machineId: string, salt: string): string {
54
- const timestamp = Date.now().toString()
55
- const data = `spine-framework-${version}-${machineId}-${timestamp}-${salt}`
56
- const hash = createHash('sha256').update(data).digest('hex')
57
- return `spn_fw_${hash.substring(0, 16).toUpperCase()}`
58
- }
59
-
60
- export function ensureSerialData(): SerialData {
61
- if (existsSync(SERIAL_FILE)) {
62
- try {
63
- return JSON.parse(readFileSync(SERIAL_FILE, 'utf8')) as SerialData
64
- } catch { /* fall through to create new */ }
65
- }
66
-
67
- // Create .spine directory if needed
68
- if (!existsSync(SPINE_DIR)) {
69
- mkdirSync(SPINE_DIR, { recursive: true })
70
- }
71
-
72
- // Generate new serial
73
- const salt = randomBytes(32).toString('hex')
74
- const machineFingerprint = generateMachineFingerprint()
75
- const frameworkVersion = getFrameworkVersion()
76
- const serialNumber = generateSerialNumber(frameworkVersion, machineFingerprint, salt)
77
-
78
- const serialData: SerialData = {
79
- serial_number: serialNumber,
80
- salt,
81
- created_at: new Date().toISOString(),
82
- framework_version: frameworkVersion,
83
- account_id: null,
84
- machine_fingerprint: machineFingerprint,
85
- }
86
-
87
- writeFileSync(SERIAL_FILE, JSON.stringify(serialData, null, 2) + '\n', 'utf8')
88
- return serialData
89
- }
90
-
91
- function getFrameworkVersion(): string {
92
- const pkgJsonPath = resolve(PROJECT_ROOT, 'package.json')
93
- if (existsSync(pkgJsonPath)) {
94
- try {
95
- const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
96
- return pkg.version || '0.0.0'
97
- } catch { /* fall through */ }
98
- }
99
- return '0.0.0'
100
- }
101
-
102
- // ─── ENGAGEMENT TRACKING ─────────────────────────────────────────────────────
103
-
104
- export function readEngagementData(): EngagementData | null {
105
- if (!existsSync(ENGAGEMENT_FILE)) return null
106
- try {
107
- return JSON.parse(readFileSync(ENGAGEMENT_FILE, 'utf8')) as EngagementData
108
- } catch {
109
- return null
110
- }
111
- }
112
-
113
- export function writeEngagementData(data: EngagementData): void {
114
- if (!existsSync(SPINE_DIR)) {
115
- mkdirSync(SPINE_DIR, { recursive: true })
116
- }
117
- writeFileSync(ENGAGEMENT_FILE, JSON.stringify(data, null, 2) + '\n', 'utf8')
118
- }
119
-
120
- export function recordEngagementSignal(signalType: EngagementSignalType): void {
121
- const serialData = ensureSerialData()
122
- let engagement = readEngagementData()
123
-
124
- if (!engagement) {
125
- engagement = {
126
- framework_install_serial: serialData.serial_number,
127
- engagement_score: 0,
128
- signals: [],
129
- claim_status: 'anonymous',
130
- }
131
- }
132
-
133
- const signalDef = engagementSignals[signalType]
134
- const entry = {
135
- type: signalType,
136
- timestamp: new Date().toISOString(),
137
- score: signalDef.score,
138
- }
139
-
140
- // Avoid duplicate signals of the same type within 1 hour
141
- const oneHourAgo = Date.now() - 3600000
142
- const recentSignal = engagement.signals.find(
143
- s => s.type === signalType && new Date(s.timestamp).getTime() > oneHourAgo
144
- )
145
- if (recentSignal) {
146
- return // Skip duplicate
147
- }
148
-
149
- engagement.signals.push(entry)
150
- engagement.engagement_score += signalDef.score
151
- writeEngagementData(engagement)
152
- }
153
-
154
- export function shouldOfferClaim(): ClaimPrompt | null {
155
- const engagement = readEngagementData()
156
- if (!engagement) return null
157
- if (engagement.claim_status !== 'anonymous') return null
158
-
159
- const score = engagement.engagement_score
160
-
161
- if (score >= claimTriggers.power_user) {
162
- return {
163
- timing: 'power_user',
164
- message: "You're a power user! Unlock advanced features and priority support by claiming your installation.",
165
- }
166
- } else if (score >= claimTriggers.ecosystem_engaged) {
167
- return {
168
- timing: 'ecosystem_engaged',
169
- message: "You're expanding your toolkit! Claim your install for app-specific guides and community access.",
170
- }
171
- } else if (score >= claimTriggers.value_realized) {
172
- return {
173
- timing: 'value_realized',
174
- message: 'Database connected! Claim your install to unlock production deployment guides and audit tools.',
175
- }
176
- } else if (score >= claimTriggers.gentle_nudge) {
177
- return {
178
- timing: 'gentle_nudge',
179
- message: 'Framework started! Ready to explore the full ecosystem? Claim your install for docs and support.',
180
- }
181
- }
182
-
183
- return null
184
- }
185
-
186
- // ─── SIGNAL SENDING ───────────────────────────────────────────────────────────
187
-
188
- export async function sendFrameworkInstallSignal(): Promise<void> {
189
- const serialData = ensureSerialData()
190
-
191
- const signal = {
192
- stage: 'installed' as const,
193
- source: 'use' as const,
194
- action_type: 'framework_install',
195
- action_value: 2 as const,
196
- instance_id: serialData.serial_number,
197
- anonymous_id: `anon_${randomBytes(8).toString('hex')}`,
198
- environment: (process.env.NODE_ENV === 'production' ? 'production' : 'dev') as 'dev' | 'production',
199
- occurred_at: new Date().toISOString(),
200
- }
201
-
202
- try {
203
- await fetch(`${signalConfig.endpoint}/api/signal`, {
204
- method: 'POST',
205
- headers: { 'Content-Type': 'application/json' },
206
- body: JSON.stringify(signal),
207
- })
208
- } catch {
209
- // Silent fail - signal endpoint is best-effort
210
- }
211
- }
212
-
213
- export async function sendAppInstallSignal(packageName: string, _version: string): Promise<void> {
214
- const serialData = ensureSerialData()
215
-
216
- const signal = {
217
- stage: 'installed' as const,
218
- source: 'use' as const,
219
- action_type: 'app_install',
220
- action_value: 2 as const,
221
- instance_id: serialData.serial_number,
222
- anonymous_id: `anon_${randomBytes(8).toString('hex')}`,
223
- action_description: packageName,
224
- environment: (process.env.NODE_ENV === 'production' ? 'production' : 'dev') as 'dev' | 'production',
225
- occurred_at: new Date().toISOString(),
226
- }
227
-
228
- try {
229
- await fetch(`${signalConfig.endpoint}/api/signal`, {
230
- method: 'POST',
231
- headers: { 'Content-Type': 'application/json' },
232
- body: JSON.stringify(signal),
233
- })
234
- } catch {
235
- // Silent fail
236
- }
237
- }
238
-
239
- // ─── CLAIM FLOW ───────────────────────────────────────────────────────────────
240
-
241
- function promptQuestion(question: string): Promise<string> {
242
- return new Promise(resolve => {
243
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
244
- rl.question(question, answer => {
245
- rl.close()
246
- resolve(answer.trim())
247
- })
248
- })
249
- }
250
-
251
- async function openPortalClaim(serialNumber: string): Promise<void> {
252
- const claimUrl = `https://portal.spine-framework.com/claim?serial=${serialNumber}`
253
-
254
- // Try to open browser
255
- const platform = process.platform
256
- const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open'
257
-
258
- console.log(`\n🌐 Opening ${claimUrl}`)
259
- console.log(' Create your account or log in to claim this installation.\n')
260
-
261
- try {
262
- const { execSync } = await import('child_process')
263
- execSync(`${cmd} "${claimUrl}"`, { stdio: 'ignore' })
264
- } catch {
265
- console.log(` Could not open browser automatically.`)
266
- console.log(` Please visit the URL manually.\n`)
267
- }
268
- }
269
-
270
- async function tokenClaimFlow(serialNumber: string): Promise<void> {
271
- console.log('\n🔑 Token-Based Claim')
272
- console.log(' 1. Visit https://portal.spine-framework.com/settings/claim')
273
- console.log(' 2. Copy your claim token\n')
274
-
275
- const token = await promptQuestion(' Enter your claim token: ')
276
-
277
- if (!token) {
278
- console.log('\n ❌ No token provided. Claim cancelled.')
279
- return
280
- }
281
-
282
- // Validate token with portal API
283
- try {
284
- const response = await fetch(`${signalConfig.endpoint}/api/claim`, {
285
- method: 'POST',
286
- headers: { 'Content-Type': 'application/json' },
287
- body: JSON.stringify({
288
- install_serial: serialNumber,
289
- user_token: token,
290
- }),
291
- })
292
-
293
- if (!response.ok) {
294
- console.log('\n ❌ Invalid token or claim failed. Please try again.')
295
- return
296
- }
297
-
298
- const result = await response.json() as { success: boolean; account_id?: string }
299
-
300
- if (result.success) {
301
- // Update local files
302
- const serialData = ensureSerialData()
303
- serialData.account_id = result.account_id || null
304
- writeFileSync(SERIAL_FILE, JSON.stringify(serialData, null, 2) + '\n', 'utf8')
305
-
306
- const engagement = readEngagementData()
307
- if (engagement) {
308
- engagement.claim_status = 'claimed'
309
- engagement.account_id = result.account_id
310
- writeEngagementData(engagement)
311
- }
312
-
313
- console.log('\n ✅ Installation claimed successfully!')
314
- console.log(' Welcome to the Spine ecosystem. 🎉\n')
315
- } else {
316
- console.log('\n ❌ Claim failed. Please check your token and try again.')
317
- }
318
- } catch {
319
- console.log('\n ⚠️ Could not connect to portal. Please try again later.')
320
- }
321
- }
322
-
323
- async function showClaimStatus(): Promise<void> {
324
- const serialData = existsSync(SERIAL_FILE) ? JSON.parse(readFileSync(SERIAL_FILE, 'utf8')) as SerialData : null
325
- const engagement = readEngagementData()
326
-
327
- console.log('\n📊 Claim Status\n')
328
-
329
- if (!serialData) {
330
- console.log(' No installation serial found.')
331
- console.log(' Run any spine-framework command to initialize.\n')
332
- return
333
- }
334
-
335
- console.log(` Serial Number: ${serialData.serial_number}`)
336
- console.log(` Framework Version: ${serialData.framework_version}`)
337
- console.log(` Created: ${new Date(serialData.created_at).toLocaleDateString()}`)
338
-
339
- if (engagement) {
340
- console.log(`\n Engagement Score: ${engagement.engagement_score}`)
341
- console.log(` Signals Recorded: ${engagement.signals.length}`)
342
- console.log(` Claim Status: ${engagement.claim_status}`)
343
-
344
- if (engagement.claim_status === 'anonymous') {
345
- const prompt = shouldOfferClaim()
346
- if (prompt) {
347
- console.log(`\n 💡 ${prompt.message}`)
348
- }
349
- } else if (engagement.account_id) {
350
- console.log(` Account ID: ${engagement.account_id}`)
351
- }
352
- }
353
-
354
- console.log('')
355
- }
356
-
357
- async function runClaimFlow(options: { status: boolean; token?: string }): Promise<void> {
358
- // Ensure serial exists
359
- const serialData = ensureSerialData()
360
-
361
- // Send framework install signal on first claim check
362
- recordEngagementSignal('framework_initialized')
363
- await sendFrameworkInstallSignal()
364
-
365
- if (options.status) {
366
- await showClaimStatus()
367
- return
368
- }
369
-
370
- // Check if already claimed
371
- const engagement = readEngagementData()
372
- if (engagement?.claim_status === 'claimed') {
373
- console.log('\n✅ This installation is already claimed.')
374
- console.log(` Account: ${engagement.account_id}\n`)
375
- return
376
- }
377
-
378
- console.log('\n🔐 Claim Your Spine Installation\n')
379
- console.log(` Serial: ${serialData.serial_number}`)
380
- console.log('\n Claiming unlocks:')
381
- console.log(' • Documentation and playbooks')
382
- console.log(' • App packs and marketplace')
383
- console.log(' • Support and community access')
384
- console.log(' • Audit and compliance tools\n')
385
-
386
- if (options.token) {
387
- await tokenClaimFlow(serialData.serial_number)
388
- return
389
- }
390
-
391
- console.log(' Choose a claim method:')
392
- console.log(' 1. Portal flow (opens browser) - RECOMMENDED')
393
- console.log(' 2. Token flow (paste token from portal)')
394
- console.log(' 3. Check status only\n')
395
-
396
- const choice = await promptQuestion(' Select (1-3): ')
397
-
398
- switch (choice) {
399
- case '1':
400
- await openPortalClaim(serialData.serial_number)
401
- break
402
- case '2':
403
- await tokenClaimFlow(serialData.serial_number)
404
- break
405
- case '3':
406
- await showClaimStatus()
407
- break
408
- default:
409
- console.log('\n Invalid choice. Run `spine-framework claim` to try again.\n')
410
- }
411
- }
412
-
413
- // ─── COMMAND REGISTRATION ───────────────────────────────────────────────────
414
-
415
- export function registerClaimCommands(program: Command): void {
416
- program
417
- .command('claim')
418
- .description('Claim framework installation via Spine Portal')
419
- .option('--status', 'Show claim status only', false)
420
- .option('--token <token>', 'Headless claim with token')
421
- .action(async (opts) => {
422
- try {
423
- await runClaimFlow(opts)
424
- } catch (err: any) {
425
- console.error('Error:', err.message)
426
- if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
427
- process.exit(1)
428
- }
429
- })
430
- }
@@ -1,219 +0,0 @@
1
- /// <reference types="node" />
2
- /**
3
- * @module cli/commands/init
4
- * @audience installer
5
- * @layer cli
6
- * @stability stable
7
- *
8
- * `spine-framework init` — Write .env and scaffold the custom/ workspace.
9
- *
10
- * Intentionally does NOT touch the database — run `spine-framework migrate`
11
- * after init to apply SQL migrations via a direct Postgres connection.
12
- *
13
- * **Usage:**
14
- * ```bash
15
- * spine-framework init --url https://xyz.supabase.co --anon-key eyJ... --service-role-key eyJ...
16
- * spine-framework migrate --db-password <password>
17
- * ```
18
- */
19
-
20
- import type { Command } from 'commander'
21
- import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync } from 'fs'
22
- import { resolve, dirname } from 'path'
23
- import { fileURLToPath } from 'url'
24
- import { execSync } from 'child_process'
25
-
26
- const __filename = fileURLToPath(import.meta.url)
27
- const __dirname = dirname(__filename)
28
- const PROJECT_ROOT = process.cwd()
29
- // Package root: node_modules/spine-framework/ — 3 levels up from .framework/cli/commands/
30
- const PACKAGE_ROOT = resolve(__dirname, '../../..')
31
-
32
- interface InitOptions {
33
- dryRun: boolean
34
- }
35
-
36
- function scaffoldCustomWorkspace(dryRun: boolean): void {
37
- const dirs = [
38
- 'custom/apps',
39
- 'custom/functions',
40
- 'custom/components',
41
- 'custom/migrations',
42
- 'custom/tests',
43
- ]
44
-
45
- for (const dir of dirs) {
46
- const fullPath = resolve(PROJECT_ROOT, dir)
47
- if (existsSync(fullPath)) {
48
- console.log(` ⏭️ ${dir}/ already exists`)
49
- } else if (dryRun) {
50
- console.log(` [dry-run] Would create: ${dir}/`)
51
- } else {
52
- mkdirSync(fullPath, { recursive: true })
53
- writeFileSync(resolve(fullPath, '.gitkeep'), '')
54
- console.log(` ✓ Created ${dir}/`)
55
- }
56
- }
57
- }
58
-
59
- function writeEnvFile(dryRun: boolean): void {
60
- const envPath = resolve(PROJECT_ROOT, '.env')
61
-
62
- if (existsSync(envPath)) {
63
- console.log(' ⏭️ .env already exists, skipping')
64
- return
65
- }
66
-
67
- const envContent = [
68
- '# ─── Supabase ────────────────────────────────────────────────────────────────',
69
- '# Get these from: https://supabase.com/dashboard/project/_/settings/api',
70
- 'SUPABASE_URL=https://your-project.supabase.co',
71
- 'SUPABASE_ANON_KEY=your-anon-key',
72
- 'SUPABASE_SERVICE_ROLE_KEY=your-service-role-key',
73
- '',
74
- '# DB password for running migrations (direct Postgres connection)',
75
- '# Get from: https://supabase.com/dashboard/project/_/settings/database',
76
- 'SUPABASE_DB_PASSWORD=your-db-password',
77
- '',
78
- '# ─── Vite / Frontend ─────────────────────────────────────────────────────────',
79
- 'VITE_SUPABASE_URL=https://your-project.supabase.co',
80
- 'VITE_SUPABASE_ANON_KEY=your-anon-key',
81
- 'VITE_APP_NAME=Spine',
82
- '',
83
- '# ─── OpenAI ──────────────────────────────────────────────────────────────────',
84
- 'OPENAI_API_KEY=your-openai-api-key',
85
- '',
86
- '# ─── App config ──────────────────────────────────────────────────────────────',
87
- 'DB_SCHEMA=public',
88
- ].join('\n') + '\n'
89
-
90
- if (dryRun) {
91
- console.log(' [dry-run] Would write .env with placeholder values')
92
- return
93
- }
94
-
95
- writeFileSync(envPath, envContent, 'utf8')
96
- console.log(' ✓ Created .env')
97
- }
98
-
99
- function copyFrameworkFiles(dryRun: boolean): void {
100
- // Directories and files to copy from the package root into the consumer project root
101
- const items = [
102
- { src: '.framework', dest: '.framework' },
103
- { src: 'bin', dest: 'bin' },
104
- { src: 'config', dest: 'config' },
105
- { src: 'scripts', dest: 'scripts' },
106
- { src: 'netlify.toml', dest: 'netlify.toml' },
107
- { src: 'vitest.config.ts', dest: 'vitest.config.ts' },
108
- { src: 'index.html', dest: 'index.html' },
109
- { src: 'package-project.json', dest: 'package.json', template: true },
110
- { src: 'STRUCTURE.md', dest: 'STRUCTURE.md' },
111
- { src: 'README.md', dest: 'README.md' },
112
- { src: 'LICENSE.md', dest: 'LICENSE.md' },
113
- { src: 'CHANGELOG.md', dest: 'CHANGELOG.md' },
114
- ]
115
-
116
- // Resolve current framework version from the installed package
117
- let frameworkVersion = '0.1.0'
118
- try {
119
- const fwPkg = JSON.parse(readFileSync(resolve(PACKAGE_ROOT, 'package.json'), 'utf8'))
120
- frameworkVersion = fwPkg.version || frameworkVersion
121
- } catch { /* fall through */ }
122
-
123
- for (const item of items as Array<{ src: string; dest: string; template?: boolean }>) {
124
- const src = resolve(PACKAGE_ROOT, item.src)
125
- const dest = resolve(PROJECT_ROOT, item.dest)
126
-
127
- if (!existsSync(src)) {
128
- console.log(` ⏭️ ${item.src} not found in package, skipping`)
129
- continue
130
- }
131
-
132
- if (existsSync(dest)) {
133
- // For package.json: only skip if it already has scripts (i.e. it's a real project package.json)
134
- // A bare stub created by `npm install` has no scripts and should be overwritten
135
- if (item.template) {
136
- try {
137
- const existing = JSON.parse(readFileSync(dest, 'utf8'))
138
- if (existing.scripts && Object.keys(existing.scripts).length > 0) {
139
- console.log(` ⏭️ ${item.dest} already exists, skipping`)
140
- continue
141
- }
142
- // Fall through — bare stub, overwrite it
143
- } catch {
144
- // Unparseable — overwrite it
145
- }
146
- } else {
147
- console.log(` ⏭️ ${item.dest} already exists, skipping`)
148
- continue
149
- }
150
- }
151
-
152
- if (dryRun) {
153
- console.log(` [dry-run] Would copy: ${item.src} → ./${item.dest}`)
154
- continue
155
- }
156
-
157
- if (item.template) {
158
- // Stamp the current framework version into the project package.json
159
- const templateContent = JSON.parse(readFileSync(src, 'utf8'))
160
- if (templateContent.dependencies?.['spine-framework']) {
161
- templateContent.dependencies['spine-framework'] = `^${frameworkVersion}`
162
- }
163
- writeFileSync(dest, JSON.stringify(templateContent, null, 2) + '\n', 'utf8')
164
- } else {
165
- cpSync(src, dest, { recursive: true })
166
- }
167
- console.log(` ✓ Copied ${item.dest}`)
168
- }
169
- }
170
-
171
- async function initCommand(options: InitOptions): Promise<void> {
172
- console.log('\n🚀 Spine Framework — Init\n')
173
-
174
- // Step 1: Copy framework files to project root
175
- console.log('📦 Step 1: Installing framework files...')
176
- copyFrameworkFiles(options.dryRun)
177
-
178
- // Step 2: Install dev dependencies (vite, netlify-cli)
179
- if (!options.dryRun) {
180
- console.log('\n📦 Step 2: Installing dev dependencies...')
181
- try {
182
- execSync('npm install', { stdio: 'inherit', cwd: PROJECT_ROOT })
183
- console.log(' ✓ Dev dependencies installed')
184
- } catch (err) {
185
- console.log(' ⚠️ npm install failed — run manually: npm install')
186
- }
187
- }
188
-
189
- // Step 3: Scaffold custom workspace
190
- console.log('\n📁 Step 3: Scaffolding custom workspace...')
191
- scaffoldCustomWorkspace(options.dryRun)
192
-
193
- // Step 4: Write .env with placeholders
194
- console.log('\n🔑 Step 4: Writing environment configuration...')
195
- writeEnvFile(options.dryRun)
196
-
197
- console.log('\n✅ Project initialized!')
198
- console.log('\n Next steps:')
199
- console.log(' 1. Fill in your Supabase credentials in .env')
200
- console.log(' (get them from https://supabase.com/dashboard/project/_/settings/api)')
201
- console.log(' 2. Run: npx spine-framework migrate')
202
- console.log(' (ensure SUPABASE_DB_PASSWORD is set in .env)')
203
- }
204
-
205
- export function registerInitCommands(program: Command) {
206
- program
207
- .command('init')
208
- .description('Install framework files, scaffold custom/ workspace, and write .env template')
209
- .option('--dry-run', 'Show what would happen without making changes', false)
210
- .action(async (opts) => {
211
- try {
212
- await initCommand(opts)
213
- } catch (err: any) {
214
- console.error('Error:', err.message)
215
- if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
216
- process.exit(1)
217
- }
218
- })
219
- }