log-llm-config-staging 1.4.1 → 1.4.3

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.
@@ -0,0 +1,134 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { createRequire } from 'node:module';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ export const SKILLS_CLI_FILE_TYPE = 'skills_cli_installed';
7
+ export const SKILLS_CLI_INSTALLED_PATH = join(homedir(), '.agents', '.skills-cli-installed.json');
8
+ /** Override fallback runner, e.g. `skills@1.5.10`. Default `skills` uses the machine npx cache. */
9
+ export const SKILLS_CLI_NPX_PACKAGE_ENV = 'SKILLS_CLI_NPX_PACKAGE';
10
+ const LIST_TIMEOUT_MS = 120_000;
11
+ function listExecEnv() {
12
+ return {
13
+ ...process.env,
14
+ DISABLE_TELEMETRY: process.env.DISABLE_TELEMETRY ?? '1',
15
+ };
16
+ }
17
+ function npxPackageSpecForFallback() {
18
+ const fromEnv = (process.env[SKILLS_CLI_NPX_PACKAGE_ENV] || '').trim();
19
+ return fromEnv || 'skills';
20
+ }
21
+ function resolveSkillsListRunner() {
22
+ try {
23
+ const require = createRequire(fileURLToPath(import.meta.url));
24
+ const bin = require.resolve('skills/bin/cli.mjs');
25
+ const pkg = require('skills/package.json');
26
+ const version = (pkg.version || '').trim() || 'unknown';
27
+ return { mode: 'bundled', bin, version };
28
+ }
29
+ catch {
30
+ return { mode: 'npx', packageSpec: npxPackageSpecForFallback() };
31
+ }
32
+ }
33
+ function execSkillsList(args, cwd, runner) {
34
+ if (runner.mode === 'bundled') {
35
+ return execFileSync(process.execPath, [runner.bin, 'list', ...args, '--json'], {
36
+ encoding: 'utf8',
37
+ cwd,
38
+ timeout: LIST_TIMEOUT_MS,
39
+ env: listExecEnv(),
40
+ });
41
+ }
42
+ return execFileSync('npx', ['--yes', runner.packageSpec, 'list', ...args, '--json'], {
43
+ encoding: 'utf8',
44
+ cwd,
45
+ timeout: LIST_TIMEOUT_MS,
46
+ env: listExecEnv(),
47
+ });
48
+ }
49
+ function runnerVersionLabel(runner) {
50
+ if (runner.mode === 'bundled')
51
+ return runner.version;
52
+ return `npx:${runner.packageSpec}`;
53
+ }
54
+ export function runSkillsListJson(args, cwd) {
55
+ const runner = resolveSkillsListRunner();
56
+ const out = execSkillsList(args, cwd, runner);
57
+ const trimmed = out.trim();
58
+ if (!trimmed)
59
+ return [];
60
+ const parsed = JSON.parse(trimmed);
61
+ if (!Array.isArray(parsed))
62
+ return [];
63
+ return parsed;
64
+ }
65
+ function normalizeListRows(rows, scope) {
66
+ const out = [];
67
+ for (const row of rows) {
68
+ const name = (row.name || '').trim();
69
+ const path = (row.path || '').trim();
70
+ if (!name || !path)
71
+ continue;
72
+ const agents = Array.isArray(row.agents)
73
+ ? row.agents.map((a) => String(a).trim()).filter(Boolean)
74
+ : [];
75
+ out.push({
76
+ name,
77
+ path,
78
+ scope: (row.scope || scope).trim() || scope,
79
+ agents,
80
+ });
81
+ }
82
+ return out;
83
+ }
84
+ /** One-line-per-scope summary for hook_request.log (matches `skills list --json`). */
85
+ export function formatSkillsListScopeForHookLog(scopeLabel, entries) {
86
+ if (entries.length === 0) {
87
+ return `skills_cli list ${scopeLabel}: 0 skill(s)`;
88
+ }
89
+ const detail = entries
90
+ .map((e) => `${e.name}@${e.path} agents=${e.agents.length ? e.agents.join(',') : 'none'}`)
91
+ .join(' | ');
92
+ return `skills_cli list ${scopeLabel}: ${entries.length} skill(s) — ${detail}`;
93
+ }
94
+ export function collectSkillsCliInstalled(projectRoot, log) {
95
+ const logLine = (message) => {
96
+ log?.(message);
97
+ };
98
+ try {
99
+ const runner = resolveSkillsListRunner();
100
+ if (runner.mode === 'bundled') {
101
+ logLine(`skills_cli: bundled skills@${runner.version} — list -g --json && list --json (projectRoot=${projectRoot})`);
102
+ }
103
+ else {
104
+ logLine(`skills_cli: npx fallback (${runner.packageSpec}) — no bundled skills dep; uses machine npx cache (projectRoot=${projectRoot})`);
105
+ }
106
+ const globalRows = runSkillsListJson(['-g'], projectRoot);
107
+ const projectRows = runSkillsListJson([], projectRoot);
108
+ const global = normalizeListRows(globalRows, 'global');
109
+ const project = normalizeListRows(projectRows, 'project');
110
+ logLine(formatSkillsListScopeForHookLog('-g', global));
111
+ logLine(formatSkillsListScopeForHookLog('project', project));
112
+ const payload = {
113
+ version: 1,
114
+ skills_cli_version: runnerVersionLabel(runner),
115
+ generated_at: new Date().toISOString(),
116
+ global,
117
+ project,
118
+ };
119
+ if (payload.global.length === 0 && payload.project.length === 0) {
120
+ logLine('skills_cli_installed: not uploaded (no global or project skills)');
121
+ return null;
122
+ }
123
+ logLine(`skills_cli_installed: upload ${SKILLS_CLI_INSTALLED_PATH} global=${global.length} project=${project.length}`);
124
+ return {
125
+ file_type: SKILLS_CLI_FILE_TYPE,
126
+ file_path: SKILLS_CLI_INSTALLED_PATH,
127
+ raw_content: payload,
128
+ };
129
+ }
130
+ catch (err) {
131
+ logLine(`skills_cli: list --json failed: ${err instanceof Error ? err.message : String(err)}`);
132
+ return null;
133
+ }
134
+ }
@@ -8,6 +8,20 @@ function normalizePathSkipPrefixes(prefixes) {
8
8
  return [];
9
9
  return prefixes.filter((p) => typeof p === 'string' && p.length > 0);
10
10
  }
11
+ /**
12
+ * Expands glob patterns with double-asterisk (recursive directory traversal).
13
+ *
14
+ * Example: ~/.cursor/plugins/cache/ ** /skills/ recursively finds all skills
15
+ * directories under the cache folder, up to RECURSIVE_GLOB_MAX_DEPTH.
16
+ *
17
+ * @param pathPattern - Glob pattern with double-asterisk for recursive descent
18
+ * @param fileType - File type classification for collected targets
19
+ * @param home - User home directory path
20
+ * @param contentFormat - Optional content format hint
21
+ * @param dirGlob - Optional directory glob pattern
22
+ * @param homeRecurseSkipDirs - Directory names to skip when recursing from home
23
+ * @returns Array of collection targets matching the pattern
24
+ */
11
25
  function expandRecursiveGlobPathPattern(pathPattern, fileType, home, contentFormat, dirGlob, homeRecurseSkipDirs = []) {
12
26
  const norm = pathPattern.replace(/\\/g, '/');
13
27
  const doubleStarIndex = norm.indexOf('**');
@@ -67,6 +81,29 @@ function expandRecursiveGlobPathPattern(pathPattern, fileType, home, contentForm
67
81
  walk(basePath, 0, true);
68
82
  return targets;
69
83
  }
84
+ /**
85
+ * Expands glob patterns with asterisk wildcards (non-recursive) into concrete file or directory paths.
86
+ *
87
+ * Supports multi-segment wildcards (e.g., star-slash-star-slash patterns).
88
+ *
89
+ * **Key behavior:** On the final segment of a file pattern (not ending in slash), wildcards match
90
+ * files; earlier segments—and directory patterns—match directories only. This allows filename
91
+ * patterns like local_STAR.json to correctly resolve files rather than directories.
92
+ *
93
+ * Examples:
94
+ * - Pattern ending with a literal filename matches that file in wildcard directories
95
+ * - Pattern ending with prefix_STAR.ext matches files with that prefix and extension
96
+ * - Pattern ending with slash matches only directories
97
+ *
98
+ * @param pathPattern - Glob pattern (may not contain double-asterisk; use expandRecursiveGlobPathPattern for that)
99
+ * @param fileType - File type classification for collected targets
100
+ * @param home - User home directory path
101
+ * @param projectRoot - Project root directory path
102
+ * @param contentFormat - Optional content format hint
103
+ * @param dirGlob - Optional directory glob pattern
104
+ * @param absolutePathPrefixes - Allowed absolute path prefixes
105
+ * @returns Array of collection targets matching the pattern
106
+ */
70
107
  function expandGlobPathPattern(pathPattern, fileType, home, projectRoot, contentFormat, dirGlob, absolutePathPrefixes = []) {
71
108
  const norm = pathPattern.replace(/\\/g, '/');
72
109
  if (!norm.includes('*'))
@@ -111,12 +148,16 @@ function expandGlobPathPattern(pathPattern, fileType, home, projectRoot, content
111
148
  return;
112
149
  }
113
150
  const seg = segments[segIdx];
151
+ // On the final segment of a file pattern (not ending in `/`) wildcards
152
+ // match files; earlier segments — and dir patterns — match directories.
153
+ const wantFile = segIdx === segments.length - 1 && !isDir;
154
+ const entryMatches = (entry) => wantFile ? entry.isFile() : entry.isDirectory();
114
155
  if (seg === '*') {
115
156
  if (!existsSync(currentPath))
116
157
  return;
117
158
  try {
118
159
  for (const entry of readdirSync(currentPath, { withFileTypes: true })) {
119
- if (!entry.isDirectory())
160
+ if (!entryMatches(entry))
120
161
  continue;
121
162
  recurse(join(currentPath, entry.name), segIdx + 1);
122
163
  }
@@ -131,7 +172,7 @@ function expandGlobPathPattern(pathPattern, fileType, home, projectRoot, content
131
172
  return;
132
173
  try {
133
174
  for (const entry of readdirSync(currentPath, { withFileTypes: true })) {
134
- if (!entry.isDirectory())
175
+ if (!entryMatches(entry))
135
176
  continue;
136
177
  if (prefix && !entry.name.startsWith(prefix))
137
178
  continue;
@@ -43,6 +43,8 @@ export function normalizeAgentToken(raw) {
43
43
  return 'copilot';
44
44
  if (s === 'opencode')
45
45
  return 'opencode';
46
+ if (s === 'codex')
47
+ return 'codex';
46
48
  return '';
47
49
  }
48
50
  function currentAgentFromEnv() {
@@ -58,6 +60,8 @@ function currentAgentFromEnv() {
58
60
  return 'copilot';
59
61
  if (hookType === 'opencode')
60
62
  return 'opencode';
63
+ if (hookType === 'codex')
64
+ return 'codex';
61
65
  return 'claude';
62
66
  }
63
67
  function targetsCurrentAgent(entry, agent) {
@@ -6,7 +6,7 @@ function normalizeToken(raw) {
6
6
  return 'claude';
7
7
  if (s === 'github_copilot')
8
8
  return 'copilot';
9
- if (s === 'cursor' || s === 'claude' || s === 'copilot' || s === 'opencode')
9
+ if (s === 'cursor' || s === 'claude' || s === 'copilot' || s === 'opencode' || s === 'codex')
10
10
  return s;
11
11
  // Legacy hooks set OPTIMUS_AGENT=Cursor (display casing)
12
12
  if (raw.trim() === 'Cursor')
@@ -24,5 +24,7 @@ export function resolveHookTypeFromEnv(env = process.env) {
24
24
  return 'copilot';
25
25
  if (token === 'opencode')
26
26
  return 'opencode';
27
+ if (token === 'codex')
28
+ return 'codex';
27
29
  return 'claude';
28
30
  }
@@ -14,6 +14,7 @@ import { readJSONFile, readMarkdownFile } from '../readers/file_readers.js';
14
14
  import { isVscdbVirtualPath, tryReadVscdbVirtualFile, summarizeComposerPayloadForDiagnostics, } from '../readers/vscdb_config_builder.js';
15
15
  import { persistVscdbComposerContractFromPatternsResponse } from '../readers/vscdb_reader.js';
16
16
  import { collectConfigFilesFromPatterns, collectMcpToolFiles, collectConfigFilesFromInstalledPlugins, collectPluginCacheMcpFiles, collectMcpFromClaudeJsonProjects, collectClaudeDesktopExtensionManifests, collectClaudeDesktopExtensionSettingsFiles, enrichClaudeDesktopExtensionsInstallationsUpload, determineFileTypeFromPath, } from '../collection/config_collector.js';
17
+ import { collectSkillsCliInstalled } from '../collection/skills_cli_collector.js';
17
18
  import { collectWorkspaceVscdbs } from '../collection/mcp_tool_collector.js';
18
19
  import { collectCursorProjectWorkspaceMcpConfigs } from '../collection/cursor_project_mcp_collector.js';
19
20
  import { normalizePathSkipPrefixes } from '../paths/pattern_resolver.js';
@@ -115,6 +116,14 @@ async function collectAllConfigFiles(endpointBase) {
115
116
  }
116
117
  }
117
118
  }
119
+ const skillsCliEntry = collectSkillsCliInstalled(PROJECT_ROOT, hookRunLog);
120
+ if (skillsCliEntry) {
121
+ const key = `${skillsCliEntry.file_type}\t${skillsCliEntry.file_path}`;
122
+ if (!existingPaths.has(key)) {
123
+ existingPaths.add(key);
124
+ configFiles.push(skillsCliEntry);
125
+ }
126
+ }
118
127
  const worktreeReport = scanActiveWorktrees(PROJECT_ROOT, HOME_DIR);
119
128
  const activeRoots = activeWorktreeRootSet(worktreeReport);
120
129
  const beforeFilter = configFiles.length;
@@ -36,6 +36,8 @@ function currentAgentFromEnv() {
36
36
  return 'copilot';
37
37
  if (override === 'opencode')
38
38
  return 'opencode';
39
+ if (override === 'codex')
40
+ return 'codex';
39
41
  if (override === 'claude' || override === 'claude_desktop')
40
42
  return 'claude';
41
43
  const hookType = normalizeAgentToken(process.env.OPTIMUS_HOOK_TYPE);
@@ -45,6 +47,8 @@ function currentAgentFromEnv() {
45
47
  return 'copilot';
46
48
  if (hookType === 'opencode')
47
49
  return 'opencode';
50
+ if (hookType === 'codex')
51
+ return 'codex';
48
52
  return 'claude';
49
53
  }
50
54
  /** Spawn each trusted command detached (same pattern as former compliance_prompt_gate fireRestartCommands). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "log-llm-config-staging",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
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,7 @@
58
58
  "dependencies": {
59
59
  "axios": "^1.15.2",
60
60
  "canonicalize": "^2.1.0",
61
- "optimus-tofu-staging": "^0.1.17"
61
+ "optimus-tofu-staging": "^0.1.17",
62
+ "skills": "1.5.10"
62
63
  }
63
64
  }