spine-framework 0.3.17 → 0.3.19

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 (270) hide show
  1. package/.framework/cli/bin.mjs +16 -0
  2. package/.framework/cli/commands/create-app.ts +1 -1
  3. package/.framework/cli/commands/dev.ts +215 -33
  4. package/.framework/cli/index.ts +37 -75
  5. package/.framework/functions/_shared/app-manifest.ts +8 -15
  6. package/.framework/functions/_shared/db.ts +11 -15
  7. package/.framework/functions/_shared/index.ts +7 -16
  8. package/.framework/functions/_shared/principal.ts +4 -62
  9. package/.framework/functions/_shared/resolve-ids.ts +6 -6
  10. package/.framework/functions/_shared/webhook-registry.ts +43 -1
  11. package/.framework/functions/admin-funnel.ts +104 -0
  12. package/.framework/functions/admin-install-funnel.ts +225 -0
  13. package/.framework/functions/admin-signals.ts +241 -0
  14. package/.framework/functions/integration-routes.ts +4 -8
  15. package/.framework/migrations/001_schema.sql +406 -0
  16. package/.framework/migrations/002_rls_and_functions.sql +710 -0
  17. package/.framework/migrations/003_system_data.sql +520 -0
  18. package/.framework/migrations/004_enhancements.sql +43 -0
  19. package/.framework/public/_redirects +3 -0
  20. package/.framework/public/spine-logo.jpg +0 -0
  21. package/.framework/public/spine-logo.svg +3 -0
  22. package/{scripts → .framework/scripts}/assemble-frontend.sh +8 -5
  23. package/{scripts → .framework/scripts}/assemble-functions.sh +4 -47
  24. package/{scripts → .framework/scripts}/assemble.sh +2 -8
  25. package/{scripts → .framework/scripts}/boundary-check.sh +5 -9
  26. package/{scripts → .framework/scripts}/check-core-integrity.sh +0 -0
  27. package/.framework/seeds/link_types/account_cli_instances.json +9 -0
  28. package/.framework/seeds/types/funnel_signal_use.json +98 -0
  29. package/.framework/seeds/types/spine_instance.json +109 -0
  30. package/.framework/src/App.tsx +11 -21
  31. package/.framework/src/components/CustomAppLoader.tsx +2 -19
  32. package/.framework/src/components/ui/accordion.tsx +1 -1
  33. package/.framework/src/components/ui/alert-dialog.tsx +1 -1
  34. package/.framework/src/components/ui/aspect-ratio.tsx +1 -1
  35. package/.framework/src/components/ui/avatar.tsx +1 -1
  36. package/.framework/src/components/ui/badge.tsx +2 -2
  37. package/.framework/src/components/ui/breadcrumb.tsx +2 -2
  38. package/.framework/src/components/ui/button-group.tsx +2 -2
  39. package/.framework/src/components/ui/button.tsx +2 -2
  40. package/.framework/src/components/ui/chart.tsx +6 -1
  41. package/.framework/src/components/ui/checkbox.tsx +1 -1
  42. package/.framework/src/components/ui/collapsible.tsx +1 -1
  43. package/.framework/src/components/ui/context-menu.tsx +1 -1
  44. package/.framework/src/components/ui/dialog.tsx +1 -1
  45. package/.framework/src/components/ui/direction.tsx +17 -1
  46. package/.framework/src/components/ui/dropdown-menu.tsx +1 -1
  47. package/.framework/src/components/ui/hover-card.tsx +1 -1
  48. package/.framework/src/components/ui/item.tsx +2 -2
  49. package/.framework/src/components/ui/label.tsx +1 -1
  50. package/.framework/src/components/ui/menubar.tsx +1 -1
  51. package/.framework/src/components/ui/navigation-menu.tsx +1 -1
  52. package/.framework/src/components/ui/popover.tsx +1 -1
  53. package/.framework/src/components/ui/progress.tsx +1 -1
  54. package/.framework/src/components/ui/radio-group.tsx +1 -1
  55. package/.framework/src/components/ui/scroll-area.tsx +1 -1
  56. package/.framework/src/components/ui/select.tsx +1 -1
  57. package/.framework/src/components/ui/separator.tsx +1 -1
  58. package/.framework/src/components/ui/sheet.tsx +1 -1
  59. package/.framework/src/components/ui/sidebar.tsx +6 -6
  60. package/.framework/src/components/ui/slider.tsx +1 -1
  61. package/.framework/src/components/ui/switch.tsx +1 -1
  62. package/.framework/src/components/ui/tabs.tsx +1 -1
  63. package/.framework/src/components/ui/toggle-group.tsx +1 -1
  64. package/.framework/src/components/ui/toggle.tsx +1 -1
  65. package/.framework/src/components/ui/tooltip.tsx +1 -1
  66. package/.framework/src/pages/auth/LoginPage.tsx +10 -9
  67. package/LICENSE.md +63 -93
  68. package/README.md +254 -129
  69. package/custom/.devin/skills/install-app/SKILL.md +39 -0
  70. package/custom/.devin/workflows/app-install.yaml +83 -0
  71. package/custom/README.md +79 -0
  72. package/docs/APPS.md +232 -0
  73. package/docs/ARCHITECTURE.md +246 -0
  74. package/docs/INSTALL.md +155 -0
  75. package/docs/README.md +170 -0
  76. package/docs/design-schema-spec.md +1837 -0
  77. package/docs/dev-guides/01-create-an-app.md +183 -0
  78. package/docs/dev-guides/02-create-a-function.md +178 -0
  79. package/docs/dev-guides/03-create-a-component.md +178 -0
  80. package/docs/dev-guides/04-webhook-handlers.md +129 -0
  81. package/docs/dev-guides/05-testing.md +168 -0
  82. package/docs/dev-guides/README.md +40 -0
  83. package/docs/npm-packages-plan.md +288 -0
  84. package/docs/spine-core--api-reference.md +822 -0
  85. package/package.json +79 -150
  86. package/.framework/cli/bin.cjs +0 -14
  87. package/.framework/cli/commands/claim.ts +0 -430
  88. package/.framework/cli/commands/init.ts +0 -219
  89. package/.framework/cli/commands/install-app.ts +0 -685
  90. package/.framework/cli/commands/migrate.ts +0 -178
  91. package/.framework/cli/commands/publish.ts +0 -191
  92. package/.framework/cli/commands/status.ts +0 -268
  93. package/.framework/cli/commands/uninstall-app.ts +0 -166
  94. package/.framework/cli/commands/update-app.ts +0 -397
  95. package/.framework/cli/welcome.cjs +0 -45
  96. package/.framework/config/signal-client.ts +0 -147
  97. package/.framework/config/signals.ts +0 -70
  98. package/.framework/docs/STABILITY.md +0 -52
  99. package/.framework/docs/examples/custom-app/README.md +0 -77
  100. package/.framework/docs/examples/custom-function/README.md +0 -27
  101. package/.framework/docs/examples/custom-function/handler.ts +0 -48
  102. package/.framework/docs/examples/custom-webhook/README.md +0 -68
  103. package/.framework/functions/handler-registry.ts +0 -22
  104. package/.framework/functions/signal.ts +0 -168
  105. package/.framework/migrations/000_foundation.sql +0 -1302
  106. package/.framework/migrations/001_seed.sql +0 -163
  107. package/.framework/migrations/002_seed_constraints.sql +0 -19
  108. package/.framework/migrations/003_auth_user_trigger.sql +0 -67
  109. package/.framework/migrations/004_app_version_tracking.sql +0 -8
  110. package/.framework/migrations/005_pipelines_slug_stages.sql +0 -21
  111. package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
  112. package/CHANGELOG.md +0 -71
  113. package/STRUCTURE.md +0 -150
  114. package/bin/spine-framework.cjs +0 -62
  115. package/bin/welcome.cjs +0 -58
  116. package/bin/ws-shim.ts +0 -10
  117. package/config/components.json +0 -25
  118. package/config/deno.lock +0 -108
  119. package/config/package-lock.json +0 -17183
  120. package/config/postcss.config.cjs +0 -10
  121. package/config/tailwind.config.cjs +0 -78
  122. package/config/tsconfig.build.json +0 -32
  123. package/config/tsconfig.cli.json +0 -18
  124. package/config/tsconfig.json +0 -41
  125. package/config/tsconfig.node.json +0 -17
  126. package/config/tsconfig.node.tsbuildinfo +0 -1
  127. package/config/tsconfig.tsbuildinfo +0 -1
  128. package/config/typedoc.json +0 -16
  129. package/config/vite.config.d.ts +0 -2
  130. package/config/vite.config.ts +0 -71
  131. package/dist/cli/commands/agents.d.ts +0 -39
  132. package/dist/cli/commands/agents.d.ts.map +0 -1
  133. package/dist/cli/commands/auth.d.ts +0 -36
  134. package/dist/cli/commands/auth.d.ts.map +0 -1
  135. package/dist/cli/commands/claim.d.ts +0 -38
  136. package/dist/cli/commands/claim.d.ts.map +0 -1
  137. package/dist/cli/commands/create-app.d.ts +0 -23
  138. package/dist/cli/commands/create-app.d.ts.map +0 -1
  139. package/dist/cli/commands/dev.d.ts +0 -24
  140. package/dist/cli/commands/dev.d.ts.map +0 -1
  141. package/dist/cli/commands/doctor.d.ts +0 -42
  142. package/dist/cli/commands/doctor.d.ts.map +0 -1
  143. package/dist/cli/commands/generate.d.ts +0 -36
  144. package/dist/cli/commands/generate.d.ts.map +0 -1
  145. package/dist/cli/commands/init.d.ts +0 -20
  146. package/dist/cli/commands/init.d.ts.map +0 -1
  147. package/dist/cli/commands/install-app.d.ts +0 -30
  148. package/dist/cli/commands/install-app.d.ts.map +0 -1
  149. package/dist/cli/commands/items.d.ts +0 -45
  150. package/dist/cli/commands/items.d.ts.map +0 -1
  151. package/dist/cli/commands/migrate.d.ts +0 -21
  152. package/dist/cli/commands/migrate.d.ts.map +0 -1
  153. package/dist/cli/commands/migrations.d.ts +0 -41
  154. package/dist/cli/commands/migrations.d.ts.map +0 -1
  155. package/dist/cli/commands/pipelines.d.ts +0 -40
  156. package/dist/cli/commands/pipelines.d.ts.map +0 -1
  157. package/dist/cli/commands/publish.d.ts +0 -23
  158. package/dist/cli/commands/publish.d.ts.map +0 -1
  159. package/dist/cli/commands/status.d.ts +0 -23
  160. package/dist/cli/commands/status.d.ts.map +0 -1
  161. package/dist/cli/commands/system.d.ts +0 -29
  162. package/dist/cli/commands/system.d.ts.map +0 -1
  163. package/dist/cli/commands/test.d.ts +0 -46
  164. package/dist/cli/commands/test.d.ts.map +0 -1
  165. package/dist/cli/commands/uninstall-app.d.ts +0 -23
  166. package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
  167. package/dist/cli/commands/update-app.d.ts +0 -24
  168. package/dist/cli/commands/update-app.d.ts.map +0 -1
  169. package/dist/cli/context.d.ts +0 -88
  170. package/dist/cli/context.d.ts.map +0 -1
  171. package/dist/cli/env-loader.d.ts +0 -14
  172. package/dist/cli/env-loader.d.ts.map +0 -1
  173. package/dist/cli/index.d.ts +0 -44
  174. package/dist/cli/index.d.ts.map +0 -1
  175. package/dist/config/signals.d.ts +0 -82
  176. package/dist/config/signals.d.ts.map +0 -1
  177. package/dist/functions/_shared/agent-runner.d.ts +0 -156
  178. package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
  179. package/dist/functions/_shared/app-manifest.d.ts +0 -68
  180. package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
  181. package/dist/functions/_shared/audit.d.ts +0 -91
  182. package/dist/functions/_shared/audit.d.ts.map +0 -1
  183. package/dist/functions/_shared/db.d.ts +0 -125
  184. package/dist/functions/_shared/db.d.ts.map +0 -1
  185. package/dist/functions/_shared/index.d.ts +0 -299
  186. package/dist/functions/_shared/index.d.ts.map +0 -1
  187. package/dist/functions/_shared/middleware.d.ts +0 -315
  188. package/dist/functions/_shared/middleware.d.ts.map +0 -1
  189. package/dist/functions/_shared/permissions.d.ts +0 -626
  190. package/dist/functions/_shared/permissions.d.ts.map +0 -1
  191. package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
  192. package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
  193. package/dist/functions/_shared/principal.d.ts +0 -284
  194. package/dist/functions/_shared/principal.d.ts.map +0 -1
  195. package/dist/functions/_shared/resolve-ids.d.ts +0 -10
  196. package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
  197. package/dist/functions/_shared/schema-utils.d.ts +0 -181
  198. package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
  199. package/dist/functions/_shared/testing.d.ts +0 -172
  200. package/dist/functions/_shared/testing.d.ts.map +0 -1
  201. package/dist/functions/_shared/trigger-engine.d.ts +0 -140
  202. package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
  203. package/dist/functions/_shared/webhook-registration.d.ts +0 -81
  204. package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
  205. package/dist/functions/_shared/webhook-registry.d.ts +0 -41
  206. package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
  207. package/dist/functions/account-nodes.d.ts +0 -48
  208. package/dist/functions/account-nodes.d.ts.map +0 -1
  209. package/dist/functions/admin-data.d.ts +0 -178
  210. package/dist/functions/admin-data.d.ts.map +0 -1
  211. package/dist/functions/ai-agents.d.ts +0 -125
  212. package/dist/functions/ai-agents.d.ts.map +0 -1
  213. package/dist/functions/api-keys.d.ts +0 -140
  214. package/dist/functions/api-keys.d.ts.map +0 -1
  215. package/dist/functions/apps.d.ts +0 -163
  216. package/dist/functions/apps.d.ts.map +0 -1
  217. package/dist/functions/auth.d.ts +0 -74
  218. package/dist/functions/auth.d.ts.map +0 -1
  219. package/dist/functions/debug-auth.d.ts +0 -33
  220. package/dist/functions/debug-auth.d.ts.map +0 -1
  221. package/dist/functions/embeddings.d.ts +0 -205
  222. package/dist/functions/embeddings.d.ts.map +0 -1
  223. package/dist/functions/handler-registry.d.ts +0 -22
  224. package/dist/functions/handler-registry.d.ts.map +0 -1
  225. package/dist/functions/integration-routes.d.ts +0 -45
  226. package/dist/functions/integration-routes.d.ts.map +0 -1
  227. package/dist/functions/integrations.d.ts +0 -124
  228. package/dist/functions/integrations.d.ts.map +0 -1
  229. package/dist/functions/item-progress.d.ts +0 -41
  230. package/dist/functions/item-progress.d.ts.map +0 -1
  231. package/dist/functions/logs.d.ts +0 -162
  232. package/dist/functions/logs.d.ts.map +0 -1
  233. package/dist/functions/observability.d.ts +0 -123
  234. package/dist/functions/observability.d.ts.map +0 -1
  235. package/dist/functions/pipeline-executions.d.ts +0 -190
  236. package/dist/functions/pipeline-executions.d.ts.map +0 -1
  237. package/dist/functions/pipelines.d.ts +0 -171
  238. package/dist/functions/pipelines.d.ts.map +0 -1
  239. package/dist/functions/prompt-configs.d.ts +0 -125
  240. package/dist/functions/prompt-configs.d.ts.map +0 -1
  241. package/dist/functions/roles.d.ts +0 -118
  242. package/dist/functions/roles.d.ts.map +0 -1
  243. package/dist/functions/signal.d.ts +0 -73
  244. package/dist/functions/signal.d.ts.map +0 -1
  245. package/dist/functions/system-cron.d.ts +0 -65
  246. package/dist/functions/system-cron.d.ts.map +0 -1
  247. package/dist/functions/system.d.ts +0 -29
  248. package/dist/functions/system.d.ts.map +0 -1
  249. package/dist/functions/tests.d.ts +0 -28
  250. package/dist/functions/tests.d.ts.map +0 -1
  251. package/dist/functions/timers.d.ts +0 -139
  252. package/dist/functions/timers.d.ts.map +0 -1
  253. package/dist/functions/triggers.d.ts +0 -203
  254. package/dist/functions/triggers.d.ts.map +0 -1
  255. package/dist/functions/types.d.ts +0 -151
  256. package/dist/functions/types.d.ts.map +0 -1
  257. package/dist/src/types/types.d.ts +0 -364
  258. package/dist/src/types/types.d.ts.map +0 -1
  259. package/index.html +0 -13
  260. package/netlify.toml +0 -36
  261. package/package-project.json +0 -71
  262. package/scripts/ingest-chunks.cjs +0 -202
  263. package/scripts/kb-chunk-parser.cjs +0 -312
  264. package/scripts/kb-chunk-parser.ts +0 -330
  265. package/scripts/load-test-app-install.ts +0 -484
  266. package/scripts/netlify-dev-wrapper.sh +0 -22
  267. package/vitest.config.ts +0 -45
  268. /package/{scripts → .framework/scripts}/app-install-cli.ts +0 -0
  269. /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
  270. /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
- }