log-llm-config 1.3.90 → 1.3.94
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/dist/log_config_files/readers/vscdb_reader.js +25 -0
- package/dist/log_config_files/runtime/compliance_check.js +30 -2
- package/dist/log_config_files/runtime/main_runner.js +3 -2
- package/dist/log_config_files/runtime/remediation_sync.js +70 -0
- package/dist/log_config_files/runtime/trusted_restarts.js +7 -1
- package/dist/tofu_environment.js +13 -8
- package/package.json +2 -2
|
@@ -25,6 +25,27 @@ function querySqlite(dbPath, key) {
|
|
|
25
25
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
26
26
|
}).trim();
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Cursor keeps live agent settings (e.g. yoloCommandAllowlist) on nested composerState inside
|
|
30
|
+
* the reactive blob while the legacy ItemTable `composerState` row is often stale/partial.
|
|
31
|
+
* Merge nested fields so compliance/autofix match policy scans on uploaded reactive data.
|
|
32
|
+
*/
|
|
33
|
+
function mergeLegacyComposerStateWithReactiveNested(dbPath, reactiveKey, legacyComposerState) {
|
|
34
|
+
try {
|
|
35
|
+
const reactive = querySqlite(dbPath, reactiveKey);
|
|
36
|
+
if (!reactive)
|
|
37
|
+
return legacyComposerState;
|
|
38
|
+
const root = JSON.parse(reactive);
|
|
39
|
+
const nested = root.composerState;
|
|
40
|
+
if (nested === null || typeof nested !== 'object' || Array.isArray(nested)) {
|
|
41
|
+
return legacyComposerState;
|
|
42
|
+
}
|
|
43
|
+
return { ...legacyComposerState, ...nested };
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return legacyComposerState;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
28
49
|
/**
|
|
29
50
|
* Fallback if the API omits vscdb_composer_contract (older server): derive from vscdb_read_queries.
|
|
30
51
|
*/
|
|
@@ -113,6 +134,10 @@ export function readVscdbItemTableJson(dbPath, itemKey) {
|
|
|
113
134
|
}
|
|
114
135
|
const parsed = JSON.parse(raw);
|
|
115
136
|
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
137
|
+
if (itemKey === 'composerState' && reactiveKey) {
|
|
138
|
+
const merged = mergeLegacyComposerStateWithReactiveNested(dbPath, reactiveKey, parsed);
|
|
139
|
+
return { [itemKey]: merged };
|
|
140
|
+
}
|
|
116
141
|
return { [itemKey]: parsed };
|
|
117
142
|
}
|
|
118
143
|
// Bare JSON arrays (e.g. cursor/disabledMcpServers) — wrap under itemKey so ops-based
|
|
@@ -39,6 +39,8 @@ export function normalizeAgentToken(raw) {
|
|
|
39
39
|
return 'claude';
|
|
40
40
|
if (s === 'cursor')
|
|
41
41
|
return 'cursor';
|
|
42
|
+
if (s === 'copilot')
|
|
43
|
+
return 'copilot';
|
|
42
44
|
return '';
|
|
43
45
|
}
|
|
44
46
|
function currentAgentFromEnv() {
|
|
@@ -46,9 +48,13 @@ function currentAgentFromEnv() {
|
|
|
46
48
|
const override = normalizeAgentToken(process.env.OPTIMUS_AGENT);
|
|
47
49
|
if (override)
|
|
48
50
|
return override;
|
|
49
|
-
// Backwards-compatible: hook wrappers set OPTIMUS_HOOK_TYPE to cursor|claude.
|
|
51
|
+
// Backwards-compatible: hook wrappers set OPTIMUS_HOOK_TYPE to cursor|claude|copilot.
|
|
50
52
|
const hookType = normalizeAgentToken(process.env.OPTIMUS_HOOK_TYPE);
|
|
51
|
-
|
|
53
|
+
if (hookType === 'cursor')
|
|
54
|
+
return 'cursor';
|
|
55
|
+
if (hookType === 'copilot')
|
|
56
|
+
return 'copilot';
|
|
57
|
+
return 'claude';
|
|
52
58
|
}
|
|
53
59
|
function targetsCurrentAgent(entry, agent) {
|
|
54
60
|
// Prefer top-level manifest field; fall back to embedded fix payload for older local files.
|
|
@@ -159,6 +165,21 @@ function deepEqual(a, b) {
|
|
|
159
165
|
function isStringArray(v) {
|
|
160
166
|
return Array.isArray(v) && v.every((x) => typeof x === 'string');
|
|
161
167
|
}
|
|
168
|
+
function escapeRegExp(s) {
|
|
169
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
170
|
+
}
|
|
171
|
+
/** Match Cursor yolo allowlist entries (same boundary idea as server filter_yolo_allowlist_entries). */
|
|
172
|
+
function allowlistEntryMatchesRemoveToken(entry, token) {
|
|
173
|
+
const valStr = String(entry).toLowerCase();
|
|
174
|
+
const t = token.toLowerCase().trim();
|
|
175
|
+
if (!t)
|
|
176
|
+
return false;
|
|
177
|
+
const first = valStr.trim().split(/\s+/)[0] ?? '';
|
|
178
|
+
if (first === t || valStr === t)
|
|
179
|
+
return true;
|
|
180
|
+
const pattern = new RegExp(`(^|[^a-z0-9_|])${escapeRegExp(t)}([^a-z0-9_|]|$)`);
|
|
181
|
+
return pattern.test(valStr);
|
|
182
|
+
}
|
|
162
183
|
function verifyOpsApplied(configJson, settingPath, ops) {
|
|
163
184
|
const parts = settingPath.split('.');
|
|
164
185
|
const leafKey = parts[parts.length - 1] ?? '';
|
|
@@ -180,7 +201,14 @@ function verifyOpsApplied(configJson, settingPath, ops) {
|
|
|
180
201
|
const curArr = Array.isArray(cur) ? cur : [];
|
|
181
202
|
const toAdd = add[k] ?? [];
|
|
182
203
|
const toRemove = remove[k] ?? [];
|
|
204
|
+
const isYoloAllowlist = leafKey === 'yoloCommandAllowlist' || targetPath.endsWith('.yoloCommandAllowlist');
|
|
183
205
|
for (const item of toRemove) {
|
|
206
|
+
if (isYoloAllowlist) {
|
|
207
|
+
if (curArr.some((entry) => allowlistEntryMatchesRemoveToken(entry, String(item)))) {
|
|
208
|
+
return { ok: false, expected: { op: 'remove', path: targetPath, value: item } };
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
184
212
|
if (curArr.some((curItem) => deepEqual(curItem, item))) {
|
|
185
213
|
return { ok: false, expected: { op: 'remove', path: targetPath, value: item } };
|
|
186
214
|
}
|
|
@@ -91,7 +91,7 @@ async function addSensitivePathsAudit(endpointBase, configFiles) {
|
|
|
91
91
|
}
|
|
92
92
|
async function sendAllConfigFiles(configFiles, worktreeReport, hardwareUuid, authKey) {
|
|
93
93
|
const hookTypeRaw = (process.env.OPTIMUS_HOOK_TYPE || 'claude').toLowerCase();
|
|
94
|
-
const hookType = hookTypeRaw === 'cursor' ? 'cursor' : 'claude';
|
|
94
|
+
const hookType = hookTypeRaw === 'cursor' ? 'cursor' : hookTypeRaw === 'copilot' ? 'copilot' : 'claude';
|
|
95
95
|
const manifest = configFiles.map((c) => canonicalCursorUserStateVscdbPath(c.file_path));
|
|
96
96
|
const workspaceRepo = ensureWorkspaceRepoEnv(manifest);
|
|
97
97
|
const hookRequestId = await sendHookRequestCreate(hardwareUuid, authKey, hookType, workspaceRepo);
|
|
@@ -132,7 +132,8 @@ async function main() {
|
|
|
132
132
|
const endpointBase = loadEndpointBase();
|
|
133
133
|
hookRunLog(`start endpoint=${endpointBase} hardware_uuid=${hardwareUuid}`);
|
|
134
134
|
hookRunLog(`endpoint_source=${getEndpointSource()} cwd=${process.cwd()}`);
|
|
135
|
-
|
|
135
|
+
const envForLog = (v) => (v && v.trim() ? v.trim() : 'unset');
|
|
136
|
+
hookRunLog(`env: OPTIMUS_HOOK_TYPE=${envForLog(process.env.OPTIMUS_HOOK_TYPE)} OPTIMUS_PROJECT_DIR=${envForLog(process.env.OPTIMUS_PROJECT_DIR)} OPTIMUS_WORKSPACE_REPO=${envForLog(process.env.OPTIMUS_WORKSPACE_REPO)} OPTIMUS_ENDPOINT=${envForLog(process.env.OPTIMUS_ENDPOINT)}`);
|
|
136
137
|
let authKey;
|
|
137
138
|
try {
|
|
138
139
|
authKey = await ensureAuthentication(hardwareUuid);
|
|
@@ -428,6 +428,23 @@ function applyCheck(configJson, check) {
|
|
|
428
428
|
}
|
|
429
429
|
setByPath(configJson, check.setting_path, value);
|
|
430
430
|
}
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
// SQLite remediation support
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
function escapeRegExpForAllowlist(s) {
|
|
435
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
436
|
+
}
|
|
437
|
+
function allowlistEntryMatchesRemoveToken(entry, token) {
|
|
438
|
+
const valStr = String(entry).toLowerCase();
|
|
439
|
+
const t = token.toLowerCase().trim();
|
|
440
|
+
if (!t)
|
|
441
|
+
return false;
|
|
442
|
+
const first = valStr.trim().split(/\s+/)[0] ?? '';
|
|
443
|
+
if (first === t || valStr === t)
|
|
444
|
+
return true;
|
|
445
|
+
const pattern = new RegExp(`(^|[^a-z0-9_|])${escapeRegExpForAllowlist(t)}([^a-z0-9_|]|$)`);
|
|
446
|
+
return pattern.test(valStr);
|
|
447
|
+
}
|
|
431
448
|
function mergePostApplyUploadIntoPayload(payload, hint) {
|
|
432
449
|
if (!hint)
|
|
433
450
|
return;
|
|
@@ -645,6 +662,59 @@ function mergeSqliteOpIntoJson(currentJson, sqliteOp) {
|
|
|
645
662
|
currentJson[key] = arr;
|
|
646
663
|
return;
|
|
647
664
|
}
|
|
665
|
+
if (sqliteOp.array_remove?.length) {
|
|
666
|
+
const jp = (sqliteOp.json_path ?? '').trim();
|
|
667
|
+
const removeSet = new Set(sqliteOp.array_remove.map((v) => String(v)));
|
|
668
|
+
if (!jp) {
|
|
669
|
+
const key = sqliteOp.target_key;
|
|
670
|
+
const existing = currentJson[key];
|
|
671
|
+
const arr = Array.isArray(existing) ? [...existing] : [];
|
|
672
|
+
currentJson[key] = arr.filter((x) => !removeSet.has(String(x)));
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const parts = jp.split('.').filter(Boolean);
|
|
676
|
+
if (parts.length === 0)
|
|
677
|
+
return;
|
|
678
|
+
let node = currentJson;
|
|
679
|
+
for (const p of parts.slice(0, -1)) {
|
|
680
|
+
if (node == null || typeof node !== 'object' || Array.isArray(node))
|
|
681
|
+
return;
|
|
682
|
+
node = node[p];
|
|
683
|
+
}
|
|
684
|
+
if (node == null || typeof node !== 'object' || Array.isArray(node))
|
|
685
|
+
return;
|
|
686
|
+
const container = node;
|
|
687
|
+
const arrayKey = parts[parts.length - 1];
|
|
688
|
+
const existing = container[arrayKey];
|
|
689
|
+
if (!Array.isArray(existing))
|
|
690
|
+
return;
|
|
691
|
+
container[arrayKey] = existing.filter((x) => !removeSet.has(String(x)));
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
if (sqliteOp.remove_commands?.length &&
|
|
695
|
+
(sqliteOp.json_path ?? '').includes('yoloCommandAllowlist') &&
|
|
696
|
+
!sqliteOp.array_remove?.length) {
|
|
697
|
+
const jp = (sqliteOp.json_path ?? '').trim();
|
|
698
|
+
const parts = jp.split('.').filter(Boolean);
|
|
699
|
+
if (parts.length === 0)
|
|
700
|
+
return;
|
|
701
|
+
let node = currentJson;
|
|
702
|
+
for (const p of parts.slice(0, -1)) {
|
|
703
|
+
if (node == null || typeof node !== 'object' || Array.isArray(node))
|
|
704
|
+
return;
|
|
705
|
+
node = node[p];
|
|
706
|
+
}
|
|
707
|
+
if (node == null || typeof node !== 'object' || Array.isArray(node))
|
|
708
|
+
return;
|
|
709
|
+
const container = node;
|
|
710
|
+
const arrayKey = parts[parts.length - 1];
|
|
711
|
+
const existing = container[arrayKey];
|
|
712
|
+
if (!Array.isArray(existing))
|
|
713
|
+
return;
|
|
714
|
+
const tokens = sqliteOp.remove_commands.map((t) => String(t).trim()).filter(Boolean);
|
|
715
|
+
container[arrayKey] = existing.filter((entry) => !tokens.some((t) => allowlistEntryMatchesRemoveToken(entry, t)));
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
648
718
|
const where = sqliteOp.array_item_where;
|
|
649
719
|
const jp = sqliteOp.json_path ?? '';
|
|
650
720
|
if (where && typeof where === 'object' && Object.keys(where).length > 0 && jp) {
|
|
@@ -32,10 +32,16 @@ function currentAgentFromEnv() {
|
|
|
32
32
|
const override = normalizeAgentToken(process.env.OPTIMUS_AGENT);
|
|
33
33
|
if (override === 'cursor')
|
|
34
34
|
return 'cursor';
|
|
35
|
+
if (override === 'copilot')
|
|
36
|
+
return 'copilot';
|
|
35
37
|
if (override === 'claude' || override === 'claude_desktop')
|
|
36
38
|
return 'claude';
|
|
37
39
|
const hookType = normalizeAgentToken(process.env.OPTIMUS_HOOK_TYPE);
|
|
38
|
-
|
|
40
|
+
if (hookType === 'cursor')
|
|
41
|
+
return 'cursor';
|
|
42
|
+
if (hookType === 'copilot')
|
|
43
|
+
return 'copilot';
|
|
44
|
+
return 'claude';
|
|
39
45
|
}
|
|
40
46
|
/** Spawn each trusted command detached (same pattern as former compliance_prompt_gate fireRestartCommands). */
|
|
41
47
|
export function executeTrustedRestartCommands(commands) {
|
package/dist/tofu_environment.js
CHANGED
|
@@ -7,25 +7,30 @@ export function managementEnvPath() {
|
|
|
7
7
|
return null;
|
|
8
8
|
return path.join(home, 'opt-ai-sec', 'management', 'optimus_dev.env');
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Resolve optimus environment label (matches shell hooks / optimus-tool-call):
|
|
12
|
+
* - No management file / unreadable → production (ignores OPTIMUS_ENVIRONMENT).
|
|
13
|
+
* - File present → environment= line in file, then OPTIMUS_ENVIRONMENT, then production.
|
|
14
|
+
*/
|
|
11
15
|
export function readEnvironment() {
|
|
12
16
|
const envPath = managementEnvPath();
|
|
13
17
|
if (!envPath) {
|
|
14
|
-
return '
|
|
18
|
+
return 'production';
|
|
15
19
|
}
|
|
20
|
+
let content;
|
|
16
21
|
try {
|
|
17
|
-
|
|
18
|
-
const match = content.match(/^(?:environment|ENVIRONMENT|OPTIMUS_ENVIRONMENT)\s*=\s*(.+)/m);
|
|
19
|
-
if (match)
|
|
20
|
-
return match[1].trim().toLowerCase();
|
|
22
|
+
content = readFileSync(envPath, 'utf8');
|
|
21
23
|
}
|
|
22
24
|
catch {
|
|
23
|
-
return '
|
|
25
|
+
return 'production';
|
|
24
26
|
}
|
|
27
|
+
const match = content?.match(/^(?:environment|ENVIRONMENT|OPTIMUS_ENVIRONMENT)\s*=\s*(.+)/m);
|
|
28
|
+
if (match)
|
|
29
|
+
return match[1].trim().toLowerCase();
|
|
25
30
|
const fromEnv = process.env.OPTIMUS_ENVIRONMENT?.trim().toLowerCase();
|
|
26
31
|
if (fromEnv)
|
|
27
32
|
return fromEnv;
|
|
28
|
-
return '
|
|
33
|
+
return 'production';
|
|
29
34
|
}
|
|
30
35
|
export function shouldUseLocalTofuDist(localTofuPath) {
|
|
31
36
|
return readEnvironment() === 'development' && existsSync(localTofuPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "log-llm-config",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.94",
|
|
4
4
|
"description": "CLI helpers for logging hardware UUIDs and posting startup payloads to Optimus Security.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -58,6 +58,6 @@
|
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"axios": "^1.15.2",
|
|
60
60
|
"canonicalize": "^2.1.0",
|
|
61
|
-
"optimus-tofu": "
|
|
61
|
+
"optimus-tofu": "^0.1.17"
|
|
62
62
|
}
|
|
63
63
|
}
|