aiox-core 5.0.0 → 5.0.2
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/.aiox-core/data/entity-registry.yaml +5297 -1814
- package/.aiox-core/data/registry-update-log.jsonl +2 -0
- package/.aiox-core/development/templates/service-template/README.md.hbs +158 -158
- package/.aiox-core/development/templates/service-template/__tests__/index.test.ts.hbs +237 -237
- package/.aiox-core/development/templates/service-template/client.ts.hbs +403 -403
- package/.aiox-core/development/templates/service-template/errors.ts.hbs +182 -182
- package/.aiox-core/development/templates/service-template/index.ts.hbs +120 -120
- package/.aiox-core/development/templates/service-template/package.json.hbs +87 -87
- package/.aiox-core/development/templates/service-template/types.ts.hbs +145 -145
- package/.aiox-core/development/templates/squad-template/LICENSE +21 -21
- package/.aiox-core/infrastructure/scripts/tool-resolver.js +4 -4
- package/.aiox-core/infrastructure/templates/aiox-sync.yaml.template +182 -182
- package/.aiox-core/infrastructure/templates/coderabbit.yaml.template +279 -279
- package/.aiox-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
- package/.aiox-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
- package/.aiox-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
- package/.aiox-core/infrastructure/templates/gitignore/gitignore-aiox-base.tmpl +63 -63
- package/.aiox-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
- package/.aiox-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
- package/.aiox-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
- package/.aiox-core/install-manifest.yaml +58 -58
- package/.aiox-core/local-config.yaml.template +71 -71
- package/.aiox-core/monitor/hooks/lib/__init__.py +1 -1
- package/.aiox-core/monitor/hooks/lib/enrich.py +58 -58
- package/.aiox-core/monitor/hooks/lib/send_event.py +47 -47
- package/.aiox-core/monitor/hooks/notification.py +29 -29
- package/.aiox-core/monitor/hooks/post_tool_use.py +45 -45
- package/.aiox-core/monitor/hooks/pre_compact.py +29 -29
- package/.aiox-core/monitor/hooks/pre_tool_use.py +40 -40
- package/.aiox-core/monitor/hooks/stop.py +29 -29
- package/.aiox-core/monitor/hooks/subagent_stop.py +29 -29
- package/.aiox-core/monitor/hooks/user_prompt_submit.py +38 -38
- package/.aiox-core/product/templates/adr.hbs +125 -125
- package/.aiox-core/product/templates/dbdr.hbs +241 -241
- package/.aiox-core/product/templates/engine/elicitation.js +2 -3
- package/.aiox-core/product/templates/epic.hbs +212 -212
- package/.aiox-core/product/templates/pmdr.hbs +186 -186
- package/.aiox-core/product/templates/prd-v2.0.hbs +216 -216
- package/.aiox-core/product/templates/prd.hbs +201 -201
- package/.aiox-core/product/templates/story.hbs +263 -263
- package/.aiox-core/product/templates/task.hbs +170 -170
- package/.aiox-core/product/templates/tmpl-comment-on-examples.sql +158 -158
- package/.aiox-core/product/templates/tmpl-migration-script.sql +91 -91
- package/.aiox-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
- package/.aiox-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
- package/.aiox-core/product/templates/tmpl-rls-roles.sql +135 -135
- package/.aiox-core/product/templates/tmpl-rls-simple.sql +77 -77
- package/.aiox-core/product/templates/tmpl-rls-tenant.sql +152 -152
- package/.aiox-core/product/templates/tmpl-rollback-script.sql +77 -77
- package/.aiox-core/product/templates/tmpl-seed-data.sql +140 -140
- package/.aiox-core/product/templates/tmpl-smoke-test.sql +16 -16
- package/.aiox-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
- package/.aiox-core/product/templates/tmpl-stored-proc.sql +140 -140
- package/.aiox-core/product/templates/tmpl-trigger.sql +152 -152
- package/.aiox-core/product/templates/tmpl-view-materialized.sql +133 -133
- package/.aiox-core/product/templates/tmpl-view.sql +177 -177
- package/.aiox-core/scripts/pm.sh +0 -0
- package/.claude/hooks/code-intel-pretool.cjs +107 -0
- package/.claude/hooks/enforce-architecture-first.py +196 -196
- package/.claude/hooks/mind-clone-governance.py +192 -192
- package/.claude/hooks/read-protection.py +151 -151
- package/.claude/hooks/slug-validation.py +176 -176
- package/.claude/hooks/sql-governance.py +182 -182
- package/.claude/hooks/write-path-validation.py +194 -194
- package/LICENSE +33 -33
- package/bin/aiox-graph.js +0 -0
- package/bin/aiox-minimal.js +0 -0
- package/bin/aiox.js +0 -0
- package/docs/guides/aios-workflows/README.md +247 -0
- package/docs/guides/aios-workflows/bob-orchestrator-workflow.md +1536 -0
- package/package.json +1 -1
- package/packages/aiox-install/bin/aiox-install.js +0 -0
- package/packages/aiox-install/bin/edmcp.js +0 -0
- package/packages/aiox-pro-cli/bin/aiox-pro.js +0 -0
- package/packages/installer/src/wizard/pro-setup.js +210 -123
- package/pro/README.md +66 -0
- package/pro/license/degradation.js +220 -0
- package/pro/license/errors.js +450 -0
- package/pro/license/feature-gate.js +354 -0
- package/pro/license/index.js +181 -0
- package/pro/license/license-api.js +679 -0
- package/pro/license/license-cache.js +523 -0
- package/pro/license/license-crypto.js +303 -0
- package/scripts/check-markdown-links.py +352 -352
- package/scripts/dashboard-parallel-dev.sh +0 -0
- package/scripts/dashboard-parallel-phase3.sh +0 -0
- package/scripts/dashboard-parallel-phase4.sh +0 -0
- package/scripts/glue/README.md +355 -0
- package/scripts/glue/compose-agent-prompt.cjs +362 -0
- package/scripts/install-monitor-hooks.sh +0 -0
- package/.aiox-core/lib/build.json +0 -1
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -36,6 +36,173 @@ try {
|
|
|
36
36
|
*/
|
|
37
37
|
const LICENSE_SERVER_URL = process.env.AIOX_LICENSE_API_URL || 'https://aiox-license-server.vercel.app';
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Inline License Client — lightweight HTTP client for pre-bootstrap license checks.
|
|
41
|
+
*
|
|
42
|
+
* Used when @aiox-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': 'aiox-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 - aiox-core 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 - aiox-core 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
|
+
|
|
39
206
|
/**
|
|
40
207
|
* License key format: PRO-XXXX-XXXX-XXXX-XXXX
|
|
41
208
|
*/
|
|
@@ -211,6 +378,27 @@ function loadFeatureGate() {
|
|
|
211
378
|
return loadProModule('feature-gate');
|
|
212
379
|
}
|
|
213
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Get a license API client instance.
|
|
383
|
+
*
|
|
384
|
+
* Prefers the full LicenseApiClient from @aiox-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 @aiox-fullstack/pro yet)
|
|
399
|
+
return new InlineLicenseClient();
|
|
400
|
+
}
|
|
401
|
+
|
|
214
402
|
/**
|
|
215
403
|
* Try to load the pro scaffolder via lazy import.
|
|
216
404
|
*
|
|
@@ -351,18 +539,7 @@ async function stepLicenseGateWithEmail() {
|
|
|
351
539
|
const trimmedEmail = email.trim();
|
|
352
540
|
|
|
353
541
|
// Step 2: Check buyer status + account existence
|
|
354
|
-
const
|
|
355
|
-
const licenseModule = loader();
|
|
356
|
-
|
|
357
|
-
if (!licenseModule) {
|
|
358
|
-
return {
|
|
359
|
-
success: false,
|
|
360
|
-
error: t('proModuleNotAvailable'),
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const { LicenseApiClient } = licenseModule;
|
|
365
|
-
const client = new LicenseApiClient();
|
|
542
|
+
const client = getLicenseClient();
|
|
366
543
|
|
|
367
544
|
// Check connectivity
|
|
368
545
|
const online = await client.isOnline();
|
|
@@ -630,18 +807,7 @@ async function createAccountFlow(client, email) {
|
|
|
630
807
|
* @returns {Promise<Object>} Result with { success, key, activationResult }
|
|
631
808
|
*/
|
|
632
809
|
async function authenticateWithEmail(email, password) {
|
|
633
|
-
const
|
|
634
|
-
const licenseModule = loader();
|
|
635
|
-
|
|
636
|
-
if (!licenseModule) {
|
|
637
|
-
return {
|
|
638
|
-
success: false,
|
|
639
|
-
error: t('proModuleNotAvailable'),
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
const { LicenseApiClient } = licenseModule;
|
|
644
|
-
const client = new LicenseApiClient();
|
|
810
|
+
const client = getLicenseClient();
|
|
645
811
|
|
|
646
812
|
// Check connectivity
|
|
647
813
|
const online = await client.isOnline();
|
|
@@ -947,19 +1113,7 @@ async function stepLicenseGateWithKey(key) {
|
|
|
947
1113
|
* @returns {Promise<Object>} Result with { success, data?, error? }
|
|
948
1114
|
*/
|
|
949
1115
|
async function validateKeyWithApi(key) {
|
|
950
|
-
|
|
951
|
-
const loader = module.exports._testing ? module.exports._testing.loadLicenseApi : loadLicenseApi;
|
|
952
|
-
const licenseModule = loader();
|
|
953
|
-
|
|
954
|
-
if (!licenseModule) {
|
|
955
|
-
return {
|
|
956
|
-
success: false,
|
|
957
|
-
error: t('proModuleNotAvailable'),
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
const { LicenseApiClient } = licenseModule;
|
|
962
|
-
const client = new LicenseApiClient();
|
|
1116
|
+
const client = getLicenseClient();
|
|
963
1117
|
|
|
964
1118
|
try {
|
|
965
1119
|
// Check if API is reachable
|
|
@@ -1036,50 +1190,23 @@ async function stepInstallScaffold(targetDir, options = {}) {
|
|
|
1036
1190
|
|
|
1037
1191
|
const path = require('path');
|
|
1038
1192
|
const fs = require('fs');
|
|
1039
|
-
const { execSync } = require('child_process');
|
|
1040
|
-
|
|
1041
|
-
const proSourceDir = path.join(targetDir, 'node_modules', '@aiox-fullstack', 'pro');
|
|
1042
|
-
|
|
1043
|
-
// Step 2a: Ensure package.json exists (greenfield projects)
|
|
1044
|
-
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
1045
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
1046
|
-
const initSpinner = createSpinner(t('proInitPackageJson'));
|
|
1047
|
-
initSpinner.start();
|
|
1048
|
-
try {
|
|
1049
|
-
execSync('npm init -y', { cwd: targetDir, stdio: 'pipe' });
|
|
1050
|
-
initSpinner.succeed(t('proPackageJsonCreated'));
|
|
1051
|
-
} catch (err) {
|
|
1052
|
-
initSpinner.fail(t('proPackageJsonFailed'));
|
|
1053
|
-
return { success: false, error: tf('proNpmInitFailed', { message: err.message }) };
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
1193
|
|
|
1057
|
-
//
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// Validate installation
|
|
1077
|
-
if (!fs.existsSync(proSourceDir)) {
|
|
1078
|
-
return {
|
|
1079
|
-
success: false,
|
|
1080
|
-
error: t('proPackageNotFound'),
|
|
1081
|
-
};
|
|
1082
|
-
}
|
|
1194
|
+
// Resolve pro source directory from multiple locations:
|
|
1195
|
+
// 1. Bundled in aiox-core package (pro/ submodule — npx and local dev)
|
|
1196
|
+
// 2. @aiox-fullstack/pro in node_modules (legacy brownfield)
|
|
1197
|
+
const bundledProDir = path.resolve(__dirname, '..', '..', '..', '..', 'pro');
|
|
1198
|
+
const npmProDir = path.join(targetDir, 'node_modules', '@aiox-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
|
+
};
|
|
1083
1210
|
}
|
|
1084
1211
|
|
|
1085
1212
|
// Step 2c: Scaffold pro content
|
|
@@ -1232,49 +1359,7 @@ async function runProWizard(options = {}) {
|
|
|
1232
1359
|
showProHeader();
|
|
1233
1360
|
}
|
|
1234
1361
|
|
|
1235
|
-
//
|
|
1236
|
-
// install @aiox-fullstack/pro first to get the license API, then proceed with gate.
|
|
1237
|
-
if (!loadLicenseApi()) {
|
|
1238
|
-
const fs = require('fs');
|
|
1239
|
-
const path = require('path');
|
|
1240
|
-
const { execSync } = require('child_process');
|
|
1241
|
-
|
|
1242
|
-
showInfo(t('proModuleBootstrap'));
|
|
1243
|
-
|
|
1244
|
-
// Ensure package.json exists
|
|
1245
|
-
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
1246
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
1247
|
-
execSync('npm init -y', { cwd: targetDir, stdio: 'pipe' });
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
// Install @aiox-fullstack/pro to get license module
|
|
1251
|
-
const proDir = path.join(targetDir, 'node_modules', '@aiox-fullstack', 'pro');
|
|
1252
|
-
if (!fs.existsSync(proDir)) {
|
|
1253
|
-
const installSpinner = createSpinner(t('proInstallingPackage'));
|
|
1254
|
-
installSpinner.start();
|
|
1255
|
-
try {
|
|
1256
|
-
execSync('npm install @aiox-fullstack/pro', {
|
|
1257
|
-
cwd: targetDir,
|
|
1258
|
-
stdio: 'pipe',
|
|
1259
|
-
timeout: 120000,
|
|
1260
|
-
});
|
|
1261
|
-
installSpinner.succeed(t('proPackageInstalled'));
|
|
1262
|
-
} catch (err) {
|
|
1263
|
-
installSpinner.fail(t('proPackageInstallFailed'));
|
|
1264
|
-
result.error = tf('proNpmInstallFailed', { message: err.message });
|
|
1265
|
-
return result;
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
// Clear require cache so loadLicenseApi() picks up newly installed module
|
|
1270
|
-
Object.keys(require.cache).forEach((key) => {
|
|
1271
|
-
if (key.includes('license-api') || key.includes('@aiox-fullstack')) {
|
|
1272
|
-
delete require.cache[key];
|
|
1273
|
-
}
|
|
1274
|
-
});
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
// Step 1: License Gate
|
|
1362
|
+
// Step 1: License Gate (uses InlineLicenseClient if @aiox-fullstack/pro not yet installed)
|
|
1278
1363
|
const licenseResult = await stepLicenseGate({
|
|
1279
1364
|
key: options.key || process.env.AIOX_PRO_KEY,
|
|
1280
1365
|
email: options.email || process.env.AIOX_PRO_EMAIL,
|
|
@@ -1341,6 +1426,8 @@ module.exports = {
|
|
|
1341
1426
|
loadLicenseApi,
|
|
1342
1427
|
loadFeatureGate,
|
|
1343
1428
|
loadProScaffolder,
|
|
1429
|
+
getLicenseClient,
|
|
1430
|
+
InlineLicenseClient,
|
|
1344
1431
|
LICENSE_SERVER_URL,
|
|
1345
1432
|
MAX_RETRIES,
|
|
1346
1433
|
LICENSE_KEY_PATTERN,
|
package/pro/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# AIOX Pro
|
|
2
|
+
|
|
3
|
+
Premium features for [Synkra AIOX](https://github.com/SynkraAI/aiox-core).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
AIOX Pro extends the open-source AIOX framework with premium capabilities:
|
|
8
|
+
|
|
9
|
+
- **Premium Squads** - Pre-built squads for SaaS, e-commerce, and fintech
|
|
10
|
+
- **Persistent Memory** - Cross-session context and memory analytics
|
|
11
|
+
- **Usage Metrics** - Dashboards, team analytics, and cost tracking
|
|
12
|
+
- **Enterprise Integrations** - ClickUp, Google Drive, GitLab, Jira, Azure DevOps
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
AIOX Pro requires an active license and is distributed via npm.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install
|
|
20
|
+
npm install @aiox-fullstack/pro
|
|
21
|
+
|
|
22
|
+
# Activate license
|
|
23
|
+
aiox pro activate --key PRO-XXXX-XXXX-XXXX-XXXX
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage with aiox-core
|
|
27
|
+
|
|
28
|
+
AIOX Pro is designed to work as a git submodule of `aiox-core`:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Clone aiox-core with pro submodule
|
|
32
|
+
git clone --recurse-submodules https://github.com/SynkraAI/aiox-core.git
|
|
33
|
+
|
|
34
|
+
# Or add to existing clone
|
|
35
|
+
cd aiox-core
|
|
36
|
+
git submodule update --init pro
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Activate License
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
aiox pro activate --key PRO-XXXX-XXXX-XXXX-XXXX
|
|
43
|
+
aiox pro status
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Structure
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
aiox-pro/
|
|
50
|
+
├── squads/ # Premium squad definitions
|
|
51
|
+
├── memory/ # Persistent memory modules
|
|
52
|
+
├── metrics/ # Usage dashboards and analytics
|
|
53
|
+
├── integrations/ # Enterprise integration adapters
|
|
54
|
+
├── license/ # License infrastructure
|
|
55
|
+
└── pro-config.yaml # Pro extension configuration
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
- Node.js >= 18
|
|
61
|
+
- aiox-core >= 3.12.0
|
|
62
|
+
- Active AIOX Pro license
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
Proprietary - Copyright (c) 2026 SynkraAI. All rights reserved.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graceful Degradation Module
|
|
3
|
+
*
|
|
4
|
+
* Utilities for handling pro feature unavailability with user-friendly
|
|
5
|
+
* messages that preserve data and provide clear next steps.
|
|
6
|
+
*
|
|
7
|
+
* Per ADR-PRO-003 and AC-8:
|
|
8
|
+
* - Never show upgrade prompts on core features
|
|
9
|
+
* - Never delete or corrupt user data
|
|
10
|
+
* - Always provide actionable next steps
|
|
11
|
+
* - Keep messages non-intrusive
|
|
12
|
+
*
|
|
13
|
+
* @module pro/license/degradation
|
|
14
|
+
* @see ADR-PRO-003 - Feature Gating & Licensing
|
|
15
|
+
* @see Story PRO-6 - License Key & Feature Gating System
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
const { featureGate } = require('./feature-gate');
|
|
21
|
+
const { ProFeatureError } = require('./errors');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default degradation message template.
|
|
25
|
+
* @private Reserved for future customization support.
|
|
26
|
+
*/
|
|
27
|
+
const _DEFAULT_MESSAGE_TEMPLATE = `
|
|
28
|
+
{featureName} requires an active AIOS Pro license.
|
|
29
|
+
|
|
30
|
+
Your data and configurations are preserved.
|
|
31
|
+
|
|
32
|
+
Activate: aios pro activate --key <KEY>
|
|
33
|
+
Purchase: https://synkra.ai/pro
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handle pro feature gracefully - return fallback instead of throwing.
|
|
38
|
+
*
|
|
39
|
+
* Use this when you want to provide a degraded experience instead
|
|
40
|
+
* of completely blocking the feature.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} featureId - Feature ID to check
|
|
43
|
+
* @param {Function} proAction - Function to execute if feature is available
|
|
44
|
+
* @param {Function} [fallbackAction] - Function to execute if not available
|
|
45
|
+
* @param {object} [options] - Options
|
|
46
|
+
* @param {boolean} [options.silent=false] - Don't log degradation message
|
|
47
|
+
* @returns {*} Result of proAction or fallbackAction
|
|
48
|
+
*/
|
|
49
|
+
function withGracefulDegradation(featureId, proAction, fallbackAction, options = {}) {
|
|
50
|
+
if (featureGate.isAvailable(featureId)) {
|
|
51
|
+
return proAction();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!options.silent) {
|
|
55
|
+
const featureName = getFeatureFriendlyName(featureId);
|
|
56
|
+
logDegradationMessage(featureName, featureId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof fallbackAction === 'function') {
|
|
60
|
+
return fallbackAction();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Execute action only if pro feature is available.
|
|
68
|
+
*
|
|
69
|
+
* Unlike featureGate.require(), this doesn't throw - it just
|
|
70
|
+
* returns undefined if the feature isn't available.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} featureId - Feature ID to check
|
|
73
|
+
* @param {Function} action - Action to execute if available
|
|
74
|
+
* @returns {*|undefined} Result of action or undefined
|
|
75
|
+
*/
|
|
76
|
+
function ifProAvailable(featureId, action) {
|
|
77
|
+
if (featureGate.isAvailable(featureId)) {
|
|
78
|
+
return action();
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get a user-friendly name for a feature.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} featureId - Feature ID
|
|
87
|
+
* @returns {string} Friendly name
|
|
88
|
+
*/
|
|
89
|
+
function getFeatureFriendlyName(featureId) {
|
|
90
|
+
const all = featureGate.listAll();
|
|
91
|
+
const feature = all.find((f) => f.id === featureId);
|
|
92
|
+
return feature ? feature.name : featureId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Log a degradation message to the console.
|
|
97
|
+
*
|
|
98
|
+
* This is used internally when a pro feature is accessed without
|
|
99
|
+
* a license. Messages are non-intrusive and informative.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} featureName - Human-friendly feature name
|
|
102
|
+
* @param {string} _featureId - Feature ID for reference (reserved for logging)
|
|
103
|
+
*/
|
|
104
|
+
function logDegradationMessage(featureName, _featureId) {
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(` ${featureName} requires an active AIOS Pro license.`);
|
|
107
|
+
console.log('');
|
|
108
|
+
console.log(' Your data and configurations are preserved.');
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(' Activate: aios pro activate --key <KEY>');
|
|
111
|
+
console.log(' Purchase: https://synkra.ai/pro');
|
|
112
|
+
console.log('');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create a degradation-aware wrapper for a pro module.
|
|
117
|
+
*
|
|
118
|
+
* This creates a proxy that catches ProFeatureError and provides
|
|
119
|
+
* a graceful degradation experience.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} proModule - Pro module with methods
|
|
122
|
+
* @param {object} fallbacks - Map of method names to fallback functions
|
|
123
|
+
* @returns {Proxy} Wrapped module
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const safePremiumSquads = createDegradationWrapper(
|
|
127
|
+
* PremiumSquads,
|
|
128
|
+
* {
|
|
129
|
+
* listTemplates: () => ['basic'], // Fallback to basic templates
|
|
130
|
+
* exportSquad: () => null, // No export without license
|
|
131
|
+
* }
|
|
132
|
+
* );
|
|
133
|
+
*/
|
|
134
|
+
function createDegradationWrapper(proModule, fallbacks = {}) {
|
|
135
|
+
return new Proxy(proModule, {
|
|
136
|
+
get(target, prop) {
|
|
137
|
+
const original = target[prop];
|
|
138
|
+
|
|
139
|
+
if (typeof original !== 'function') {
|
|
140
|
+
return original;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return function (...args) {
|
|
144
|
+
try {
|
|
145
|
+
return original.apply(target, args);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (error instanceof ProFeatureError) {
|
|
148
|
+
if (fallbacks[prop]) {
|
|
149
|
+
logDegradationMessage(error.friendlyName, error.featureId);
|
|
150
|
+
return fallbacks[prop](...args);
|
|
151
|
+
}
|
|
152
|
+
// Re-throw if no fallback
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
// Re-throw non-license errors
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Check if the system is in degraded mode (expired/no license).
|
|
165
|
+
*
|
|
166
|
+
* @returns {boolean} true if pro features are unavailable
|
|
167
|
+
*/
|
|
168
|
+
function isInDegradedMode() {
|
|
169
|
+
const state = featureGate.getLicenseState();
|
|
170
|
+
return state === 'Expired' || state === 'Not Activated';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get degradation status summary.
|
|
175
|
+
*
|
|
176
|
+
* @returns {{ degraded: boolean, reason: string, action: string }}
|
|
177
|
+
*/
|
|
178
|
+
function getDegradationStatus() {
|
|
179
|
+
const state = featureGate.getLicenseState();
|
|
180
|
+
|
|
181
|
+
if (state === 'Active') {
|
|
182
|
+
return {
|
|
183
|
+
degraded: false,
|
|
184
|
+
reason: 'License is active',
|
|
185
|
+
action: null,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (state === 'Grace') {
|
|
190
|
+
return {
|
|
191
|
+
degraded: false,
|
|
192
|
+
reason: 'License in grace period - revalidate soon',
|
|
193
|
+
action: 'aios pro validate',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (state === 'Expired') {
|
|
198
|
+
return {
|
|
199
|
+
degraded: true,
|
|
200
|
+
reason: 'License has expired',
|
|
201
|
+
action: 'aios pro activate --key <KEY>',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
degraded: true,
|
|
207
|
+
reason: 'No license activated',
|
|
208
|
+
action: 'aios pro activate --key <KEY>',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = {
|
|
213
|
+
withGracefulDegradation,
|
|
214
|
+
ifProAvailable,
|
|
215
|
+
getFeatureFriendlyName,
|
|
216
|
+
logDegradationMessage,
|
|
217
|
+
createDegradationWrapper,
|
|
218
|
+
isInDegradedMode,
|
|
219
|
+
getDegradationStatus,
|
|
220
|
+
};
|