@ornexus/neocortex 3.9.19 → 3.9.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/install.ps1 +1 -1
- package/install.sh +12 -1
- package/package.json +4 -2
- package/packages/client/dist/commands/activate.js +23 -2
- package/packages/client/dist/commands/invoke.js +91 -25
- package/packages/client/dist/config/secure-config.d.ts +69 -0
- package/packages/client/dist/config/secure-config.js +179 -0
- package/targets-stubs/antigravity/gemini.md +1 -1
- package/targets-stubs/antigravity/skill/SKILL.md +1 -1
- package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex.md +2 -2
- package/targets-stubs/codex/agents.md +1 -1
- package/targets-stubs/cursor/agent.md +2 -2
- package/targets-stubs/gemini-cli/agent.md +2 -2
- package/targets-stubs/vscode/agent.md +2 -2
- package/packages/client/dist/adapters/adapter-registry.d.ts.map +0 -1
- package/packages/client/dist/adapters/adapter-registry.js.map +0 -1
- package/packages/client/dist/adapters/antigravity-adapter.d.ts.map +0 -1
- package/packages/client/dist/adapters/antigravity-adapter.js.map +0 -1
- package/packages/client/dist/adapters/claude-code-adapter.d.ts.map +0 -1
- package/packages/client/dist/adapters/claude-code-adapter.js.map +0 -1
- package/packages/client/dist/adapters/codex-adapter.d.ts.map +0 -1
- package/packages/client/dist/adapters/codex-adapter.js.map +0 -1
- package/packages/client/dist/adapters/cursor-adapter.d.ts.map +0 -1
- package/packages/client/dist/adapters/cursor-adapter.js.map +0 -1
- package/packages/client/dist/adapters/gemini-adapter.d.ts.map +0 -1
- package/packages/client/dist/adapters/gemini-adapter.js.map +0 -1
- package/packages/client/dist/adapters/index.d.ts.map +0 -1
- package/packages/client/dist/adapters/index.js.map +0 -1
- package/packages/client/dist/adapters/platform-detector.d.ts.map +0 -1
- package/packages/client/dist/adapters/platform-detector.js.map +0 -1
- package/packages/client/dist/adapters/target-adapter.d.ts.map +0 -1
- package/packages/client/dist/adapters/target-adapter.js.map +0 -1
- package/packages/client/dist/adapters/vscode-adapter.d.ts.map +0 -1
- package/packages/client/dist/adapters/vscode-adapter.js.map +0 -1
- package/packages/client/dist/agent/refresh-stubs.d.ts.map +0 -1
- package/packages/client/dist/agent/refresh-stubs.js.map +0 -1
- package/packages/client/dist/agent/update-agent-yaml.d.ts.map +0 -1
- package/packages/client/dist/agent/update-agent-yaml.js.map +0 -1
- package/packages/client/dist/agent/update-description.d.ts.map +0 -1
- package/packages/client/dist/agent/update-description.js.map +0 -1
- package/packages/client/dist/cache/crypto-utils.d.ts.map +0 -1
- package/packages/client/dist/cache/crypto-utils.js.map +0 -1
- package/packages/client/dist/cache/encrypted-cache.d.ts.map +0 -1
- package/packages/client/dist/cache/encrypted-cache.js.map +0 -1
- package/packages/client/dist/cache/index.d.ts.map +0 -1
- package/packages/client/dist/cache/index.js.map +0 -1
- package/packages/client/dist/cli.d.ts.map +0 -1
- package/packages/client/dist/cli.js.map +0 -1
- package/packages/client/dist/commands/activate.d.ts.map +0 -1
- package/packages/client/dist/commands/activate.js.map +0 -1
- package/packages/client/dist/commands/cache-status.d.ts.map +0 -1
- package/packages/client/dist/commands/cache-status.js.map +0 -1
- package/packages/client/dist/commands/invoke.d.ts.map +0 -1
- package/packages/client/dist/commands/invoke.js.map +0 -1
- package/packages/client/dist/config/resolver-selection.d.ts.map +0 -1
- package/packages/client/dist/config/resolver-selection.js.map +0 -1
- package/packages/client/dist/context/context-collector.d.ts.map +0 -1
- package/packages/client/dist/context/context-collector.js.map +0 -1
- package/packages/client/dist/context/context-sanitizer.d.ts.map +0 -1
- package/packages/client/dist/context/context-sanitizer.js.map +0 -1
- package/packages/client/dist/index.d.ts.map +0 -1
- package/packages/client/dist/index.js.map +0 -1
- package/packages/client/dist/license/index.d.ts.map +0 -1
- package/packages/client/dist/license/index.js.map +0 -1
- package/packages/client/dist/license/license-client.d.ts.map +0 -1
- package/packages/client/dist/license/license-client.js.map +0 -1
- package/packages/client/dist/machine/fingerprint.d.ts.map +0 -1
- package/packages/client/dist/machine/fingerprint.js.map +0 -1
- package/packages/client/dist/machine/index.d.ts.map +0 -1
- package/packages/client/dist/machine/index.js.map +0 -1
- package/packages/client/dist/resilience/circuit-breaker.d.ts.map +0 -1
- package/packages/client/dist/resilience/circuit-breaker.js.map +0 -1
- package/packages/client/dist/resilience/degradation-manager.d.ts.map +0 -1
- package/packages/client/dist/resilience/degradation-manager.js.map +0 -1
- package/packages/client/dist/resilience/freshness-indicator.d.ts.map +0 -1
- package/packages/client/dist/resilience/freshness-indicator.js.map +0 -1
- package/packages/client/dist/resilience/index.d.ts.map +0 -1
- package/packages/client/dist/resilience/index.js.map +0 -1
- package/packages/client/dist/resilience/recovery-detector.d.ts.map +0 -1
- package/packages/client/dist/resilience/recovery-detector.js.map +0 -1
- package/packages/client/dist/resolvers/asset-resolver.d.ts.map +0 -1
- package/packages/client/dist/resolvers/asset-resolver.js.map +0 -1
- package/packages/client/dist/resolvers/local-resolver.d.ts.map +0 -1
- package/packages/client/dist/resolvers/local-resolver.js.map +0 -1
- package/packages/client/dist/resolvers/remote-resolver.d.ts.map +0 -1
- package/packages/client/dist/resolvers/remote-resolver.js.map +0 -1
- package/packages/client/dist/telemetry/index.d.ts.map +0 -1
- package/packages/client/dist/telemetry/index.js.map +0 -1
- package/packages/client/dist/telemetry/offline-queue.d.ts.map +0 -1
- package/packages/client/dist/telemetry/offline-queue.js.map +0 -1
- package/packages/client/dist/tier/index.d.ts.map +0 -1
- package/packages/client/dist/tier/index.js.map +0 -1
- package/packages/client/dist/tier/tier-aware-client.d.ts.map +0 -1
- package/packages/client/dist/tier/tier-aware-client.js.map +0 -1
- package/packages/client/dist/types/index.d.ts.map +0 -1
- package/packages/client/dist/types/index.js.map +0 -1
package/install.ps1
CHANGED
package/install.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Development Orchestrator
|
|
5
5
|
|
|
6
6
|
# Versao do instalador
|
|
7
|
-
VERSION="3.9.
|
|
7
|
+
VERSION="3.9.22"
|
|
8
8
|
|
|
9
9
|
# Flags
|
|
10
10
|
MIGRATION_DETECTED=false
|
|
@@ -569,12 +569,18 @@ setup_thin_client_config() {
|
|
|
569
569
|
mkdir -p "$config_dir" 2>/dev/null
|
|
570
570
|
mkdir -p "$config_dir/cache" 2>/dev/null
|
|
571
571
|
|
|
572
|
+
# Story 61.4 - F4 remediation: restrictive permissions
|
|
573
|
+
chmod 700 "$config_dir" 2>/dev/null
|
|
574
|
+
chmod 700 "$config_dir/cache" 2>/dev/null
|
|
575
|
+
|
|
572
576
|
if [ -f "$config_file" ]; then
|
|
573
577
|
# Preservar config existente, atualizar apenas serverUrl se necessario
|
|
574
578
|
local existing_mode
|
|
575
579
|
existing_mode=$(grep '"mode"' "$config_file" 2>/dev/null | head -1 | sed 's/.*"mode"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
|
|
576
580
|
|
|
577
581
|
if [ "$existing_mode" = "active" ] || [ "$existing_mode" = "local" ] || [ "$existing_mode" = "remote" ]; then
|
|
582
|
+
# Story 61.4 - ensure permissions are always enforced even on existing configs
|
|
583
|
+
chmod 600 "$config_file" 2>/dev/null
|
|
578
584
|
debug "Config existente preservada (mode=$existing_mode)"
|
|
579
585
|
return 0
|
|
580
586
|
fi
|
|
@@ -602,6 +608,9 @@ setup_thin_client_config() {
|
|
|
602
608
|
}
|
|
603
609
|
EOFCONFIG
|
|
604
610
|
|
|
611
|
+
# Story 61.4 - F4 remediation: config file readable only by owner
|
|
612
|
+
chmod 600 "$config_file" 2>/dev/null
|
|
613
|
+
|
|
605
614
|
debug "Thin client config criada: $config_file"
|
|
606
615
|
}
|
|
607
616
|
|
|
@@ -711,6 +720,7 @@ install_agent() {
|
|
|
711
720
|
|
|
712
721
|
# Dynamic description: patch tier from existing config (if activated)
|
|
713
722
|
patch_description_tier "$DEST_DIR/neocortex.md"
|
|
723
|
+
patch_description_tier "$DEST_DIR/neocortex.agent.yaml"
|
|
714
724
|
|
|
715
725
|
# Cleanup: remover workflow.md de instalacoes anteriores (v3.8 -> v3.9)
|
|
716
726
|
if [ -f "$DEST_DIR/workflow.md" ]; then
|
|
@@ -1187,6 +1197,7 @@ create_project_dirs() {
|
|
|
1187
1197
|
|
|
1188
1198
|
# Dynamic description: patch tier
|
|
1189
1199
|
patch_description_tier "$project_dir/.claude/agents/neocortex/neocortex.md"
|
|
1200
|
+
patch_description_tier "$project_dir/.claude/agents/neocortex/neocortex.agent.yaml"
|
|
1190
1201
|
|
|
1191
1202
|
# Thin-client: cleanup legacy IP from previous installs
|
|
1192
1203
|
auto_cleanup_legacy_project "$project_dir"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ornexus/neocortex",
|
|
3
|
-
"version": "3.9.
|
|
4
|
-
"description": "Neocortex v3.9.
|
|
3
|
+
"version": "3.9.22",
|
|
4
|
+
"description": "Neocortex v3.9.22 - Orquestrador de Desenvolvimento de Epics & Stories para Claude Code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
7
7
|
"claude-code",
|
|
@@ -53,6 +53,8 @@
|
|
|
53
53
|
"install.sh",
|
|
54
54
|
"install.ps1",
|
|
55
55
|
"packages/client/dist/",
|
|
56
|
+
"!packages/client/dist/**/*.js.map",
|
|
57
|
+
"!packages/client/dist/**/*.d.ts.map",
|
|
56
58
|
"targets-stubs/"
|
|
57
59
|
],
|
|
58
60
|
"scripts": {
|
|
@@ -29,6 +29,7 @@ import { getMachineFingerprint } from '../machine/fingerprint.js';
|
|
|
29
29
|
import { updateAgentDescription } from '../agent/update-description.js';
|
|
30
30
|
import { refreshStubs } from '../agent/refresh-stubs.js';
|
|
31
31
|
import { updateAgentYaml } from '../agent/update-agent-yaml.js';
|
|
32
|
+
import { saveSecureConfig, setSecureDirPermissions } from '../config/secure-config.js';
|
|
32
33
|
// ── Version Resolution ────────────────────────────────────────────────────
|
|
33
34
|
function getInstalledVersion() {
|
|
34
35
|
try {
|
|
@@ -94,11 +95,31 @@ function loadExistingConfig() {
|
|
|
94
95
|
}
|
|
95
96
|
/**
|
|
96
97
|
* Save user config after successful activation.
|
|
97
|
-
* License key is
|
|
98
|
+
* License key is encrypted using machine fingerprint (Story 61.2).
|
|
99
|
+
* File permissions set to 600 (Story 61.4).
|
|
98
100
|
*/
|
|
99
101
|
function saveConfig(config) {
|
|
102
|
+
// Ensure directories exist with secure permissions
|
|
100
103
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
101
|
-
|
|
104
|
+
setSecureDirPermissions(CONFIG_DIR);
|
|
105
|
+
const cacheDir = join(CONFIG_DIR, 'cache');
|
|
106
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
107
|
+
setSecureDirPermissions(cacheDir);
|
|
108
|
+
if (config.licenseKey) {
|
|
109
|
+
// Use secure config writer which encrypts the license key
|
|
110
|
+
saveSecureConfig({
|
|
111
|
+
serverUrl: config.serverUrl,
|
|
112
|
+
mode: config.mode,
|
|
113
|
+
machineId: config.machineId,
|
|
114
|
+
activatedAt: config.activatedAt,
|
|
115
|
+
tier: config.tier,
|
|
116
|
+
licenseKey: config.licenseKey,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Fallback: write without license key (should not happen in normal flow)
|
|
121
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
122
|
+
}
|
|
102
123
|
}
|
|
103
124
|
// ── Activate Command ──────────────────────────────────────────────────────
|
|
104
125
|
/**
|
|
@@ -19,20 +19,22 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Story 45.2 - AC1-AC6
|
|
21
21
|
*/
|
|
22
|
-
import { existsSync, readFileSync,
|
|
22
|
+
import { existsSync, readFileSync, unlinkSync } from 'node:fs';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
24
|
import { homedir } from 'node:os';
|
|
25
25
|
import { LicenseClient } from '../license/license-client.js';
|
|
26
26
|
import { EncryptedCache } from '../cache/encrypted-cache.js';
|
|
27
27
|
import { NoOpCache } from '../types/index.js';
|
|
28
|
+
import { TierAwareClient } from '../tier/tier-aware-client.js';
|
|
29
|
+
import { loadSecureConfig } from '../config/secure-config.js';
|
|
28
30
|
// ── Constants ─────────────────────────────────────────────────────────────
|
|
29
31
|
const DEFAULT_SERVER_URL = 'https://api.neocortex.ornexus.com';
|
|
30
32
|
const CONFIG_DIR = join(homedir(), '.neocortex');
|
|
31
|
-
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
32
33
|
const CACHE_DIR = join(CONFIG_DIR, 'cache');
|
|
33
34
|
const MENU_CACHE_FILE = join(CACHE_DIR, 'menu-cache.json');
|
|
34
35
|
const MENU_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
35
36
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
37
|
+
const CLIENT_VERSION = '0.1.0';
|
|
36
38
|
// ── State Snapshot Collection ──────────────────────────────────────────────
|
|
37
39
|
/**
|
|
38
40
|
* Read state.json and construct a sanitized snapshot for the server.
|
|
@@ -113,14 +115,24 @@ export function collectStateSnapshot(projectRoot) {
|
|
|
113
115
|
epics,
|
|
114
116
|
};
|
|
115
117
|
}
|
|
116
|
-
// ── Menu Cache
|
|
117
|
-
|
|
118
|
+
// ── Menu Cache (Encrypted - Story 61.1) ──────────────────────────────────
|
|
119
|
+
const MENU_CACHE_KEY = 'neocortex:menu:cache';
|
|
120
|
+
/**
|
|
121
|
+
* Read menu cache from EncryptedCache.
|
|
122
|
+
* Falls back gracefully: if decryption fails or data is stale, returns null.
|
|
123
|
+
* Also cleans up legacy plaintext menu-cache.json if it exists.
|
|
124
|
+
*/
|
|
125
|
+
async function getMenuCache(encryptedCache) {
|
|
118
126
|
try {
|
|
119
|
-
|
|
127
|
+
const raw = await encryptedCache.get(MENU_CACHE_KEY);
|
|
128
|
+
if (!raw)
|
|
120
129
|
return null;
|
|
121
|
-
const raw = readFileSync(MENU_CACHE_FILE, 'utf-8');
|
|
122
130
|
const cache = JSON.parse(raw);
|
|
123
|
-
//
|
|
131
|
+
// Invalidate on version mismatch (stale cache from previous install)
|
|
132
|
+
if (cache.version !== CLIENT_VERSION) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
// Check TTL (EncryptedCache also has TTL, but we double-check for version-based invalidation)
|
|
124
136
|
if (Date.now() - cache.cachedAt > MENU_CACHE_TTL_MS) {
|
|
125
137
|
return null; // Expired
|
|
126
138
|
}
|
|
@@ -130,32 +142,47 @@ function getMenuCache() {
|
|
|
130
142
|
return null;
|
|
131
143
|
}
|
|
132
144
|
}
|
|
133
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Write menu cache to EncryptedCache.
|
|
147
|
+
* Deletes legacy plaintext menu-cache.json on first encrypted write.
|
|
148
|
+
*/
|
|
149
|
+
async function setMenuCache(encryptedCache, instructions, metadata) {
|
|
134
150
|
try {
|
|
135
|
-
mkdirSync(CACHE_DIR, { recursive: true });
|
|
136
151
|
const cache = {
|
|
137
152
|
instructions,
|
|
138
153
|
metadata,
|
|
139
154
|
cachedAt: Date.now(),
|
|
155
|
+
version: CLIENT_VERSION,
|
|
140
156
|
};
|
|
141
|
-
|
|
157
|
+
await encryptedCache.set(MENU_CACHE_KEY, JSON.stringify(cache), MENU_CACHE_TTL_MS);
|
|
158
|
+
// Delete legacy plaintext menu-cache.json if it exists (F1 remediation)
|
|
159
|
+
deleteLegacyMenuCache();
|
|
142
160
|
}
|
|
143
161
|
catch {
|
|
144
162
|
// Cache write failure is non-critical
|
|
145
163
|
}
|
|
146
164
|
}
|
|
147
|
-
|
|
148
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Remove legacy plaintext menu-cache.json file.
|
|
167
|
+
* Called after successful encrypted write to prevent IP leakage.
|
|
168
|
+
*/
|
|
169
|
+
function deleteLegacyMenuCache() {
|
|
149
170
|
try {
|
|
150
|
-
if (existsSync(
|
|
151
|
-
|
|
152
|
-
return JSON.parse(raw);
|
|
171
|
+
if (existsSync(MENU_CACHE_FILE)) {
|
|
172
|
+
unlinkSync(MENU_CACHE_FILE);
|
|
153
173
|
}
|
|
154
174
|
}
|
|
155
175
|
catch {
|
|
156
|
-
//
|
|
176
|
+
// Non-critical: best-effort cleanup
|
|
157
177
|
}
|
|
158
|
-
|
|
178
|
+
}
|
|
179
|
+
// ── Config Loading (Story 61.2 - Secure) ─────────────────────────────────
|
|
180
|
+
/**
|
|
181
|
+
* Load config with automatic decryption of license key.
|
|
182
|
+
* Handles migration from plaintext licenseKey to encryptedLicenseKey.
|
|
183
|
+
*/
|
|
184
|
+
function loadConfig() {
|
|
185
|
+
return loadSecureConfig();
|
|
159
186
|
}
|
|
160
187
|
async function getAuthTokenAndClient(serverUrl, licenseKey) {
|
|
161
188
|
try {
|
|
@@ -177,7 +204,11 @@ async function getAuthTokenAndClient(serverUrl, licenseKey) {
|
|
|
177
204
|
const token = await client.getToken();
|
|
178
205
|
if (!token)
|
|
179
206
|
return null;
|
|
180
|
-
|
|
207
|
+
const tierClient = new TierAwareClient({
|
|
208
|
+
cacheProvider,
|
|
209
|
+
licenseClient: client,
|
|
210
|
+
});
|
|
211
|
+
return { token, client, tierClient };
|
|
181
212
|
}
|
|
182
213
|
catch {
|
|
183
214
|
return null;
|
|
@@ -193,7 +224,7 @@ async function sendInvokeRequest(serverUrl, body, authToken) {
|
|
|
193
224
|
headers: {
|
|
194
225
|
'Content-Type': 'application/json',
|
|
195
226
|
'Authorization': `Bearer ${authToken}`,
|
|
196
|
-
'X-Client-Version':
|
|
227
|
+
'X-Client-Version': CLIENT_VERSION,
|
|
197
228
|
},
|
|
198
229
|
body: JSON.stringify(body),
|
|
199
230
|
signal: controller.signal,
|
|
@@ -245,17 +276,20 @@ async function sendInvokeRequest(serverUrl, body, authToken) {
|
|
|
245
276
|
*/
|
|
246
277
|
export async function invoke(options) {
|
|
247
278
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
248
|
-
const format = options.format ?? 'plain';
|
|
249
279
|
const platformTarget = options.platformTarget ?? 'claude-code';
|
|
250
280
|
// 1. Determine server URL
|
|
251
281
|
const config = loadConfig();
|
|
252
282
|
const serverUrl = (options.serverUrl ?? config?.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/+$/, '');
|
|
253
283
|
// 2. Collect state snapshot
|
|
254
284
|
const stateSnapshot = collectStateSnapshot(projectRoot);
|
|
285
|
+
// 2a. Create encrypted cache for menu (uses licenseKey as passphrase)
|
|
286
|
+
const menuCache = config?.licenseKey
|
|
287
|
+
? new EncryptedCache({ cacheDir: CACHE_DIR, passphrase: config.licenseKey })
|
|
288
|
+
: null;
|
|
255
289
|
// 3. Check menu cache for empty invocations (AC6)
|
|
256
290
|
const trimmedArgs = options.args.trim();
|
|
257
|
-
if (!trimmedArgs) {
|
|
258
|
-
const cachedMenu = getMenuCache();
|
|
291
|
+
if (!trimmedArgs && menuCache) {
|
|
292
|
+
const cachedMenu = await getMenuCache(menuCache);
|
|
259
293
|
if (cachedMenu) {
|
|
260
294
|
return {
|
|
261
295
|
success: true,
|
|
@@ -274,6 +308,24 @@ export async function invoke(options) {
|
|
|
274
308
|
exitCode: 2, // Not configured
|
|
275
309
|
};
|
|
276
310
|
}
|
|
311
|
+
// 4a. Pre-flight tier check (optimistic -- if it fails, still proceed)
|
|
312
|
+
const trigger = extractTrigger(trimmedArgs);
|
|
313
|
+
if (trigger) {
|
|
314
|
+
try {
|
|
315
|
+
const preFlightResult = await auth.tierClient.preFlightCheck(trigger);
|
|
316
|
+
if (!preFlightResult.allowed) {
|
|
317
|
+
process.stderr.write(`[neocortex] ${preFlightResult.message}\n`);
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
error: preFlightResult.message ?? 'Trigger not available on your plan',
|
|
321
|
+
exitCode: 1,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
// Fail-open: pre-flight errors should not block invocation
|
|
327
|
+
}
|
|
328
|
+
}
|
|
277
329
|
const requestBody = {
|
|
278
330
|
args: trimmedArgs,
|
|
279
331
|
projectRoot: projectRoot.replace(homedir(), '~'), // Sanitize absolute path
|
|
@@ -303,9 +355,13 @@ export async function invoke(options) {
|
|
|
303
355
|
exitCode,
|
|
304
356
|
};
|
|
305
357
|
}
|
|
306
|
-
// 6. Cache menu responses (AC6)
|
|
307
|
-
if (!trimmedArgs && result.data.metadata?.mode === 'menu') {
|
|
308
|
-
setMenuCache(result.data.instructions, result.data.metadata);
|
|
358
|
+
// 6. Cache menu responses (AC6) - encrypted (Story 61.1)
|
|
359
|
+
if (!trimmedArgs && result.data.metadata?.mode === 'menu' && menuCache) {
|
|
360
|
+
setMenuCache(menuCache, result.data.instructions, result.data.metadata).catch(() => { });
|
|
361
|
+
}
|
|
362
|
+
// 6a. Update cached quota from server response metadata (Epic 60)
|
|
363
|
+
if (result.data.metadata) {
|
|
364
|
+
auth.tierClient.updateQuotaFromResponse(result.data.metadata).catch(() => { });
|
|
309
365
|
}
|
|
310
366
|
return {
|
|
311
367
|
success: true,
|
|
@@ -314,6 +370,16 @@ export async function invoke(options) {
|
|
|
314
370
|
exitCode: 0,
|
|
315
371
|
};
|
|
316
372
|
}
|
|
373
|
+
// ── Trigger Extraction ──────────────────────────────────────────────────
|
|
374
|
+
/**
|
|
375
|
+
* Extract trigger name from args string.
|
|
376
|
+
* Triggers start with '*' (e.g., "*yolo", "*implement", "*status").
|
|
377
|
+
* Returns the trigger name without the '*' prefix, or null if no trigger.
|
|
378
|
+
*/
|
|
379
|
+
function extractTrigger(args) {
|
|
380
|
+
const match = args.match(/^\*([a-zA-Z][\w-]*)/);
|
|
381
|
+
return match ? match[1] : null;
|
|
382
|
+
}
|
|
317
383
|
// ── CLI Entry Point ───────────────────────────────────────────────────────
|
|
318
384
|
/**
|
|
319
385
|
* CLI handler for the invoke command.
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
/** Resolved config with decrypted license key */
|
|
14
|
+
export interface SecureConfig {
|
|
15
|
+
readonly serverUrl?: string;
|
|
16
|
+
readonly mode?: string;
|
|
17
|
+
readonly machineId?: string;
|
|
18
|
+
readonly activatedAt?: string;
|
|
19
|
+
readonly tier?: string;
|
|
20
|
+
readonly licenseKey?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Encrypt a license key using the machine fingerprint as passphrase.
|
|
24
|
+
* Returns the encrypted envelope string.
|
|
25
|
+
*/
|
|
26
|
+
export declare function encryptLicenseKey(licenseKey: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Decrypt a license key using the machine fingerprint as passphrase.
|
|
29
|
+
* Returns the plaintext key or null if decryption fails (hardware changed).
|
|
30
|
+
*/
|
|
31
|
+
export declare function decryptLicenseKey(encryptedKey: string): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Set restrictive file permissions (600) on config files.
|
|
34
|
+
* Skipped on Windows where chmod is not meaningful.
|
|
35
|
+
* Story 61.4 - F4 remediation.
|
|
36
|
+
*/
|
|
37
|
+
export declare function setSecureFilePermissions(filePath: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* Set restrictive directory permissions (700) on config directories.
|
|
40
|
+
* Skipped on Windows where chmod is not meaningful.
|
|
41
|
+
* Story 61.4 - F4 remediation.
|
|
42
|
+
*/
|
|
43
|
+
export declare function setSecureDirPermissions(dirPath: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* Load config from ~/.neocortex/config.json with automatic migration.
|
|
46
|
+
*
|
|
47
|
+
* If the config contains a plaintext `licenseKey` (old format), it is:
|
|
48
|
+
* 1. Encrypted using machine fingerprint
|
|
49
|
+
* 2. Stored as `encryptedLicenseKey`
|
|
50
|
+
* 3. Old `licenseKey` field removed
|
|
51
|
+
* 4. Config rewritten to disk
|
|
52
|
+
*
|
|
53
|
+
* If decryption of `encryptedLicenseKey` fails (hardware change),
|
|
54
|
+
* returns config with licenseKey = undefined.
|
|
55
|
+
*/
|
|
56
|
+
export declare function loadSecureConfig(): SecureConfig | null;
|
|
57
|
+
/**
|
|
58
|
+
* Save config after activation with encrypted license key.
|
|
59
|
+
* This is the primary write path called from activate.ts.
|
|
60
|
+
*/
|
|
61
|
+
export declare function saveSecureConfig(config: {
|
|
62
|
+
serverUrl: string;
|
|
63
|
+
mode: string;
|
|
64
|
+
machineId: string;
|
|
65
|
+
activatedAt: string;
|
|
66
|
+
tier?: string;
|
|
67
|
+
licenseKey: string;
|
|
68
|
+
}): void;
|
|
69
|
+
//# sourceMappingURL=secure-config.d.ts.map
|
|
@@ -0,0 +1,179 @@
|
|
|
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 - Secure Config
|
|
15
|
+
*
|
|
16
|
+
* Handles reading and writing config.json with encrypted license key.
|
|
17
|
+
* Uses machine fingerprint as encryption seed so the key is bound
|
|
18
|
+
* to the physical machine.
|
|
19
|
+
*
|
|
20
|
+
* Story 61.2 - F2 remediation: license key no longer stored in plaintext.
|
|
21
|
+
*
|
|
22
|
+
* Migration: if config has plaintext `licenseKey`, it is silently
|
|
23
|
+
* migrated to `encryptedLicenseKey` on first read.
|
|
24
|
+
*
|
|
25
|
+
* If decryption fails (e.g. hardware change), returns null for
|
|
26
|
+
* licenseKey, requiring re-activation.
|
|
27
|
+
*/
|
|
28
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'node:fs';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
import { homedir } from 'node:os';
|
|
31
|
+
import { encrypt, decrypt } from '../cache/crypto-utils.js';
|
|
32
|
+
import { getMachineFingerprint } from '../machine/fingerprint.js';
|
|
33
|
+
// ── Constants ────────────────────────────────────────────────────────────
|
|
34
|
+
const CONFIG_DIR = join(homedir(), '.neocortex');
|
|
35
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
36
|
+
// ── License Key Encryption ──────────────────────────────────────────────
|
|
37
|
+
/**
|
|
38
|
+
* Encrypt a license key using the machine fingerprint as passphrase.
|
|
39
|
+
* Returns the encrypted envelope string.
|
|
40
|
+
*/
|
|
41
|
+
export function encryptLicenseKey(licenseKey) {
|
|
42
|
+
const fingerprint = getMachineFingerprint();
|
|
43
|
+
return encrypt(licenseKey, fingerprint);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Decrypt a license key using the machine fingerprint as passphrase.
|
|
47
|
+
* Returns the plaintext key or null if decryption fails (hardware changed).
|
|
48
|
+
*/
|
|
49
|
+
export function decryptLicenseKey(encryptedKey) {
|
|
50
|
+
try {
|
|
51
|
+
const fingerprint = getMachineFingerprint();
|
|
52
|
+
const result = decrypt(encryptedKey, fingerprint);
|
|
53
|
+
// expired flag is not relevant for license keys (no TTL)
|
|
54
|
+
return result.plaintext;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ── Secure File Permissions ─────────────────────────────────────────────
|
|
61
|
+
/**
|
|
62
|
+
* Set restrictive file permissions (600) on config files.
|
|
63
|
+
* Skipped on Windows where chmod is not meaningful.
|
|
64
|
+
* Story 61.4 - F4 remediation.
|
|
65
|
+
*/
|
|
66
|
+
export function setSecureFilePermissions(filePath) {
|
|
67
|
+
if (process.platform === 'win32')
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
chmodSync(filePath, 0o600);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Non-critical: best-effort
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Set restrictive directory permissions (700) on config directories.
|
|
78
|
+
* Skipped on Windows where chmod is not meaningful.
|
|
79
|
+
* Story 61.4 - F4 remediation.
|
|
80
|
+
*/
|
|
81
|
+
export function setSecureDirPermissions(dirPath) {
|
|
82
|
+
if (process.platform === 'win32')
|
|
83
|
+
return;
|
|
84
|
+
try {
|
|
85
|
+
chmodSync(dirPath, 0o700);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Non-critical: best-effort
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ── Config Read/Write ───────────────────────────────────────────────────
|
|
92
|
+
/**
|
|
93
|
+
* Load config from ~/.neocortex/config.json with automatic migration.
|
|
94
|
+
*
|
|
95
|
+
* If the config contains a plaintext `licenseKey` (old format), it is:
|
|
96
|
+
* 1. Encrypted using machine fingerprint
|
|
97
|
+
* 2. Stored as `encryptedLicenseKey`
|
|
98
|
+
* 3. Old `licenseKey` field removed
|
|
99
|
+
* 4. Config rewritten to disk
|
|
100
|
+
*
|
|
101
|
+
* If decryption of `encryptedLicenseKey` fails (hardware change),
|
|
102
|
+
* returns config with licenseKey = undefined.
|
|
103
|
+
*/
|
|
104
|
+
export function loadSecureConfig() {
|
|
105
|
+
try {
|
|
106
|
+
if (!existsSync(CONFIG_FILE))
|
|
107
|
+
return null;
|
|
108
|
+
const raw = readFileSync(CONFIG_FILE, 'utf-8');
|
|
109
|
+
const config = JSON.parse(raw);
|
|
110
|
+
// Migration path: plaintext licenseKey -> encrypted
|
|
111
|
+
if (config.licenseKey && !config.encryptedLicenseKey) {
|
|
112
|
+
const encrypted = encryptLicenseKey(config.licenseKey);
|
|
113
|
+
const migratedConfig = { ...config };
|
|
114
|
+
const plainKey = migratedConfig.licenseKey;
|
|
115
|
+
delete migratedConfig.licenseKey;
|
|
116
|
+
migratedConfig.encryptedLicenseKey = encrypted;
|
|
117
|
+
writeSecureConfig(migratedConfig);
|
|
118
|
+
return {
|
|
119
|
+
serverUrl: config.serverUrl,
|
|
120
|
+
mode: config.mode,
|
|
121
|
+
machineId: config.machineId,
|
|
122
|
+
activatedAt: config.activatedAt,
|
|
123
|
+
tier: config.tier,
|
|
124
|
+
licenseKey: plainKey,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Decrypt encrypted license key
|
|
128
|
+
let licenseKey;
|
|
129
|
+
if (config.encryptedLicenseKey) {
|
|
130
|
+
const decrypted = decryptLicenseKey(config.encryptedLicenseKey);
|
|
131
|
+
if (decrypted) {
|
|
132
|
+
licenseKey = decrypted;
|
|
133
|
+
}
|
|
134
|
+
// If decryption fails, licenseKey remains undefined -> requires re-activation
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
serverUrl: config.serverUrl,
|
|
138
|
+
mode: config.mode,
|
|
139
|
+
machineId: config.machineId,
|
|
140
|
+
activatedAt: config.activatedAt,
|
|
141
|
+
tier: config.tier,
|
|
142
|
+
licenseKey,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Write config to disk with encrypted license key and secure permissions.
|
|
151
|
+
* The licenseKey field should already be encrypted as encryptedLicenseKey.
|
|
152
|
+
*/
|
|
153
|
+
function writeSecureConfig(config) {
|
|
154
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
155
|
+
setSecureDirPermissions(CONFIG_DIR);
|
|
156
|
+
const cacheDir = join(CONFIG_DIR, 'cache');
|
|
157
|
+
if (existsSync(cacheDir)) {
|
|
158
|
+
setSecureDirPermissions(cacheDir);
|
|
159
|
+
}
|
|
160
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
161
|
+
setSecureFilePermissions(CONFIG_FILE);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Save config after activation with encrypted license key.
|
|
165
|
+
* This is the primary write path called from activate.ts.
|
|
166
|
+
*/
|
|
167
|
+
export function saveSecureConfig(config) {
|
|
168
|
+
const encrypted = encryptLicenseKey(config.licenseKey);
|
|
169
|
+
const diskConfig = {
|
|
170
|
+
serverUrl: config.serverUrl,
|
|
171
|
+
mode: config.mode,
|
|
172
|
+
machineId: config.machineId,
|
|
173
|
+
activatedAt: config.activatedAt,
|
|
174
|
+
tier: config.tier,
|
|
175
|
+
encryptedLicenseKey: encrypted,
|
|
176
|
+
};
|
|
177
|
+
writeSecureConfig(diskConfig);
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=secure-config.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: neocortex
|
|
3
|
-
description: "🧠 Neocortex v3.9.
|
|
3
|
+
description: "🧠 Neocortex v3.9.22 (Free) | OrNexus Team"
|
|
4
4
|
model: opus
|
|
5
5
|
color: blue
|
|
6
6
|
tools:
|
|
@@ -56,7 +56,7 @@ SEMPRE que este agente for invocado, imprima o banner abaixo como PRIMEIRO outpu
|
|
|
56
56
|
┌────────────────────────────────────────────────────────────┐
|
|
57
57
|
│ │
|
|
58
58
|
│ ####### N E O C O R T E X │
|
|
59
|
-
│ ### ######## v3.9.
|
|
59
|
+
│ ### ######## v3.9.22 │
|
|
60
60
|
│ ######### ##### │
|
|
61
61
|
│ ## ############## Development Orchestrator │
|
|
62
62
|
│ ## ### ###### ## OrNexus Team (Free) │
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: neocortex
|
|
3
|
-
description: "🧠 Neocortex v3.9.
|
|
3
|
+
description: "🧠 Neocortex v3.9.22 (Free) | OrNexus Team"
|
|
4
4
|
model: fast
|
|
5
5
|
readonly: false
|
|
6
6
|
is_background: false
|
|
@@ -18,7 +18,7 @@ SEMPRE que este agente for invocado, imprima o banner abaixo como PRIMEIRO outpu
|
|
|
18
18
|
┌────────────────────────────────────────────────────────────┐
|
|
19
19
|
│ │
|
|
20
20
|
│ ####### N E O C O R T E X │
|
|
21
|
-
│ ### ######## v3.9.
|
|
21
|
+
│ ### ######## v3.9.22 │
|
|
22
22
|
│ ######### ##### │
|
|
23
23
|
│ ## ############## Development Orchestrator │
|
|
24
24
|
│ ## ### ###### ## OrNexus Team (Free) │
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: neocortex
|
|
3
|
-
description: "🧠 Neocortex v3.9.
|
|
3
|
+
description: "🧠 Neocortex v3.9.22 (Free) | OrNexus Team"
|
|
4
4
|
kind: local
|
|
5
5
|
tools:
|
|
6
6
|
- read_file
|
|
@@ -25,7 +25,7 @@ SEMPRE que este agente for invocado, imprima o banner abaixo como PRIMEIRO outpu
|
|
|
25
25
|
┌────────────────────────────────────────────────────────────┐
|
|
26
26
|
│ │
|
|
27
27
|
│ ####### N E O C O R T E X │
|
|
28
|
-
│ ### ######## v3.9.
|
|
28
|
+
│ ### ######## v3.9.22 │
|
|
29
29
|
│ ######### ##### │
|
|
30
30
|
│ ## ############## Development Orchestrator │
|
|
31
31
|
│ ## ### ###### ## OrNexus Team (Free) │
|