@ornexus/neocortex 4.0.1

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.

Potentially problematic release.


This version of @ornexus/neocortex might be problematic. Click here for more details.

Files changed (121) hide show
  1. package/LICENSE +56 -0
  2. package/README.md +32 -0
  3. package/install.js +486 -0
  4. package/install.ps1 +1790 -0
  5. package/install.sh +1587 -0
  6. package/package.json +104 -0
  7. package/packages/client/dist/adapters/adapter-registry.d.ts +61 -0
  8. package/packages/client/dist/adapters/adapter-registry.js +106 -0
  9. package/packages/client/dist/adapters/antigravity-adapter.d.ts +18 -0
  10. package/packages/client/dist/adapters/antigravity-adapter.js +77 -0
  11. package/packages/client/dist/adapters/claude-code-adapter.d.ts +19 -0
  12. package/packages/client/dist/adapters/claude-code-adapter.js +79 -0
  13. package/packages/client/dist/adapters/codex-adapter.d.ts +19 -0
  14. package/packages/client/dist/adapters/codex-adapter.js +80 -0
  15. package/packages/client/dist/adapters/cursor-adapter.d.ts +19 -0
  16. package/packages/client/dist/adapters/cursor-adapter.js +115 -0
  17. package/packages/client/dist/adapters/gemini-adapter.d.ts +18 -0
  18. package/packages/client/dist/adapters/gemini-adapter.js +71 -0
  19. package/packages/client/dist/adapters/index.d.ts +19 -0
  20. package/packages/client/dist/adapters/index.js +21 -0
  21. package/packages/client/dist/adapters/platform-detector.d.ts +46 -0
  22. package/packages/client/dist/adapters/platform-detector.js +106 -0
  23. package/packages/client/dist/adapters/target-adapter.d.ts +70 -0
  24. package/packages/client/dist/adapters/target-adapter.js +12 -0
  25. package/packages/client/dist/adapters/vscode-adapter.d.ts +19 -0
  26. package/packages/client/dist/adapters/vscode-adapter.js +72 -0
  27. package/packages/client/dist/agent/refresh-stubs.d.ts +65 -0
  28. package/packages/client/dist/agent/refresh-stubs.js +234 -0
  29. package/packages/client/dist/agent/update-agent-yaml.d.ts +26 -0
  30. package/packages/client/dist/agent/update-agent-yaml.js +102 -0
  31. package/packages/client/dist/agent/update-description.d.ts +45 -0
  32. package/packages/client/dist/agent/update-description.js +251 -0
  33. package/packages/client/dist/cache/crypto-utils.d.ts +30 -0
  34. package/packages/client/dist/cache/crypto-utils.js +76 -0
  35. package/packages/client/dist/cache/encrypted-cache.d.ts +30 -0
  36. package/packages/client/dist/cache/encrypted-cache.js +94 -0
  37. package/packages/client/dist/cache/in-memory-asset-cache.d.ts +59 -0
  38. package/packages/client/dist/cache/in-memory-asset-cache.js +70 -0
  39. package/packages/client/dist/cache/index.d.ts +13 -0
  40. package/packages/client/dist/cache/index.js +13 -0
  41. package/packages/client/dist/cli.d.ts +14 -0
  42. package/packages/client/dist/cli.js +194 -0
  43. package/packages/client/dist/commands/activate.d.ts +55 -0
  44. package/packages/client/dist/commands/activate.js +390 -0
  45. package/packages/client/dist/commands/cache-status.d.ts +39 -0
  46. package/packages/client/dist/commands/cache-status.js +112 -0
  47. package/packages/client/dist/commands/invoke.d.ts +70 -0
  48. package/packages/client/dist/commands/invoke.js +490 -0
  49. package/packages/client/dist/config/resolver-selection.d.ts +40 -0
  50. package/packages/client/dist/config/resolver-selection.js +278 -0
  51. package/packages/client/dist/config/secure-config.d.ts +78 -0
  52. package/packages/client/dist/config/secure-config.js +269 -0
  53. package/packages/client/dist/constants.d.ts +25 -0
  54. package/packages/client/dist/constants.js +25 -0
  55. package/packages/client/dist/context/context-collector.d.ts +28 -0
  56. package/packages/client/dist/context/context-collector.js +222 -0
  57. package/packages/client/dist/context/context-sanitizer.d.ts +28 -0
  58. package/packages/client/dist/context/context-sanitizer.js +145 -0
  59. package/packages/client/dist/index.d.ts +55 -0
  60. package/packages/client/dist/index.js +38 -0
  61. package/packages/client/dist/license/index.d.ts +5 -0
  62. package/packages/client/dist/license/index.js +5 -0
  63. package/packages/client/dist/license/license-client.d.ts +79 -0
  64. package/packages/client/dist/license/license-client.js +257 -0
  65. package/packages/client/dist/machine/fingerprint.d.ts +34 -0
  66. package/packages/client/dist/machine/fingerprint.js +160 -0
  67. package/packages/client/dist/machine/index.d.ts +5 -0
  68. package/packages/client/dist/machine/index.js +5 -0
  69. package/packages/client/dist/resilience/circuit-breaker.d.ts +70 -0
  70. package/packages/client/dist/resilience/circuit-breaker.js +170 -0
  71. package/packages/client/dist/resilience/degradation-manager.d.ts +67 -0
  72. package/packages/client/dist/resilience/degradation-manager.js +164 -0
  73. package/packages/client/dist/resilience/freshness-indicator.d.ts +59 -0
  74. package/packages/client/dist/resilience/freshness-indicator.js +100 -0
  75. package/packages/client/dist/resilience/index.d.ts +8 -0
  76. package/packages/client/dist/resilience/index.js +8 -0
  77. package/packages/client/dist/resilience/recovery-detector.d.ts +59 -0
  78. package/packages/client/dist/resilience/recovery-detector.js +74 -0
  79. package/packages/client/dist/resolvers/asset-resolver.d.ts +79 -0
  80. package/packages/client/dist/resolvers/asset-resolver.js +13 -0
  81. package/packages/client/dist/resolvers/local-resolver.d.ts +26 -0
  82. package/packages/client/dist/resolvers/local-resolver.js +218 -0
  83. package/packages/client/dist/resolvers/remote-resolver.d.ts +91 -0
  84. package/packages/client/dist/resolvers/remote-resolver.js +282 -0
  85. package/packages/client/dist/telemetry/index.d.ts +5 -0
  86. package/packages/client/dist/telemetry/index.js +5 -0
  87. package/packages/client/dist/telemetry/offline-queue.d.ts +57 -0
  88. package/packages/client/dist/telemetry/offline-queue.js +131 -0
  89. package/packages/client/dist/tier/index.d.ts +5 -0
  90. package/packages/client/dist/tier/index.js +5 -0
  91. package/packages/client/dist/tier/tier-aware-client.d.ts +97 -0
  92. package/packages/client/dist/tier/tier-aware-client.js +260 -0
  93. package/packages/client/dist/types/index.d.ts +140 -0
  94. package/packages/client/dist/types/index.js +38 -0
  95. package/postinstall.js +272 -0
  96. package/targets-stubs/antigravity/README.md +36 -0
  97. package/targets-stubs/antigravity/gemini.md +22 -0
  98. package/targets-stubs/antigravity/install-antigravity.sh +44 -0
  99. package/targets-stubs/antigravity/mcp-config.json +9 -0
  100. package/targets-stubs/antigravity/skill/SKILL.md +67 -0
  101. package/targets-stubs/claude-code/README.md +20 -0
  102. package/targets-stubs/claude-code/neocortex.agent.yaml +24 -0
  103. package/targets-stubs/claude-code/neocortex.md +125 -0
  104. package/targets-stubs/codex/README.md +32 -0
  105. package/targets-stubs/codex/agents.md +61 -0
  106. package/targets-stubs/codex/config-mcp.toml +6 -0
  107. package/targets-stubs/codex/install-codex.sh +61 -0
  108. package/targets-stubs/cursor/README.md +33 -0
  109. package/targets-stubs/cursor/agent.md +94 -0
  110. package/targets-stubs/cursor/install-cursor.sh +35 -0
  111. package/targets-stubs/cursor/mcp.json +11 -0
  112. package/targets-stubs/gemini-cli/README.md +34 -0
  113. package/targets-stubs/gemini-cli/agent.md +101 -0
  114. package/targets-stubs/gemini-cli/gemini.md +16 -0
  115. package/targets-stubs/gemini-cli/install-gemini.sh +56 -0
  116. package/targets-stubs/gemini-cli/settings-mcp.json +11 -0
  117. package/targets-stubs/vscode/README.md +34 -0
  118. package/targets-stubs/vscode/agent.md +102 -0
  119. package/targets-stubs/vscode/copilot-instructions.md +16 -0
  120. package/targets-stubs/vscode/install-vscode.sh +42 -0
  121. package/targets-stubs/vscode/mcp.json +13 -0
@@ -0,0 +1,390 @@
1
+ /**
2
+ * @license FSL-1.1
3
+ * Copyright (c) 2026 OrNexus AI
4
+ *
5
+ * This file is part of Neocortex CLI, licensed under the
6
+ * Functional Source License, Version 1.1 (FSL-1.1).
7
+ *
8
+ * Change Date: February 20, 2029
9
+ * Change License: MIT
10
+ *
11
+ * See the LICENSE file in the project root for full license text.
12
+ */
13
+ /**
14
+ * @neocortex/client - Activate Command
15
+ *
16
+ * Manages license activation for the Neocortex CLI.
17
+ * Validates license key with the IP Protection Server,
18
+ * caches JWT token, and creates user config file.
19
+ *
20
+ * Story 43.3 - AC1, AC3, AC4, AC5
21
+ */
22
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
23
+ import { join, dirname } from 'node:path';
24
+ import { homedir } from 'node:os';
25
+ import { fileURLToPath } from 'node:url';
26
+ import { LicenseClient } from '../license/license-client.js';
27
+ import { EncryptedCache } from '../cache/encrypted-cache.js';
28
+ import { getMachineFingerprint } from '../machine/fingerprint.js';
29
+ import { updateAgentDescription } from '../agent/update-description.js';
30
+ import { refreshStubs } from '../agent/refresh-stubs.js';
31
+ import { updateAgentYaml } from '../agent/update-agent-yaml.js';
32
+ import { saveSecureConfig, setSecureDirPermissions } from '../config/secure-config.js';
33
+ import { DEFAULT_SERVER_URL } from '../constants.js';
34
+ // ── Version Resolution ────────────────────────────────────────────────────
35
+ function getInstalledVersion() {
36
+ try {
37
+ const thisFile = fileURLToPath(import.meta.url);
38
+ let dir = dirname(thisFile);
39
+ for (let i = 0; i < 5; i++) {
40
+ try {
41
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
42
+ if (pkg.name === '@ornexus/neocortex' || pkg.name === '@neocortex/client') {
43
+ return pkg.version ?? '0.0.0';
44
+ }
45
+ }
46
+ catch { /* no package.json at this level */ }
47
+ dir = dirname(dir);
48
+ }
49
+ }
50
+ catch { /* fallback */ }
51
+ return '0.0.0';
52
+ }
53
+ // ── Constants ─────────────────────────────────────────────────────────────
54
+ const CONFIG_DIR = join(homedir(), '.neocortex');
55
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
56
+ // License key format: NX-{F|P|E}-{24 hex}-{4 hex checksum}
57
+ const LICENSE_KEY_PATTERN = /^NX-(F|P|E)-[a-f0-9]+-[a-f0-9]+$/i;
58
+ // API key format: nxk_{24hex}_{4check} (new, P32) or nxk_{tier}_{24hex}_{4check} (legacy)
59
+ const API_KEY_PATTERN = /^nxk_(?:[a-z]+_)?[a-f0-9]+_[a-f0-9]+$/;
60
+ // ── Validation ────────────────────────────────────────────────────────────
61
+ /**
62
+ * Validate license key format.
63
+ * Format: NX-{TIER}-{random}-{random}
64
+ * Tiers: FREE, PRO, ENT
65
+ */
66
+ export function validateLicenseKeyFormat(key) {
67
+ if (!key || key.trim().length === 0) {
68
+ return { valid: false, error: 'License key cannot be empty' };
69
+ }
70
+ const trimmed = key.trim().toUpperCase();
71
+ if (!trimmed.startsWith('NX-')) {
72
+ return { valid: false, error: 'License key must start with "NX-"' };
73
+ }
74
+ if (!LICENSE_KEY_PATTERN.test(trimmed)) {
75
+ return {
76
+ valid: false,
77
+ error: 'Invalid license key format. Expected: NX-{TIER}-{ID} (e.g., NX-PRO-ABC-123)',
78
+ };
79
+ }
80
+ return { valid: true };
81
+ }
82
+ /**
83
+ * Validate API key format.
84
+ * Format: nxk_{tier}_{random}_{check}
85
+ */
86
+ export function validateApiKeyFormat(key) {
87
+ if (!key || key.trim().length === 0) {
88
+ return { valid: false, error: 'API key cannot be empty' };
89
+ }
90
+ const trimmed = key.trim();
91
+ if (!trimmed.startsWith('nxk_')) {
92
+ return { valid: false, error: 'API key must start with "nxk_"' };
93
+ }
94
+ if (trimmed.length < 20) {
95
+ return { valid: false, error: 'API key is too short' };
96
+ }
97
+ if (!API_KEY_PATTERN.test(trimmed)) {
98
+ return {
99
+ valid: false,
100
+ error: 'Invalid API key format. Expected: nxk_{id}_{check} (e.g., nxk_abc123def456...)',
101
+ };
102
+ }
103
+ return { valid: true };
104
+ }
105
+ // ── Config Management ─────────────────────────────────────────────────────
106
+ /**
107
+ * Load existing user config, if any.
108
+ */
109
+ function loadExistingConfig() {
110
+ try {
111
+ if (existsSync(CONFIG_FILE)) {
112
+ const raw = readFileSync(CONFIG_FILE, 'utf-8');
113
+ return JSON.parse(raw);
114
+ }
115
+ }
116
+ catch {
117
+ // Corrupted config - will be overwritten
118
+ }
119
+ return null;
120
+ }
121
+ /**
122
+ * Save user config after successful activation.
123
+ * License key is encrypted using machine fingerprint (Story 61.2).
124
+ * File permissions set to 600 (Story 61.4).
125
+ */
126
+ function saveConfig(config) {
127
+ // Ensure directories exist with secure permissions
128
+ mkdirSync(CONFIG_DIR, { recursive: true });
129
+ setSecureDirPermissions(CONFIG_DIR);
130
+ const cacheDir = join(CONFIG_DIR, 'cache');
131
+ mkdirSync(cacheDir, { recursive: true });
132
+ setSecureDirPermissions(cacheDir);
133
+ if (config.licenseKey || config.apiKey) {
134
+ // Use secure config writer which encrypts the key
135
+ saveSecureConfig({
136
+ serverUrl: config.serverUrl,
137
+ mode: config.mode,
138
+ machineId: config.machineId,
139
+ activatedAt: config.activatedAt,
140
+ tier: config.tier,
141
+ licenseKey: (config.licenseKey ?? config.apiKey),
142
+ });
143
+ }
144
+ else {
145
+ // Fallback: write without key (should not happen in normal flow)
146
+ const fallbackConfig = { configVersion: 1, ...config };
147
+ writeFileSync(CONFIG_FILE, JSON.stringify(fallbackConfig, null, 2) + '\n', 'utf-8');
148
+ }
149
+ }
150
+ // ── Activate Command ──────────────────────────────────────────────────────
151
+ /**
152
+ * Activate a Neocortex license.
153
+ *
154
+ * Flow:
155
+ * 1. Validate license key format
156
+ * 2. Call IP Protection Server to activate
157
+ * 3. Cache JWT token via LicenseClient
158
+ * 4. Create/update ~/.neocortex/config.json
159
+ * 5. Return result with feedback
160
+ */
161
+ export async function activate(options) {
162
+ const { licenseKey, serverUrl = DEFAULT_SERVER_URL } = options;
163
+ // 1. Check if key is provided
164
+ if (!licenseKey) {
165
+ return {
166
+ success: false,
167
+ message: [
168
+ 'No key provided.',
169
+ '',
170
+ 'Get your license key at https://neocortex.ornexus.com/login',
171
+ '',
172
+ 'Usage:',
173
+ ' neocortex activate YOUR-LICENSE-KEY',
174
+ ' neocortex activate YOUR-API-KEY',
175
+ ].join('\n'),
176
+ };
177
+ }
178
+ const trimmedKey = licenseKey.trim();
179
+ // 2. Detect key type: API key (nxk_) or license key (NX-)
180
+ const isApiKey = trimmedKey.startsWith('nxk_');
181
+ if (isApiKey) {
182
+ // ── API Key Activation Flow (Story 22.04) ──────────────────────────
183
+ const apiValidation = validateApiKeyFormat(trimmedKey);
184
+ if (!apiValidation.valid) {
185
+ return {
186
+ success: false,
187
+ message: `Invalid API key: ${apiValidation.error}`,
188
+ };
189
+ }
190
+ // Extract tier from API key -- new format has no tier prefix, server returns tier in response
191
+ const tierMatch = trimmedKey.match(/^nxk_(free|pro|ent)_/);
192
+ const apiTierMap = { free: 'free', pro: 'pro', ent: 'enterprise' };
193
+ const tier = tierMatch?.[1] ? (apiTierMap[tierMatch[1]] ?? 'unknown') : 'unknown'; // will be overridden by server response
194
+ const machineId = getMachineFingerprint();
195
+ const cliVersion = getInstalledVersion();
196
+ // Call /api/v1/license/activate-key
197
+ let response;
198
+ try {
199
+ const res = await fetch(`${serverUrl}/api/v1/license/activate-key`, {
200
+ method: 'POST',
201
+ headers: { 'Content-Type': 'application/json' },
202
+ body: JSON.stringify({
203
+ api_key: trimmedKey,
204
+ machine_id: machineId,
205
+ client_version: cliVersion,
206
+ }),
207
+ });
208
+ if (!res.ok) {
209
+ const body = await res.json().catch(() => ({}));
210
+ const errorBody = body;
211
+ return {
212
+ success: false,
213
+ message: `API key activation failed: ${errorBody.message ?? `HTTP ${res.status}`}`,
214
+ };
215
+ }
216
+ response = await res.json();
217
+ }
218
+ catch {
219
+ return {
220
+ success: false,
221
+ message: [
222
+ 'Failed to connect to IP Protection Server.',
223
+ '',
224
+ 'Possible causes:',
225
+ ` - Server at ${serverUrl} is unreachable`,
226
+ ' - Network connectivity issue',
227
+ '',
228
+ 'Workaround: Use --local flag to run in local mode',
229
+ ].join('\n'),
230
+ };
231
+ }
232
+ const activatedTier = response.tier ?? tier;
233
+ // Cache JWT token via EncryptedCache
234
+ const cacheDir = join(CONFIG_DIR, 'cache');
235
+ try {
236
+ const encryptedCache = new EncryptedCache({
237
+ cacheDir,
238
+ passphrase: trimmedKey,
239
+ });
240
+ await encryptedCache.set('neocortex:jwt:token', response.token, response.expires_in * 1000);
241
+ }
242
+ catch {
243
+ // Cache is non-critical (fail-open)
244
+ }
245
+ // Save config with API key
246
+ const config = {
247
+ serverUrl,
248
+ mode: 'remote',
249
+ machineId,
250
+ activatedAt: new Date().toISOString(),
251
+ tier: activatedTier,
252
+ apiKey: trimmedKey,
253
+ };
254
+ try {
255
+ saveConfig(config);
256
+ }
257
+ catch (err) {
258
+ return {
259
+ success: false,
260
+ message: `Failed to save configuration: ${err instanceof Error ? err.message : 'unknown error'}`,
261
+ };
262
+ }
263
+ // Refresh stubs and agent description
264
+ const stubsRefreshed = refreshStubs(cliVersion, { forceCreate: true, targetFilter: 'claude-code' });
265
+ refreshStubs(cliVersion);
266
+ updateAgentDescription(cliVersion, activatedTier);
267
+ updateAgentYaml(cliVersion, activatedTier);
268
+ return {
269
+ success: true,
270
+ message: [
271
+ 'API key activated successfully!',
272
+ '',
273
+ ` Tier: ${activatedTier}`,
274
+ ` Server: ${serverUrl}`,
275
+ ` Expires: ${Math.floor(response.expires_in / 3600)}h`,
276
+ ...(stubsRefreshed > 0 ? [' Agents: updated'] : []),
277
+ '',
278
+ 'You can now use Neocortex. Run *menu to get started.',
279
+ ].join('\n'),
280
+ tier: activatedTier,
281
+ serverUrl,
282
+ configPath: CONFIG_FILE,
283
+ };
284
+ }
285
+ // ── License Key Activation Flow (existing) ──────────────────────────────
286
+ const validation = validateLicenseKeyFormat(trimmedKey);
287
+ if (!validation.valid) {
288
+ return {
289
+ success: false,
290
+ message: `Invalid license key: ${validation.error}`,
291
+ };
292
+ }
293
+ const normalizedKey = trimmedKey;
294
+ // Extract tier from key
295
+ const tierMatch = normalizedKey.match(/^NX-(F|P|E)-/i);
296
+ const tierMap = { F: 'free', P: 'pro', E: 'enterprise' };
297
+ const tier = tierMatch?.[1] ? (tierMap[tierMatch[1].toUpperCase()] ?? 'unknown') : 'unknown';
298
+ // Attempt activation via LicenseClient with EncryptedCache
299
+ const cacheDir = join(CONFIG_DIR, 'cache');
300
+ const encryptedCache = new EncryptedCache({
301
+ cacheDir,
302
+ passphrase: normalizedKey,
303
+ });
304
+ const client = new LicenseClient({
305
+ serverUrl,
306
+ licenseKey: normalizedKey,
307
+ cacheProvider: encryptedCache,
308
+ });
309
+ let activationResult;
310
+ try {
311
+ activationResult = await client.activate();
312
+ }
313
+ catch {
314
+ return {
315
+ success: false,
316
+ message: [
317
+ 'Failed to connect to IP Protection Server.',
318
+ '',
319
+ 'Possible causes:',
320
+ ` - Server at ${serverUrl} is unreachable`,
321
+ ' - Network connectivity issue',
322
+ ' - Server is still being deployed',
323
+ '',
324
+ 'Workaround: Use --local flag to run in local mode',
325
+ ].join('\n'),
326
+ };
327
+ }
328
+ if (!activationResult) {
329
+ return {
330
+ success: false,
331
+ message: [
332
+ 'License activation failed.',
333
+ '',
334
+ 'Possible causes:',
335
+ ' - Invalid or expired license key',
336
+ ' - License already activated on another machine',
337
+ ' - Server rejected the activation request',
338
+ '',
339
+ 'Check your license key and try again.',
340
+ 'Contact support if the problem persists.',
341
+ ].join('\n'),
342
+ };
343
+ }
344
+ // Save config with license key for invoke re-authentication
345
+ const config = {
346
+ serverUrl,
347
+ mode: 'remote',
348
+ machineId: getMachineFingerprint(),
349
+ activatedAt: new Date().toISOString(),
350
+ tier,
351
+ licenseKey: normalizedKey,
352
+ };
353
+ try {
354
+ saveConfig(config);
355
+ }
356
+ catch (err) {
357
+ return {
358
+ success: false,
359
+ message: `Failed to save config to ${CONFIG_FILE}: ${err instanceof Error ? err.message : String(err)}`,
360
+ };
361
+ }
362
+ // Refresh stubs if version mismatch detected (Story 55.1)
363
+ const cliVersion = getInstalledVersion();
364
+ const stubsRefreshed = refreshStubs(cliVersion, {
365
+ forceCreate: true,
366
+ targetFilter: 'claude-code',
367
+ });
368
+ // Also refresh other existing targets (without forceCreate)
369
+ refreshStubs(cliVersion);
370
+ // Update agent description with activated tier and version
371
+ updateAgentDescription(cliVersion, tier);
372
+ // Update agent YAML with version and tier (Story 55.2)
373
+ updateAgentYaml(cliVersion, tier);
374
+ return {
375
+ success: true,
376
+ message: [
377
+ `License activated successfully!`,
378
+ '',
379
+ ` Tier: ${tier}`,
380
+ ` Server: ${serverUrl}`,
381
+ ` Expires: ${Math.floor(activationResult.expiresIn / 3600)}h`,
382
+ ...(stubsRefreshed > 0 ? [' Agents: updated'] : []),
383
+ '',
384
+ 'You can now use Neocortex. Run *menu to get started.',
385
+ ].join('\n'),
386
+ tier,
387
+ serverUrl,
388
+ configPath: CONFIG_FILE,
389
+ };
390
+ }
@@ -0,0 +1,39 @@
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 { ClientCircuitBreaker, CircuitState } from '../resilience/circuit-breaker.js';
14
+ import type { OfflineTelemetryQueue } from '../telemetry/offline-queue.js';
15
+ export interface CacheStatusInfo {
16
+ totalEntries: number;
17
+ totalSizeBytes: number;
18
+ totalSizeFormatted: string;
19
+ oldestEntry: Date | null;
20
+ newestEntry: Date | null;
21
+ staleEntries: number;
22
+ circuitState: CircuitState;
23
+ lastSync: Date | null;
24
+ telemetryQueueSize: number;
25
+ }
26
+ export interface CacheStatusOptions {
27
+ cacheDir: string;
28
+ circuitBreaker: ClientCircuitBreaker;
29
+ telemetryQueue: OfflineTelemetryQueue;
30
+ staleThresholdMs?: number;
31
+ }
32
+ /**
33
+ * Gather comprehensive cache status information.
34
+ */
35
+ export declare function getCacheStatus(options: CacheStatusOptions): Promise<CacheStatusInfo>;
36
+ /**
37
+ * Format cache status as a human-readable string for terminal output.
38
+ */
39
+ export declare function formatCacheStatus(info: CacheStatusInfo): string;
@@ -0,0 +1,112 @@
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 - Cache Status Command
15
+ *
16
+ * Displays comprehensive cache and circuit breaker status information.
17
+ * Shows entry count, total size, age range, stale entries, circuit state,
18
+ * and telemetry queue stats.
19
+ *
20
+ * Story 42.9 - AC7
21
+ */
22
+ import { readdir, stat } from 'node:fs/promises';
23
+ import { join } from 'node:path';
24
+ // ── Implementation ──────────────────────────────────────────────────────
25
+ /**
26
+ * Gather comprehensive cache status information.
27
+ */
28
+ export async function getCacheStatus(options) {
29
+ const staleThreshold = options.staleThresholdMs ?? 86_400_000; // 24h default
30
+ let totalEntries = 0;
31
+ let totalSizeBytes = 0;
32
+ let staleEntries = 0;
33
+ let oldestEntry = null;
34
+ let newestEntry = null;
35
+ try {
36
+ const entries = await readdir(options.cacheDir);
37
+ const encFiles = entries.filter((e) => e.endsWith('.enc'));
38
+ totalEntries = encFiles.length;
39
+ const now = Date.now();
40
+ for (const file of encFiles) {
41
+ try {
42
+ const filePath = join(options.cacheDir, file);
43
+ const fileStat = await stat(filePath);
44
+ totalSizeBytes += fileStat.size;
45
+ const mtime = fileStat.mtime;
46
+ if (!oldestEntry || mtime < oldestEntry)
47
+ oldestEntry = mtime;
48
+ if (!newestEntry || mtime > newestEntry)
49
+ newestEntry = mtime;
50
+ if (now - mtime.getTime() > staleThreshold) {
51
+ staleEntries++;
52
+ }
53
+ }
54
+ catch {
55
+ // Skip files that can't be stat'd
56
+ }
57
+ }
58
+ }
59
+ catch {
60
+ // Cache directory doesn't exist or can't be read
61
+ }
62
+ const circuitState = options.circuitBreaker.getState().state;
63
+ let telemetryQueueSize = 0;
64
+ try {
65
+ const queueStats = await options.telemetryQueue.getStats();
66
+ telemetryQueueSize = queueStats.count;
67
+ }
68
+ catch {
69
+ // Queue unavailable
70
+ }
71
+ return {
72
+ totalEntries,
73
+ totalSizeBytes,
74
+ totalSizeFormatted: formatBytes(totalSizeBytes),
75
+ oldestEntry,
76
+ newestEntry,
77
+ staleEntries,
78
+ circuitState,
79
+ lastSync: newestEntry, // Most recent cache write approximates last sync
80
+ telemetryQueueSize,
81
+ };
82
+ }
83
+ /**
84
+ * Format cache status as a human-readable string for terminal output.
85
+ */
86
+ export function formatCacheStatus(info) {
87
+ const lines = [
88
+ '+-------------------------------------------------+',
89
+ '| NEOCORTEX CACHE STATUS |',
90
+ '+-------------------------------------------------+',
91
+ `| Total entries: ${String(info.totalEntries).padEnd(29)}|`,
92
+ `| Total size: ${info.totalSizeFormatted.padEnd(29)}|`,
93
+ `| Oldest entry: ${(info.oldestEntry?.toISOString() ?? 'N/A').padEnd(29)}|`,
94
+ `| Newest entry: ${(info.newestEntry?.toISOString() ?? 'N/A').padEnd(29)}|`,
95
+ `| Stale entries: ${String(info.staleEntries).padEnd(29)}|`,
96
+ '|-------------------------------------------------|',
97
+ `| Circuit breaker: ${info.circuitState.padEnd(29)}|`,
98
+ `| Last sync: ${(info.lastSync?.toISOString() ?? 'N/A').padEnd(29)}|`,
99
+ `| Telemetry queue: ${(info.telemetryQueueSize + ' events').padEnd(29)}|`,
100
+ '+-------------------------------------------------+',
101
+ ];
102
+ return lines.join('\n');
103
+ }
104
+ // ── Helpers ─────────────────────────────────────────────────────────────
105
+ function formatBytes(bytes) {
106
+ if (bytes === 0)
107
+ return '0 B';
108
+ const units = ['B', 'KB', 'MB', 'GB'];
109
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
110
+ const value = bytes / Math.pow(1024, i);
111
+ return `${value.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
112
+ }
@@ -0,0 +1,70 @@
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
+ export interface InvokeOptions {
14
+ /** Raw args from the user (e.g., "*yolo @docs/stories/1.1.story.md") */
15
+ readonly args: string;
16
+ /** Project root directory (default: cwd) */
17
+ readonly projectRoot?: string;
18
+ /** Output format: 'json' for full response, 'plain' for instructions only */
19
+ readonly format?: 'json' | 'plain';
20
+ /** Server URL override */
21
+ readonly serverUrl?: string;
22
+ /** Platform target (default: 'claude-code') */
23
+ readonly platformTarget?: string;
24
+ }
25
+ export interface InvokeResult {
26
+ readonly success: boolean;
27
+ readonly instructions?: string;
28
+ readonly metadata?: Record<string, unknown>;
29
+ readonly error?: string;
30
+ readonly exitCode: number;
31
+ }
32
+ interface StateSnapshot {
33
+ readonly config: {
34
+ readonly project_name: string;
35
+ readonly default_branch: string;
36
+ readonly language: string;
37
+ readonly yolo_mode?: boolean;
38
+ readonly user_name?: string;
39
+ readonly worktree_base?: string;
40
+ readonly max_parallel_stories?: number;
41
+ };
42
+ readonly stories: Record<string, Record<string, unknown>>;
43
+ readonly epics: Record<string, Record<string, unknown>>;
44
+ }
45
+ /**
46
+ * Read state.json and construct a sanitized snapshot for the server.
47
+ * Relative paths only - no absolute paths sent to server.
48
+ */
49
+ export declare function collectStateSnapshot(projectRoot: string): StateSnapshot;
50
+ /**
51
+ * Execute the invoke command.
52
+ *
53
+ * Flow:
54
+ * 1. Load config to get server URL
55
+ * 2. Collect state snapshot from project root
56
+ * 3. Check menu cache for empty invocations
57
+ * 4. Send POST /api/v1/invoke
58
+ * 5. Format and return result
59
+ */
60
+ export declare function invoke(options: InvokeOptions): Promise<InvokeResult>;
61
+ /**
62
+ * CLI handler for the invoke command.
63
+ * Parses CLI args and delegates to invoke().
64
+ *
65
+ * Usage:
66
+ * neocortex-client invoke --args "*yolo @story.md" --project-root /path
67
+ * neocortex-client invoke --args "*status" --format json
68
+ */
69
+ export declare function invokeCliHandler(argv: string[]): Promise<number>;
70
+ export {};