@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.
Files changed (156) hide show
  1. package/LICENSE +56 -0
  2. package/README.md +661 -0
  3. package/install.js +453 -0
  4. package/install.ps1 +1478 -0
  5. package/install.sh +1409 -0
  6. package/package.json +93 -0
  7. package/packages/client/dist/adapters/adapter-registry.d.ts +62 -0
  8. package/packages/client/dist/adapters/adapter-registry.d.ts.map +1 -0
  9. package/packages/client/dist/adapters/adapter-registry.js +107 -0
  10. package/packages/client/dist/adapters/adapter-registry.js.map +1 -0
  11. package/packages/client/dist/adapters/antigravity-adapter.d.ts +19 -0
  12. package/packages/client/dist/adapters/antigravity-adapter.d.ts.map +1 -0
  13. package/packages/client/dist/adapters/antigravity-adapter.js +78 -0
  14. package/packages/client/dist/adapters/antigravity-adapter.js.map +1 -0
  15. package/packages/client/dist/adapters/claude-code-adapter.d.ts +20 -0
  16. package/packages/client/dist/adapters/claude-code-adapter.d.ts.map +1 -0
  17. package/packages/client/dist/adapters/claude-code-adapter.js +80 -0
  18. package/packages/client/dist/adapters/claude-code-adapter.js.map +1 -0
  19. package/packages/client/dist/adapters/codex-adapter.d.ts +20 -0
  20. package/packages/client/dist/adapters/codex-adapter.d.ts.map +1 -0
  21. package/packages/client/dist/adapters/codex-adapter.js +81 -0
  22. package/packages/client/dist/adapters/codex-adapter.js.map +1 -0
  23. package/packages/client/dist/adapters/cursor-adapter.d.ts +20 -0
  24. package/packages/client/dist/adapters/cursor-adapter.d.ts.map +1 -0
  25. package/packages/client/dist/adapters/cursor-adapter.js +116 -0
  26. package/packages/client/dist/adapters/cursor-adapter.js.map +1 -0
  27. package/packages/client/dist/adapters/gemini-adapter.d.ts +19 -0
  28. package/packages/client/dist/adapters/gemini-adapter.d.ts.map +1 -0
  29. package/packages/client/dist/adapters/gemini-adapter.js +72 -0
  30. package/packages/client/dist/adapters/gemini-adapter.js.map +1 -0
  31. package/packages/client/dist/adapters/index.d.ts +20 -0
  32. package/packages/client/dist/adapters/index.d.ts.map +1 -0
  33. package/packages/client/dist/adapters/index.js +22 -0
  34. package/packages/client/dist/adapters/index.js.map +1 -0
  35. package/packages/client/dist/adapters/platform-detector.d.ts +47 -0
  36. package/packages/client/dist/adapters/platform-detector.d.ts.map +1 -0
  37. package/packages/client/dist/adapters/platform-detector.js +107 -0
  38. package/packages/client/dist/adapters/platform-detector.js.map +1 -0
  39. package/packages/client/dist/adapters/target-adapter.d.ts +71 -0
  40. package/packages/client/dist/adapters/target-adapter.d.ts.map +1 -0
  41. package/packages/client/dist/adapters/target-adapter.js +13 -0
  42. package/packages/client/dist/adapters/target-adapter.js.map +1 -0
  43. package/packages/client/dist/adapters/vscode-adapter.d.ts +20 -0
  44. package/packages/client/dist/adapters/vscode-adapter.d.ts.map +1 -0
  45. package/packages/client/dist/adapters/vscode-adapter.js +73 -0
  46. package/packages/client/dist/adapters/vscode-adapter.js.map +1 -0
  47. package/packages/client/dist/cache/crypto-utils.d.ts +31 -0
  48. package/packages/client/dist/cache/crypto-utils.d.ts.map +1 -0
  49. package/packages/client/dist/cache/crypto-utils.js +77 -0
  50. package/packages/client/dist/cache/crypto-utils.js.map +1 -0
  51. package/packages/client/dist/cache/encrypted-cache.d.ts +31 -0
  52. package/packages/client/dist/cache/encrypted-cache.d.ts.map +1 -0
  53. package/packages/client/dist/cache/encrypted-cache.js +92 -0
  54. package/packages/client/dist/cache/encrypted-cache.js.map +1 -0
  55. package/packages/client/dist/cache/index.d.ts +14 -0
  56. package/packages/client/dist/cache/index.d.ts.map +1 -0
  57. package/packages/client/dist/cache/index.js +14 -0
  58. package/packages/client/dist/cache/index.js.map +1 -0
  59. package/packages/client/dist/cli.d.ts +15 -0
  60. package/packages/client/dist/cli.d.ts.map +1 -0
  61. package/packages/client/dist/cli.js +182 -0
  62. package/packages/client/dist/cli.js.map +1 -0
  63. package/packages/client/dist/commands/activate.d.ts +48 -0
  64. package/packages/client/dist/commands/activate.d.ts.map +1 -0
  65. package/packages/client/dist/commands/activate.js +186 -0
  66. package/packages/client/dist/commands/activate.js.map +1 -0
  67. package/packages/client/dist/commands/cache-status.d.ts +40 -0
  68. package/packages/client/dist/commands/cache-status.d.ts.map +1 -0
  69. package/packages/client/dist/commands/cache-status.js +113 -0
  70. package/packages/client/dist/commands/cache-status.js.map +1 -0
  71. package/packages/client/dist/commands/invoke.d.ts +71 -0
  72. package/packages/client/dist/commands/invoke.d.ts.map +1 -0
  73. package/packages/client/dist/commands/invoke.js +345 -0
  74. package/packages/client/dist/commands/invoke.js.map +1 -0
  75. package/packages/client/dist/config/resolver-selection.d.ts +41 -0
  76. package/packages/client/dist/config/resolver-selection.d.ts.map +1 -0
  77. package/packages/client/dist/config/resolver-selection.js +278 -0
  78. package/packages/client/dist/config/resolver-selection.js.map +1 -0
  79. package/packages/client/dist/context/context-collector.d.ts +29 -0
  80. package/packages/client/dist/context/context-collector.d.ts.map +1 -0
  81. package/packages/client/dist/context/context-collector.js +223 -0
  82. package/packages/client/dist/context/context-collector.js.map +1 -0
  83. package/packages/client/dist/context/context-sanitizer.d.ts +29 -0
  84. package/packages/client/dist/context/context-sanitizer.d.ts.map +1 -0
  85. package/packages/client/dist/context/context-sanitizer.js +146 -0
  86. package/packages/client/dist/context/context-sanitizer.js.map +1 -0
  87. package/packages/client/dist/index.d.ts +55 -0
  88. package/packages/client/dist/index.d.ts.map +1 -0
  89. package/packages/client/dist/index.js +37 -0
  90. package/packages/client/dist/index.js.map +1 -0
  91. package/packages/client/dist/license/index.d.ts +6 -0
  92. package/packages/client/dist/license/index.d.ts.map +1 -0
  93. package/packages/client/dist/license/index.js +6 -0
  94. package/packages/client/dist/license/index.js.map +1 -0
  95. package/packages/client/dist/license/license-client.d.ts +53 -0
  96. package/packages/client/dist/license/license-client.d.ts.map +1 -0
  97. package/packages/client/dist/license/license-client.js +164 -0
  98. package/packages/client/dist/license/license-client.js.map +1 -0
  99. package/packages/client/dist/machine/fingerprint.d.ts +24 -0
  100. package/packages/client/dist/machine/fingerprint.d.ts.map +1 -0
  101. package/packages/client/dist/machine/fingerprint.js +61 -0
  102. package/packages/client/dist/machine/fingerprint.js.map +1 -0
  103. package/packages/client/dist/machine/index.d.ts +6 -0
  104. package/packages/client/dist/machine/index.d.ts.map +1 -0
  105. package/packages/client/dist/machine/index.js +6 -0
  106. package/packages/client/dist/machine/index.js.map +1 -0
  107. package/packages/client/dist/resilience/circuit-breaker.d.ts +71 -0
  108. package/packages/client/dist/resilience/circuit-breaker.d.ts.map +1 -0
  109. package/packages/client/dist/resilience/circuit-breaker.js +171 -0
  110. package/packages/client/dist/resilience/circuit-breaker.js.map +1 -0
  111. package/packages/client/dist/resilience/degradation-manager.d.ts +68 -0
  112. package/packages/client/dist/resilience/degradation-manager.d.ts.map +1 -0
  113. package/packages/client/dist/resilience/degradation-manager.js +165 -0
  114. package/packages/client/dist/resilience/degradation-manager.js.map +1 -0
  115. package/packages/client/dist/resilience/freshness-indicator.d.ts +60 -0
  116. package/packages/client/dist/resilience/freshness-indicator.d.ts.map +1 -0
  117. package/packages/client/dist/resilience/freshness-indicator.js +101 -0
  118. package/packages/client/dist/resilience/freshness-indicator.js.map +1 -0
  119. package/packages/client/dist/resilience/index.d.ts +9 -0
  120. package/packages/client/dist/resilience/index.d.ts.map +1 -0
  121. package/packages/client/dist/resilience/index.js +9 -0
  122. package/packages/client/dist/resilience/index.js.map +1 -0
  123. package/packages/client/dist/resilience/recovery-detector.d.ts +60 -0
  124. package/packages/client/dist/resilience/recovery-detector.d.ts.map +1 -0
  125. package/packages/client/dist/resilience/recovery-detector.js +75 -0
  126. package/packages/client/dist/resilience/recovery-detector.js.map +1 -0
  127. package/packages/client/dist/resolvers/asset-resolver.d.ts +80 -0
  128. package/packages/client/dist/resolvers/asset-resolver.d.ts.map +1 -0
  129. package/packages/client/dist/resolvers/asset-resolver.js +14 -0
  130. package/packages/client/dist/resolvers/asset-resolver.js.map +1 -0
  131. package/packages/client/dist/resolvers/local-resolver.d.ts +27 -0
  132. package/packages/client/dist/resolvers/local-resolver.d.ts.map +1 -0
  133. package/packages/client/dist/resolvers/local-resolver.js +219 -0
  134. package/packages/client/dist/resolvers/local-resolver.js.map +1 -0
  135. package/packages/client/dist/resolvers/remote-resolver.d.ts +63 -0
  136. package/packages/client/dist/resolvers/remote-resolver.d.ts.map +1 -0
  137. package/packages/client/dist/resolvers/remote-resolver.js +207 -0
  138. package/packages/client/dist/resolvers/remote-resolver.js.map +1 -0
  139. package/packages/client/dist/telemetry/index.d.ts +6 -0
  140. package/packages/client/dist/telemetry/index.d.ts.map +1 -0
  141. package/packages/client/dist/telemetry/index.js +6 -0
  142. package/packages/client/dist/telemetry/index.js.map +1 -0
  143. package/packages/client/dist/telemetry/offline-queue.d.ts +58 -0
  144. package/packages/client/dist/telemetry/offline-queue.d.ts.map +1 -0
  145. package/packages/client/dist/telemetry/offline-queue.js +132 -0
  146. package/packages/client/dist/telemetry/offline-queue.js.map +1 -0
  147. package/packages/client/dist/types/index.d.ts +141 -0
  148. package/packages/client/dist/types/index.d.ts.map +1 -0
  149. package/packages/client/dist/types/index.js +39 -0
  150. package/packages/client/dist/types/index.js.map +1 -0
  151. package/targets-stubs/antigravity/README.md +20 -0
  152. package/targets-stubs/claude-code/README.md +20 -0
  153. package/targets-stubs/codex/README.md +20 -0
  154. package/targets-stubs/cursor/README.md +20 -0
  155. package/targets-stubs/gemini-cli/README.md +20 -0
  156. 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