@ornexus/neocortex 4.0.1 → 4.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.
Files changed (62) hide show
  1. package/install.ps1 +92 -33
  2. package/install.sh +15 -1
  3. package/package.json +3 -3
  4. package/packages/client/dist/adapters/adapter-registry.js +1 -106
  5. package/packages/client/dist/adapters/antigravity-adapter.js +2 -77
  6. package/packages/client/dist/adapters/claude-code-adapter.js +3 -79
  7. package/packages/client/dist/adapters/codex-adapter.js +2 -80
  8. package/packages/client/dist/adapters/cursor-adapter.js +4 -115
  9. package/packages/client/dist/adapters/gemini-adapter.js +2 -71
  10. package/packages/client/dist/adapters/index.js +1 -21
  11. package/packages/client/dist/adapters/platform-detector.js +1 -106
  12. package/packages/client/dist/adapters/target-adapter.js +0 -12
  13. package/packages/client/dist/adapters/vscode-adapter.js +2 -72
  14. package/packages/client/dist/agent/refresh-stubs.js +2 -234
  15. package/packages/client/dist/agent/update-agent-yaml.js +1 -102
  16. package/packages/client/dist/agent/update-description.js +1 -251
  17. package/packages/client/dist/cache/crypto-utils.js +1 -76
  18. package/packages/client/dist/cache/encrypted-cache.js +1 -94
  19. package/packages/client/dist/cache/in-memory-asset-cache.js +1 -70
  20. package/packages/client/dist/cache/index.js +1 -13
  21. package/packages/client/dist/cli.js +2 -163
  22. package/packages/client/dist/commands/activate.js +8 -390
  23. package/packages/client/dist/commands/cache-status.js +2 -112
  24. package/packages/client/dist/commands/invoke.js +28 -490
  25. package/packages/client/dist/config/resolver-selection.js +1 -278
  26. package/packages/client/dist/config/secure-config.js +12 -269
  27. package/packages/client/dist/constants.js +1 -25
  28. package/packages/client/dist/context/context-collector.js +2 -222
  29. package/packages/client/dist/context/context-sanitizer.js +1 -145
  30. package/packages/client/dist/index.js +1 -38
  31. package/packages/client/dist/license/index.js +1 -5
  32. package/packages/client/dist/license/license-client.js +1 -257
  33. package/packages/client/dist/machine/fingerprint.js +2 -160
  34. package/packages/client/dist/machine/index.js +1 -5
  35. package/packages/client/dist/resilience/circuit-breaker.js +1 -170
  36. package/packages/client/dist/resilience/degradation-manager.js +1 -164
  37. package/packages/client/dist/resilience/freshness-indicator.js +1 -100
  38. package/packages/client/dist/resilience/index.js +1 -8
  39. package/packages/client/dist/resilience/recovery-detector.js +1 -74
  40. package/packages/client/dist/resolvers/asset-resolver.js +0 -13
  41. package/packages/client/dist/resolvers/local-resolver.js +8 -218
  42. package/packages/client/dist/resolvers/remote-resolver.js +1 -282
  43. package/packages/client/dist/telemetry/index.js +1 -5
  44. package/packages/client/dist/telemetry/offline-queue.js +1 -131
  45. package/packages/client/dist/tier/index.js +1 -5
  46. package/packages/client/dist/tier/tier-aware-client.js +1 -260
  47. package/packages/client/dist/types/index.js +1 -38
  48. package/targets-stubs/antigravity/gemini.md +1 -1
  49. package/targets-stubs/antigravity/install-antigravity.sh +49 -3
  50. package/targets-stubs/antigravity/skill/SKILL.md +23 -4
  51. package/targets-stubs/claude-code/neocortex.agent.yaml +19 -1
  52. package/targets-stubs/claude-code/neocortex.md +64 -29
  53. package/targets-stubs/codex/agents.md +20 -3
  54. package/targets-stubs/codex/config-mcp.toml +5 -0
  55. package/targets-stubs/cursor/agent.md +23 -5
  56. package/targets-stubs/cursor/install-cursor.sh +51 -3
  57. package/targets-stubs/cursor/mcp.json +7 -0
  58. package/targets-stubs/gemini-cli/agent.md +37 -6
  59. package/targets-stubs/gemini-cli/install-gemini.sh +50 -17
  60. package/targets-stubs/vscode/agent.md +47 -10
  61. package/targets-stubs/vscode/install-vscode.sh +50 -3
  62. package/targets-stubs/vscode/mcp.json +8 -0
@@ -1,58 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * @license FSL-1.1
4
- * Copyright (c) 2026 OrNexus AI
5
- *
6
- * This file is part of Neocortex CLI, licensed under the
7
- * Functional Source License, Version 1.1 (FSL-1.1).
8
- *
9
- * Change Date: February 20, 2029
10
- * Change License: MIT
11
- *
12
- * See the LICENSE file in the project root for full license text.
13
- */
14
- /**
15
- * @neocortex/client - CLI Entry Point
16
- *
17
- * Routes subcommands to their respective handlers:
18
- * neocortex-client invoke --args "..."
19
- * neocortex-client activate YOUR-LICENSE-KEY
20
- * neocortex-client cache-status
21
- * neocortex-client --help
22
- * neocortex-client --version
23
- *
24
- * Story 46.2 - AC1-AC7
25
- */
26
- import { readFileSync } from 'node:fs';
27
- import { fileURLToPath } from 'node:url';
28
- import { dirname, join } from 'node:path';
29
- // ── Version Resolution ─────────────────────────────────────────────────────
30
- function getVersion() {
31
- try {
32
- // Walk up from dist/cli.js to find the root package.json
33
- const thisFile = fileURLToPath(import.meta.url);
34
- let dir = dirname(thisFile);
35
- // Try up to 5 levels
36
- for (let i = 0; i < 5; i++) {
37
- try {
38
- const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
39
- if (pkg.name === '@ornexus/neocortex' || pkg.name === '@neocortex/client') {
40
- return pkg.version ?? '0.0.0';
41
- }
42
- }
43
- catch {
44
- // no package.json at this level
45
- }
46
- dir = dirname(dir);
47
- }
48
- }
49
- catch {
50
- // fallback
51
- }
52
- return '0.0.0';
53
- }
54
- // ── Help Text ──────────────────────────────────────────────────────────────
55
- const HELP_TEXT = `
2
+ import{readFileSync as m}from"node:fs";import{fileURLToPath as u}from"node:url";import{dirname as l,join as g}from"node:path";function f(){try{const t=u(import.meta.url);let o=l(t);for(let s=0;s<5;s++){try{const n=JSON.parse(m(g(o,"package.json"),"utf-8"));if(n.name==="@ornexus/neocortex"||n.name==="@neocortex/client")return n.version??"0.0.0"}catch{}o=l(o)}}catch{}return"0.0.0"}const p=`
56
3
  neocortex-client - Neocortex Client CLI
57
4
 
58
5
  Usage:
@@ -83,112 +30,4 @@ Examples:
83
30
  neocortex-client invoke --args "*yolo @docs/stories/1.1.story.md" --format json
84
31
  neocortex-client activate NX-PRO-ABC-123
85
32
  neocortex-client cache-status
86
- `.trim();
87
- // ── Main ───────────────────────────────────────────────────────────────────
88
- // ── npm Lifecycle Guard ──────────────────────────────────────────────────
89
- // Epic 67 - Story 67.2: On Windows, npm lifecycle can invoke bin entries
90
- // with stray arguments like "pm", "install", "postinstall" etc.
91
- // Silently ignore these to avoid "Unknown command" warnings during npm install.
92
- const NPM_LIFECYCLE_ARGS = new Set([
93
- 'pm', 'install', 'postinstall', 'preinstall', 'prepublish',
94
- 'postpublish', 'preuninstall', 'postuninstall', 'preprepare',
95
- 'prepare', 'postprepare',
96
- ]);
97
- async function main() {
98
- const args = process.argv.slice(2);
99
- const command = args[0];
100
- // Silently exit if invoked with npm lifecycle arguments (Epic 67 - Story 67.2)
101
- if (command && NPM_LIFECYCLE_ARGS.has(command.toLowerCase())) {
102
- process.exit(0);
103
- }
104
- if (!command || command === '--help' || command === '-h') {
105
- console.log(HELP_TEXT);
106
- process.exit(0);
107
- }
108
- if (command === '--version' || command === '-v') {
109
- console.log(getVersion());
110
- process.exit(0);
111
- }
112
- const subArgs = args.slice(1);
113
- switch (command) {
114
- case 'invoke': {
115
- const { invokeCliHandler } = await import('./commands/invoke.js');
116
- const exitCode = await invokeCliHandler(subArgs);
117
- process.exit(exitCode);
118
- break;
119
- }
120
- case 'activate': {
121
- const { activate } = await import('./commands/activate.js');
122
- // Parse activate args
123
- let licenseKey;
124
- let serverUrl;
125
- let nonInteractive = false;
126
- for (let i = 0; i < subArgs.length; i++) {
127
- const arg = subArgs[i];
128
- if (arg === '--server-url') {
129
- serverUrl = subArgs[++i];
130
- }
131
- else if (arg === '--non-interactive') {
132
- nonInteractive = true;
133
- }
134
- else if (arg === '--help' || arg === '-h') {
135
- console.log(HELP_TEXT);
136
- process.exit(0);
137
- }
138
- else if (!arg.startsWith('-')) {
139
- licenseKey = arg;
140
- }
141
- }
142
- if (!licenseKey) {
143
- console.error('Error: License key is required.');
144
- console.error('Usage: neocortex-client activate <license-key>');
145
- console.error('Example: neocortex-client activate NX-PRO-ABC-123');
146
- process.exit(1);
147
- }
148
- try {
149
- const result = await activate({
150
- licenseKey,
151
- serverUrl,
152
- nonInteractive,
153
- });
154
- if (result.success) {
155
- console.log(result.message);
156
- if (result.tier) {
157
- console.log(`Tier: ${result.tier}`);
158
- }
159
- if (result.configPath) {
160
- console.log(`Config: ${result.configPath}`);
161
- }
162
- process.exit(0);
163
- }
164
- else {
165
- console.error(result.message);
166
- process.exit(1);
167
- }
168
- }
169
- catch (err) {
170
- const message = err instanceof Error ? err.message : String(err);
171
- console.error(`Activation failed: ${message}`);
172
- process.exit(1);
173
- }
174
- break;
175
- }
176
- case 'cache-status': {
177
- // cache-status requires initialized circuit breaker and telemetry queue.
178
- // For CLI use, we provide a simplified status report.
179
- console.log('Cache status requires an active client session.');
180
- console.log('Use this command within a running Neocortex client context.');
181
- process.exit(0);
182
- break;
183
- }
184
- default: {
185
- console.error(`Unknown command: ${command}`);
186
- console.error('Run "neocortex-client --help" for usage information.');
187
- process.exit(1);
188
- }
189
- }
190
- }
191
- main().catch((err) => {
192
- console.error(`Fatal error: ${err instanceof Error ? err.message : String(err)}`);
193
- process.exit(1);
194
- });
33
+ `.trim(),v=new Set(["pm","install","postinstall","preinstall","prepublish","postpublish","preuninstall","postuninstall","preprepare","prepare","postprepare"]);async function h(){const t=process.argv.slice(2),o=t[0];o&&v.has(o.toLowerCase())&&process.exit(0),(!o||o==="--help"||o==="-h")&&(console.log(p),process.exit(0)),(o==="--version"||o==="-v")&&(console.log(f()),process.exit(0));const s=t.slice(1);switch(o){case"invoke":{const{invokeCliHandler:n}=await import("./commands/invoke.js"),i=await n(s);process.exit(i);break}case"activate":{const{activate:n}=await import("./commands/activate.js");let i,c,a=!1;for(let e=0;e<s.length;e++){const r=s[e];r==="--server-url"?c=s[++e]:r==="--non-interactive"?a=!0:r==="--help"||r==="-h"?(console.log(p),process.exit(0)):r.startsWith("-")||(i=r)}i||(console.error("Error: License key is required."),console.error("Usage: neocortex-client activate <license-key>"),console.error("Example: neocortex-client activate NX-PRO-ABC-123"),process.exit(1));try{const e=await n({licenseKey:i,serverUrl:c,nonInteractive:a});e.success?(console.log(e.message),e.tier&&console.log(`Tier: ${e.tier}`),e.configPath&&console.log(`Config: ${e.configPath}`),process.exit(0)):(console.error(e.message),process.exit(1))}catch(e){const r=e instanceof Error?e.message:String(e);console.error(`Activation failed: ${r}`),process.exit(1)}break}case"cache-status":{console.log("Cache status requires an active client session."),console.log("Use this command within a running Neocortex client context."),process.exit(0);break}default:console.error(`Unknown command: ${o}`),console.error('Run "neocortex-client --help" for usage information.'),process.exit(1)}}h().catch(t=>{console.error(`Fatal error: ${t instanceof Error?t.message:String(t)}`),process.exit(1)});
@@ -1,390 +1,8 @@
1
- /**
2
- * @license FSL-1.1
3
- * Copyright (c) 2026 OrNexus AI
4
- *
5
- * This file is part of Neocortex CLI, licensed under the
6
- * Functional Source License, Version 1.1 (FSL-1.1).
7
- *
8
- * Change Date: February 20, 2029
9
- * Change License: MIT
10
- *
11
- * See the LICENSE file in the project root for full license text.
12
- */
13
- /**
14
- * @neocortex/client - Activate Command
15
- *
16
- * Manages license activation for the Neocortex CLI.
17
- * Validates license key with the IP Protection Server,
18
- * caches JWT token, and creates user config file.
19
- *
20
- * Story 43.3 - AC1, AC3, AC4, AC5
21
- */
22
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
23
- import { join, dirname } from 'node:path';
24
- import { homedir } from 'node:os';
25
- import { fileURLToPath } from 'node:url';
26
- import { LicenseClient } from '../license/license-client.js';
27
- import { EncryptedCache } from '../cache/encrypted-cache.js';
28
- import { getMachineFingerprint } from '../machine/fingerprint.js';
29
- import { updateAgentDescription } from '../agent/update-description.js';
30
- import { refreshStubs } from '../agent/refresh-stubs.js';
31
- import { updateAgentYaml } from '../agent/update-agent-yaml.js';
32
- import { saveSecureConfig, setSecureDirPermissions } from '../config/secure-config.js';
33
- import { DEFAULT_SERVER_URL } from '../constants.js';
34
- // ── Version Resolution ────────────────────────────────────────────────────
35
- function getInstalledVersion() {
36
- try {
37
- const thisFile = fileURLToPath(import.meta.url);
38
- let dir = dirname(thisFile);
39
- for (let i = 0; i < 5; i++) {
40
- try {
41
- const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
42
- if (pkg.name === '@ornexus/neocortex' || pkg.name === '@neocortex/client') {
43
- return pkg.version ?? '0.0.0';
44
- }
45
- }
46
- catch { /* no package.json at this level */ }
47
- dir = dirname(dir);
48
- }
49
- }
50
- catch { /* fallback */ }
51
- return '0.0.0';
52
- }
53
- // ── Constants ─────────────────────────────────────────────────────────────
54
- const CONFIG_DIR = join(homedir(), '.neocortex');
55
- const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
56
- // License key format: NX-{F|P|E}-{24 hex}-{4 hex checksum}
57
- const LICENSE_KEY_PATTERN = /^NX-(F|P|E)-[a-f0-9]+-[a-f0-9]+$/i;
58
- // API key format: nxk_{24hex}_{4check} (new, P32) or nxk_{tier}_{24hex}_{4check} (legacy)
59
- const API_KEY_PATTERN = /^nxk_(?:[a-z]+_)?[a-f0-9]+_[a-f0-9]+$/;
60
- // ── Validation ────────────────────────────────────────────────────────────
61
- /**
62
- * Validate license key format.
63
- * Format: NX-{TIER}-{random}-{random}
64
- * Tiers: FREE, PRO, ENT
65
- */
66
- export function validateLicenseKeyFormat(key) {
67
- if (!key || key.trim().length === 0) {
68
- return { valid: false, error: 'License key cannot be empty' };
69
- }
70
- const trimmed = key.trim().toUpperCase();
71
- if (!trimmed.startsWith('NX-')) {
72
- return { valid: false, error: 'License key must start with "NX-"' };
73
- }
74
- if (!LICENSE_KEY_PATTERN.test(trimmed)) {
75
- return {
76
- valid: false,
77
- error: 'Invalid license key format. Expected: NX-{TIER}-{ID} (e.g., NX-PRO-ABC-123)',
78
- };
79
- }
80
- return { valid: true };
81
- }
82
- /**
83
- * Validate API key format.
84
- * Format: nxk_{tier}_{random}_{check}
85
- */
86
- export function validateApiKeyFormat(key) {
87
- if (!key || key.trim().length === 0) {
88
- return { valid: false, error: 'API key cannot be empty' };
89
- }
90
- const trimmed = key.trim();
91
- if (!trimmed.startsWith('nxk_')) {
92
- return { valid: false, error: 'API key must start with "nxk_"' };
93
- }
94
- if (trimmed.length < 20) {
95
- return { valid: false, error: 'API key is too short' };
96
- }
97
- if (!API_KEY_PATTERN.test(trimmed)) {
98
- return {
99
- valid: false,
100
- error: 'Invalid API key format. Expected: nxk_{id}_{check} (e.g., nxk_abc123def456...)',
101
- };
102
- }
103
- return { valid: true };
104
- }
105
- // ── Config Management ─────────────────────────────────────────────────────
106
- /**
107
- * Load existing user config, if any.
108
- */
109
- function loadExistingConfig() {
110
- try {
111
- if (existsSync(CONFIG_FILE)) {
112
- const raw = readFileSync(CONFIG_FILE, 'utf-8');
113
- return JSON.parse(raw);
114
- }
115
- }
116
- catch {
117
- // Corrupted config - will be overwritten
118
- }
119
- return null;
120
- }
121
- /**
122
- * Save user config after successful activation.
123
- * License key is encrypted using machine fingerprint (Story 61.2).
124
- * File permissions set to 600 (Story 61.4).
125
- */
126
- function saveConfig(config) {
127
- // Ensure directories exist with secure permissions
128
- mkdirSync(CONFIG_DIR, { recursive: true });
129
- setSecureDirPermissions(CONFIG_DIR);
130
- const cacheDir = join(CONFIG_DIR, 'cache');
131
- mkdirSync(cacheDir, { recursive: true });
132
- setSecureDirPermissions(cacheDir);
133
- if (config.licenseKey || config.apiKey) {
134
- // Use secure config writer which encrypts the key
135
- saveSecureConfig({
136
- serverUrl: config.serverUrl,
137
- mode: config.mode,
138
- machineId: config.machineId,
139
- activatedAt: config.activatedAt,
140
- tier: config.tier,
141
- licenseKey: (config.licenseKey ?? config.apiKey),
142
- });
143
- }
144
- else {
145
- // Fallback: write without key (should not happen in normal flow)
146
- const fallbackConfig = { configVersion: 1, ...config };
147
- writeFileSync(CONFIG_FILE, JSON.stringify(fallbackConfig, null, 2) + '\n', 'utf-8');
148
- }
149
- }
150
- // ── Activate Command ──────────────────────────────────────────────────────
151
- /**
152
- * Activate a Neocortex license.
153
- *
154
- * Flow:
155
- * 1. Validate license key format
156
- * 2. Call IP Protection Server to activate
157
- * 3. Cache JWT token via LicenseClient
158
- * 4. Create/update ~/.neocortex/config.json
159
- * 5. Return result with feedback
160
- */
161
- export async function activate(options) {
162
- const { licenseKey, serverUrl = DEFAULT_SERVER_URL } = options;
163
- // 1. Check if key is provided
164
- if (!licenseKey) {
165
- return {
166
- success: false,
167
- message: [
168
- 'No key provided.',
169
- '',
170
- 'Get your license key at https://neocortex.ornexus.com/login',
171
- '',
172
- 'Usage:',
173
- ' neocortex activate YOUR-LICENSE-KEY',
174
- ' neocortex activate YOUR-API-KEY',
175
- ].join('\n'),
176
- };
177
- }
178
- const trimmedKey = licenseKey.trim();
179
- // 2. Detect key type: API key (nxk_) or license key (NX-)
180
- const isApiKey = trimmedKey.startsWith('nxk_');
181
- if (isApiKey) {
182
- // ── API Key Activation Flow (Story 22.04) ──────────────────────────
183
- const apiValidation = validateApiKeyFormat(trimmedKey);
184
- if (!apiValidation.valid) {
185
- return {
186
- success: false,
187
- message: `Invalid API key: ${apiValidation.error}`,
188
- };
189
- }
190
- // Extract tier from API key -- new format has no tier prefix, server returns tier in response
191
- const tierMatch = trimmedKey.match(/^nxk_(free|pro|ent)_/);
192
- const apiTierMap = { free: 'free', pro: 'pro', ent: 'enterprise' };
193
- const tier = tierMatch?.[1] ? (apiTierMap[tierMatch[1]] ?? 'unknown') : 'unknown'; // will be overridden by server response
194
- const machineId = getMachineFingerprint();
195
- const cliVersion = getInstalledVersion();
196
- // Call /api/v1/license/activate-key
197
- let response;
198
- try {
199
- const res = await fetch(`${serverUrl}/api/v1/license/activate-key`, {
200
- method: 'POST',
201
- headers: { 'Content-Type': 'application/json' },
202
- body: JSON.stringify({
203
- api_key: trimmedKey,
204
- machine_id: machineId,
205
- client_version: cliVersion,
206
- }),
207
- });
208
- if (!res.ok) {
209
- const body = await res.json().catch(() => ({}));
210
- const errorBody = body;
211
- return {
212
- success: false,
213
- message: `API key activation failed: ${errorBody.message ?? `HTTP ${res.status}`}`,
214
- };
215
- }
216
- response = await res.json();
217
- }
218
- catch {
219
- return {
220
- success: false,
221
- message: [
222
- 'Failed to connect to IP Protection Server.',
223
- '',
224
- 'Possible causes:',
225
- ` - Server at ${serverUrl} is unreachable`,
226
- ' - Network connectivity issue',
227
- '',
228
- 'Workaround: Use --local flag to run in local mode',
229
- ].join('\n'),
230
- };
231
- }
232
- const activatedTier = response.tier ?? tier;
233
- // Cache JWT token via EncryptedCache
234
- const cacheDir = join(CONFIG_DIR, 'cache');
235
- try {
236
- const encryptedCache = new EncryptedCache({
237
- cacheDir,
238
- passphrase: trimmedKey,
239
- });
240
- await encryptedCache.set('neocortex:jwt:token', response.token, response.expires_in * 1000);
241
- }
242
- catch {
243
- // Cache is non-critical (fail-open)
244
- }
245
- // Save config with API key
246
- const config = {
247
- serverUrl,
248
- mode: 'remote',
249
- machineId,
250
- activatedAt: new Date().toISOString(),
251
- tier: activatedTier,
252
- apiKey: trimmedKey,
253
- };
254
- try {
255
- saveConfig(config);
256
- }
257
- catch (err) {
258
- return {
259
- success: false,
260
- message: `Failed to save configuration: ${err instanceof Error ? err.message : 'unknown error'}`,
261
- };
262
- }
263
- // Refresh stubs and agent description
264
- const stubsRefreshed = refreshStubs(cliVersion, { forceCreate: true, targetFilter: 'claude-code' });
265
- refreshStubs(cliVersion);
266
- updateAgentDescription(cliVersion, activatedTier);
267
- updateAgentYaml(cliVersion, activatedTier);
268
- return {
269
- success: true,
270
- message: [
271
- 'API key activated successfully!',
272
- '',
273
- ` Tier: ${activatedTier}`,
274
- ` Server: ${serverUrl}`,
275
- ` Expires: ${Math.floor(response.expires_in / 3600)}h`,
276
- ...(stubsRefreshed > 0 ? [' Agents: updated'] : []),
277
- '',
278
- 'You can now use Neocortex. Run *menu to get started.',
279
- ].join('\n'),
280
- tier: activatedTier,
281
- serverUrl,
282
- configPath: CONFIG_FILE,
283
- };
284
- }
285
- // ── License Key Activation Flow (existing) ──────────────────────────────
286
- const validation = validateLicenseKeyFormat(trimmedKey);
287
- if (!validation.valid) {
288
- return {
289
- success: false,
290
- message: `Invalid license key: ${validation.error}`,
291
- };
292
- }
293
- const normalizedKey = trimmedKey;
294
- // Extract tier from key
295
- const tierMatch = normalizedKey.match(/^NX-(F|P|E)-/i);
296
- const tierMap = { F: 'free', P: 'pro', E: 'enterprise' };
297
- const tier = tierMatch?.[1] ? (tierMap[tierMatch[1].toUpperCase()] ?? 'unknown') : 'unknown';
298
- // Attempt activation via LicenseClient with EncryptedCache
299
- const cacheDir = join(CONFIG_DIR, 'cache');
300
- const encryptedCache = new EncryptedCache({
301
- cacheDir,
302
- passphrase: normalizedKey,
303
- });
304
- const client = new LicenseClient({
305
- serverUrl,
306
- licenseKey: normalizedKey,
307
- cacheProvider: encryptedCache,
308
- });
309
- let activationResult;
310
- try {
311
- activationResult = await client.activate();
312
- }
313
- catch {
314
- return {
315
- success: false,
316
- message: [
317
- 'Failed to connect to IP Protection Server.',
318
- '',
319
- 'Possible causes:',
320
- ` - Server at ${serverUrl} is unreachable`,
321
- ' - Network connectivity issue',
322
- ' - Server is still being deployed',
323
- '',
324
- 'Workaround: Use --local flag to run in local mode',
325
- ].join('\n'),
326
- };
327
- }
328
- if (!activationResult) {
329
- return {
330
- success: false,
331
- message: [
332
- 'License activation failed.',
333
- '',
334
- 'Possible causes:',
335
- ' - Invalid or expired license key',
336
- ' - License already activated on another machine',
337
- ' - Server rejected the activation request',
338
- '',
339
- 'Check your license key and try again.',
340
- 'Contact support if the problem persists.',
341
- ].join('\n'),
342
- };
343
- }
344
- // Save config with license key for invoke re-authentication
345
- const config = {
346
- serverUrl,
347
- mode: 'remote',
348
- machineId: getMachineFingerprint(),
349
- activatedAt: new Date().toISOString(),
350
- tier,
351
- licenseKey: normalizedKey,
352
- };
353
- try {
354
- saveConfig(config);
355
- }
356
- catch (err) {
357
- return {
358
- success: false,
359
- message: `Failed to save config to ${CONFIG_FILE}: ${err instanceof Error ? err.message : String(err)}`,
360
- };
361
- }
362
- // Refresh stubs if version mismatch detected (Story 55.1)
363
- const cliVersion = getInstalledVersion();
364
- const stubsRefreshed = refreshStubs(cliVersion, {
365
- forceCreate: true,
366
- targetFilter: 'claude-code',
367
- });
368
- // Also refresh other existing targets (without forceCreate)
369
- refreshStubs(cliVersion);
370
- // Update agent description with activated tier and version
371
- updateAgentDescription(cliVersion, tier);
372
- // Update agent YAML with version and tier (Story 55.2)
373
- updateAgentYaml(cliVersion, tier);
374
- return {
375
- success: true,
376
- message: [
377
- `License activated successfully!`,
378
- '',
379
- ` Tier: ${tier}`,
380
- ` Server: ${serverUrl}`,
381
- ` Expires: ${Math.floor(activationResult.expiresIn / 3600)}h`,
382
- ...(stubsRefreshed > 0 ? [' Agents: updated'] : []),
383
- '',
384
- 'You can now use Neocortex. Run *menu to get started.',
385
- ].join('\n'),
386
- tier,
387
- serverUrl,
388
- configPath: CONFIG_FILE,
389
- };
390
- }
1
+ import{existsSync as M,mkdirSync as x,writeFileSync as V,readFileSync as P}from"node:fs";import{join as o,dirname as S}from"node:path";import{homedir as X}from"node:os";import{fileURLToPath as W}from"node:url";import{LicenseClient as J}from"../license/license-client.js";import{EncryptedCache as E}from"../cache/encrypted-cache.js";import{getMachineFingerprint as w}from"../machine/fingerprint.js";import{updateAgentDescription as A}from"../agent/update-description.js";import{refreshStubs as h}from"../agent/refresh-stubs.js";import{updateAgentYaml as _}from"../agent/update-agent-yaml.js";import{saveSecureConfig as B,setSecureDirPermissions as C}from"../config/secure-config.js";import{DEFAULT_SERVER_URL as G}from"../constants.js";function N(){try{const e=W(import.meta.url);let t=S(e);for(let r=0;r<5;r++){try{const n=JSON.parse(P(o(t,"package.json"),"utf-8"));if(n.name==="@ornexus/neocortex"||n.name==="@neocortex/client")return n.version??"0.0.0"}catch{}t=S(t)}}catch{}return"0.0.0"}const a=o(X(),".neocortex"),c=o(a,"config.json"),z=/^NX-(F|P|E)-[a-f0-9]+-[a-f0-9]+$/i,q=/^nxk_(?:[a-z]+_)?[a-f0-9]+_[a-f0-9]+$/;function H(e){if(!e||e.trim().length===0)return{valid:!1,error:"License key cannot be empty"};const t=e.trim().toUpperCase();return t.startsWith("NX-")?z.test(t)?{valid:!0}:{valid:!1,error:"Invalid license key format. Expected: NX-{TIER}-{ID} (e.g., NX-PRO-ABC-123)"}:{valid:!1,error:'License key must start with "NX-"'}}function Q(e){if(!e||e.trim().length===0)return{valid:!1,error:"API key cannot be empty"};const t=e.trim();return t.startsWith("nxk_")?t.length<20?{valid:!1,error:"API key is too short"}:q.test(t)?{valid:!0}:{valid:!1,error:"Invalid API key format. Expected: nxk_{id}_{check} (e.g., nxk_abc123def456...)"}:{valid:!1,error:'API key must start with "nxk_"'}}function pe(){try{if(M(c)){const e=P(c,"utf-8");return JSON.parse(e)}}catch{}return null}function F(e){x(a,{recursive:!0}),C(a);const t=o(a,"cache");if(x(t,{recursive:!0}),C(t),e.licenseKey||e.apiKey)B({serverUrl:e.serverUrl,mode:e.mode,machineId:e.machineId,activatedAt:e.activatedAt,tier:e.tier,licenseKey:e.licenseKey??e.apiKey});else{const r={configVersion:1,...e};V(c,JSON.stringify(r,null,2)+`
2
+ `,"utf-8")}}async function he(e){const{licenseKey:t,serverUrl:r=G}=e;if(!t)return{success:!1,message:["No key provided.","","Get your license key at https://neocortex.ornexus.com/login","","Usage:"," neocortex activate YOUR-LICENSE-KEY"," neocortex activate YOUR-API-KEY"].join(`
3
+ `)};const n=t.trim();if(n.startsWith("nxk_")){const i=Q(n);if(!i.valid)return{success:!1,message:`Invalid API key: ${i.error}`};const k=n.match(/^nxk_(free|pro|ent)_/),L={free:"free",pro:"pro",ent:"enterprise"},U=k?.[1]?L[k[1]]??"unknown":"unknown",I=w(),u=N();let d;try{const s=await fetch(`${r}/api/v1/license/activate-key`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({api_key:n,machine_id:I,client_version:u})});if(!s.ok)return{success:!1,message:`API key activation failed: ${(await s.json().catch(()=>({}))).message??`HTTP ${s.status}`}`};d=await s.json()}catch{return{success:!1,message:["Failed to connect to IP Protection Server.","","Possible causes:",` - Server at ${r} is unreachable`," - Network connectivity issue","","Workaround: Use --local flag to run in local mode"].join(`
4
+ `)}}const f=d.tier??U,O=o(a,"cache");try{await new E({cacheDir:O,passphrase:n}).set("neocortex:jwt:token",d.token,d.expires_in*1e3)}catch{}const D={serverUrl:r,mode:"remote",machineId:I,activatedAt:new Date().toISOString(),tier:f,apiKey:n};try{F(D)}catch(s){return{success:!1,message:`Failed to save configuration: ${s instanceof Error?s.message:"unknown error"}`}}const Y=h(u,{forceCreate:!0,targetFilter:"claude-code"});return h(u),A(u,f),_(u,f),{success:!0,message:["API key activated successfully!","",` Tier: ${f}`,` Server: ${r}`,` Expires: ${Math.floor(d.expires_in/3600)}h`,...Y>0?[" Agents: updated"]:[],"","You can now use Neocortex. Run *menu to get started."].join(`
5
+ `),tier:f,serverUrl:r,configPath:c}}const v=H(n);if(!v.valid)return{success:!1,message:`Invalid license key: ${v.error}`};const m=n,g=m.match(/^NX-(F|P|E)-/i),K={F:"free",P:"pro",E:"enterprise"},l=g?.[1]?K[g[1].toUpperCase()]??"unknown":"unknown",$=o(a,"cache"),b=new E({cacheDir:$,passphrase:m}),R=new J({serverUrl:r,licenseKey:m,cacheProvider:b});let y;try{y=await R.activate()}catch{return{success:!1,message:["Failed to connect to IP Protection Server.","","Possible causes:",` - Server at ${r} is unreachable`," - Network connectivity issue"," - Server is still being deployed","","Workaround: Use --local flag to run in local mode"].join(`
6
+ `)}}if(!y)return{success:!1,message:["License activation failed.","","Possible causes:"," - Invalid or expired license key"," - License already activated on another machine"," - Server rejected the activation request","","Check your license key and try again.","Contact support if the problem persists."].join(`
7
+ `)};const T={serverUrl:r,mode:"remote",machineId:w(),activatedAt:new Date().toISOString(),tier:l,licenseKey:m};try{F(T)}catch(i){return{success:!1,message:`Failed to save config to ${c}: ${i instanceof Error?i.message:String(i)}`}}const p=N(),j=h(p,{forceCreate:!0,targetFilter:"claude-code"});return h(p),A(p,l),_(p,l),{success:!0,message:["License activated successfully!","",` Tier: ${l}`,` Server: ${r}`,` Expires: ${Math.floor(y.expiresIn/3600)}h`,...j>0?[" Agents: updated"]:[],"","You can now use Neocortex. Run *menu to get started."].join(`
8
+ `),tier:l,serverUrl:r,configPath:c}}export{he as activate,Q as validateApiKeyFormat,H as validateLicenseKeyFormat};