@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.
- package/install.ps1 +92 -33
- package/install.sh +15 -1
- package/package.json +3 -3
- package/packages/client/dist/adapters/adapter-registry.js +1 -106
- package/packages/client/dist/adapters/antigravity-adapter.js +2 -77
- package/packages/client/dist/adapters/claude-code-adapter.js +3 -79
- package/packages/client/dist/adapters/codex-adapter.js +2 -80
- package/packages/client/dist/adapters/cursor-adapter.js +4 -115
- package/packages/client/dist/adapters/gemini-adapter.js +2 -71
- package/packages/client/dist/adapters/index.js +1 -21
- package/packages/client/dist/adapters/platform-detector.js +1 -106
- package/packages/client/dist/adapters/target-adapter.js +0 -12
- package/packages/client/dist/adapters/vscode-adapter.js +2 -72
- package/packages/client/dist/agent/refresh-stubs.js +2 -234
- package/packages/client/dist/agent/update-agent-yaml.js +1 -102
- package/packages/client/dist/agent/update-description.js +1 -251
- package/packages/client/dist/cache/crypto-utils.js +1 -76
- package/packages/client/dist/cache/encrypted-cache.js +1 -94
- package/packages/client/dist/cache/in-memory-asset-cache.js +1 -70
- package/packages/client/dist/cache/index.js +1 -13
- package/packages/client/dist/cli.js +2 -163
- package/packages/client/dist/commands/activate.js +8 -390
- package/packages/client/dist/commands/cache-status.js +2 -112
- package/packages/client/dist/commands/invoke.js +28 -490
- package/packages/client/dist/config/resolver-selection.js +1 -278
- package/packages/client/dist/config/secure-config.js +12 -269
- package/packages/client/dist/constants.js +1 -25
- package/packages/client/dist/context/context-collector.js +2 -222
- package/packages/client/dist/context/context-sanitizer.js +1 -145
- package/packages/client/dist/index.js +1 -38
- package/packages/client/dist/license/index.js +1 -5
- package/packages/client/dist/license/license-client.js +1 -257
- package/packages/client/dist/machine/fingerprint.js +2 -160
- package/packages/client/dist/machine/index.js +1 -5
- package/packages/client/dist/resilience/circuit-breaker.js +1 -170
- package/packages/client/dist/resilience/degradation-manager.js +1 -164
- package/packages/client/dist/resilience/freshness-indicator.js +1 -100
- package/packages/client/dist/resilience/index.js +1 -8
- package/packages/client/dist/resilience/recovery-detector.js +1 -74
- package/packages/client/dist/resolvers/asset-resolver.js +0 -13
- package/packages/client/dist/resolvers/local-resolver.js +8 -218
- package/packages/client/dist/resolvers/remote-resolver.js +1 -282
- package/packages/client/dist/telemetry/index.js +1 -5
- package/packages/client/dist/telemetry/offline-queue.js +1 -131
- package/packages/client/dist/tier/index.js +1 -5
- package/packages/client/dist/tier/tier-aware-client.js +1 -260
- package/packages/client/dist/types/index.js +1 -38
- package/targets-stubs/antigravity/gemini.md +1 -1
- package/targets-stubs/antigravity/install-antigravity.sh +49 -3
- package/targets-stubs/antigravity/skill/SKILL.md +23 -4
- package/targets-stubs/claude-code/neocortex.agent.yaml +19 -1
- package/targets-stubs/claude-code/neocortex.md +64 -29
- package/targets-stubs/codex/agents.md +20 -3
- package/targets-stubs/codex/config-mcp.toml +5 -0
- package/targets-stubs/cursor/agent.md +23 -5
- package/targets-stubs/cursor/install-cursor.sh +51 -3
- package/targets-stubs/cursor/mcp.json +7 -0
- package/targets-stubs/gemini-cli/agent.md +37 -6
- package/targets-stubs/gemini-cli/install-gemini.sh +50 -17
- package/targets-stubs/vscode/agent.md +47 -10
- package/targets-stubs/vscode/install-vscode.sh +50 -3
- 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
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
|
|
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};
|