@ornexus/neocortex 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +56 -0
- package/README.md +661 -0
- package/install.js +453 -0
- package/install.ps1 +1478 -0
- package/install.sh +1409 -0
- package/package.json +93 -0
- package/packages/client/dist/adapters/adapter-registry.d.ts +62 -0
- package/packages/client/dist/adapters/adapter-registry.d.ts.map +1 -0
- package/packages/client/dist/adapters/adapter-registry.js +107 -0
- package/packages/client/dist/adapters/adapter-registry.js.map +1 -0
- package/packages/client/dist/adapters/antigravity-adapter.d.ts +19 -0
- package/packages/client/dist/adapters/antigravity-adapter.d.ts.map +1 -0
- package/packages/client/dist/adapters/antigravity-adapter.js +78 -0
- package/packages/client/dist/adapters/antigravity-adapter.js.map +1 -0
- package/packages/client/dist/adapters/claude-code-adapter.d.ts +20 -0
- package/packages/client/dist/adapters/claude-code-adapter.d.ts.map +1 -0
- package/packages/client/dist/adapters/claude-code-adapter.js +80 -0
- package/packages/client/dist/adapters/claude-code-adapter.js.map +1 -0
- package/packages/client/dist/adapters/codex-adapter.d.ts +20 -0
- package/packages/client/dist/adapters/codex-adapter.d.ts.map +1 -0
- package/packages/client/dist/adapters/codex-adapter.js +81 -0
- package/packages/client/dist/adapters/codex-adapter.js.map +1 -0
- package/packages/client/dist/adapters/cursor-adapter.d.ts +20 -0
- package/packages/client/dist/adapters/cursor-adapter.d.ts.map +1 -0
- package/packages/client/dist/adapters/cursor-adapter.js +116 -0
- package/packages/client/dist/adapters/cursor-adapter.js.map +1 -0
- package/packages/client/dist/adapters/gemini-adapter.d.ts +19 -0
- package/packages/client/dist/adapters/gemini-adapter.d.ts.map +1 -0
- package/packages/client/dist/adapters/gemini-adapter.js +72 -0
- package/packages/client/dist/adapters/gemini-adapter.js.map +1 -0
- package/packages/client/dist/adapters/index.d.ts +20 -0
- package/packages/client/dist/adapters/index.d.ts.map +1 -0
- package/packages/client/dist/adapters/index.js +22 -0
- package/packages/client/dist/adapters/index.js.map +1 -0
- package/packages/client/dist/adapters/platform-detector.d.ts +47 -0
- package/packages/client/dist/adapters/platform-detector.d.ts.map +1 -0
- package/packages/client/dist/adapters/platform-detector.js +107 -0
- package/packages/client/dist/adapters/platform-detector.js.map +1 -0
- package/packages/client/dist/adapters/target-adapter.d.ts +71 -0
- package/packages/client/dist/adapters/target-adapter.d.ts.map +1 -0
- package/packages/client/dist/adapters/target-adapter.js +13 -0
- package/packages/client/dist/adapters/target-adapter.js.map +1 -0
- package/packages/client/dist/adapters/vscode-adapter.d.ts +20 -0
- package/packages/client/dist/adapters/vscode-adapter.d.ts.map +1 -0
- package/packages/client/dist/adapters/vscode-adapter.js +73 -0
- package/packages/client/dist/adapters/vscode-adapter.js.map +1 -0
- package/packages/client/dist/cache/crypto-utils.d.ts +31 -0
- package/packages/client/dist/cache/crypto-utils.d.ts.map +1 -0
- package/packages/client/dist/cache/crypto-utils.js +77 -0
- package/packages/client/dist/cache/crypto-utils.js.map +1 -0
- package/packages/client/dist/cache/encrypted-cache.d.ts +31 -0
- package/packages/client/dist/cache/encrypted-cache.d.ts.map +1 -0
- package/packages/client/dist/cache/encrypted-cache.js +92 -0
- package/packages/client/dist/cache/encrypted-cache.js.map +1 -0
- package/packages/client/dist/cache/index.d.ts +14 -0
- package/packages/client/dist/cache/index.d.ts.map +1 -0
- package/packages/client/dist/cache/index.js +14 -0
- package/packages/client/dist/cache/index.js.map +1 -0
- package/packages/client/dist/cli.d.ts +15 -0
- package/packages/client/dist/cli.d.ts.map +1 -0
- package/packages/client/dist/cli.js +182 -0
- package/packages/client/dist/cli.js.map +1 -0
- package/packages/client/dist/commands/activate.d.ts +48 -0
- package/packages/client/dist/commands/activate.d.ts.map +1 -0
- package/packages/client/dist/commands/activate.js +186 -0
- package/packages/client/dist/commands/activate.js.map +1 -0
- package/packages/client/dist/commands/cache-status.d.ts +40 -0
- package/packages/client/dist/commands/cache-status.d.ts.map +1 -0
- package/packages/client/dist/commands/cache-status.js +113 -0
- package/packages/client/dist/commands/cache-status.js.map +1 -0
- package/packages/client/dist/commands/invoke.d.ts +71 -0
- package/packages/client/dist/commands/invoke.d.ts.map +1 -0
- package/packages/client/dist/commands/invoke.js +345 -0
- package/packages/client/dist/commands/invoke.js.map +1 -0
- package/packages/client/dist/config/resolver-selection.d.ts +41 -0
- package/packages/client/dist/config/resolver-selection.d.ts.map +1 -0
- package/packages/client/dist/config/resolver-selection.js +278 -0
- package/packages/client/dist/config/resolver-selection.js.map +1 -0
- package/packages/client/dist/context/context-collector.d.ts +29 -0
- package/packages/client/dist/context/context-collector.d.ts.map +1 -0
- package/packages/client/dist/context/context-collector.js +223 -0
- package/packages/client/dist/context/context-collector.js.map +1 -0
- package/packages/client/dist/context/context-sanitizer.d.ts +29 -0
- package/packages/client/dist/context/context-sanitizer.d.ts.map +1 -0
- package/packages/client/dist/context/context-sanitizer.js +146 -0
- package/packages/client/dist/context/context-sanitizer.js.map +1 -0
- package/packages/client/dist/index.d.ts +55 -0
- package/packages/client/dist/index.d.ts.map +1 -0
- package/packages/client/dist/index.js +37 -0
- package/packages/client/dist/index.js.map +1 -0
- package/packages/client/dist/license/index.d.ts +6 -0
- package/packages/client/dist/license/index.d.ts.map +1 -0
- package/packages/client/dist/license/index.js +6 -0
- package/packages/client/dist/license/index.js.map +1 -0
- package/packages/client/dist/license/license-client.d.ts +53 -0
- package/packages/client/dist/license/license-client.d.ts.map +1 -0
- package/packages/client/dist/license/license-client.js +164 -0
- package/packages/client/dist/license/license-client.js.map +1 -0
- package/packages/client/dist/machine/fingerprint.d.ts +24 -0
- package/packages/client/dist/machine/fingerprint.d.ts.map +1 -0
- package/packages/client/dist/machine/fingerprint.js +61 -0
- package/packages/client/dist/machine/fingerprint.js.map +1 -0
- package/packages/client/dist/machine/index.d.ts +6 -0
- package/packages/client/dist/machine/index.d.ts.map +1 -0
- package/packages/client/dist/machine/index.js +6 -0
- package/packages/client/dist/machine/index.js.map +1 -0
- package/packages/client/dist/resilience/circuit-breaker.d.ts +71 -0
- package/packages/client/dist/resilience/circuit-breaker.d.ts.map +1 -0
- package/packages/client/dist/resilience/circuit-breaker.js +171 -0
- package/packages/client/dist/resilience/circuit-breaker.js.map +1 -0
- package/packages/client/dist/resilience/degradation-manager.d.ts +68 -0
- package/packages/client/dist/resilience/degradation-manager.d.ts.map +1 -0
- package/packages/client/dist/resilience/degradation-manager.js +165 -0
- package/packages/client/dist/resilience/degradation-manager.js.map +1 -0
- package/packages/client/dist/resilience/freshness-indicator.d.ts +60 -0
- package/packages/client/dist/resilience/freshness-indicator.d.ts.map +1 -0
- package/packages/client/dist/resilience/freshness-indicator.js +101 -0
- package/packages/client/dist/resilience/freshness-indicator.js.map +1 -0
- package/packages/client/dist/resilience/index.d.ts +9 -0
- package/packages/client/dist/resilience/index.d.ts.map +1 -0
- package/packages/client/dist/resilience/index.js +9 -0
- package/packages/client/dist/resilience/index.js.map +1 -0
- package/packages/client/dist/resilience/recovery-detector.d.ts +60 -0
- package/packages/client/dist/resilience/recovery-detector.d.ts.map +1 -0
- package/packages/client/dist/resilience/recovery-detector.js +75 -0
- package/packages/client/dist/resilience/recovery-detector.js.map +1 -0
- package/packages/client/dist/resolvers/asset-resolver.d.ts +80 -0
- package/packages/client/dist/resolvers/asset-resolver.d.ts.map +1 -0
- package/packages/client/dist/resolvers/asset-resolver.js +14 -0
- package/packages/client/dist/resolvers/asset-resolver.js.map +1 -0
- package/packages/client/dist/resolvers/local-resolver.d.ts +27 -0
- package/packages/client/dist/resolvers/local-resolver.d.ts.map +1 -0
- package/packages/client/dist/resolvers/local-resolver.js +219 -0
- package/packages/client/dist/resolvers/local-resolver.js.map +1 -0
- package/packages/client/dist/resolvers/remote-resolver.d.ts +63 -0
- package/packages/client/dist/resolvers/remote-resolver.d.ts.map +1 -0
- package/packages/client/dist/resolvers/remote-resolver.js +207 -0
- package/packages/client/dist/resolvers/remote-resolver.js.map +1 -0
- package/packages/client/dist/telemetry/index.d.ts +6 -0
- package/packages/client/dist/telemetry/index.d.ts.map +1 -0
- package/packages/client/dist/telemetry/index.js +6 -0
- package/packages/client/dist/telemetry/index.js.map +1 -0
- package/packages/client/dist/telemetry/offline-queue.d.ts +58 -0
- package/packages/client/dist/telemetry/offline-queue.d.ts.map +1 -0
- package/packages/client/dist/telemetry/offline-queue.js +132 -0
- package/packages/client/dist/telemetry/offline-queue.js.map +1 -0
- package/packages/client/dist/types/index.d.ts +141 -0
- package/packages/client/dist/types/index.d.ts.map +1 -0
- package/packages/client/dist/types/index.js +39 -0
- package/packages/client/dist/types/index.js.map +1 -0
- package/targets-stubs/antigravity/README.md +20 -0
- package/targets-stubs/claude-code/README.md +20 -0
- package/targets-stubs/codex/README.md +20 -0
- package/targets-stubs/cursor/README.md +20 -0
- package/targets-stubs/gemini-cli/README.md +20 -0
- package/targets-stubs/vscode/README.md +20 -0
|
@@ -0,0 +1,345 @@
|
|
|
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 - Invoke Command
|
|
15
|
+
*
|
|
16
|
+
* Primary entry point for server-side orchestration.
|
|
17
|
+
* Sends raw user args to POST /api/v1/invoke and returns
|
|
18
|
+
* complete orchestration instructions from the server.
|
|
19
|
+
*
|
|
20
|
+
* Story 45.2 - AC1-AC6
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { homedir } from 'node:os';
|
|
25
|
+
import { LicenseClient } from '../license/license-client.js';
|
|
26
|
+
// ── Constants ─────────────────────────────────────────────────────────────
|
|
27
|
+
const DEFAULT_SERVER_URL = 'https://api.neocortex.ornexus.com';
|
|
28
|
+
const CONFIG_DIR = join(homedir(), '.neocortex');
|
|
29
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
30
|
+
const CACHE_DIR = join(CONFIG_DIR, 'cache');
|
|
31
|
+
const MENU_CACHE_FILE = join(CACHE_DIR, 'menu-cache.json');
|
|
32
|
+
const MENU_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
33
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
34
|
+
// ── State Snapshot Collection ──────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* Read state.json and construct a sanitized snapshot for the server.
|
|
37
|
+
* Relative paths only - no absolute paths sent to server.
|
|
38
|
+
*/
|
|
39
|
+
export function collectStateSnapshot(projectRoot) {
|
|
40
|
+
const stateJsonPath = join(projectRoot, '.neocortex', 'state.json');
|
|
41
|
+
if (!existsSync(stateJsonPath)) {
|
|
42
|
+
// Return minimal snapshot if state.json doesn't exist
|
|
43
|
+
return {
|
|
44
|
+
config: {
|
|
45
|
+
project_name: 'unknown',
|
|
46
|
+
default_branch: 'main',
|
|
47
|
+
language: 'pt-BR',
|
|
48
|
+
},
|
|
49
|
+
stories: {},
|
|
50
|
+
epics: {},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
let stateData;
|
|
54
|
+
try {
|
|
55
|
+
const raw = readFileSync(stateJsonPath, 'utf-8');
|
|
56
|
+
stateData = JSON.parse(raw);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return {
|
|
60
|
+
config: {
|
|
61
|
+
project_name: 'unknown',
|
|
62
|
+
default_branch: 'main',
|
|
63
|
+
language: 'pt-BR',
|
|
64
|
+
},
|
|
65
|
+
stories: {},
|
|
66
|
+
epics: {},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Extract config - check both locations for compatibility
|
|
70
|
+
const config = (stateData.config ?? stateData.project ?? {});
|
|
71
|
+
// Extract stories - sanitize sensitive fields
|
|
72
|
+
const rawStories = (stateData.stories ?? {});
|
|
73
|
+
const stories = {};
|
|
74
|
+
for (const [id, story] of Object.entries(rawStories)) {
|
|
75
|
+
stories[id] = {
|
|
76
|
+
id: story.id ?? id,
|
|
77
|
+
title: story.title,
|
|
78
|
+
epic_id: story.epic_id,
|
|
79
|
+
status: story.status ?? 'backlog',
|
|
80
|
+
steps_completed: story.steps_completed ?? [],
|
|
81
|
+
last_step: story.last_step ?? null,
|
|
82
|
+
branch_name: story.branch_name ?? null,
|
|
83
|
+
pr_number: story.pr_number,
|
|
84
|
+
workflow_issue: story.workflow_issue,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Extract epics
|
|
88
|
+
const rawEpics = (stateData.epics ?? {});
|
|
89
|
+
const epics = {};
|
|
90
|
+
for (const [id, epic] of Object.entries(rawEpics)) {
|
|
91
|
+
epics[id] = {
|
|
92
|
+
id: epic.id ?? id,
|
|
93
|
+
title: epic.title,
|
|
94
|
+
status: epic.status,
|
|
95
|
+
stories: epic.stories,
|
|
96
|
+
total_stories: epic.total_stories,
|
|
97
|
+
completed_stories: epic.completed_stories,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
config: {
|
|
102
|
+
project_name: (config.project_name ?? config.name ?? 'unknown'),
|
|
103
|
+
default_branch: (config.default_branch ?? 'main'),
|
|
104
|
+
language: (config.language ?? 'pt-BR'),
|
|
105
|
+
yolo_mode: config.yolo_mode,
|
|
106
|
+
user_name: config.user_name,
|
|
107
|
+
worktree_base: config.worktree_base,
|
|
108
|
+
max_parallel_stories: config.max_parallel_stories,
|
|
109
|
+
},
|
|
110
|
+
stories,
|
|
111
|
+
epics,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
// ── Menu Cache ────────────────────────────────────────────────────────────
|
|
115
|
+
function getMenuCache() {
|
|
116
|
+
try {
|
|
117
|
+
if (!existsSync(MENU_CACHE_FILE))
|
|
118
|
+
return null;
|
|
119
|
+
const raw = readFileSync(MENU_CACHE_FILE, 'utf-8');
|
|
120
|
+
const cache = JSON.parse(raw);
|
|
121
|
+
// Check TTL
|
|
122
|
+
if (Date.now() - cache.cachedAt > MENU_CACHE_TTL_MS) {
|
|
123
|
+
return null; // Expired
|
|
124
|
+
}
|
|
125
|
+
return cache;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function setMenuCache(instructions, metadata) {
|
|
132
|
+
try {
|
|
133
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
134
|
+
const cache = {
|
|
135
|
+
instructions,
|
|
136
|
+
metadata,
|
|
137
|
+
cachedAt: Date.now(),
|
|
138
|
+
};
|
|
139
|
+
writeFileSync(MENU_CACHE_FILE, JSON.stringify(cache), 'utf-8');
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Cache write failure is non-critical
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// ── Config Loading ────────────────────────────────────────────────────────
|
|
146
|
+
function loadConfig() {
|
|
147
|
+
try {
|
|
148
|
+
if (existsSync(CONFIG_FILE)) {
|
|
149
|
+
const raw = readFileSync(CONFIG_FILE, 'utf-8');
|
|
150
|
+
return JSON.parse(raw);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Ignore
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
// ── Auth Token ────────────────────────────────────────────────────────────
|
|
159
|
+
async function getAuthToken(serverUrl) {
|
|
160
|
+
try {
|
|
161
|
+
const client = new LicenseClient({ serverUrl, licenseKey: '' });
|
|
162
|
+
const token = await client.getToken();
|
|
163
|
+
return token;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// ── HTTP Request ──────────────────────────────────────────────────────────
|
|
170
|
+
async function sendInvokeRequest(serverUrl, body, authToken) {
|
|
171
|
+
const url = `${serverUrl}/api/v1/invoke`;
|
|
172
|
+
const controller = new AbortController();
|
|
173
|
+
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
174
|
+
try {
|
|
175
|
+
const response = await fetch(url, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: {
|
|
178
|
+
'Content-Type': 'application/json',
|
|
179
|
+
'Authorization': `Bearer ${authToken}`,
|
|
180
|
+
'X-Client-Version': '0.1.0',
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify(body),
|
|
183
|
+
signal: controller.signal,
|
|
184
|
+
});
|
|
185
|
+
clearTimeout(timeoutId);
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
const errorText = await response.text().catch(() => 'Unknown error');
|
|
188
|
+
let errorJson;
|
|
189
|
+
try {
|
|
190
|
+
errorJson = JSON.parse(errorText);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Not JSON
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
status: response.status,
|
|
198
|
+
error: errorJson
|
|
199
|
+
? `${errorJson.error_code ?? 'ERROR'}: ${errorJson.message ?? errorText}`
|
|
200
|
+
: `HTTP ${response.status}: ${errorText}`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const data = (await response.json());
|
|
204
|
+
return { ok: true, status: response.status, data };
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
clearTimeout(timeoutId);
|
|
208
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
status: 0,
|
|
212
|
+
error: message.includes('abort')
|
|
213
|
+
? `Request timeout after ${DEFAULT_TIMEOUT_MS / 1000}s`
|
|
214
|
+
: `Network error: ${message}`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// ── Main Invoke Function ──────────────────────────────────────────────────
|
|
219
|
+
/**
|
|
220
|
+
* Execute the invoke command.
|
|
221
|
+
*
|
|
222
|
+
* Flow:
|
|
223
|
+
* 1. Load config to get server URL
|
|
224
|
+
* 2. Collect state snapshot from project root
|
|
225
|
+
* 3. Check menu cache for empty invocations
|
|
226
|
+
* 4. Send POST /api/v1/invoke
|
|
227
|
+
* 5. Format and return result
|
|
228
|
+
*/
|
|
229
|
+
export async function invoke(options) {
|
|
230
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
231
|
+
const format = options.format ?? 'plain';
|
|
232
|
+
const platformTarget = options.platformTarget ?? 'claude-code';
|
|
233
|
+
// 1. Determine server URL
|
|
234
|
+
const config = loadConfig();
|
|
235
|
+
const serverUrl = (options.serverUrl ?? config?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, '');
|
|
236
|
+
// 2. Collect state snapshot
|
|
237
|
+
const stateSnapshot = collectStateSnapshot(projectRoot);
|
|
238
|
+
// 3. Check menu cache for empty invocations (AC6)
|
|
239
|
+
const trimmedArgs = options.args.trim();
|
|
240
|
+
if (!trimmedArgs) {
|
|
241
|
+
const cachedMenu = getMenuCache();
|
|
242
|
+
if (cachedMenu) {
|
|
243
|
+
return {
|
|
244
|
+
success: true,
|
|
245
|
+
instructions: cachedMenu.instructions,
|
|
246
|
+
metadata: cachedMenu.metadata,
|
|
247
|
+
exitCode: 0,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// 4. Get auth token
|
|
252
|
+
const authToken = await getAuthToken(serverUrl);
|
|
253
|
+
if (!authToken) {
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
error: 'Not authenticated. Run: neocortex activate NX-PRO-xxx',
|
|
257
|
+
exitCode: 2, // Not configured
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
// 5. Send request
|
|
261
|
+
const result = await sendInvokeRequest(serverUrl, {
|
|
262
|
+
args: trimmedArgs,
|
|
263
|
+
projectRoot: projectRoot.replace(homedir(), '~'), // Sanitize absolute path
|
|
264
|
+
stateSnapshot,
|
|
265
|
+
platformTarget,
|
|
266
|
+
}, authToken);
|
|
267
|
+
if (!result.ok || !result.data) {
|
|
268
|
+
// AC5: Error exit code
|
|
269
|
+
const exitCode = result.status === 401 ? 2 :
|
|
270
|
+
result.status === 429 ? 1 :
|
|
271
|
+
result.status >= 500 ? 1 : 1;
|
|
272
|
+
return {
|
|
273
|
+
success: false,
|
|
274
|
+
error: result.error ?? 'Unknown error from server',
|
|
275
|
+
exitCode,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// 6. Cache menu responses (AC6)
|
|
279
|
+
if (!trimmedArgs && result.data.metadata?.mode === 'menu') {
|
|
280
|
+
setMenuCache(result.data.instructions, result.data.metadata);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
success: true,
|
|
284
|
+
instructions: result.data.instructions,
|
|
285
|
+
metadata: result.data.metadata,
|
|
286
|
+
exitCode: 0,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
// ── CLI Entry Point ───────────────────────────────────────────────────────
|
|
290
|
+
/**
|
|
291
|
+
* CLI handler for the invoke command.
|
|
292
|
+
* Parses CLI args and delegates to invoke().
|
|
293
|
+
*
|
|
294
|
+
* Usage:
|
|
295
|
+
* neocortex-client invoke --args "*yolo @story.md" --project-root /path
|
|
296
|
+
* neocortex-client invoke --args "*status" --format json
|
|
297
|
+
*/
|
|
298
|
+
export async function invokeCliHandler(argv) {
|
|
299
|
+
let args = '';
|
|
300
|
+
let projectRoot = process.cwd();
|
|
301
|
+
let format = 'plain';
|
|
302
|
+
let serverUrl;
|
|
303
|
+
// Parse CLI arguments
|
|
304
|
+
for (let i = 0; i < argv.length; i++) {
|
|
305
|
+
switch (argv[i]) {
|
|
306
|
+
case '--args':
|
|
307
|
+
args = argv[++i] ?? '';
|
|
308
|
+
break;
|
|
309
|
+
case '--project-root':
|
|
310
|
+
projectRoot = argv[++i] ?? process.cwd();
|
|
311
|
+
break;
|
|
312
|
+
case '--format':
|
|
313
|
+
format = (argv[++i] ?? 'plain');
|
|
314
|
+
break;
|
|
315
|
+
case '--server-url':
|
|
316
|
+
serverUrl = argv[++i];
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const result = await invoke({ args, projectRoot, format, serverUrl });
|
|
321
|
+
if (!result.success) {
|
|
322
|
+
// AC5: Error to stderr as JSON
|
|
323
|
+
process.stderr.write(JSON.stringify({
|
|
324
|
+
error_code: result.exitCode === 2 ? 'NOT_CONFIGURED' : 'INVOKE_ERROR',
|
|
325
|
+
message: result.error,
|
|
326
|
+
}) + '\n');
|
|
327
|
+
return result.exitCode;
|
|
328
|
+
}
|
|
329
|
+
if (format === 'json') {
|
|
330
|
+
// AC3: Full JSON to stdout
|
|
331
|
+
process.stdout.write(JSON.stringify({
|
|
332
|
+
instructions: result.instructions,
|
|
333
|
+
metadata: result.metadata,
|
|
334
|
+
}) + '\n');
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
// AC4: Instructions to stdout, metadata to stderr
|
|
338
|
+
process.stdout.write((result.instructions ?? '') + '\n');
|
|
339
|
+
if (result.metadata) {
|
|
340
|
+
process.stderr.write(JSON.stringify(result.metadata) + '\n');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=invoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoke.js","sourceRoot":"","sources":["../../src/commands/invoke.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
import type { AssetResolver } from '../resolvers/asset-resolver.js';
|
|
14
|
+
import type { CreateResolverOptions } from '../types/index.js';
|
|
15
|
+
/** Result of resolver selection including the reason for the choice */
|
|
16
|
+
export interface ResolverSelectionResult {
|
|
17
|
+
readonly resolver: AssetResolver;
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
readonly mode: 'local' | 'remote';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create the appropriate AssetResolver based on configuration.
|
|
23
|
+
*
|
|
24
|
+
* Selection follows a strict priority chain:
|
|
25
|
+
* 1. forceLocal option -> LocalResolver
|
|
26
|
+
* 2. NEOCORTEX_MODE=local -> LocalResolver
|
|
27
|
+
* 3. NEOCORTEX_MODE=remote -> RemoteResolver
|
|
28
|
+
* 4. core/ exists locally -> LocalResolver
|
|
29
|
+
* 5. License key + no core/ -> RemoteResolver
|
|
30
|
+
* 6. Default -> LocalResolver
|
|
31
|
+
*
|
|
32
|
+
* @param options - Optional configuration overrides
|
|
33
|
+
* @returns Configured AssetResolver ready for use
|
|
34
|
+
*/
|
|
35
|
+
export declare function createResolver(options?: CreateResolverOptions): Promise<AssetResolver>;
|
|
36
|
+
/**
|
|
37
|
+
* Select resolver with full result including reason.
|
|
38
|
+
* Useful for logging/debugging why a specific resolver was chosen.
|
|
39
|
+
*/
|
|
40
|
+
export declare function selectResolver(options?: CreateResolverOptions): Promise<ResolverSelectionResult>;
|
|
41
|
+
//# sourceMappingURL=resolver-selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver-selection.d.ts","sourceRoot":"","sources":["../../src/config/resolver-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuBH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAGpE,OAAO,KAAK,EAAiB,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAI9E,uEAAuE;AACvE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;CACnC;AA0JD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAClC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,aAAa,CAAC,CAGxB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,uBAAuB,CAAC,CAgGlC"}
|
|
@@ -0,0 +1,278 @@
|
|
|
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 - Resolver Selection Factory
|
|
15
|
+
*
|
|
16
|
+
* Factory function that selects the appropriate AssetResolver
|
|
17
|
+
* based on CLI flags, environment variables, and auto-detection.
|
|
18
|
+
*
|
|
19
|
+
* Decision chain (priority order):
|
|
20
|
+
* 1. forceLocal option (--local flag) -> LocalResolver
|
|
21
|
+
* 2. NEOCORTEX_MODE=local env -> LocalResolver
|
|
22
|
+
* 3. NEOCORTEX_MODE=remote env -> RemoteResolver
|
|
23
|
+
* 4. core/ directory exists locally -> LocalResolver (dev mode)
|
|
24
|
+
* 4.5. Feature flag cutover (hash(machine) % 100 < remotePercentage) -> RemoteResolver
|
|
25
|
+
* 5. Valid license key present + no core/ -> RemoteResolver
|
|
26
|
+
* 6. Default -> LocalResolver (safe fallback)
|
|
27
|
+
*/
|
|
28
|
+
import { access, readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
29
|
+
import { join, resolve } from 'node:path';
|
|
30
|
+
import { homedir } from 'node:os';
|
|
31
|
+
import { createHash } from 'node:crypto';
|
|
32
|
+
import { LocalResolver } from '../resolvers/local-resolver.js';
|
|
33
|
+
import { RemoteResolver } from '../resolvers/remote-resolver.js';
|
|
34
|
+
// ── Feature Flag (Story 43.7) ───────────────────────────────────────────
|
|
35
|
+
const CONFIG_CACHE_FILE = join(homedir(), '.neocortex', 'feature-flags.json');
|
|
36
|
+
const CONFIG_CACHE_TTL_MS = 3600_000; // 1 hour
|
|
37
|
+
/**
|
|
38
|
+
* Check if this machine should use remote mode based on server feature flags.
|
|
39
|
+
* Uses hash(machine_fingerprint) % 100 < remotePercentage to determine bucket.
|
|
40
|
+
* Caches the server config for 1 hour.
|
|
41
|
+
*
|
|
42
|
+
* Returns 'remote' | 'local' | 'skip' (skip = no flag applies)
|
|
43
|
+
*/
|
|
44
|
+
async function checkFeatureFlag(options) {
|
|
45
|
+
try {
|
|
46
|
+
// Check env override
|
|
47
|
+
const envPercentage = process.env['NEOCORTEX_REMOTE_PERCENTAGE'];
|
|
48
|
+
if (envPercentage !== undefined) {
|
|
49
|
+
const pct = parseInt(envPercentage, 10);
|
|
50
|
+
if (isNaN(pct) || pct <= 0)
|
|
51
|
+
return 'skip';
|
|
52
|
+
return isInRemoteBucket(pct) ? 'remote' : 'local';
|
|
53
|
+
}
|
|
54
|
+
// Try cached config
|
|
55
|
+
const cached = await loadFeatureFlagCache();
|
|
56
|
+
if (cached) {
|
|
57
|
+
if (cached.forceLocal)
|
|
58
|
+
return 'local';
|
|
59
|
+
if (cached.forceRemote)
|
|
60
|
+
return 'remote';
|
|
61
|
+
if (cached.remotePercentage <= 0)
|
|
62
|
+
return 'skip';
|
|
63
|
+
return isInRemoteBucket(cached.remotePercentage) ? 'remote' : 'local';
|
|
64
|
+
}
|
|
65
|
+
// Fetch from server (non-blocking, fail gracefully)
|
|
66
|
+
const serverUrl = getServerUrl(options);
|
|
67
|
+
const config = await fetchFeatureFlags(serverUrl);
|
|
68
|
+
if (config) {
|
|
69
|
+
await saveFeatureFlagCache(config);
|
|
70
|
+
if (config.forceLocal)
|
|
71
|
+
return 'local';
|
|
72
|
+
if (config.forceRemote)
|
|
73
|
+
return 'remote';
|
|
74
|
+
if (config.remotePercentage <= 0)
|
|
75
|
+
return 'skip';
|
|
76
|
+
return isInRemoteBucket(config.remotePercentage) ? 'remote' : 'local';
|
|
77
|
+
}
|
|
78
|
+
return 'skip'; // No flag info available
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return 'skip'; // Never block on feature flag errors
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Deterministic bucket assignment using machine fingerprint hash.
|
|
86
|
+
* hash(machine_id) % 100 < percentage = in remote bucket
|
|
87
|
+
*/
|
|
88
|
+
function isInRemoteBucket(percentage) {
|
|
89
|
+
const machineId = process.env['NEOCORTEX_MACHINE_ID'] ?? 'default';
|
|
90
|
+
const hash = createHash('sha256').update(machineId).digest();
|
|
91
|
+
const bucket = hash.readUInt16BE(0) % 100;
|
|
92
|
+
return bucket < percentage;
|
|
93
|
+
}
|
|
94
|
+
async function loadFeatureFlagCache() {
|
|
95
|
+
try {
|
|
96
|
+
const raw = await readFile(CONFIG_CACHE_FILE, 'utf-8');
|
|
97
|
+
const cached = JSON.parse(raw);
|
|
98
|
+
if (Date.now() - cached.fetchedAt < CONFIG_CACHE_TTL_MS) {
|
|
99
|
+
return cached;
|
|
100
|
+
}
|
|
101
|
+
return null; // Expired
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function saveFeatureFlagCache(config) {
|
|
108
|
+
try {
|
|
109
|
+
await mkdir(join(homedir(), '.neocortex'), { recursive: true });
|
|
110
|
+
await writeFile(CONFIG_CACHE_FILE, JSON.stringify(config), 'utf-8');
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Non-critical - ignore
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function fetchFeatureFlags(serverUrl) {
|
|
117
|
+
try {
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
120
|
+
const response = await fetch(`${serverUrl}/api/v1/config`, {
|
|
121
|
+
signal: controller.signal,
|
|
122
|
+
});
|
|
123
|
+
clearTimeout(timeout);
|
|
124
|
+
if (!response.ok)
|
|
125
|
+
return null;
|
|
126
|
+
const data = (await response.json());
|
|
127
|
+
return {
|
|
128
|
+
remotePercentage: data.remotePercentage ?? 0,
|
|
129
|
+
forceRemote: data.forceRemote ?? false,
|
|
130
|
+
forceLocal: data.forceLocal ?? false,
|
|
131
|
+
fetchedAt: Date.now(),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ── Detection Helpers ───────────────────────────────────────────────────
|
|
139
|
+
/**
|
|
140
|
+
* Check if the core/ directory exists at the given project root.
|
|
141
|
+
* This indicates development mode.
|
|
142
|
+
*/
|
|
143
|
+
async function detectLocalMode(projectRoot) {
|
|
144
|
+
try {
|
|
145
|
+
await access(join(projectRoot, 'core'));
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get license key from environment or options.
|
|
154
|
+
*/
|
|
155
|
+
function getLicenseKey(options) {
|
|
156
|
+
return options?.licenseKey || process.env['NEOCORTEX_LICENSE_KEY'] || undefined;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get server URL from environment or options.
|
|
160
|
+
*/
|
|
161
|
+
function getServerUrl(options) {
|
|
162
|
+
return (options?.serverUrl ||
|
|
163
|
+
process.env['NEOCORTEX_SERVER_URL'] ||
|
|
164
|
+
'https://api.neocortex.dev');
|
|
165
|
+
}
|
|
166
|
+
// ── Factory Function ────────────────────────────────────────────────────
|
|
167
|
+
/**
|
|
168
|
+
* Create the appropriate AssetResolver based on configuration.
|
|
169
|
+
*
|
|
170
|
+
* Selection follows a strict priority chain:
|
|
171
|
+
* 1. forceLocal option -> LocalResolver
|
|
172
|
+
* 2. NEOCORTEX_MODE=local -> LocalResolver
|
|
173
|
+
* 3. NEOCORTEX_MODE=remote -> RemoteResolver
|
|
174
|
+
* 4. core/ exists locally -> LocalResolver
|
|
175
|
+
* 5. License key + no core/ -> RemoteResolver
|
|
176
|
+
* 6. Default -> LocalResolver
|
|
177
|
+
*
|
|
178
|
+
* @param options - Optional configuration overrides
|
|
179
|
+
* @returns Configured AssetResolver ready for use
|
|
180
|
+
*/
|
|
181
|
+
export async function createResolver(options) {
|
|
182
|
+
const result = await selectResolver(options);
|
|
183
|
+
return result.resolver;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Select resolver with full result including reason.
|
|
187
|
+
* Useful for logging/debugging why a specific resolver was chosen.
|
|
188
|
+
*/
|
|
189
|
+
export async function selectResolver(options) {
|
|
190
|
+
const projectRoot = resolve(options?.projectRoot || process.cwd());
|
|
191
|
+
const neocortexMode = process.env['NEOCORTEX_MODE']?.toLowerCase();
|
|
192
|
+
// 1. forceLocal option (--local CLI flag)
|
|
193
|
+
if (options?.forceLocal) {
|
|
194
|
+
return {
|
|
195
|
+
resolver: new LocalResolver({ projectRoot }),
|
|
196
|
+
reason: 'Forced local mode via --local flag',
|
|
197
|
+
mode: 'local',
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// 2. NEOCORTEX_MODE=local
|
|
201
|
+
if (neocortexMode === 'local') {
|
|
202
|
+
return {
|
|
203
|
+
resolver: new LocalResolver({ projectRoot }),
|
|
204
|
+
reason: 'NEOCORTEX_MODE=local environment variable',
|
|
205
|
+
mode: 'local',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
// 3. NEOCORTEX_MODE=remote
|
|
209
|
+
if (neocortexMode === 'remote') {
|
|
210
|
+
const licenseKey = getLicenseKey(options);
|
|
211
|
+
if (!licenseKey) {
|
|
212
|
+
// Fall back to local if no license key for remote mode
|
|
213
|
+
return {
|
|
214
|
+
resolver: new LocalResolver({ projectRoot }),
|
|
215
|
+
reason: 'NEOCORTEX_MODE=remote but no license key found, falling back to local',
|
|
216
|
+
mode: 'local',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
resolver: new RemoteResolver({
|
|
221
|
+
serverUrl: getServerUrl(options),
|
|
222
|
+
licenseKey,
|
|
223
|
+
cacheProvider: options?.cacheProvider,
|
|
224
|
+
licenseClient: options?.licenseClient,
|
|
225
|
+
}),
|
|
226
|
+
reason: 'NEOCORTEX_MODE=remote environment variable',
|
|
227
|
+
mode: 'remote',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
// 4. Auto-detect: core/ directory exists -> dev mode
|
|
231
|
+
const hasLocalCore = await detectLocalMode(projectRoot);
|
|
232
|
+
if (hasLocalCore) {
|
|
233
|
+
return {
|
|
234
|
+
resolver: new LocalResolver({ projectRoot }),
|
|
235
|
+
reason: 'Auto-detected development mode (core/ directory exists)',
|
|
236
|
+
mode: 'local',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// 4.5. Feature flag cutover check (Story 43.7)
|
|
240
|
+
// If server config says remotePercentage > 0, check if this machine is in the bucket
|
|
241
|
+
const featureFlagResult = await checkFeatureFlag(options);
|
|
242
|
+
if (featureFlagResult === 'remote') {
|
|
243
|
+
const licenseKey = getLicenseKey(options);
|
|
244
|
+
if (licenseKey) {
|
|
245
|
+
return {
|
|
246
|
+
resolver: new RemoteResolver({
|
|
247
|
+
serverUrl: getServerUrl(options),
|
|
248
|
+
licenseKey,
|
|
249
|
+
cacheProvider: options?.cacheProvider,
|
|
250
|
+
licenseClient: options?.licenseClient,
|
|
251
|
+
}),
|
|
252
|
+
reason: 'Feature flag cutover: machine in remote bucket',
|
|
253
|
+
mode: 'remote',
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// 5. License key present + no core/ -> production mode
|
|
258
|
+
const licenseKey = getLicenseKey(options);
|
|
259
|
+
if (licenseKey) {
|
|
260
|
+
return {
|
|
261
|
+
resolver: new RemoteResolver({
|
|
262
|
+
serverUrl: getServerUrl(options),
|
|
263
|
+
licenseKey,
|
|
264
|
+
cacheProvider: options?.cacheProvider,
|
|
265
|
+
licenseClient: options?.licenseClient,
|
|
266
|
+
}),
|
|
267
|
+
reason: 'Auto-detected production mode (license key present, no core/ directory)',
|
|
268
|
+
mode: 'remote',
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
// 6. Default -> LocalResolver (safe fallback)
|
|
272
|
+
return {
|
|
273
|
+
resolver: new LocalResolver({ projectRoot }),
|
|
274
|
+
reason: 'Default fallback to local mode',
|
|
275
|
+
mode: 'local',
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=resolver-selection.js.map
|