aios-core 4.0.4 → 4.2.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.
- package/.aios-core/cli/commands/migrate/analyze.js +6 -6
- package/.aios-core/cli/commands/migrate/backup.js +2 -2
- package/.aios-core/cli/commands/migrate/execute.js +4 -4
- package/.aios-core/cli/commands/migrate/index.js +5 -5
- package/.aios-core/cli/commands/migrate/rollback.js +6 -6
- package/.aios-core/cli/commands/migrate/update-imports.js +2 -2
- package/.aios-core/cli/commands/migrate/validate.js +2 -2
- package/.aios-core/cli/commands/pro/index.js +52 -0
- package/.aios-core/cli/index.js +1 -1
- package/.aios-core/core/ids/registry-updater.js +29 -3
- package/.aios-core/core/migration/migration-config.yaml +2 -2
- package/.aios-core/core/migration/module-mapping.yaml +2 -2
- package/.aios-core/core/registry/README.md +2 -2
- package/.aios-core/core/synapse/context/context-builder.js +34 -0
- package/.aios-core/core/synapse/diagnostics/collectors/consistency-collector.js +168 -0
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +129 -0
- package/.aios-core/core/synapse/diagnostics/collectors/manifest-collector.js +82 -0
- package/.aios-core/core/synapse/diagnostics/collectors/output-analyzer.js +134 -0
- package/.aios-core/core/synapse/diagnostics/collectors/pipeline-collector.js +75 -0
- package/.aios-core/core/synapse/diagnostics/collectors/quality-collector.js +252 -0
- package/.aios-core/core/synapse/diagnostics/collectors/relevance-matrix.js +174 -0
- package/.aios-core/core/synapse/diagnostics/collectors/safe-read-json.js +31 -0
- package/.aios-core/core/synapse/diagnostics/collectors/session-collector.js +102 -0
- package/.aios-core/core/synapse/diagnostics/collectors/timing-collector.js +126 -0
- package/.aios-core/core/synapse/diagnostics/collectors/uap-collector.js +83 -0
- package/.aios-core/core/synapse/diagnostics/report-formatter.js +484 -0
- package/.aios-core/core/synapse/diagnostics/synapse-diagnostics.js +95 -0
- package/.aios-core/core/synapse/engine.js +73 -20
- package/.aios-core/core/synapse/runtime/hook-runtime.js +60 -0
- package/.aios-core/core-config.yaml +6 -0
- package/.aios-core/data/agent-config-requirements.yaml +2 -2
- package/.aios-core/data/aios-kb.md +4 -4
- package/.aios-core/data/entity-registry.yaml +210 -10
- package/.aios-core/data/registry-update-log.jsonl +52 -0
- package/.aios-core/development/agents/architect.md +10 -10
- package/.aios-core/development/agents/devops.md +93 -50
- package/.aios-core/development/agents/qa.md +94 -40
- package/.aios-core/development/agents/ux-design-expert.md +25 -25
- package/.aios-core/development/scripts/activation-runtime.js +63 -0
- package/.aios-core/development/scripts/generate-greeting.js +9 -8
- package/.aios-core/development/scripts/unified-activation-pipeline.js +102 -2
- package/.aios-core/development/tasks/{db-expansion-pack-integration.md → db-squad-integration.md} +5 -5
- package/.aios-core/development/tasks/{integrate-expansion-pack.md → integrate-squad.md} +2 -2
- package/.aios-core/development/tasks/next.md +3 -3
- package/.aios-core/development/tasks/pr-automation.md +2 -2
- package/.aios-core/development/tasks/publish-npm.md +257 -0
- package/.aios-core/development/tasks/release-management.md +4 -4
- package/.aios-core/development/tasks/setup-github.md +1 -1
- package/.aios-core/development/tasks/squad-creator-migrate.md +1 -1
- package/.aios-core/development/tasks/squad-creator-sync-ide-command.md +14 -14
- package/.aios-core/development/tasks/update-aios.md +1 -1
- package/.aios-core/development/tasks/validate-next-story.md +99 -2
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-QUICK-REFERENCE.md +1 -1
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-V2.1.md +5 -5
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +21 -21
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +25 -25
- package/.aios-core/docs/standards/OPEN-SOURCE-VS-SERVICE-DIFFERENCES.md +4 -4
- package/.aios-core/docs/standards/QUALITY-GATES-SPECIFICATION.md +3 -3
- package/.aios-core/docs/standards/STANDARDS-INDEX.md +13 -13
- package/.aios-core/docs/standards/STORY-TEMPLATE-V2-SPECIFICATION.md +1 -1
- package/.aios-core/framework-config.yaml +4 -0
- package/.aios-core/infrastructure/scripts/codex-skills-sync/index.js +182 -0
- package/.aios-core/infrastructure/scripts/codex-skills-sync/validate.js +172 -0
- package/.aios-core/infrastructure/scripts/ide-sync/README.md +14 -0
- package/.aios-core/infrastructure/scripts/ide-sync/index.js +6 -0
- package/.aios-core/infrastructure/scripts/tool-resolver.js +4 -4
- package/.aios-core/infrastructure/scripts/validate-paths.js +142 -0
- package/.aios-core/infrastructure/templates/aios-sync.yaml.template +11 -11
- package/.aios-core/infrastructure/templates/github-workflows/README.md +1 -1
- package/.aios-core/install-manifest.yaml +193 -109
- package/.aios-core/local-config.yaml.template +2 -0
- package/.aios-core/manifests/agents.csv +29 -1
- package/.aios-core/manifests/tasks.csv +80 -3
- package/.aios-core/product/README.md +2 -2
- package/.aios-core/product/data/integration-patterns.md +1 -1
- package/.aios-core/product/templates/ide-rules/cline-rules.md +1 -1
- package/.aios-core/product/templates/ide-rules/codex-rules.md +65 -0
- package/.aios-core/product/templates/ide-rules/copilot-rules.md +1 -1
- package/.aios-core/product/templates/ide-rules/roo-rules.md +1 -1
- package/.aios-core/user-guide.md +15 -14
- package/.aios-core/workflow-intelligence/engine/output-formatter.js +1 -1
- package/.claude/hooks/synapse-engine.js +9 -20
- package/README.md +14 -7
- package/bin/aios-init.js +255 -184
- package/bin/aios-minimal.js +2 -2
- package/bin/aios.js +4 -4
- package/package.json +6 -1
- package/packages/aios-pro-cli/bin/aios-pro.js +75 -2
- package/packages/aios-pro-cli/package.json +5 -1
- package/packages/aios-pro-cli/src/recover.js +100 -0
- package/packages/installer/src/__tests__/performance-benchmark.js +382 -0
- package/packages/installer/src/config/ide-configs.js +12 -1
- package/packages/installer/src/config/templates/core-config-template.js +2 -2
- package/packages/installer/src/installer/aios-core-installer.js +2 -2
- package/packages/installer/src/installer/file-hasher.js +97 -0
- package/packages/installer/src/installer/post-install-validator.js +41 -1
- package/packages/installer/src/pro/pro-scaffolder.js +335 -0
- package/packages/installer/src/utils/aios-colors.js +2 -2
- package/packages/installer/src/wizard/feedback.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +2 -2
- package/packages/installer/src/wizard/index.js +58 -19
- package/packages/installer/src/wizard/pro-setup.js +931 -0
- package/packages/installer/src/wizard/questions.js +20 -14
- package/packages/installer/src/wizard/validators.js +1 -1
- package/scripts/code-intel-health-check.js +343 -0
- package/scripts/package-synapse.js +323 -0
- package/scripts/validate-package-completeness.js +317 -0
|
@@ -0,0 +1,931 @@
|
|
|
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 (AIOS_PRO_KEY/AIOS_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, headings, status } = require('../utils/aios-colors');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Gold color for Pro branding.
|
|
23
|
+
* Falls back gracefully if chalk hex is unavailable.
|
|
24
|
+
*/
|
|
25
|
+
let gold;
|
|
26
|
+
try {
|
|
27
|
+
const chalk = require('chalk');
|
|
28
|
+
gold = chalk.hex('#FFD700').bold;
|
|
29
|
+
} catch {
|
|
30
|
+
gold = (text) => text;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* License key format: PRO-XXXX-XXXX-XXXX-XXXX
|
|
35
|
+
*/
|
|
36
|
+
const LICENSE_KEY_PATTERN = /^PRO-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Maximum retry attempts for license validation.
|
|
40
|
+
*/
|
|
41
|
+
const MAX_RETRIES = 3;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Email verification polling interval in milliseconds.
|
|
45
|
+
*/
|
|
46
|
+
const VERIFY_POLL_INTERVAL_MS = 5000;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Email verification polling timeout in milliseconds (10 minutes).
|
|
50
|
+
*/
|
|
51
|
+
const VERIFY_POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Minimum password length.
|
|
55
|
+
*/
|
|
56
|
+
const MIN_PASSWORD_LENGTH = 8;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Email format regex.
|
|
60
|
+
*/
|
|
61
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Detect CI environment.
|
|
65
|
+
*
|
|
66
|
+
* @returns {boolean} true if running in CI or non-interactive terminal
|
|
67
|
+
*/
|
|
68
|
+
function isCIEnvironment() {
|
|
69
|
+
return process.env.CI === 'true' || !process.stdout.isTTY;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Mask a license key for safe display.
|
|
74
|
+
* Shows first and last segments, masks middle two.
|
|
75
|
+
* Example: PRO-ABCD-****-****-WXYZ
|
|
76
|
+
*
|
|
77
|
+
* @param {string} key - License key
|
|
78
|
+
* @returns {string} Masked key
|
|
79
|
+
*/
|
|
80
|
+
function maskLicenseKey(key) {
|
|
81
|
+
if (!key || typeof key !== 'string') {
|
|
82
|
+
return '****';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const trimmed = key.trim().toUpperCase();
|
|
86
|
+
|
|
87
|
+
if (!LICENSE_KEY_PATTERN.test(trimmed)) {
|
|
88
|
+
return '****';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const parts = trimmed.split('-');
|
|
92
|
+
return `${parts[0]}-${parts[1]}-****-****-${parts[4]}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Validate license key format before sending to API.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} key - License key
|
|
99
|
+
* @returns {boolean} true if format is valid
|
|
100
|
+
*/
|
|
101
|
+
function validateKeyFormat(key) {
|
|
102
|
+
if (!key || typeof key !== 'string') {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return LICENSE_KEY_PATTERN.test(key.trim().toUpperCase());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Show the Pro branding header.
|
|
110
|
+
*/
|
|
111
|
+
function showProHeader() {
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(gold(' ╔══════════════════════════════════════════════╗'));
|
|
114
|
+
console.log(gold(' ║ AIOS Pro Installation Wizard ║'));
|
|
115
|
+
console.log(gold(' ║ Premium Content & Features ║'));
|
|
116
|
+
console.log(gold(' ╚══════════════════════════════════════════════╝'));
|
|
117
|
+
console.log('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Show step indicator.
|
|
122
|
+
*
|
|
123
|
+
* @param {number} current - Current step (1-based)
|
|
124
|
+
* @param {number} total - Total steps
|
|
125
|
+
* @param {string} label - Step label
|
|
126
|
+
*/
|
|
127
|
+
function showStep(current, total, label) {
|
|
128
|
+
const progress = `[${current}/${total}]`;
|
|
129
|
+
console.log(gold(`\n ${progress} ${label}`));
|
|
130
|
+
console.log(colors.dim(' ' + '─'.repeat(44)));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Try to load the license API client via lazy import.
|
|
135
|
+
*
|
|
136
|
+
* @returns {{ LicenseApiClient: Function, licenseApi: Object }|null} License API or null
|
|
137
|
+
*/
|
|
138
|
+
function loadLicenseApi() {
|
|
139
|
+
try {
|
|
140
|
+
return require('../../../../pro/license/license-api');
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Try to load the feature gate via lazy import.
|
|
148
|
+
*
|
|
149
|
+
* @returns {{ featureGate: Object }|null} Feature gate or null
|
|
150
|
+
*/
|
|
151
|
+
function loadFeatureGate() {
|
|
152
|
+
try {
|
|
153
|
+
return require('../../../../pro/license/feature-gate');
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Try to load the pro scaffolder via lazy import.
|
|
161
|
+
*
|
|
162
|
+
* @returns {{ scaffoldProContent: Function }|null} Scaffolder or null
|
|
163
|
+
*/
|
|
164
|
+
function loadProScaffolder() {
|
|
165
|
+
try {
|
|
166
|
+
return require('../pro/pro-scaffolder');
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Step 1: License Gate — authenticate and validate license.
|
|
174
|
+
*
|
|
175
|
+
* Supports two activation methods:
|
|
176
|
+
* 1. Email + Password authentication (recommended, PRO-11)
|
|
177
|
+
* 2. License key (legacy, PRO-6)
|
|
178
|
+
*
|
|
179
|
+
* In CI mode, reads from AIOS_PRO_EMAIL + AIOS_PRO_PASSWORD or AIOS_PRO_KEY env vars.
|
|
180
|
+
* In interactive mode, prompts user to choose method.
|
|
181
|
+
*
|
|
182
|
+
* @param {Object} [options={}] - Options
|
|
183
|
+
* @param {string} [options.key] - Pre-provided key (from CLI args or env)
|
|
184
|
+
* @param {string} [options.email] - Pre-provided email (from CLI args or env)
|
|
185
|
+
* @param {string} [options.password] - Pre-provided password (from CLI args or env)
|
|
186
|
+
* @returns {Promise<Object>} Result with { success, key, activationResult }
|
|
187
|
+
*/
|
|
188
|
+
async function stepLicenseGate(options = {}) {
|
|
189
|
+
showStep(1, 3, 'License Activation');
|
|
190
|
+
|
|
191
|
+
const isCI = isCIEnvironment();
|
|
192
|
+
|
|
193
|
+
// CI mode: check env vars
|
|
194
|
+
if (isCI) {
|
|
195
|
+
return stepLicenseGateCI(options);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Pre-provided key (from CLI args)
|
|
199
|
+
if (options.key) {
|
|
200
|
+
return stepLicenseGateWithKey(options.key);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Pre-provided email credentials (from CLI args)
|
|
204
|
+
if (options.email && options.password) {
|
|
205
|
+
return authenticateWithEmail(options.email, options.password);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Interactive mode: prompt for method
|
|
209
|
+
const inquirer = require('inquirer');
|
|
210
|
+
|
|
211
|
+
const { method } = await inquirer.prompt([
|
|
212
|
+
{
|
|
213
|
+
type: 'list',
|
|
214
|
+
name: 'method',
|
|
215
|
+
message: colors.primary('How would you like to activate Pro?'),
|
|
216
|
+
choices: [
|
|
217
|
+
{
|
|
218
|
+
name: 'Login with email and password (Recommended)',
|
|
219
|
+
value: 'email',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: 'Enter license key',
|
|
223
|
+
value: 'key',
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
if (method === 'email') {
|
|
230
|
+
return stepLicenseGateWithEmail();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return stepLicenseGateWithKeyInteractive();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* CI mode license gate — reads from env vars.
|
|
238
|
+
*
|
|
239
|
+
* Priority: AIOS_PRO_EMAIL + AIOS_PRO_PASSWORD > AIOS_PRO_KEY
|
|
240
|
+
*
|
|
241
|
+
* @param {Object} options - Options with possible pre-provided credentials
|
|
242
|
+
* @returns {Promise<Object>} Result with { success, key, activationResult }
|
|
243
|
+
*/
|
|
244
|
+
async function stepLicenseGateCI(options) {
|
|
245
|
+
const email = options.email || process.env.AIOS_PRO_EMAIL;
|
|
246
|
+
const password = options.password || process.env.AIOS_PRO_PASSWORD;
|
|
247
|
+
const key = options.key || process.env.AIOS_PRO_KEY;
|
|
248
|
+
|
|
249
|
+
// Prefer email auth over key
|
|
250
|
+
if (email && password) {
|
|
251
|
+
return authenticateWithEmail(email, password);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (key) {
|
|
255
|
+
return stepLicenseGateWithKey(key);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
success: false,
|
|
260
|
+
error: 'CI mode: Set AIOS_PRO_EMAIL + AIOS_PRO_PASSWORD or AIOS_PRO_KEY environment variables.',
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Interactive email/password license gate flow.
|
|
266
|
+
*
|
|
267
|
+
* Prompts for email, then checks if account exists to determine signup vs login.
|
|
268
|
+
*
|
|
269
|
+
* @returns {Promise<Object>} Result with { success, key, activationResult }
|
|
270
|
+
*/
|
|
271
|
+
async function stepLicenseGateWithEmail() {
|
|
272
|
+
const inquirer = require('inquirer');
|
|
273
|
+
|
|
274
|
+
const { email } = await inquirer.prompt([
|
|
275
|
+
{
|
|
276
|
+
type: 'input',
|
|
277
|
+
name: 'email',
|
|
278
|
+
message: colors.primary('Email:'),
|
|
279
|
+
validate: (input) => {
|
|
280
|
+
if (!input || !input.trim()) {
|
|
281
|
+
return 'Email is required';
|
|
282
|
+
}
|
|
283
|
+
if (!EMAIL_PATTERN.test(input.trim())) {
|
|
284
|
+
return 'Please enter a valid email address';
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
]);
|
|
290
|
+
|
|
291
|
+
const { password } = await inquirer.prompt([
|
|
292
|
+
{
|
|
293
|
+
type: 'password',
|
|
294
|
+
name: 'password',
|
|
295
|
+
message: colors.primary('Password:'),
|
|
296
|
+
mask: '*',
|
|
297
|
+
validate: (input) => {
|
|
298
|
+
if (!input || input.length < MIN_PASSWORD_LENGTH) {
|
|
299
|
+
return `Password must be at least ${MIN_PASSWORD_LENGTH} characters`;
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
]);
|
|
305
|
+
|
|
306
|
+
return authenticateWithEmail(email.trim(), password);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Authenticate with email and password.
|
|
311
|
+
*
|
|
312
|
+
* Tries login first. If user doesn't exist, offers to create account.
|
|
313
|
+
* Handles email verification polling for new signups.
|
|
314
|
+
*
|
|
315
|
+
* @param {string} email - User email
|
|
316
|
+
* @param {string} password - User password
|
|
317
|
+
* @returns {Promise<Object>} Result with { success, key, activationResult }
|
|
318
|
+
*/
|
|
319
|
+
async function authenticateWithEmail(email, password) {
|
|
320
|
+
const loader = module.exports._testing ? module.exports._testing.loadLicenseApi : loadLicenseApi;
|
|
321
|
+
const licenseModule = loader();
|
|
322
|
+
|
|
323
|
+
if (!licenseModule) {
|
|
324
|
+
return {
|
|
325
|
+
success: false,
|
|
326
|
+
error: 'Pro license module not available. Ensure @aios-fullstack/pro is installed.',
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const { LicenseApiClient } = licenseModule;
|
|
331
|
+
const client = new LicenseApiClient();
|
|
332
|
+
|
|
333
|
+
// Check connectivity
|
|
334
|
+
const online = await client.isOnline();
|
|
335
|
+
if (!online) {
|
|
336
|
+
return {
|
|
337
|
+
success: false,
|
|
338
|
+
error: 'License server is unreachable. Check your internet connection and try again.',
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Try login first
|
|
343
|
+
const spinner = createSpinner('Authenticating...');
|
|
344
|
+
spinner.start();
|
|
345
|
+
|
|
346
|
+
let sessionToken;
|
|
347
|
+
let emailVerified;
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const loginResult = await client.login(email, password);
|
|
351
|
+
sessionToken = loginResult.sessionToken;
|
|
352
|
+
emailVerified = loginResult.emailVerified;
|
|
353
|
+
spinner.succeed('Authenticated successfully.');
|
|
354
|
+
} catch (loginError) {
|
|
355
|
+
// If invalid credentials, try signup for new users
|
|
356
|
+
if (loginError.code === 'INVALID_CREDENTIALS') {
|
|
357
|
+
spinner.info('No account found. Creating a new account...');
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
await client.signup(email, password);
|
|
361
|
+
showSuccess('Account created. Verification email sent!');
|
|
362
|
+
emailVerified = false;
|
|
363
|
+
|
|
364
|
+
// Login after signup to get session token
|
|
365
|
+
const loginAfterSignup = await client.login(email, password);
|
|
366
|
+
sessionToken = loginAfterSignup.sessionToken;
|
|
367
|
+
} catch (signupError) {
|
|
368
|
+
if (signupError.code === 'EMAIL_ALREADY_REGISTERED') {
|
|
369
|
+
showError('An account exists with this email but the password is incorrect.');
|
|
370
|
+
showInfo('Forgot your password? Visit https://pro.synkra.ai/reset-password or contact support@synkra.ai');
|
|
371
|
+
return { success: false, error: signupError.message };
|
|
372
|
+
}
|
|
373
|
+
return { success: false, error: signupError.message };
|
|
374
|
+
}
|
|
375
|
+
} else if (loginError.code === 'AUTH_RATE_LIMITED') {
|
|
376
|
+
spinner.fail(loginError.message);
|
|
377
|
+
return { success: false, error: loginError.message };
|
|
378
|
+
} else {
|
|
379
|
+
spinner.fail(`Authentication failed: ${loginError.message}`);
|
|
380
|
+
return { success: false, error: loginError.message };
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Wait for email verification if needed
|
|
385
|
+
if (!emailVerified) {
|
|
386
|
+
const verifyResult = await waitForEmailVerification(client, sessionToken);
|
|
387
|
+
if (!verifyResult.success) {
|
|
388
|
+
return verifyResult;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Activate Pro
|
|
393
|
+
return activateProByAuth(client, sessionToken);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Wait for email verification with polling.
|
|
398
|
+
*
|
|
399
|
+
* Polls the server every 5 seconds for up to 10 minutes.
|
|
400
|
+
* User can press R to resend verification email.
|
|
401
|
+
*
|
|
402
|
+
* @param {object} client - LicenseApiClient instance
|
|
403
|
+
* @param {string} sessionToken - Session token
|
|
404
|
+
* @returns {Promise<Object>} Result with { success }
|
|
405
|
+
*/
|
|
406
|
+
async function waitForEmailVerification(client, sessionToken) {
|
|
407
|
+
console.log('');
|
|
408
|
+
showInfo('Waiting for email verification...');
|
|
409
|
+
showInfo('Open your email and click the verification link.');
|
|
410
|
+
console.log(colors.dim(' (Checking every 5 seconds... timeout in 10 minutes)'));
|
|
411
|
+
|
|
412
|
+
if (!isCIEnvironment()) {
|
|
413
|
+
console.log(colors.dim(' [Press R to resend verification email]'));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const startTime = Date.now();
|
|
417
|
+
let resendHint = false;
|
|
418
|
+
|
|
419
|
+
// Set up keyboard listener for resend (non-CI only)
|
|
420
|
+
let keyListener;
|
|
421
|
+
if (!isCIEnvironment() && process.stdin.setRawMode) {
|
|
422
|
+
process.stdin.setRawMode(true);
|
|
423
|
+
process.stdin.resume();
|
|
424
|
+
keyListener = (key) => {
|
|
425
|
+
if (key.toString().toLowerCase() === 'r') {
|
|
426
|
+
resendHint = true;
|
|
427
|
+
}
|
|
428
|
+
// Ctrl+C
|
|
429
|
+
if (key.toString() === '\u0003') {
|
|
430
|
+
cleanupKeyListener();
|
|
431
|
+
process.exit(0);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
process.stdin.on('data', keyListener);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function cleanupKeyListener() {
|
|
438
|
+
if (keyListener) {
|
|
439
|
+
process.stdin.removeListener('data', keyListener);
|
|
440
|
+
if (process.stdin.setRawMode) {
|
|
441
|
+
process.stdin.setRawMode(false);
|
|
442
|
+
}
|
|
443
|
+
process.stdin.pause();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
while (Date.now() - startTime < VERIFY_POLL_TIMEOUT_MS) {
|
|
449
|
+
// Handle resend request
|
|
450
|
+
if (resendHint) {
|
|
451
|
+
resendHint = false;
|
|
452
|
+
try {
|
|
453
|
+
await client.resendVerification(sessionToken);
|
|
454
|
+
showInfo('Verification email resent.');
|
|
455
|
+
} catch (error) {
|
|
456
|
+
showWarning(`Could not resend: ${error.message}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Poll verification status
|
|
461
|
+
try {
|
|
462
|
+
const status = await client.checkEmailVerified(sessionToken);
|
|
463
|
+
if (status.verified) {
|
|
464
|
+
showSuccess('Email verified!');
|
|
465
|
+
return { success: true };
|
|
466
|
+
}
|
|
467
|
+
} catch {
|
|
468
|
+
// Polling failure is non-fatal, continue
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Wait before next poll
|
|
472
|
+
await new Promise((resolve) => setTimeout(resolve, VERIFY_POLL_INTERVAL_MS));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Timeout
|
|
476
|
+
showError('Email verification timed out after 10 minutes.');
|
|
477
|
+
showInfo('Run the installer again to retry verification.');
|
|
478
|
+
return { success: false, error: 'Email verification timed out.' };
|
|
479
|
+
} finally {
|
|
480
|
+
cleanupKeyListener();
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Activate Pro using an authenticated session.
|
|
486
|
+
*
|
|
487
|
+
* @param {object} client - LicenseApiClient instance
|
|
488
|
+
* @param {string} sessionToken - Authenticated session token
|
|
489
|
+
* @returns {Promise<Object>} Result with { success, key, activationResult }
|
|
490
|
+
*/
|
|
491
|
+
async function activateProByAuth(client, sessionToken) {
|
|
492
|
+
const spinner = createSpinner('Validating Pro subscription...');
|
|
493
|
+
spinner.start();
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
// Generate machine fingerprint
|
|
497
|
+
const os = require('os');
|
|
498
|
+
const crypto = require('crypto');
|
|
499
|
+
const machineId = crypto
|
|
500
|
+
.createHash('sha256')
|
|
501
|
+
.update(`${os.hostname()}-${os.platform()}-${os.arch()}`)
|
|
502
|
+
.digest('hex')
|
|
503
|
+
.substring(0, 32);
|
|
504
|
+
|
|
505
|
+
// Read aios-core version
|
|
506
|
+
let aiosCoreVersion = 'unknown';
|
|
507
|
+
try {
|
|
508
|
+
const path = require('path');
|
|
509
|
+
const fs = require('fs');
|
|
510
|
+
const pkgPath = path.join(__dirname, '..', '..', '..', '..', 'package.json');
|
|
511
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
512
|
+
aiosCoreVersion = pkg.version || 'unknown';
|
|
513
|
+
} catch {
|
|
514
|
+
// Keep 'unknown'
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const activationResult = await client.activateByAuth(sessionToken, machineId, aiosCoreVersion);
|
|
518
|
+
|
|
519
|
+
spinner.succeed(`Pro subscription confirmed! License: ${maskLicenseKey(activationResult.key)}`);
|
|
520
|
+
return { success: true, key: activationResult.key, activationResult };
|
|
521
|
+
} catch (error) {
|
|
522
|
+
if (error.code === 'NOT_A_BUYER') {
|
|
523
|
+
spinner.fail('No active Pro subscription found for this email.');
|
|
524
|
+
showInfo('Purchase Pro at https://pro.synkra.ai');
|
|
525
|
+
return { success: false, error: error.message };
|
|
526
|
+
}
|
|
527
|
+
if (error.code === 'SEAT_LIMIT_EXCEEDED') {
|
|
528
|
+
spinner.fail(error.message);
|
|
529
|
+
showInfo('Deactivate another device or upgrade your license.');
|
|
530
|
+
return { success: false, error: error.message };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
spinner.fail(`Activation failed: ${error.message}`);
|
|
534
|
+
return { success: false, error: error.message };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Interactive license key gate (legacy flow).
|
|
540
|
+
*
|
|
541
|
+
* @returns {Promise<Object>} Result with { success, key, activationResult }
|
|
542
|
+
*/
|
|
543
|
+
async function stepLicenseGateWithKeyInteractive() {
|
|
544
|
+
const inquirer = require('inquirer');
|
|
545
|
+
|
|
546
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
547
|
+
const { licenseKey } = await inquirer.prompt([
|
|
548
|
+
{
|
|
549
|
+
type: 'password',
|
|
550
|
+
name: 'licenseKey',
|
|
551
|
+
message: colors.primary('Enter your Pro license key:'),
|
|
552
|
+
mask: '*',
|
|
553
|
+
validate: (input) => {
|
|
554
|
+
if (!input || !input.trim()) {
|
|
555
|
+
return 'License key is required';
|
|
556
|
+
}
|
|
557
|
+
if (!validateKeyFormat(input)) {
|
|
558
|
+
return 'Invalid format. Expected: PRO-XXXX-XXXX-XXXX-XXXX';
|
|
559
|
+
}
|
|
560
|
+
return true;
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
]);
|
|
564
|
+
|
|
565
|
+
const key = licenseKey.trim().toUpperCase();
|
|
566
|
+
const result = await validateKeyWithApi(key);
|
|
567
|
+
|
|
568
|
+
if (result.success) {
|
|
569
|
+
showSuccess(`License validated: ${maskLicenseKey(key)}`);
|
|
570
|
+
return { success: true, key, activationResult: result.data };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const remaining = MAX_RETRIES - attempt;
|
|
574
|
+
if (remaining > 0) {
|
|
575
|
+
showError(`${result.error} (${remaining} attempt${remaining > 1 ? 's' : ''} remaining)`);
|
|
576
|
+
} else {
|
|
577
|
+
showError(`${result.error} — no attempts remaining.`);
|
|
578
|
+
return { success: false, error: result.error };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return { success: false, error: 'Maximum attempts reached.' };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Validate with pre-provided license key (CI or CLI arg).
|
|
587
|
+
*
|
|
588
|
+
* @param {string} key - License key
|
|
589
|
+
* @returns {Promise<Object>} Result with { success, key, activationResult }
|
|
590
|
+
*/
|
|
591
|
+
async function stepLicenseGateWithKey(key) {
|
|
592
|
+
if (!validateKeyFormat(key)) {
|
|
593
|
+
return {
|
|
594
|
+
success: false,
|
|
595
|
+
error: `Invalid key format: ${maskLicenseKey(key)}. Expected: PRO-XXXX-XXXX-XXXX-XXXX`,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const spinner = createSpinner(`Validating license ${maskLicenseKey(key)}...`);
|
|
600
|
+
spinner.start();
|
|
601
|
+
|
|
602
|
+
const result = await validateKeyWithApi(key);
|
|
603
|
+
|
|
604
|
+
if (result.success) {
|
|
605
|
+
spinner.succeed(`License validated: ${maskLicenseKey(key)}`);
|
|
606
|
+
return { success: true, key, activationResult: result.data };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
spinner.fail(result.error);
|
|
610
|
+
return { success: false, error: result.error };
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Validate a key against the license API.
|
|
615
|
+
*
|
|
616
|
+
* @param {string} key - License key
|
|
617
|
+
* @returns {Promise<Object>} Result with { success, data?, error? }
|
|
618
|
+
*/
|
|
619
|
+
async function validateKeyWithApi(key) {
|
|
620
|
+
// Use exports._testing for testability (allows mock injection)
|
|
621
|
+
const loader = module.exports._testing ? module.exports._testing.loadLicenseApi : loadLicenseApi;
|
|
622
|
+
const licenseModule = loader();
|
|
623
|
+
|
|
624
|
+
if (!licenseModule) {
|
|
625
|
+
return {
|
|
626
|
+
success: false,
|
|
627
|
+
error: 'Pro license module not available. Ensure @aios-fullstack/pro is installed.',
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const { LicenseApiClient } = licenseModule;
|
|
632
|
+
const client = new LicenseApiClient();
|
|
633
|
+
|
|
634
|
+
try {
|
|
635
|
+
// Check if API is reachable
|
|
636
|
+
const online = await client.isOnline();
|
|
637
|
+
|
|
638
|
+
if (!online) {
|
|
639
|
+
return {
|
|
640
|
+
success: false,
|
|
641
|
+
error: 'License server is unreachable. Check your internet connection and try again.',
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Generate a simple machine fingerprint
|
|
646
|
+
const os = require('os');
|
|
647
|
+
const crypto = require('crypto');
|
|
648
|
+
const machineId = crypto
|
|
649
|
+
.createHash('sha256')
|
|
650
|
+
.update(`${os.hostname()}-${os.platform()}-${os.arch()}`)
|
|
651
|
+
.digest('hex')
|
|
652
|
+
.substring(0, 32);
|
|
653
|
+
|
|
654
|
+
// Read aios-core version
|
|
655
|
+
let aiosCoreVersion = 'unknown';
|
|
656
|
+
try {
|
|
657
|
+
const path = require('path');
|
|
658
|
+
const fs = require('fs');
|
|
659
|
+
const pkgPath = path.join(__dirname, '..', '..', '..', '..', 'package.json');
|
|
660
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
661
|
+
aiosCoreVersion = pkg.version || 'unknown';
|
|
662
|
+
} catch {
|
|
663
|
+
// Keep 'unknown'
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const activationResult = await client.activate(key, machineId, aiosCoreVersion);
|
|
667
|
+
|
|
668
|
+
return { success: true, data: activationResult };
|
|
669
|
+
} catch (error) {
|
|
670
|
+
// Handle specific error codes from license-api
|
|
671
|
+
if (error.code === 'INVALID_KEY') {
|
|
672
|
+
return { success: false, error: 'Invalid license key.' };
|
|
673
|
+
}
|
|
674
|
+
if (error.code === 'EXPIRED_KEY') {
|
|
675
|
+
return { success: false, error: 'License key has expired.' };
|
|
676
|
+
}
|
|
677
|
+
if (error.code === 'SEAT_LIMIT_EXCEEDED') {
|
|
678
|
+
return { success: false, error: 'Maximum activations reached for this key.' };
|
|
679
|
+
}
|
|
680
|
+
if (error.code === 'RATE_LIMITED') {
|
|
681
|
+
return { success: false, error: 'Too many requests. Please wait and try again.' };
|
|
682
|
+
}
|
|
683
|
+
if (error.code === 'NETWORK_ERROR') {
|
|
684
|
+
return {
|
|
685
|
+
success: false,
|
|
686
|
+
error: 'License server is unreachable. Check your internet connection and try again.',
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return {
|
|
691
|
+
success: false,
|
|
692
|
+
error: `License validation failed: ${error.message || 'Unknown error'}`,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Step 2: Install/Scaffold — copy pro content into the project.
|
|
699
|
+
*
|
|
700
|
+
* @param {string} targetDir - Project root directory
|
|
701
|
+
* @param {Object} [options={}] - Options
|
|
702
|
+
* @returns {Promise<Object>} Result with { success, scaffoldResult }
|
|
703
|
+
*/
|
|
704
|
+
async function stepInstallScaffold(targetDir, options = {}) {
|
|
705
|
+
showStep(2, 3, 'Pro Content Installation');
|
|
706
|
+
|
|
707
|
+
const scaffolderModule = loadProScaffolder();
|
|
708
|
+
|
|
709
|
+
if (!scaffolderModule) {
|
|
710
|
+
showWarning('Pro scaffolder not available. Ensure @aios-fullstack/pro is installed.');
|
|
711
|
+
return { success: false, error: 'Pro scaffolder module not found.' };
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const { scaffoldProContent } = scaffolderModule;
|
|
715
|
+
const path = require('path');
|
|
716
|
+
|
|
717
|
+
// Determine pro source directory
|
|
718
|
+
const proSourceDir = path.join(targetDir, 'node_modules', '@aios-fullstack', 'pro');
|
|
719
|
+
|
|
720
|
+
const spinner = createSpinner('Scaffolding pro content...');
|
|
721
|
+
spinner.start();
|
|
722
|
+
|
|
723
|
+
try {
|
|
724
|
+
const scaffoldResult = await scaffoldProContent(targetDir, proSourceDir, {
|
|
725
|
+
onProgress: (progress) => {
|
|
726
|
+
spinner.text = `Scaffolding: ${progress.message}`;
|
|
727
|
+
},
|
|
728
|
+
force: options.force || false,
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
if (scaffoldResult.success) {
|
|
732
|
+
spinner.succeed(`Pro content installed (${scaffoldResult.copiedFiles.length} files)`);
|
|
733
|
+
|
|
734
|
+
if (scaffoldResult.warnings.length > 0) {
|
|
735
|
+
for (const warning of scaffoldResult.warnings) {
|
|
736
|
+
showWarning(warning);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return { success: true, scaffoldResult };
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
spinner.fail('Scaffolding failed');
|
|
744
|
+
for (const error of scaffoldResult.errors) {
|
|
745
|
+
showError(error);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return { success: false, error: scaffoldResult.errors.join('; '), scaffoldResult };
|
|
749
|
+
} catch (error) {
|
|
750
|
+
spinner.fail(`Scaffolding error: ${error.message}`);
|
|
751
|
+
return { success: false, error: error.message };
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Step 3: Verify — check installed pro content and list features.
|
|
757
|
+
*
|
|
758
|
+
* @param {Object} [scaffoldResult] - Result from step 2
|
|
759
|
+
* @returns {Promise<Object>} Verification result
|
|
760
|
+
*/
|
|
761
|
+
async function stepVerify(scaffoldResult) {
|
|
762
|
+
showStep(3, 3, 'Verification');
|
|
763
|
+
|
|
764
|
+
const result = {
|
|
765
|
+
success: true,
|
|
766
|
+
features: [],
|
|
767
|
+
squads: [],
|
|
768
|
+
configs: [],
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// Show scaffolded content summary
|
|
772
|
+
if (scaffoldResult && scaffoldResult.copiedFiles) {
|
|
773
|
+
const files = scaffoldResult.copiedFiles;
|
|
774
|
+
|
|
775
|
+
// Categorize files
|
|
776
|
+
result.squads = files.filter((f) => f.startsWith('squads/'));
|
|
777
|
+
result.configs = files.filter(
|
|
778
|
+
(f) => f.endsWith('.yaml') || f.endsWith('.json'),
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
showInfo(`Files installed: ${files.length}`);
|
|
782
|
+
|
|
783
|
+
if (result.squads.length > 0) {
|
|
784
|
+
// Extract unique squad names
|
|
785
|
+
const squadNames = [...new Set(
|
|
786
|
+
result.squads
|
|
787
|
+
.map((f) => f.split('/')[1])
|
|
788
|
+
.filter(Boolean),
|
|
789
|
+
)];
|
|
790
|
+
showSuccess(`Squads: ${squadNames.join(', ')}`);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (result.configs.length > 0) {
|
|
794
|
+
showSuccess(`Configs: ${result.configs.length} files`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Check feature gate if available
|
|
799
|
+
const featureModule = loadFeatureGate();
|
|
800
|
+
|
|
801
|
+
if (featureModule) {
|
|
802
|
+
const { featureGate } = featureModule;
|
|
803
|
+
featureGate.reload();
|
|
804
|
+
|
|
805
|
+
const available = featureGate.listAvailable();
|
|
806
|
+
result.features = available;
|
|
807
|
+
|
|
808
|
+
if (available.length > 0) {
|
|
809
|
+
showSuccess(`Features unlocked: ${available.length}`);
|
|
810
|
+
for (const feature of available.slice(0, 5)) {
|
|
811
|
+
console.log(colors.dim(` ${feature}`));
|
|
812
|
+
}
|
|
813
|
+
if (available.length > 5) {
|
|
814
|
+
console.log(colors.dim(` ... and ${available.length - 5} more`));
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Final status
|
|
820
|
+
console.log('');
|
|
821
|
+
console.log(gold(' ════════════════════════════════════════════════'));
|
|
822
|
+
console.log(status.celebrate('AIOS Pro installation complete!'));
|
|
823
|
+
console.log(gold(' ════════════════════════════════════════════════'));
|
|
824
|
+
console.log('');
|
|
825
|
+
|
|
826
|
+
return result;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Run the full Pro Installation Wizard.
|
|
831
|
+
*
|
|
832
|
+
* Main entry point. Orchestrates the 3-step flow:
|
|
833
|
+
* 1. License Gate (validate key)
|
|
834
|
+
* 2. Install/Scaffold (copy pro content)
|
|
835
|
+
* 3. Verify (list installed features)
|
|
836
|
+
*
|
|
837
|
+
* @param {Object} [options={}] - Wizard options
|
|
838
|
+
* @param {string} [options.key] - Pre-provided license key
|
|
839
|
+
* @param {string} [options.targetDir] - Project root (default: process.cwd())
|
|
840
|
+
* @param {boolean} [options.force] - Force overwrite existing content
|
|
841
|
+
* @param {boolean} [options.quiet] - Suppress non-essential output
|
|
842
|
+
* @returns {Promise<Object>} Wizard result
|
|
843
|
+
*/
|
|
844
|
+
async function runProWizard(options = {}) {
|
|
845
|
+
const targetDir = options.targetDir || process.cwd();
|
|
846
|
+
const isCI = isCIEnvironment();
|
|
847
|
+
|
|
848
|
+
const result = {
|
|
849
|
+
success: false,
|
|
850
|
+
licenseValidated: false,
|
|
851
|
+
scaffolded: false,
|
|
852
|
+
verified: false,
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// Show branding (skip in CI or quiet mode)
|
|
856
|
+
if (!isCI && !options.quiet) {
|
|
857
|
+
showProHeader();
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Step 1: License Gate
|
|
861
|
+
const licenseResult = await stepLicenseGate({
|
|
862
|
+
key: options.key || process.env.AIOS_PRO_KEY,
|
|
863
|
+
email: options.email || process.env.AIOS_PRO_EMAIL,
|
|
864
|
+
password: options.password || process.env.AIOS_PRO_PASSWORD,
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
if (!licenseResult.success) {
|
|
868
|
+
showError(licenseResult.error);
|
|
869
|
+
|
|
870
|
+
if (!isCI) {
|
|
871
|
+
showInfo('Need help? Run: npx aios-pro recover');
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
result.error = licenseResult.error;
|
|
875
|
+
return result;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
result.licenseValidated = true;
|
|
879
|
+
|
|
880
|
+
// Step 2: Install/Scaffold
|
|
881
|
+
const scaffoldResult = await stepInstallScaffold(targetDir, {
|
|
882
|
+
force: options.force,
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
if (!scaffoldResult.success) {
|
|
886
|
+
result.error = scaffoldResult.error;
|
|
887
|
+
return result;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
result.scaffolded = true;
|
|
891
|
+
|
|
892
|
+
// Step 3: Verify
|
|
893
|
+
const verifyResult = await stepVerify(scaffoldResult.scaffoldResult);
|
|
894
|
+
result.verified = verifyResult.success;
|
|
895
|
+
result.features = verifyResult.features;
|
|
896
|
+
result.squads = verifyResult.squads;
|
|
897
|
+
result.success = true;
|
|
898
|
+
|
|
899
|
+
return result;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
module.exports = {
|
|
903
|
+
runProWizard,
|
|
904
|
+
stepLicenseGate,
|
|
905
|
+
stepInstallScaffold,
|
|
906
|
+
stepVerify,
|
|
907
|
+
maskLicenseKey,
|
|
908
|
+
validateKeyFormat,
|
|
909
|
+
isCIEnvironment,
|
|
910
|
+
showProHeader,
|
|
911
|
+
// Internal helpers exported for testing
|
|
912
|
+
_testing: {
|
|
913
|
+
validateKeyWithApi,
|
|
914
|
+
authenticateWithEmail,
|
|
915
|
+
waitForEmailVerification,
|
|
916
|
+
activateProByAuth,
|
|
917
|
+
stepLicenseGateCI,
|
|
918
|
+
stepLicenseGateWithKey,
|
|
919
|
+
stepLicenseGateWithKeyInteractive,
|
|
920
|
+
stepLicenseGateWithEmail,
|
|
921
|
+
loadLicenseApi,
|
|
922
|
+
loadFeatureGate,
|
|
923
|
+
loadProScaffolder,
|
|
924
|
+
MAX_RETRIES,
|
|
925
|
+
LICENSE_KEY_PATTERN,
|
|
926
|
+
EMAIL_PATTERN,
|
|
927
|
+
MIN_PASSWORD_LENGTH,
|
|
928
|
+
VERIFY_POLL_INTERVAL_MS,
|
|
929
|
+
VERIFY_POLL_TIMEOUT_MS,
|
|
930
|
+
},
|
|
931
|
+
};
|