sinapse-ai 1.12.2 → 1.14.0

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 (244) hide show
  1. package/.claude/CLAUDE.md +2 -2
  2. package/.claude/rules/agent-memory-imports.md +4 -0
  3. package/.sinapse-ai/cli/commands/pro/index.js +8 -48
  4. package/.sinapse-ai/constitution.md +6 -2
  5. package/.sinapse-ai/data/entity-registry.yaml +63 -429
  6. package/.sinapse-ai/development/agents/analyst/MEMORY.md +19 -0
  7. package/.sinapse-ai/development/agents/architect/MEMORY.md +52 -0
  8. package/.sinapse-ai/development/agents/data-engineer/MEMORY.md +55 -0
  9. package/.sinapse-ai/development/agents/developer/MEMORY.md +53 -0
  10. package/.sinapse-ai/development/agents/devops/MEMORY.md +56 -0
  11. package/.sinapse-ai/development/agents/product-lead/MEMORY.md +53 -0
  12. package/.sinapse-ai/development/agents/project-lead/MEMORY.md +53 -0
  13. package/.sinapse-ai/development/agents/quality-gate/MEMORY.md +52 -0
  14. package/.sinapse-ai/development/agents/snps-orqx.md +15 -0
  15. package/.sinapse-ai/development/agents/sprint-lead/MEMORY.md +53 -0
  16. package/.sinapse-ai/development/agents/squad-creator.md +15 -0
  17. package/.sinapse-ai/development/agents/ux/MEMORY.md +21 -0
  18. package/.sinapse-ai/development/templates/chrome-brain/knowledge-base/chrome-brain.md +2 -2
  19. package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +15 -0
  20. package/.sinapse-ai/infrastructure/scripts/validate-agents.js +159 -8
  21. package/.sinapse-ai/install-manifest.yaml +16 -152
  22. package/AGENTS.md +13 -2
  23. package/CHANGELOG.md +135 -3
  24. package/README.en.md +1 -1
  25. package/README.md +6 -26
  26. package/bin/cli.js +2 -7
  27. package/bin/commands/install.js +19 -257
  28. package/bin/commands/update.js +18 -78
  29. package/bin/lib/command-generator.js +261 -0
  30. package/bin/lib/prompts.js +2 -65
  31. package/bin/modules/chrome-brain-installer.js +2 -1
  32. package/bin/postinstall.js +6 -6
  33. package/docs/getting-started.md +0 -1
  34. package/docs/pt/getting-started.md +1 -1
  35. package/docs/security/dependabot-triage.md +17 -17
  36. package/package.json +6 -3
  37. package/packages/installer/src/wizard/i18n.js +3 -204
  38. package/packages/installer/src/wizard/index.js +1 -1
  39. package/packages/installer/src/wizard/questions.js +5 -368
  40. package/packages/installer/src/wizard/wizard.js +0 -9
  41. package/scripts/generate-install-manifest.js +6 -0
  42. package/scripts/validate-changelog-tags.js +97 -0
  43. package/scripts/validate-no-personal-leaks.js +184 -28
  44. package/scripts/validate-schemas.js +55 -10
  45. package/scripts/validate-story-meta.js +25 -12
  46. package/squads/claude-code-mastery/agents/claude-mastery-chief.md +15 -1
  47. package/squads/claude-code-mastery/agents/config-engineer.md +15 -0
  48. package/squads/claude-code-mastery/agents/hooks-architect.md +27 -20
  49. package/squads/claude-code-mastery/agents/mcp-integrator.md +15 -0
  50. package/squads/claude-code-mastery/agents/project-integrator.md +15 -0
  51. package/squads/claude-code-mastery/agents/roadmap-sentinel.md +15 -0
  52. package/squads/claude-code-mastery/agents/skill-craftsman.md +15 -0
  53. package/squads/claude-code-mastery/agents/swarm-orqx.md +15 -0
  54. package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
  55. package/squads/squad-animations/agents/animation-interpreter.md +15 -0
  56. package/squads/squad-animations/agents/animation-performance-engineer.md +15 -0
  57. package/squads/squad-animations/agents/animations-orqx.md +13 -0
  58. package/squads/squad-animations/agents/css-motion-artist.md +15 -0
  59. package/squads/squad-animations/agents/generative-particle-engineer.md +15 -0
  60. package/squads/squad-animations/agents/motion-choreographer.md +15 -0
  61. package/squads/squad-animations/agents/scroll-narrative-engineer.md +15 -0
  62. package/squads/squad-animations/agents/shader-artist.md +15 -0
  63. package/squads/squad-animations/agents/threejs-architect.md +15 -0
  64. package/squads/squad-brand/agents/brand-archetype-strategist.md +13 -0
  65. package/squads/squad-brand/agents/brand-auditor.md +15 -0
  66. package/squads/squad-brand/agents/brand-collateral-designer.md +15 -0
  67. package/squads/squad-brand/agents/brand-compiler.md +13 -0
  68. package/squads/squad-brand/agents/brand-creative-engineer.md +15 -0
  69. package/squads/squad-brand/agents/brand-culture-architect.md +15 -0
  70. package/squads/squad-brand/agents/brand-growth-strategist.md +15 -0
  71. package/squads/squad-brand/agents/brand-identity-designer.md +15 -0
  72. package/squads/squad-brand/agents/brand-motion-vfx.md +15 -0
  73. package/squads/squad-brand/agents/brand-naming-specialist.md +15 -0
  74. package/squads/squad-brand/agents/brand-orqx.md +13 -0
  75. package/squads/squad-brand/agents/brand-positioning-strategist.md +15 -0
  76. package/squads/squad-brand/agents/brand-sonic-designer.md +15 -0
  77. package/squads/squad-brand/agents/brand-strategist.md +15 -0
  78. package/squads/squad-brand/agents/brand-system-architect.md +15 -0
  79. package/squads/squad-cloning/agents/agent-forger.md +15 -0
  80. package/squads/squad-cloning/agents/cloning-orqx.md +15 -0
  81. package/squads/squad-cloning/agents/cognitive-extractor.md +15 -0
  82. package/squads/squad-cloning/agents/content-capturer.md +15 -0
  83. package/squads/squad-cloning/agents/kb-architect.md +15 -0
  84. package/squads/squad-cloning/agents/mind-synthesizer.md +15 -0
  85. package/squads/squad-cloning/agents/sop-extractor.md +15 -0
  86. package/squads/squad-cloning/agents/source-hunter.md +13 -0
  87. package/squads/squad-cloning/agents/squad-assembler.md +15 -0
  88. package/squads/squad-commercial/agents/commercial-orqx.md +13 -0
  89. package/squads/squad-commercial/agents/cs-business-auditor.md +15 -0
  90. package/squads/squad-commercial/agents/cs-client-success.md +15 -0
  91. package/squads/squad-commercial/agents/cs-crm-specialist.md +15 -0
  92. package/squads/squad-commercial/agents/cs-funnel-architect.md +15 -0
  93. package/squads/squad-commercial/agents/cs-lead-generation-strategist.md +15 -0
  94. package/squads/squad-commercial/agents/cs-offer-designer.md +15 -0
  95. package/squads/squad-commercial/agents/cs-revops-analyst.md +15 -0
  96. package/squads/squad-commercial/agents/cs-sales-closer.md +13 -0
  97. package/squads/squad-commercial/agents/cs-sales-enablement.md +15 -0
  98. package/squads/squad-content/agents/content-analyst.md +13 -0
  99. package/squads/squad-content/agents/content-engineer.md +15 -0
  100. package/squads/squad-content/agents/content-governor.md +13 -0
  101. package/squads/squad-content/agents/content-orqx.md +13 -0
  102. package/squads/squad-content/agents/editorial-strategist.md +15 -0
  103. package/squads/squad-content/agents/platform-specialist.md +15 -0
  104. package/squads/squad-content/agents/signal-intelligence.md +13 -0
  105. package/squads/squad-copy/agents/ad-copywriter.md +13 -0
  106. package/squads/squad-copy/agents/brand-voice-writer.md +15 -0
  107. package/squads/squad-copy/agents/conversion-writer.md +13 -0
  108. package/squads/squad-copy/agents/copy-editor.md +15 -0
  109. package/squads/squad-copy/agents/copy-orqx.md +13 -0
  110. package/squads/squad-copy/agents/copy-strategist.md +15 -0
  111. package/squads/squad-copy/agents/direct-response-writer.md +15 -0
  112. package/squads/squad-copy/agents/email-sequence-strategist.md +15 -0
  113. package/squads/squad-copy/agents/funnel-copywriter.md +15 -0
  114. package/squads/squad-copy/agents/headline-specialist.md +13 -0
  115. package/squads/squad-copy/agents/long-form-writer.md +15 -0
  116. package/squads/squad-copy/agents/persuasion-psychologist.md +15 -0
  117. package/squads/squad-copy/agents/proof-architect.md +15 -0
  118. package/squads/squad-council/agents/brene-brown.md +13 -0
  119. package/squads/squad-council/agents/charlie-munger.md +13 -0
  120. package/squads/squad-council/agents/council-orqx.md +13 -0
  121. package/squads/squad-council/agents/derek-sivers.md +13 -0
  122. package/squads/squad-council/agents/naval-ravikant.md +13 -0
  123. package/squads/squad-council/agents/patrick-lencioni.md +15 -0
  124. package/squads/squad-council/agents/peter-thiel.md +13 -0
  125. package/squads/squad-council/agents/ray-dalio.md +13 -0
  126. package/squads/squad-council/agents/reid-hoffman.md +13 -0
  127. package/squads/squad-council/agents/simon-sinek.md +15 -0
  128. package/squads/squad-council/agents/yvon-chouinard.md +13 -0
  129. package/squads/squad-courses/agents/assessment-creator.md +15 -0
  130. package/squads/squad-courses/agents/content-writer.md +15 -0
  131. package/squads/squad-courses/agents/courses-orqx.md +15 -0
  132. package/squads/squad-courses/agents/curriculum-designer.md +15 -0
  133. package/squads/squad-courses/agents/launch-strategist.md +15 -0
  134. package/squads/squad-courses/agents/lesson-architect.md +15 -0
  135. package/squads/squad-courses/agents/production-director.md +13 -0
  136. package/squads/squad-courses/agents/slide-designer.md +15 -0
  137. package/squads/squad-cybersecurity/agents/cloud-security-engineer.md +15 -0
  138. package/squads/squad-cybersecurity/agents/compliance-officer.md +15 -0
  139. package/squads/squad-cybersecurity/agents/cyber-orqx.md +15 -0
  140. package/squads/squad-cybersecurity/agents/incident-responder.md +15 -0
  141. package/squads/squad-cybersecurity/agents/network-security-engineer.md +15 -0
  142. package/squads/squad-cybersecurity/agents/penetration-tester.md +15 -0
  143. package/squads/squad-cybersecurity/agents/soc-analyst.md +15 -0
  144. package/squads/squad-cybersecurity/agents/threat-analyst.md +15 -0
  145. package/squads/squad-design/agents/cro-persuasion.md +15 -0
  146. package/squads/squad-design/agents/design-orqx.md +13 -0
  147. package/squads/squad-design/agents/design-system.md +15 -0
  148. package/squads/squad-design/agents/dx-accessibility-specialist.md +15 -0
  149. package/squads/squad-design/agents/dx-design-system-architect.md +15 -0
  150. package/squads/squad-design/agents/dx-frontend-engineer.md +15 -0
  151. package/squads/squad-design/agents/dx-interaction-designer.md +15 -0
  152. package/squads/squad-design/agents/dx-performance-engineer.md +15 -0
  153. package/squads/squad-design/agents/dx-ui-designer.md +15 -0
  154. package/squads/squad-design/agents/dx-ux-strategist.md +15 -0
  155. package/squads/squad-design/agents/platform-aesthetic-director.md +15 -0
  156. package/squads/squad-design/agents/premium-packaging-strategist.md +15 -0
  157. package/squads/squad-design/agents/product-surface-director.md +15 -0
  158. package/squads/squad-design/agents/ux-designer.md +15 -0
  159. package/squads/squad-finance/agents/budget-controller.md +15 -0
  160. package/squads/squad-finance/agents/cost-optimizer.md +15 -0
  161. package/squads/squad-finance/agents/finance-orqx.md +15 -0
  162. package/squads/squad-finance/agents/fiscal-compliance-br.md +15 -0
  163. package/squads/squad-finance/agents/forecast-strategist.md +15 -0
  164. package/squads/squad-finance/agents/pricing-strategist.md +15 -0
  165. package/squads/squad-finance/agents/profitability-analyst.md +15 -0
  166. package/squads/squad-finance/agents/revenue-analyst.md +15 -0
  167. package/squads/squad-growth/agents/ga-analytics-engineer.md +15 -0
  168. package/squads/squad-growth/agents/ga-campaign-analyst.md +15 -0
  169. package/squads/squad-growth/agents/ga-cro-specialist.md +15 -0
  170. package/squads/squad-growth/agents/ga-data-analyst.md +13 -0
  171. package/squads/squad-growth/agents/ga-growth-hacker.md +15 -0
  172. package/squads/squad-growth/agents/ga-seo-strategist.md +15 -0
  173. package/squads/squad-growth/agents/growth-orqx.md +15 -0
  174. package/squads/squad-paidmedia/agents/campaign-analyst.md +15 -0
  175. package/squads/squad-paidmedia/agents/creative-strategist.md +15 -0
  176. package/squads/squad-paidmedia/agents/cro-specialist.md +15 -0
  177. package/squads/squad-paidmedia/agents/google-ads-specialist.md +15 -0
  178. package/squads/squad-paidmedia/agents/meta-ads-specialist.md +15 -0
  179. package/squads/squad-paidmedia/agents/paidmedia-orqx.md +15 -0
  180. package/squads/squad-paidmedia/agents/performance-engineer.md +15 -0
  181. package/squads/squad-paidmedia/agents/pm-creative-performance-analyst.md +15 -0
  182. package/squads/squad-paidmedia/agents/pm-youtube-ads-specialist.md +15 -0
  183. package/squads/squad-product/agents/product-orqx.md +13 -1
  184. package/squads/squad-product/agents/ps-client-product-manager.md +13 -1
  185. package/squads/squad-product/agents/ps-delivery-manager.md +13 -1
  186. package/squads/squad-product/agents/ps-discovery-lead.md +15 -1
  187. package/squads/squad-product/agents/ps-product-analyst.md +15 -1
  188. package/squads/squad-product/agents/ps-product-ops-specialist.md +15 -1
  189. package/squads/squad-product/agents/ps-product-strategist.md +15 -1
  190. package/squads/squad-research/agents/audience-intelligence.md +15 -0
  191. package/squads/squad-research/agents/competitive-intelligence.md +15 -0
  192. package/squads/squad-research/agents/data-synthesizer.md +15 -0
  193. package/squads/squad-research/agents/deep-researcher.md +15 -0
  194. package/squads/squad-research/agents/market-analyst.md +15 -0
  195. package/squads/squad-research/agents/research-orqx.md +15 -0
  196. package/squads/squad-research/agents/trend-forecaster.md +15 -0
  197. package/squads/squad-storytelling/agents/blake-snyder.md +13 -0
  198. package/squads/squad-storytelling/agents/dan-harmon.md +13 -0
  199. package/squads/squad-storytelling/agents/joseph-campbell.md +13 -0
  200. package/squads/squad-storytelling/agents/keith-johnstone.md +13 -0
  201. package/squads/squad-storytelling/agents/kindra-hall.md +15 -0
  202. package/squads/squad-storytelling/agents/marshall-ganz.md +15 -0
  203. package/squads/squad-storytelling/agents/nancy-duarte.md +15 -0
  204. package/squads/squad-storytelling/agents/oren-klaff.md +15 -0
  205. package/squads/squad-storytelling/agents/park-howell.md +13 -0
  206. package/squads/squad-storytelling/agents/storytelling-orqx.md +13 -0
  207. package/.sinapse-ai/core/grounding/README.md +0 -83
  208. package/.sinapse-ai/core/grounding/brand.cjs +0 -29
  209. package/.sinapse-ai/core/grounding/config-loader.cjs +0 -52
  210. package/.sinapse-ai/core/grounding/design-system.cjs +0 -29
  211. package/.sinapse-ai/core/grounding/vault.cjs +0 -36
  212. package/.sinapse-ai/development/scripts/approval-workflow.js +0 -643
  213. package/.sinapse-ai/development/scripts/backup-manager.js +0 -607
  214. package/.sinapse-ai/development/scripts/branch-manager.js +0 -390
  215. package/.sinapse-ai/development/scripts/code-quality-improver.js +0 -1329
  216. package/.sinapse-ai/development/scripts/commit-message-generator.js +0 -850
  217. package/.sinapse-ai/development/scripts/conflict-resolver.js +0 -675
  218. package/.sinapse-ai/development/scripts/dependency-analyzer.js +0 -638
  219. package/.sinapse-ai/development/scripts/diff-generator.js +0 -352
  220. package/.sinapse-ai/development/scripts/git-wrapper.js +0 -462
  221. package/.sinapse-ai/development/scripts/modification-validator.js +0 -555
  222. package/.sinapse-ai/development/scripts/performance-analyzer.js +0 -758
  223. package/.sinapse-ai/development/scripts/refactoring-suggester.js +0 -1148
  224. package/.sinapse-ai/development/scripts/rollback-handler.js +0 -531
  225. package/.sinapse-ai/development/scripts/security-checker.js +0 -359
  226. package/.sinapse-ai/development/scripts/template-engine.js +0 -240
  227. package/.sinapse-ai/development/scripts/template-validator.js +0 -279
  228. package/.sinapse-ai/development/scripts/test-generator.js +0 -844
  229. package/.sinapse-ai/development/scripts/transaction-manager.js +0 -590
  230. package/.sinapse-ai/development/scripts/yaml-validator.js +0 -397
  231. package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +0 -162
  232. package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +0 -211
  233. package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +0 -194
  234. package/bin/lib/register-grounding-hooks.js +0 -145
  235. package/docs/guides/agents/traces/execution-traces.zip +0 -0
  236. package/docs/guides/grounding-setup.md +0 -154
  237. package/docs/guides/workflows/SINAPSE-WORKFLOWS.zip +0 -0
  238. package/packages/installer/src/pro/pro-scaffolder.js +0 -449
  239. package/packages/installer/src/wizard/grounding-config.js +0 -131
  240. package/packages/installer/src/wizard/pro-setup.js +0 -1439
  241. package/packages/installer/templates/README.md +0 -16
  242. package/packages/installer/templates/brand-routing.example.json +0 -12
  243. package/packages/installer/templates/ds-routing.example.json +0 -12
  244. package/packages/installer/templates/vault-routing.example.json +0 -12
@@ -1,1439 +0,0 @@
1
- /**
2
- * Pro Installation Wizard with License Gate
3
- *
4
- * 3-step wizard: (1) License Gate, (2) Install/Scaffold, (3) Verify
5
- * Supports interactive mode, CI mode (SINAPSE_PRO_KEY/SINAPSE_PRO_EMAIL env vars), and lazy import.
6
- *
7
- * License Gate supports two activation methods:
8
- * - Email + Password authentication (recommended, PRO-11)
9
- * - License key (legacy, PRO-6)
10
- *
11
- * @module wizard/pro-setup
12
- * @story INS-3.2 — Implement Pro Installation Wizard with License Gate
13
- * @story PRO-11 — Email Authentication & Buyer-Based Pro Activation
14
- */
15
-
16
- 'use strict';
17
-
18
- const { createSpinner, showSuccess, showError, showWarning, showInfo } = require('./feedback');
19
- const { colors, status } = require('../utils/sinapse-colors');
20
- const { t, tf } = require('./i18n');
21
-
22
- /**
23
- * Gold color for Pro branding.
24
- * Falls back gracefully if chalk hex is unavailable.
25
- */
26
- let gold;
27
- try {
28
- const chalk = require('chalk');
29
- gold = chalk.hex('#FFD700').bold;
30
- } catch {
31
- gold = (text) => text;
32
- }
33
-
34
- /**
35
- * License server base URL (same source of truth as license-api.js CONFIG.BASE_URL).
36
- */
37
- const LICENSE_SERVER_URL = process.env.SINAPSE_LICENSE_API_URL || 'https://sinapse-license-server.vercel.app';
38
-
39
- /**
40
- * Inline License Client — lightweight HTTP client for pre-bootstrap license checks.
41
- *
42
- * Used when @sinapse-fullstack/pro is not yet installed (first install scenario).
43
- * Implements the same interface subset as LicenseApiClient using Node.js native https.
44
- */
45
- class InlineLicenseClient {
46
- constructor(baseUrl = LICENSE_SERVER_URL) {
47
- this.baseUrl = baseUrl;
48
- }
49
-
50
- /**
51
- * Make an HTTPS request and return parsed JSON.
52
- * @param {string} method - HTTP method
53
- * @param {string} urlPath - URL path (e.g., '/api/v1/auth/check-email')
54
- * @param {Object} [body] - JSON body for POST requests
55
- * @param {Object} [headers] - Additional headers
56
- * @returns {Promise<Object>} Parsed JSON response
57
- */
58
- _request(method, urlPath, body, headers = {}) {
59
- return new Promise((resolve, reject) => {
60
- const https = require('https');
61
- const url = new URL(urlPath, this.baseUrl);
62
-
63
- const options = {
64
- hostname: url.hostname,
65
- port: url.port || 443,
66
- path: url.pathname + url.search,
67
- method,
68
- headers: {
69
- 'Content-Type': 'application/json',
70
- 'User-Agent': 'sinapse-installer',
71
- ...headers,
72
- },
73
- timeout: 15000,
74
- };
75
-
76
- const req = https.request(options, (res) => {
77
- let data = '';
78
- res.on('data', (chunk) => { data += chunk; });
79
- res.on('end', () => {
80
- try {
81
- const parsed = JSON.parse(data);
82
- if (res.statusCode >= 400) {
83
- const err = new Error(parsed.message || `HTTP ${res.statusCode}`);
84
- err.code = parsed.code;
85
- reject(err);
86
- } else {
87
- resolve(parsed);
88
- }
89
- } catch {
90
- reject(new Error(`Invalid JSON response (HTTP ${res.statusCode})`));
91
- }
92
- });
93
- });
94
-
95
- req.on('error', (err) => {
96
- const networkErr = new Error(err.message);
97
- networkErr.code = 'NETWORK_ERROR';
98
- reject(networkErr);
99
- });
100
-
101
- req.on('timeout', () => {
102
- req.destroy();
103
- const timeoutErr = new Error('Request timeout');
104
- timeoutErr.code = 'NETWORK_ERROR';
105
- reject(timeoutErr);
106
- });
107
-
108
- if (body) {
109
- req.write(JSON.stringify(body));
110
- }
111
- req.end();
112
- });
113
- }
114
-
115
- /** @returns {Promise<boolean>} true if license server is reachable */
116
- async isOnline() {
117
- try {
118
- await this._request('GET', '/health');
119
- return true;
120
- } catch {
121
- return false;
122
- }
123
- }
124
-
125
- /**
126
- * Check if email is a buyer and has an account.
127
- * @param {string} email
128
- * @returns {Promise<{isBuyer: boolean, hasAccount: boolean}>}
129
- */
130
- async checkEmail(email) {
131
- return this._request('POST', '/api/v1/auth/check-email', { email });
132
- }
133
-
134
- /**
135
- * Login with email and password.
136
- * @param {string} email
137
- * @param {string} password
138
- * @returns {Promise<{sessionToken: string, emailVerified: boolean}>}
139
- */
140
- async login(email, password) {
141
- return this._request('POST', '/api/v1/auth/login', { email, password });
142
- }
143
-
144
- /**
145
- * Create a new account.
146
- * @param {string} email
147
- * @param {string} password
148
- * @returns {Promise<Object>}
149
- */
150
- async signup(email, password) {
151
- return this._request('POST', '/api/v1/auth/signup', { email, password });
152
- }
153
-
154
- /**
155
- * Activate Pro using an authenticated session.
156
- * @param {string} token - Session token
157
- * @param {string} machineId - Machine fingerprint
158
- * @param {string} version - sinapse-ai version
159
- * @returns {Promise<Object>} Activation result
160
- */
161
- async activateByAuth(token, machineId, version) {
162
- return this._request('POST', '/api/v1/auth/activate-pro', {
163
- machineId,
164
- version,
165
- }, {
166
- Authorization: `Bearer ${token}`,
167
- });
168
- }
169
-
170
- /**
171
- * Activate Pro using a license key (legacy flow).
172
- * @param {string} licenseKey - License key
173
- * @param {string} machineId - Machine fingerprint
174
- * @param {string} version - sinapse-ai version
175
- * @returns {Promise<Object>} Activation result
176
- */
177
- async activate(licenseKey, machineId, version) {
178
- return this._request('POST', '/api/v1/licenses/activate', {
179
- key: licenseKey,
180
- machineId,
181
- version,
182
- });
183
- }
184
-
185
- /**
186
- * Check if user's email has been verified.
187
- * @param {string} sessionToken - Session token
188
- * @returns {Promise<{verified: boolean}>}
189
- */
190
- async checkEmailVerified(sessionToken) {
191
- return this._request('GET', '/api/v1/auth/email-verified', null, {
192
- Authorization: `Bearer ${sessionToken}`,
193
- });
194
- }
195
-
196
- /**
197
- * Resend verification email.
198
- * @param {string} email - User email
199
- * @returns {Promise<Object>}
200
- */
201
- async resendVerification(email) {
202
- return this._request('POST', '/api/v1/auth/resend-verification', { email });
203
- }
204
- }
205
-
206
- /**
207
- * License key format: PRO-XXXX-XXXX-XXXX-XXXX
208
- */
209
- const LICENSE_KEY_PATTERN = /^PRO-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
210
-
211
- /**
212
- * Maximum retry attempts for license validation.
213
- */
214
- const MAX_RETRIES = 3;
215
-
216
- /**
217
- * Email verification polling interval in milliseconds.
218
- */
219
- const VERIFY_POLL_INTERVAL_MS = 5000;
220
-
221
- /**
222
- * Email verification polling timeout in milliseconds (10 minutes).
223
- */
224
- const VERIFY_POLL_TIMEOUT_MS = 10 * 60 * 1000;
225
-
226
- /**
227
- * Minimum password length.
228
- */
229
- const MIN_PASSWORD_LENGTH = 8;
230
-
231
- /**
232
- * Email format regex.
233
- */
234
- const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
235
-
236
- /**
237
- * Detect CI environment.
238
- *
239
- * @returns {boolean} true if running in CI or non-interactive terminal
240
- */
241
- function isCIEnvironment() {
242
- return process.env.CI === 'true' || !process.stdout.isTTY;
243
- }
244
-
245
- /**
246
- * Mask a license key for safe display.
247
- * Shows first and last segments, masks middle two.
248
- * Example: PRO-ABCD-****-****-WXYZ
249
- *
250
- * @param {string} key - License key
251
- * @returns {string} Masked key
252
- */
253
- function maskLicenseKey(key) {
254
- if (!key || typeof key !== 'string') {
255
- return '****';
256
- }
257
-
258
- const trimmed = key.trim().toUpperCase();
259
-
260
- if (!LICENSE_KEY_PATTERN.test(trimmed)) {
261
- return '****';
262
- }
263
-
264
- const parts = trimmed.split('-');
265
- return `${parts[0]}-${parts[1]}-****-****-${parts[4]}`;
266
- }
267
-
268
- /**
269
- * Validate license key format before sending to API.
270
- *
271
- * @param {string} key - License key
272
- * @returns {boolean} true if format is valid
273
- */
274
- function validateKeyFormat(key) {
275
- if (!key || typeof key !== 'string') {
276
- return false;
277
- }
278
- return LICENSE_KEY_PATTERN.test(key.trim().toUpperCase());
279
- }
280
-
281
- /**
282
- * Show the Pro branding header.
283
- */
284
- function showProHeader() {
285
- const title = t('proWizardTitle');
286
- const subtitle = t('proWizardSubtitle');
287
- const maxLen = Math.max(title.length, subtitle.length) + 10;
288
- const pad = (str) => {
289
- const totalPad = maxLen - str.length;
290
- const left = Math.floor(totalPad / 2);
291
- const right = totalPad - left;
292
- return ' '.repeat(left) + str + ' '.repeat(right);
293
- };
294
- console.log('');
295
- console.log(gold(' ╔' + '═'.repeat(maxLen) + '╗'));
296
- console.log(gold(' ║' + pad(title) + '║'));
297
- console.log(gold(' ║' + pad(subtitle) + '║'));
298
- console.log(gold(' ╚' + '═'.repeat(maxLen) + '╝'));
299
- console.log('');
300
- }
301
-
302
- /**
303
- * Show step indicator.
304
- *
305
- * @param {number} current - Current step (1-based)
306
- * @param {number} total - Total steps
307
- * @param {string} label - Step label
308
- */
309
- function showStep(current, total, label) {
310
- const progress = `[${current}/${total}]`;
311
- console.log(gold(`\n ${progress} ${label}`));
312
- console.log(colors.dim(' ' + '─'.repeat(44)));
313
- }
314
-
315
- /**
316
- * Try to load a pro license module via multiple resolution paths.
317
- *
318
- * Resolution order:
319
- * 1. Relative path (framework-dev mode: ../../../../pro/license/{name})
320
- * 2. @sinapse-fullstack/pro package (brownfield: node_modules/@sinapse-fullstack/pro/license/{name})
321
- * 3. Absolute path via sinapse-ai in node_modules (brownfield upgrade)
322
- * 4. Absolute path via @sinapse-fullstack/pro in user project (npx context)
323
- *
324
- * Path 4 is critical for npx execution: when running `npx sinapse-ai install`,
325
- * require() resolves from the npx temp directory, not process.cwd(). After
326
- * bootstrap installs @sinapse-fullstack/pro in the user's project, only an
327
- * absolute path to process.cwd()/node_modules/@sinapse-fullstack/pro/... works.
328
- *
329
- * @param {string} moduleName - Module filename without extension (e.g., 'license-api')
330
- * @returns {Object|null} Loaded module or null
331
- */
332
- function loadProModule(moduleName) {
333
- const path = require('path');
334
-
335
- // 1. Framework-dev mode (cloned repo with pro/ submodule)
336
- try {
337
- return require(`../../../../pro/license/${moduleName}`);
338
- } catch { /* not available */ }
339
-
340
- // 2. @sinapse-fullstack/pro package (works when sinapse-ai is a local dependency)
341
- try {
342
- return require(`@sinapse-fullstack/pro/license/${moduleName}`);
343
- } catch { /* not available */ }
344
-
345
- // 3. sinapse-ai in node_modules (brownfield upgrade from >= v4.2.15)
346
- try {
347
- const absPath = path.join(process.cwd(), 'node_modules', 'sinapse-ai', 'pro', 'license', moduleName);
348
- return require(absPath);
349
- } catch { /* not available */ }
350
-
351
- // 4. @sinapse-fullstack/pro in user project (npx context — require resolves from
352
- // temp dir, so we need absolute path to where bootstrap installed the package)
353
- try {
354
- const absPath = path.join(process.cwd(), 'node_modules', '@sinapse-fullstack', 'pro', 'license', moduleName);
355
- return require(absPath);
356
- } catch { /* not available */ }
357
-
358
- return null;
359
- }
360
-
361
- /**
362
- * Try to load the license API client via lazy import.
363
- * Attempts multiple resolution paths for framework-dev, greenfield, and brownfield.
364
- *
365
- * @returns {{ LicenseApiClient: Function, licenseApi: Object }|null} License API or null
366
- */
367
- function loadLicenseApi() {
368
- return loadProModule('license-api');
369
- }
370
-
371
- /**
372
- * Try to load the feature gate via lazy import.
373
- * Attempts multiple resolution paths for framework-dev, greenfield, and brownfield.
374
- *
375
- * @returns {{ featureGate: Object }|null} Feature gate or null
376
- */
377
- function loadFeatureGate() {
378
- return loadProModule('feature-gate');
379
- }
380
-
381
- /**
382
- * Get a license API client instance.
383
- *
384
- * Prefers the full LicenseApiClient from @sinapse-fullstack/pro when available.
385
- * Falls back to InlineLicenseClient (native https) for pre-bootstrap scenarios.
386
- *
387
- * @returns {Object} Client instance with isOnline, checkEmail, login, signup, activateByAuth
388
- */
389
- function getLicenseClient() {
390
- const loader = module.exports._testing ? module.exports._testing.loadLicenseApi : loadLicenseApi;
391
- const licenseModule = loader();
392
-
393
- if (licenseModule) {
394
- const { LicenseApiClient } = licenseModule;
395
- return new LicenseApiClient();
396
- }
397
-
398
- // Fallback: use inline client for pre-bootstrap (no @sinapse-fullstack/pro yet)
399
- return new InlineLicenseClient();
400
- }
401
-
402
- /**
403
- * Try to load the pro scaffolder via lazy import.
404
- *
405
- * @returns {{ scaffoldProContent: Function }|null} Scaffolder or null
406
- */
407
- function loadProScaffolder() {
408
- try {
409
- return require('../pro/pro-scaffolder');
410
- } catch {
411
- return null;
412
- }
413
- }
414
-
415
- /**
416
- * Step 1: License Gate — authenticate and validate license.
417
- *
418
- * Supports two activation methods:
419
- * 1. Email + Password authentication (recommended, PRO-11)
420
- * 2. License key (legacy, PRO-6)
421
- *
422
- * In CI mode, reads from SINAPSE_PRO_EMAIL + SINAPSE_PRO_PASSWORD or SINAPSE_PRO_KEY env vars.
423
- * In interactive mode, prompts user to choose method.
424
- *
425
- * @param {Object} [options={}] - Options
426
- * @param {string} [options.key] - Pre-provided key (from CLI args or env)
427
- * @param {string} [options.email] - Pre-provided email (from CLI args or env)
428
- * @param {string} [options.password] - Pre-provided password (from CLI args or env)
429
- * @returns {Promise<Object>} Result with { success, key, activationResult }
430
- */
431
- async function stepLicenseGate(options = {}) {
432
- showStep(1, 3, t('proLicenseActivation'));
433
-
434
- const isCI = isCIEnvironment();
435
-
436
- // CI mode: check env vars
437
- if (isCI) {
438
- return stepLicenseGateCI(options);
439
- }
440
-
441
- // Pre-provided key (from CLI args)
442
- if (options.key) {
443
- return stepLicenseGateWithKey(options.key);
444
- }
445
-
446
- // Pre-provided email credentials (from CLI args)
447
- if (options.email && options.password) {
448
- return authenticateWithEmail(options.email, options.password);
449
- }
450
-
451
- // Interactive mode: prompt for method
452
- const inquirer = require('inquirer');
453
-
454
- const { method } = await inquirer.prompt([
455
- {
456
- type: 'list',
457
- name: 'method',
458
- message: colors.primary(t('proHowActivate')),
459
- choices: [
460
- {
461
- name: t('proLoginOrCreate'),
462
- value: 'email',
463
- },
464
- {
465
- name: t('proEnterKey'),
466
- value: 'key',
467
- },
468
- ],
469
- },
470
- ]);
471
-
472
- if (method === 'email') {
473
- return stepLicenseGateWithEmail();
474
- }
475
-
476
- return stepLicenseGateWithKeyInteractive();
477
- }
478
-
479
- /**
480
- * CI mode license gate — reads from env vars.
481
- *
482
- * Priority: SINAPSE_PRO_EMAIL + SINAPSE_PRO_PASSWORD > SINAPSE_PRO_KEY
483
- *
484
- * @param {Object} options - Options with possible pre-provided credentials
485
- * @returns {Promise<Object>} Result with { success, key, activationResult }
486
- */
487
- async function stepLicenseGateCI(options) {
488
- const email = options.email || process.env.SINAPSE_PRO_EMAIL;
489
- const password = options.password || process.env.SINAPSE_PRO_PASSWORD;
490
- const key = options.key || process.env.SINAPSE_PRO_KEY;
491
-
492
- // Prefer email auth over key
493
- if (email && password) {
494
- return authenticateWithEmail(email, password);
495
- }
496
-
497
- if (key) {
498
- return stepLicenseGateWithKey(key);
499
- }
500
-
501
- return {
502
- success: false,
503
- error: t('proCISetEnv'),
504
- };
505
- }
506
-
507
- /**
508
- * Interactive email/password license gate flow.
509
- *
510
- * New flow (PRO-11 v2):
511
- * 1. Email → checkEmail API → { isBuyer, hasAccount }
512
- * 2. NOT buyer → "No Pro access found" → STOP
513
- * 3. IS buyer + HAS account → Password → Login (with retry) → Activate
514
- * 4. IS buyer + NO account → Password + Confirm → Signup → Verify email → Login → Activate
515
- *
516
- * @returns {Promise<Object>} Result with { success, key, activationResult }
517
- */
518
- async function stepLicenseGateWithEmail() {
519
- const inquirer = require('inquirer');
520
-
521
- // Step 1: Get email
522
- const { email } = await inquirer.prompt([
523
- {
524
- type: 'input',
525
- name: 'email',
526
- message: colors.primary(t('proEmailLabel')),
527
- validate: (input) => {
528
- if (!input || !input.trim()) {
529
- return t('proEmailRequired');
530
- }
531
- if (!EMAIL_PATTERN.test(input.trim())) {
532
- return t('proEmailInvalid');
533
- }
534
- return true;
535
- },
536
- },
537
- ]);
538
-
539
- const trimmedEmail = email.trim();
540
-
541
- // Step 2: Check buyer status + account existence
542
- const client = getLicenseClient();
543
-
544
- // Check connectivity
545
- const online = await client.isOnline();
546
- if (!online) {
547
- return {
548
- success: false,
549
- error: t('proServerUnreachable'),
550
- };
551
- }
552
-
553
- const checkSpinner = createSpinner(t('proVerifyingAccess'));
554
- checkSpinner.start();
555
-
556
- let checkResult;
557
- try {
558
- checkResult = await client.checkEmail(trimmedEmail);
559
- } catch (checkError) {
560
- checkSpinner.fail(tf('proVerificationFailed', { message: checkError.message }));
561
- return { success: false, error: checkError.message };
562
- }
563
-
564
- // Step 2a: NOT a buyer → stop
565
- if (!checkResult.isBuyer) {
566
- checkSpinner.fail(t('proNoAccess'));
567
- console.log('');
568
- showInfo(t('proContactSupport'));
569
- showInfo(' Issues: https://github.com/caioimori/sinapse-ai/issues');
570
- showInfo(' ' + t('proPurchase'));
571
- return { success: false, error: t('proEmailNotBuyer') };
572
- }
573
-
574
- // Step 2b: IS a buyer
575
- if (checkResult.hasAccount) {
576
- checkSpinner.succeed(t('proAccessConfirmedAccount'));
577
- // Flow 3: Existing account → Login with password (retry loop)
578
- return loginWithRetry(client, trimmedEmail);
579
- }
580
-
581
- checkSpinner.succeed(t('proAccessConfirmedCreate'));
582
- // Flow 4: New account → Create account flow
583
- return createAccountFlow(client, trimmedEmail);
584
- }
585
-
586
- /**
587
- * Login flow with password retry (max 3 attempts).
588
- *
589
- * @param {object} client - LicenseApiClient instance
590
- * @param {string} email - Verified buyer email
591
- * @returns {Promise<Object>} Result with { success, key, activationResult }
592
- */
593
- async function loginWithRetry(client, email) {
594
- const inquirer = require('inquirer');
595
-
596
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
597
- const { password } = await inquirer.prompt([
598
- {
599
- type: 'password',
600
- name: 'password',
601
- message: colors.primary(t('proPasswordLabel')),
602
- mask: '*',
603
- validate: (input) => {
604
- if (!input || input.length < MIN_PASSWORD_LENGTH) {
605
- return tf('proPasswordMin', { min: MIN_PASSWORD_LENGTH });
606
- }
607
- return true;
608
- },
609
- },
610
- ]);
611
-
612
- const spinner = createSpinner(t('proAuthenticating'));
613
- spinner.start();
614
-
615
- try {
616
- const loginResult = await client.login(email, password);
617
- spinner.succeed(t('proAuthSuccess'));
618
-
619
- // Wait for email verification if needed
620
- if (!loginResult.emailVerified) {
621
- const verifyResult = await waitForEmailVerification(client, loginResult.sessionToken, email);
622
- if (!verifyResult.success) {
623
- return verifyResult;
624
- }
625
- }
626
-
627
- // Activate Pro
628
- return activateProByAuth(client, loginResult.sessionToken);
629
- } catch (loginError) {
630
- if (loginError.code === 'EMAIL_NOT_VERIFIED') {
631
- // Email not verified — poll by retrying login until verified
632
- spinner.info(t('proEmailNotVerified'));
633
- console.log(colors.dim(' ' + t('proCheckingEvery')));
634
-
635
- const startTime = Date.now();
636
- while (Date.now() - startTime < VERIFY_POLL_TIMEOUT_MS) {
637
- await new Promise((resolve) => setTimeout(resolve, VERIFY_POLL_INTERVAL_MS));
638
- try {
639
- const retryLogin = await client.login(email, password);
640
- showSuccess(t('proEmailVerified'));
641
- if (!retryLogin.emailVerified) {
642
- const verifyResult = await waitForEmailVerification(client, retryLogin.sessionToken, email);
643
- if (!verifyResult.success) return verifyResult;
644
- }
645
- return activateProByAuth(client, retryLogin.sessionToken);
646
- } catch (retryError) {
647
- if (retryError.code !== 'EMAIL_NOT_VERIFIED') {
648
- return { success: false, error: retryError.message };
649
- }
650
- // Still not verified, continue polling
651
- }
652
- }
653
-
654
- showError(t('proVerificationTimeout'));
655
- showInfo(t('proRunAgain'));
656
- return { success: false, error: t('proVerificationTimeout') };
657
- } else if (loginError.code === 'INVALID_CREDENTIALS') {
658
- const remaining = MAX_RETRIES - attempt;
659
- if (remaining > 0) {
660
- spinner.fail(`Incorrect password. ${remaining} attempt${remaining > 1 ? 's' : ''} remaining.`);
661
- showInfo('Forgot your password? Visit https://sinapse-license-server.vercel.app/reset-password');
662
- } else {
663
- spinner.fail('Maximum login attempts reached.');
664
- showInfo('Forgot your password? Visit https://sinapse-license-server.vercel.app/reset-password');
665
- showInfo('Or open an issue: https://github.com/caioimori/sinapse-ai/issues');
666
- return { success: false, error: 'Maximum login attempts reached.' };
667
- }
668
- } else if (loginError.code === 'AUTH_RATE_LIMITED') {
669
- spinner.fail(loginError.message);
670
- return { success: false, error: loginError.message };
671
- } else {
672
- spinner.fail(tf('proAuthFailed', { message: loginError.message }));
673
- return { success: false, error: loginError.message };
674
- }
675
- }
676
- }
677
-
678
- return { success: false, error: t('proMaxAttempts') };
679
- }
680
-
681
- /**
682
- * Create account flow for new buyers.
683
- *
684
- * Asks for password, creates account, waits for email verification.
685
- *
686
- * @param {object} client - LicenseApiClient instance
687
- * @param {string} email - Verified buyer email
688
- * @returns {Promise<Object>} Result with { success, key, activationResult }
689
- */
690
- async function createAccountFlow(client, email) {
691
- const inquirer = require('inquirer');
692
-
693
- console.log('');
694
- showInfo(t('proCreateAccount'));
695
-
696
- // Ask for password with confirmation
697
- const { newPassword } = await inquirer.prompt([
698
- {
699
- type: 'password',
700
- name: 'newPassword',
701
- message: colors.primary(t('proChoosePassword')),
702
- mask: '*',
703
- validate: (input) => {
704
- if (!input || input.length < MIN_PASSWORD_LENGTH) {
705
- return tf('proPasswordMin', { min: MIN_PASSWORD_LENGTH });
706
- }
707
- return true;
708
- },
709
- },
710
- ]);
711
-
712
- const { confirmPassword } = await inquirer.prompt([
713
- {
714
- type: 'password',
715
- name: 'confirmPassword',
716
- message: colors.primary(t('proConfirmPassword')),
717
- mask: '*',
718
- validate: (input) => {
719
- if (input !== newPassword) {
720
- return t('proPasswordsNoMatch');
721
- }
722
- return true;
723
- },
724
- },
725
- ]);
726
-
727
- // Create account
728
- const spinner = createSpinner(t('proCreatingAccount'));
729
- spinner.start();
730
-
731
- let sessionToken;
732
- try {
733
- await client.signup(email, confirmPassword);
734
- spinner.succeed(t('proAccountCreated'));
735
- } catch (signupError) {
736
- if (signupError.code === 'EMAIL_ALREADY_REGISTERED') {
737
- spinner.info(t('proAccountExists'));
738
- return loginWithRetry(client, email);
739
- }
740
- spinner.fail(tf('proAccountFailed', { message: signupError.message }));
741
- return { success: false, error: signupError.message };
742
- }
743
-
744
- // Wait for email verification
745
- console.log('');
746
- showInfo(t('proCheckEmail'));
747
-
748
- // Login after signup to get session token
749
- try {
750
- const loginResult = await client.login(email, confirmPassword);
751
- sessionToken = loginResult.sessionToken;
752
- } catch {
753
- // Login might fail if email not verified yet — that's OK, we'll poll
754
- }
755
-
756
- if (sessionToken) {
757
- const verifyResult = await waitForEmailVerification(client, sessionToken, email);
758
- if (!verifyResult.success) {
759
- return verifyResult;
760
- }
761
- } else {
762
- // Need to wait for verification then login
763
- showInfo(t('proWaitingVerification'));
764
- showInfo(t('proAfterVerifying'));
765
-
766
- // Poll by trying to login periodically
767
- const startTime = Date.now();
768
- while (Date.now() - startTime < VERIFY_POLL_TIMEOUT_MS) {
769
- await new Promise((resolve) => setTimeout(resolve, VERIFY_POLL_INTERVAL_MS));
770
- try {
771
- const loginResult = await client.login(email, confirmPassword);
772
- sessionToken = loginResult.sessionToken;
773
- if (loginResult.emailVerified) {
774
- showSuccess('Email verified!');
775
- break;
776
- }
777
- // Got session but not verified yet — use the verification polling
778
- const verifyResult = await waitForEmailVerification(client, sessionToken, email);
779
- if (!verifyResult.success) {
780
- return verifyResult;
781
- }
782
- break;
783
- } catch {
784
- // Still waiting for verification
785
- }
786
- }
787
-
788
- if (!sessionToken) {
789
- showError(t('proVerificationTimeout'));
790
- showInfo(t('proRunAgain'));
791
- return { success: false, error: t('proVerificationTimeout') };
792
- }
793
- }
794
-
795
- // Activate Pro
796
- return activateProByAuth(client, sessionToken);
797
- }
798
-
799
- /**
800
- * Authenticate with email and password (CI mode / pre-provided credentials).
801
- *
802
- * For interactive mode, use stepLicenseGateWithEmail() instead (buyer-first flow).
803
- * This function is used when credentials are pre-provided (CI, CLI args).
804
- *
805
- * @param {string} email - User email
806
- * @param {string} password - User password
807
- * @returns {Promise<Object>} Result with { success, key, activationResult }
808
- */
809
- async function authenticateWithEmail(email, password) {
810
- const client = getLicenseClient();
811
-
812
- // Check connectivity
813
- const online = await client.isOnline();
814
- if (!online) {
815
- return {
816
- success: false,
817
- error: t('proServerUnreachable'),
818
- };
819
- }
820
-
821
- // CI mode: check buyer first, then try login or auto-signup
822
- const checkSpinner = createSpinner(t('proVerifyingAccessShort'));
823
- checkSpinner.start();
824
-
825
- try {
826
- const checkResult = await client.checkEmail(email);
827
- if (!checkResult.isBuyer) {
828
- checkSpinner.fail(t('proNoAccess'));
829
- return { success: false, error: t('proEmailNotBuyer') };
830
- }
831
- checkSpinner.succeed(t('proAccessConfirmed'));
832
- } catch {
833
- checkSpinner.info(t('proBuyerCheckUnavailable'));
834
- }
835
-
836
- // Try login
837
- const spinner = createSpinner(t('proAuthenticating'));
838
- spinner.start();
839
-
840
- let sessionToken;
841
- let emailVerified;
842
-
843
- try {
844
- const loginResult = await client.login(email, password);
845
- sessionToken = loginResult.sessionToken;
846
- emailVerified = loginResult.emailVerified;
847
- spinner.succeed(t('proAuthSuccess'));
848
- } catch (loginError) {
849
- if (loginError.code === 'INVALID_CREDENTIALS') {
850
- spinner.info(t('proLoginFailedSignup'));
851
- try {
852
- await client.signup(email, password);
853
- showSuccess(t('proAccountCreatedVerify'));
854
- emailVerified = false;
855
- const loginAfterSignup = await client.login(email, password);
856
- sessionToken = loginAfterSignup.sessionToken;
857
- } catch (signupError) {
858
- if (signupError.code === 'EMAIL_ALREADY_REGISTERED') {
859
- showError(t('proAccountExistsWrongPw'));
860
- return { success: false, error: t('proAccountExistsWrongPw') };
861
- }
862
- return { success: false, error: signupError.message };
863
- }
864
- } else {
865
- spinner.fail(tf('proAuthFailed', { message: loginError.message }));
866
- return { success: false, error: loginError.message };
867
- }
868
- }
869
-
870
- if (!sessionToken) {
871
- return { success: false, error: t('proAuthFailedShort') };
872
- }
873
-
874
- // Wait for email verification if needed
875
- if (!emailVerified) {
876
- const verifyResult = await waitForEmailVerification(client, sessionToken, email);
877
- if (!verifyResult.success) {
878
- return verifyResult;
879
- }
880
- }
881
-
882
- // Activate Pro
883
- return activateProByAuth(client, sessionToken);
884
- }
885
-
886
- /**
887
- * Wait for email verification with polling.
888
- *
889
- * Polls the server every 5 seconds for up to 10 minutes.
890
- * User can press R to resend verification email.
891
- *
892
- * @param {object} client - LicenseApiClient instance
893
- * @param {string} sessionToken - Session token (accessToken)
894
- * @param {string} email - User email for resend functionality
895
- * @returns {Promise<Object>} Result with { success }
896
- */
897
- async function waitForEmailVerification(client, sessionToken, email) {
898
- console.log('');
899
- showInfo(t('proWaitingVerification'));
900
- showInfo(t('proCheckEmail'));
901
- console.log(colors.dim(' ' + t('proCheckingEvery')));
902
-
903
- if (!isCIEnvironment()) {
904
- console.log(colors.dim(' ' + t('proPressResend')));
905
- }
906
-
907
- const startTime = Date.now();
908
- let resendHint = false;
909
-
910
- // Set up keyboard listener for resend (non-CI only)
911
- let keyListener;
912
- if (!isCIEnvironment() && process.stdin.setRawMode) {
913
- process.stdin.setRawMode(true);
914
- process.stdin.resume();
915
- keyListener = (key) => {
916
- if (key.toString().toLowerCase() === 'r') {
917
- resendHint = true;
918
- }
919
- // Ctrl+C
920
- if (key.toString() === '\u0003') {
921
- cleanupKeyListener();
922
- process.exit(0);
923
- }
924
- };
925
- process.stdin.on('data', keyListener);
926
- }
927
-
928
- function cleanupKeyListener() {
929
- if (keyListener) {
930
- process.stdin.removeListener('data', keyListener);
931
- if (process.stdin.setRawMode) {
932
- process.stdin.setRawMode(false);
933
- }
934
- process.stdin.pause();
935
- }
936
- }
937
-
938
- try {
939
- while (Date.now() - startTime < VERIFY_POLL_TIMEOUT_MS) {
940
- // Handle resend request
941
- if (resendHint) {
942
- resendHint = false;
943
- try {
944
- await client.resendVerification(email);
945
- showInfo(t('proVerificationResent'));
946
- } catch (error) {
947
- showWarning(tf('proCouldNotResend', { message: error.message }));
948
- }
949
- }
950
-
951
- // Poll verification status
952
- try {
953
- const status = await client.checkEmailVerified(sessionToken);
954
- if (status.verified) {
955
- showSuccess(t('proEmailVerified'));
956
- return { success: true };
957
- }
958
- } catch {
959
- // Polling failure is non-fatal, continue
960
- }
961
-
962
- // Wait before next poll
963
- await new Promise((resolve) => setTimeout(resolve, VERIFY_POLL_INTERVAL_MS));
964
- }
965
-
966
- // Timeout
967
- showError(t('proVerificationTimeout'));
968
- showInfo(t('proRunAgainRetry'));
969
- return { success: false, error: t('proVerificationTimeout') };
970
- } finally {
971
- cleanupKeyListener();
972
- }
973
- }
974
-
975
- /**
976
- * Activate Pro using an authenticated session.
977
- *
978
- * @param {object} client - LicenseApiClient instance
979
- * @param {string} sessionToken - Authenticated session token
980
- * @returns {Promise<Object>} Result with { success, key, activationResult }
981
- */
982
- async function activateProByAuth(client, sessionToken) {
983
- const spinner = createSpinner(t('proValidatingSubscription'));
984
- spinner.start();
985
-
986
- try {
987
- // Generate machine fingerprint
988
- const os = require('os');
989
- const crypto = require('crypto');
990
- const machineId = crypto
991
- .createHash('sha256')
992
- .update(`${os.hostname()}-${os.platform()}-${os.arch()}`)
993
- .digest('hex')
994
- .substring(0, 32);
995
-
996
- // Read sinapse-ai version
997
- let sinapseCoreVersion = 'unknown';
998
- try {
999
- const path = require('path');
1000
- const fs = require('fs');
1001
- const pkgPath = path.join(__dirname, '..', '..', '..', '..', 'package.json');
1002
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
1003
- sinapseCoreVersion = pkg.version || 'unknown';
1004
- } catch {
1005
- // Keep 'unknown'
1006
- }
1007
-
1008
- const activationResult = await client.activateByAuth(sessionToken, machineId, sinapseCoreVersion);
1009
-
1010
- spinner.succeed(tf('proSubscriptionConfirmed', { key: maskLicenseKey(activationResult.key) }));
1011
- return { success: true, key: activationResult.key, activationResult };
1012
- } catch (error) {
1013
- if (error.code === 'NOT_A_BUYER') {
1014
- spinner.fail(t('proNoSubscription'));
1015
- showInfo(t('proPurchaseAt'));
1016
- return { success: false, error: error.message };
1017
- }
1018
- if (error.code === 'SEAT_LIMIT_EXCEEDED') {
1019
- spinner.fail(error.message);
1020
- showInfo(t('proSeatLimit'));
1021
- return { success: false, error: error.message };
1022
- }
1023
- if (error.code === 'ALREADY_ACTIVATED') {
1024
- // License already exists — treat as success (re-install scenario)
1025
- spinner.succeed(t('proAlreadyActivated'));
1026
- return { success: true, key: 'existing', activationResult: { reactivation: true } };
1027
- }
1028
-
1029
- spinner.fail(tf('proActivationFailed', { message: error.message }));
1030
- return { success: false, error: error.message };
1031
- }
1032
- }
1033
-
1034
- /**
1035
- * Interactive license key gate (legacy flow).
1036
- *
1037
- * @returns {Promise<Object>} Result with { success, key, activationResult }
1038
- */
1039
- async function stepLicenseGateWithKeyInteractive() {
1040
- const inquirer = require('inquirer');
1041
-
1042
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
1043
- const { licenseKey } = await inquirer.prompt([
1044
- {
1045
- type: 'password',
1046
- name: 'licenseKey',
1047
- message: colors.primary(t('proEnterKeyPrompt')),
1048
- mask: '*',
1049
- validate: (input) => {
1050
- if (!input || !input.trim()) {
1051
- return t('proKeyRequired');
1052
- }
1053
- if (!validateKeyFormat(input)) {
1054
- return t('proKeyInvalid');
1055
- }
1056
- return true;
1057
- },
1058
- },
1059
- ]);
1060
-
1061
- const key = licenseKey.trim().toUpperCase();
1062
- const result = await validateKeyWithApi(key);
1063
-
1064
- if (result.success) {
1065
- showSuccess(tf('proKeyValidated', { key: maskLicenseKey(key) }));
1066
- return { success: true, key, activationResult: result.data };
1067
- }
1068
-
1069
- const remaining = MAX_RETRIES - attempt;
1070
- if (remaining > 0) {
1071
- showError(`${result.error} (${remaining} attempt${remaining > 1 ? 's' : ''} remaining)`);
1072
- } else {
1073
- showError(`${result.error} — no attempts remaining.`);
1074
- return { success: false, error: result.error };
1075
- }
1076
- }
1077
-
1078
- return { success: false, error: 'Maximum attempts reached.' };
1079
- }
1080
-
1081
- /**
1082
- * Validate with pre-provided license key (CI or CLI arg).
1083
- *
1084
- * @param {string} key - License key
1085
- * @returns {Promise<Object>} Result with { success, key, activationResult }
1086
- */
1087
- async function stepLicenseGateWithKey(key) {
1088
- if (!validateKeyFormat(key)) {
1089
- return {
1090
- success: false,
1091
- error: tf('proInvalidKeyFormat', { key: maskLicenseKey(key) }),
1092
- };
1093
- }
1094
-
1095
- const spinner = createSpinner(tf('proValidatingKey', { key: maskLicenseKey(key) }));
1096
- spinner.start();
1097
-
1098
- const result = await validateKeyWithApi(key);
1099
-
1100
- if (result.success) {
1101
- spinner.succeed(tf('proKeyValidated', { key: maskLicenseKey(key) }));
1102
- return { success: true, key, activationResult: result.data };
1103
- }
1104
-
1105
- spinner.fail(result.error);
1106
- return { success: false, error: result.error };
1107
- }
1108
-
1109
- /**
1110
- * Validate a key against the license API.
1111
- *
1112
- * @param {string} key - License key
1113
- * @returns {Promise<Object>} Result with { success, data?, error? }
1114
- */
1115
- async function validateKeyWithApi(key) {
1116
- const client = getLicenseClient();
1117
-
1118
- try {
1119
- // Check if API is reachable
1120
- const online = await client.isOnline();
1121
-
1122
- if (!online) {
1123
- return {
1124
- success: false,
1125
- error: t('proServerUnreachable'),
1126
- };
1127
- }
1128
-
1129
- // Generate a simple machine fingerprint
1130
- const os = require('os');
1131
- const crypto = require('crypto');
1132
- const machineId = crypto
1133
- .createHash('sha256')
1134
- .update(`${os.hostname()}-${os.platform()}-${os.arch()}`)
1135
- .digest('hex')
1136
- .substring(0, 32);
1137
-
1138
- // Read sinapse-ai version
1139
- let sinapseCoreVersion = 'unknown';
1140
- try {
1141
- const path = require('path');
1142
- const fs = require('fs');
1143
- const pkgPath = path.join(__dirname, '..', '..', '..', '..', 'package.json');
1144
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
1145
- sinapseCoreVersion = pkg.version || 'unknown';
1146
- } catch {
1147
- // Keep 'unknown'
1148
- }
1149
-
1150
- const activationResult = await client.activate(key, machineId, sinapseCoreVersion);
1151
-
1152
- return { success: true, data: activationResult };
1153
- } catch (error) {
1154
- // Handle specific error codes from license-api
1155
- if (error.code === 'INVALID_KEY') {
1156
- return { success: false, error: t('proInvalidKey') };
1157
- }
1158
- if (error.code === 'EXPIRED_KEY') {
1159
- return { success: false, error: t('proExpiredKey') };
1160
- }
1161
- if (error.code === 'SEAT_LIMIT_EXCEEDED') {
1162
- return { success: false, error: t('proMaxActivations') };
1163
- }
1164
- if (error.code === 'RATE_LIMITED') {
1165
- return { success: false, error: t('proRateLimited') };
1166
- }
1167
- if (error.code === 'NETWORK_ERROR') {
1168
- return {
1169
- success: false,
1170
- error: t('proServerUnreachable'),
1171
- };
1172
- }
1173
-
1174
- return {
1175
- success: false,
1176
- error: tf('proValidationFailed', { message: error.message || 'Unknown error' }),
1177
- };
1178
- }
1179
- }
1180
-
1181
- /**
1182
- * Step 2: Install/Scaffold — copy pro content into the project.
1183
- *
1184
- * @param {string} targetDir - Project root directory
1185
- * @param {Object} [options={}] - Options
1186
- * @returns {Promise<Object>} Result with { success, scaffoldResult }
1187
- */
1188
- async function stepInstallScaffold(targetDir, options = {}) {
1189
- showStep(2, 3, t('proContentInstallation'));
1190
-
1191
- const path = require('path');
1192
- const fs = require('fs');
1193
-
1194
- // Resolve pro source directory from multiple locations:
1195
- // 1. Bundled in sinapse-ai package (pro/ submodule — npx and local dev)
1196
- // 2. @sinapse-fullstack/pro in node_modules (legacy brownfield)
1197
- const bundledProDir = path.resolve(__dirname, '..', '..', '..', '..', 'pro');
1198
- const npmProDir = path.join(targetDir, 'node_modules', '@sinapse-fullstack', 'pro');
1199
-
1200
- let proSourceDir;
1201
- if (fs.existsSync(bundledProDir) && fs.existsSync(path.join(bundledProDir, 'squads'))) {
1202
- proSourceDir = bundledProDir;
1203
- } else if (fs.existsSync(npmProDir)) {
1204
- proSourceDir = npmProDir;
1205
- } else {
1206
- return {
1207
- success: false,
1208
- error: t('proPackageNotFound'),
1209
- };
1210
- }
1211
-
1212
- // Step 2c: Scaffold pro content
1213
- const scaffolderModule = loadProScaffolder();
1214
-
1215
- if (!scaffolderModule) {
1216
- showWarning(t('proScaffolderNotAvailable'));
1217
- return { success: false, error: t('proScaffolderNotFound') };
1218
- }
1219
-
1220
- const { scaffoldProContent } = scaffolderModule;
1221
-
1222
- const spinner = createSpinner(t('proScaffolding'));
1223
- spinner.start();
1224
-
1225
- try {
1226
- const scaffoldResult = await scaffoldProContent(targetDir, proSourceDir, {
1227
- onProgress: (progress) => {
1228
- spinner.text = tf('proScaffoldingProgress', { message: progress.message });
1229
- },
1230
- force: options.force || false,
1231
- });
1232
-
1233
- if (scaffoldResult.success) {
1234
- spinner.succeed(tf('proContentInstalled', { count: scaffoldResult.copiedFiles.length }));
1235
-
1236
- if (scaffoldResult.warnings.length > 0) {
1237
- for (const warning of scaffoldResult.warnings) {
1238
- showWarning(warning);
1239
- }
1240
- }
1241
-
1242
- return { success: true, scaffoldResult };
1243
- }
1244
-
1245
- spinner.fail(t('proScaffoldFailed'));
1246
- for (const error of scaffoldResult.errors) {
1247
- showError(error);
1248
- }
1249
-
1250
- return { success: false, error: scaffoldResult.errors.join('; '), scaffoldResult };
1251
- } catch (error) {
1252
- spinner.fail(tf('proScaffoldError', { message: error.message }));
1253
- return { success: false, error: error.message };
1254
- }
1255
- }
1256
-
1257
- /**
1258
- * Step 3: Verify — check installed pro content and list features.
1259
- *
1260
- * @param {Object} [scaffoldResult] - Result from step 2
1261
- * @returns {Promise<Object>} Verification result
1262
- */
1263
- async function stepVerify(scaffoldResult) {
1264
- showStep(3, 3, t('proVerification'));
1265
-
1266
- const result = {
1267
- success: true,
1268
- features: [],
1269
- squads: [],
1270
- configs: [],
1271
- };
1272
-
1273
- // Show scaffolded content summary
1274
- if (scaffoldResult && scaffoldResult.copiedFiles) {
1275
- const files = scaffoldResult.copiedFiles;
1276
-
1277
- // Categorize files
1278
- result.squads = files.filter((f) => f.startsWith('squads/'));
1279
- result.configs = files.filter(
1280
- (f) => f.endsWith('.yaml') || f.endsWith('.json'),
1281
- );
1282
-
1283
- showInfo(tf('proFilesInstalled', { count: files.length }));
1284
-
1285
- if (result.squads.length > 0) {
1286
- // Extract unique squad names
1287
- const squadNames = [...new Set(
1288
- result.squads
1289
- .map((f) => f.split('/')[1])
1290
- .filter(Boolean),
1291
- )];
1292
- showSuccess(tf('proSquads', { names: squadNames.join(', ') }));
1293
- }
1294
-
1295
- if (result.configs.length > 0) {
1296
- showSuccess(tf('proConfigs', { count: result.configs.length }));
1297
- }
1298
- }
1299
-
1300
- // Check feature gate if available
1301
- const featureModule = loadFeatureGate();
1302
-
1303
- if (featureModule) {
1304
- const { featureGate } = featureModule;
1305
- featureGate.reload();
1306
-
1307
- const available = featureGate.listAvailable();
1308
- result.features = available;
1309
-
1310
- if (available.length > 0) {
1311
- showSuccess(tf('proFeaturesUnlocked', { count: available.length }));
1312
- for (const feature of available.slice(0, 5)) {
1313
- console.log(colors.dim(` ${feature}`));
1314
- }
1315
- if (available.length > 5) {
1316
- console.log(colors.dim(` ... and ${available.length - 5} more`));
1317
- }
1318
- }
1319
- }
1320
-
1321
- // Final status
1322
- console.log('');
1323
- console.log(gold(' ════════════════════════════════════════════════'));
1324
- console.log(status.celebrate(t('proInstallComplete')));
1325
- console.log(gold(' ════════════════════════════════════════════════'));
1326
- console.log('');
1327
-
1328
- return result;
1329
- }
1330
-
1331
- /**
1332
- * Run the full Pro Installation Wizard.
1333
- *
1334
- * Main entry point. Orchestrates the 3-step flow:
1335
- * 1. License Gate (validate key)
1336
- * 2. Install/Scaffold (copy pro content)
1337
- * 3. Verify (list installed features)
1338
- *
1339
- * @param {Object} [options={}] - Wizard options
1340
- * @param {string} [options.key] - Pre-provided license key
1341
- * @param {string} [options.targetDir] - Project root (default: process.cwd())
1342
- * @param {boolean} [options.force] - Force overwrite existing content
1343
- * @param {boolean} [options.quiet] - Suppress non-essential output
1344
- * @returns {Promise<Object>} Wizard result
1345
- */
1346
- async function runProWizard(options = {}) {
1347
- const targetDir = options.targetDir || process.cwd();
1348
- const isCI = isCIEnvironment();
1349
-
1350
- const result = {
1351
- success: false,
1352
- licenseValidated: false,
1353
- scaffolded: false,
1354
- verified: false,
1355
- };
1356
-
1357
- // Show branding (skip in CI or quiet mode)
1358
- if (!isCI && !options.quiet) {
1359
- showProHeader();
1360
- }
1361
-
1362
- // Step 1: License Gate (uses InlineLicenseClient if @sinapse-fullstack/pro not yet installed)
1363
- const licenseResult = await stepLicenseGate({
1364
- key: options.key || process.env.SINAPSE_PRO_KEY,
1365
- email: options.email || process.env.SINAPSE_PRO_EMAIL,
1366
- password: options.password || process.env.SINAPSE_PRO_PASSWORD,
1367
- });
1368
-
1369
- if (!licenseResult.success) {
1370
- showError(licenseResult.error);
1371
-
1372
- if (!isCI) {
1373
- showInfo(t('proNeedHelp'));
1374
- }
1375
-
1376
- result.error = licenseResult.error;
1377
- return result;
1378
- }
1379
-
1380
- result.licenseValidated = true;
1381
-
1382
- // Step 2: Install/Scaffold
1383
- const scaffoldResult = await stepInstallScaffold(targetDir, {
1384
- force: options.force,
1385
- });
1386
-
1387
- if (!scaffoldResult.success) {
1388
- result.error = scaffoldResult.error;
1389
- return result;
1390
- }
1391
-
1392
- result.scaffolded = true;
1393
-
1394
- // Step 3: Verify
1395
- const verifyResult = await stepVerify(scaffoldResult.scaffoldResult);
1396
- result.verified = verifyResult.success;
1397
- result.features = verifyResult.features;
1398
- result.squads = verifyResult.squads;
1399
- result.success = true;
1400
-
1401
- return result;
1402
- }
1403
-
1404
- module.exports = {
1405
- runProWizard,
1406
- stepLicenseGate,
1407
- stepInstallScaffold,
1408
- stepVerify,
1409
- maskLicenseKey,
1410
- validateKeyFormat,
1411
- isCIEnvironment,
1412
- showProHeader,
1413
- // Internal helpers exported for testing
1414
- _testing: {
1415
- validateKeyWithApi,
1416
- authenticateWithEmail,
1417
- waitForEmailVerification,
1418
- activateProByAuth,
1419
- loginWithRetry,
1420
- createAccountFlow,
1421
- stepLicenseGateCI,
1422
- stepLicenseGateWithKey,
1423
- stepLicenseGateWithKeyInteractive,
1424
- stepLicenseGateWithEmail,
1425
- loadProModule,
1426
- loadLicenseApi,
1427
- loadFeatureGate,
1428
- loadProScaffolder,
1429
- getLicenseClient,
1430
- InlineLicenseClient,
1431
- LICENSE_SERVER_URL,
1432
- MAX_RETRIES,
1433
- LICENSE_KEY_PATTERN,
1434
- EMAIL_PATTERN,
1435
- MIN_PASSWORD_LENGTH,
1436
- VERIFY_POLL_INTERVAL_MS,
1437
- VERIFY_POLL_TIMEOUT_MS,
1438
- },
1439
- };