log-llm-config 1.4.9 → 1.4.10

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.
@@ -39,6 +39,8 @@ function parseIde() {
39
39
  return 'claude';
40
40
  if (v === 'copilot')
41
41
  return 'copilot';
42
+ if (v === 'opencode')
43
+ return 'opencode';
42
44
  return 'cursor';
43
45
  }
44
46
  if (process.argv.includes('--claude'))
@@ -48,6 +50,8 @@ function parseIde() {
48
50
  function defaultAgentFromIde(ide) {
49
51
  if (ide === 'copilot')
50
52
  return 'copilot';
53
+ if (ide === 'opencode')
54
+ return 'opencode';
51
55
  return ide === 'claude' ? 'claude' : 'cursor';
52
56
  }
53
57
  function parseAgent(ide) {
@@ -60,13 +64,13 @@ function parseAgent(ide) {
60
64
  return defaultAgentFromIde(ide);
61
65
  }
62
66
  function printAllow(ide) {
63
- if (ide === 'claude' || ide === 'copilot')
67
+ if (ide === 'claude' || ide === 'copilot' || ide === 'opencode')
64
68
  console.log('{}');
65
69
  else
66
70
  console.log(JSON.stringify({ continue: true }));
67
71
  }
68
72
  function printAllowWithAdvisory(ide, advisoryMessage) {
69
- if (ide === 'claude' || ide === 'copilot') {
73
+ if (ide === 'claude' || ide === 'copilot' || ide === 'opencode') {
70
74
  console.log(JSON.stringify({ __optimus_advisory: true, advisory_message: advisoryMessage }));
71
75
  }
72
76
  else {
@@ -80,7 +84,7 @@ function printAllowWithAdvisory(ide, advisoryMessage) {
80
84
  function blockPayload(ide, violationMessage) {
81
85
  const prefix = 'Prompt blocked by Optimus: ';
82
86
  const text = prefix + violationMessage;
83
- if (ide === 'claude' || ide === 'copilot') {
87
+ if (ide === 'claude' || ide === 'copilot' || ide === 'opencode') {
84
88
  return JSON.stringify({ decision: 'block', reason: text, systemMessage: text });
85
89
  }
86
90
  return JSON.stringify({ continue: false, user_message: text });
@@ -138,6 +142,10 @@ export function formatClaudeAutofixDialog(appliedViolations) {
138
142
  export function formatCopilotAutofixDialog(appliedViolations) {
139
143
  return formatPreventiveAutofixDialog(appliedViolations, 'Copilot will now apply this policy to your environment.');
140
144
  }
145
+ /** OpenCode dialog after enforced/preventive remediation is applied locally (terminal, no restart). */
146
+ export function formatOpenCodeAutofixDialog(appliedViolations) {
147
+ return formatPreventiveAutofixDialog(appliedViolations, 'OpenCode will now apply this policy to your environment.');
148
+ }
141
149
  /** Cursor restart dialog after enforced/preventive remediation is applied locally. */
142
150
  export function formatCursorRestartAutofixDialog(appliedViolations) {
143
151
  return formatPreventiveAutofixDialog(appliedViolations, 'Cursor will now restart to apply this policy, and your context will be retained.');
@@ -270,7 +278,7 @@ export async function runCompliancePromptGate() {
270
278
  // Claude / Copilot: JSON remediations are written immediately; merge/verify timing can still leave
271
279
  // the in-process recheck red for the same UUID — allow in that case for immediate JSON agents.
272
280
  const appliedUuids = new Set(appliedViolations.map((v) => v.uuid));
273
- const claudeRecheckStaleAfterImmediateApply = (ide === 'claude' || ide === 'copilot') &&
281
+ const claudeRecheckStaleAfterImmediateApply = (ide === 'claude' || ide === 'copilot' || ide === 'opencode') &&
274
282
  !recheckOk &&
275
283
  recheck.violations.length > 0 &&
276
284
  recheck.violations.every((v) => appliedUuids.has(v.uuid));
@@ -294,9 +302,11 @@ export async function runCompliancePromptGate() {
294
302
  ? formatClaudeAutofixDialog(appliedViolations)
295
303
  : ide === 'copilot'
296
304
  ? formatCopilotAutofixDialog(appliedViolations)
297
- : `Optimus Labs auto-fixed ${fixed} ${fixed === 1 ? 'policy violation' : 'policy violations'}:\n\n${appliedViolations
298
- .map((v) => autofixDialogLine(v))
299
- .join('\n')}${changePreviewSuffix}`;
305
+ : ide === 'opencode'
306
+ ? formatOpenCodeAutofixDialog(appliedViolations)
307
+ : `Optimus Labs auto-fixed ${fixed} ${fixed === 1 ? 'policy violation' : 'policy violations'}:\n\n${appliedViolations
308
+ .map((v) => autofixDialogLine(v))
309
+ .join('\n')}${changePreviewSuffix}`;
300
310
  const payload = { __optimus_autofix: true, autofix_message: autofixMessage };
301
311
  if (restartCommands.length > 0)
302
312
  payload.restart_commands = restartCommands;
@@ -69,7 +69,7 @@ function buildCollectionContext(patterns, projectRoot, home, homeRecurseSkipDirs
69
69
  t.dir_subdir_filename = p.dir_subdir_filename;
70
70
  if (p.dir_subdir_source)
71
71
  t.dir_subdir_source = p.dir_subdir_source;
72
- const key = `${t.path}\t${t.file_type}`;
72
+ const key = `${t.path}\t${t.file_type}\t${t.isDirectory ? 'dir' : 'file'}\t${t.dir_glob ?? ''}`;
73
73
  if (!seenPaths.has(key)) {
74
74
  seenPaths.add(key);
75
75
  targets.push(t);
@@ -176,4 +176,4 @@ function readInstalledExtensions(extensionsCachePath) {
176
176
  }
177
177
  return extensions;
178
178
  }
179
- export { readMCPConfig, readJSONFile, readMarkdownFile, readInstalledExtensions };
179
+ export { readMCPConfig, readJSONFile, readMarkdownFile, readInstalledExtensions, parseJsonWithJsoncFallback, };
@@ -13,6 +13,7 @@
13
13
  import { existsSync, readFileSync } from 'node:fs';
14
14
  import { homedir } from 'node:os';
15
15
  import { join } from 'node:path';
16
+ import { parseJsonWithJsoncFallback } from '../readers/file_readers.js';
16
17
  import { mergeComposerShadowKeysFromReactiveBlob, readVscdbItemTableJson, } from '../readers/vscdb_reader.js';
17
18
  import { readRemediationInstructionsFile, writeRemediationInstructionsFile, } from './management_storage.js';
18
19
  import { resolveRemediationConfigPath } from './remediation_config_path.js';
@@ -294,12 +295,12 @@ function loadRemediationConfigJson(configFilePath, checkSettingPaths = []) {
294
295
  }
295
296
  if (!existsSync(resolvedPath))
296
297
  return { ok: false, reason: 'file_not_found' };
297
- try {
298
- return { ok: true, json: JSON.parse(readFileSync(resolvedPath, 'utf8')) };
299
- }
300
- catch {
298
+ // OpenCode configs are JSONC (comments / trailing commas); parse strict JSON first, then
299
+ // fall back to JSONC sanitization so comment-bearing files are not skipped as parse_error.
300
+ const parsed = parseJsonWithJsoncFallback(readFileSync(resolvedPath, 'utf8'));
301
+ if (parsed === null)
301
302
  return { ok: false, reason: 'parse_error' };
302
- }
303
+ return { ok: true, json: parsed };
303
304
  }
304
305
  /**
305
306
  * Evaluate all checks in a secondary group against the group's config file.
@@ -678,12 +679,8 @@ export function applyAutofixViolations(violations, agent = 'cursor') {
678
679
  updatedContent = itemKey ? (readVscdbItemTableJson(dbPath, itemKey) ?? undefined) : undefined;
679
680
  }
680
681
  else {
681
- try {
682
- updatedContent = JSON.parse(readFileSync(configPathForDisk, 'utf8'));
683
- }
684
- catch {
685
- updatedContent = undefined;
686
- }
682
+ updatedContent =
683
+ parseJsonWithJsoncFallback(readFileSync(configPathForDisk, 'utf8')) ?? undefined;
687
684
  }
688
685
  if (updatedContent !== undefined) {
689
686
  const fileType = (inst.file_type ?? '').trim();
@@ -906,11 +903,8 @@ export function uploadSatisfiedManifestConfigs(agent = 'cursor') {
906
903
  continue;
907
904
  }
908
905
  }
909
- let rawContent;
910
- try {
911
- rawContent = JSON.parse(readFileSync(diskPath, 'utf8'));
912
- }
913
- catch {
906
+ const rawContent = parseJsonWithJsoncFallback(readFileSync(diskPath, 'utf8'));
907
+ if (rawContent === null) {
914
908
  hookRunLog(`satisfied_upload: could not read path=${diskPath} uuid=${entry.uuid}`);
915
909
  continue;
916
910
  }
@@ -3,6 +3,7 @@ import { delimiter, dirname, join } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  import { execFileSync } from 'node:child_process';
5
5
  import { executeBody } from '../../endpoint_client/http_transport.js';
6
+ import { parseJsonWithJsoncFallback } from '../readers/file_readers.js';
6
7
  import { complianceRunnerDiag, hookRunLog, logRemediationApplyFailure } from './hook_logger.js';
7
8
  import { atomicWriteJson, getDeferredVscdbApplyPath, getFileCollectionVscdbContractPath, getRemediationInstructionsPath, readRemediationInstructionsFile, writeRemediationInstructionsFile, } from './management_storage.js';
8
9
  import { readStoredAuthKey } from '../auth/auth_key_store.js';
@@ -1396,10 +1397,16 @@ export function enforceRemediation(instruction) {
1396
1397
  }
1397
1398
  let configJson = {};
1398
1399
  if (existsSync(inst.config_file_path)) {
1399
- try {
1400
- configJson = JSON.parse(readFileSync(inst.config_file_path, 'utf8'));
1400
+ // OpenCode configs are JSONC; parse strict JSON first, then JSONC fallback so a
1401
+ // comment-bearing opencode.json(c) keeps its existing settings instead of being reset to
1402
+ // {} on a parse failure. NOTE: the write-back below is JSON.stringify, so comments and
1403
+ // trailing commas in the original file are not preserved (same as every other agent's
1404
+ // JSON config) — only the key/value settings are retained and patched.
1405
+ const parsed = parseJsonWithJsoncFallback(readFileSync(inst.config_file_path, 'utf8'));
1406
+ if (parsed !== null) {
1407
+ configJson = parsed;
1401
1408
  }
1402
- catch {
1409
+ else {
1403
1410
  hookRunLog(`remediation_enforce: could not parse existing file, starting fresh uuid=${inst.uuid}`);
1404
1411
  }
1405
1412
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "log-llm-config",
3
- "version": "1.4.9",
3
+ "version": "1.4.10",
4
4
  "description": "CLI helpers for logging hardware UUIDs and posting startup payloads to Optimus Security.",
5
5
  "type": "module",
6
6
  "bin": {