log-llm-config-staging 1.4.2 → 1.4.4

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,98 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ export const SKILLS_CLI_FILE_TYPE = 'skills_cli_installed';
5
+ export const SKILLS_CLI_INSTALLED_PATH = join(homedir(), '.agents', '.skills-cli-installed.json');
6
+ /** Override the skills package spec, e.g. `skills@1.5.10`. Default uses whatever is on the machine. */
7
+ export const SKILLS_CLI_NPX_PACKAGE_ENV = 'SKILLS_CLI_NPX_PACKAGE';
8
+ const LIST_TIMEOUT_MS = 120_000;
9
+ function listExecEnv() {
10
+ return {
11
+ ...process.env,
12
+ DISABLE_TELEMETRY: process.env.DISABLE_TELEMETRY ?? '1',
13
+ };
14
+ }
15
+ function npxPackageSpec() {
16
+ const fromEnv = (process.env[SKILLS_CLI_NPX_PACKAGE_ENV] || '').trim();
17
+ return fromEnv || 'skills';
18
+ }
19
+ export function runSkillsListJson(args, cwd) {
20
+ const out = execFileSync('npx', [npxPackageSpec(), 'list', ...args, '--json'], {
21
+ encoding: 'utf8',
22
+ cwd,
23
+ timeout: LIST_TIMEOUT_MS,
24
+ env: listExecEnv(),
25
+ });
26
+ const trimmed = out.trim();
27
+ if (!trimmed)
28
+ return [];
29
+ const parsed = JSON.parse(trimmed);
30
+ if (!Array.isArray(parsed))
31
+ return [];
32
+ return parsed;
33
+ }
34
+ function normalizeListRows(rows, scope) {
35
+ const out = [];
36
+ for (const row of rows) {
37
+ const name = (row.name || '').trim();
38
+ const path = (row.path || '').trim();
39
+ if (!name || !path)
40
+ continue;
41
+ const agents = Array.isArray(row.agents)
42
+ ? row.agents.map((a) => String(a).trim()).filter(Boolean)
43
+ : [];
44
+ out.push({
45
+ name,
46
+ path,
47
+ scope: (row.scope || scope).trim() || scope,
48
+ agents,
49
+ });
50
+ }
51
+ return out;
52
+ }
53
+ /** One-line-per-scope summary for hook_request.log (matches `skills list --json`). */
54
+ export function formatSkillsListScopeForHookLog(scopeLabel, entries) {
55
+ if (entries.length === 0) {
56
+ return `skills_cli list ${scopeLabel}: 0 skill(s)`;
57
+ }
58
+ const detail = entries
59
+ .map((e) => `${e.name}@${e.path} agents=${e.agents.length ? e.agents.join(',') : 'none'}`)
60
+ .join(' | ');
61
+ return `skills_cli list ${scopeLabel}: ${entries.length} skill(s) — ${detail}`;
62
+ }
63
+ export function collectSkillsCliInstalled(projectRoot, log) {
64
+ const logLine = (message) => {
65
+ log?.(message);
66
+ };
67
+ try {
68
+ const packageSpec = npxPackageSpec();
69
+ logLine(`skills_cli: npx ${packageSpec} — list -g --json && list --json (projectRoot=${projectRoot})`);
70
+ const globalRows = runSkillsListJson(['-g'], projectRoot);
71
+ const projectRows = runSkillsListJson([], projectRoot);
72
+ const global = normalizeListRows(globalRows, 'global');
73
+ const project = normalizeListRows(projectRows, 'project');
74
+ logLine(formatSkillsListScopeForHookLog('-g', global));
75
+ logLine(formatSkillsListScopeForHookLog('project', project));
76
+ const payload = {
77
+ version: 1,
78
+ skills_cli_version: packageSpec,
79
+ generated_at: new Date().toISOString(),
80
+ global,
81
+ project,
82
+ };
83
+ if (payload.global.length === 0 && payload.project.length === 0) {
84
+ logLine('skills_cli_installed: not uploaded (no global or project skills)');
85
+ return null;
86
+ }
87
+ logLine(`skills_cli_installed: upload ${SKILLS_CLI_INSTALLED_PATH} global=${global.length} project=${project.length}`);
88
+ return {
89
+ file_type: SKILLS_CLI_FILE_TYPE,
90
+ file_path: SKILLS_CLI_INSTALLED_PATH,
91
+ raw_content: payload,
92
+ };
93
+ }
94
+ catch (err) {
95
+ logLine(`skills_cli: list --json failed: ${err instanceof Error ? err.message : String(err)}`);
96
+ return null;
97
+ }
98
+ }
@@ -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;
@@ -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;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * npm/npx --package=... sets lifecycle env vars that break unrelated scoped `npx @scope/pkg`
3
+ * (e.g. MCP servers). Strip before spawning shells that may reopen Cursor or run other npx.
4
+ */
5
+ export const NPM_EXEC_POLLUTION_KEYS = [
6
+ 'npm_command',
7
+ 'npm_config_local_prefix',
8
+ 'npm_config_package',
9
+ 'npm_lifecycle_event',
10
+ 'npm_lifecycle_script',
11
+ 'npm_lifecycle_inject_node',
12
+ 'npm_package_json',
13
+ 'npm_execpath',
14
+ 'npm_config_yes',
15
+ 'npm_config_node_gyp',
16
+ 'npm_config_npm_version',
17
+ 'npm_config_user_agent',
18
+ 'NPM_COMMAND',
19
+ 'NPM_CONFIG_LOCAL_PREFIX',
20
+ 'NPM_CONFIG_PACKAGE',
21
+ 'NPM_LIFECYCLE_EVENT',
22
+ 'NPM_LIFECYCLE_SCRIPT',
23
+ 'NPM_EXECPATH',
24
+ 'NPM_CONFIG_YES',
25
+ ];
26
+ export function envWithoutNpmExecPollution(env) {
27
+ const clean = { ...env };
28
+ for (const key of NPM_EXEC_POLLUTION_KEYS) {
29
+ delete clean[key];
30
+ }
31
+ return clean;
32
+ }
33
+ /** Bash prefix: unset lifecycle vars before running npx in deferred restart shells. */
34
+ export const DEFERRED_RESTART_UNSET_NPM_ENV = 'unset npm_command npm_config_local_prefix npm_config_package npm_lifecycle_event ' +
35
+ 'npm_lifecycle_script npm_lifecycle_inject_node npm_package_json npm_execpath npm_config_yes ' +
36
+ 'npm_config_node_gyp npm_config_npm_version npm_config_user_agent ' +
37
+ 'NPM_COMMAND NPM_CONFIG_LOCAL_PREFIX NPM_CONFIG_PACKAGE NPM_LIFECYCLE_EVENT NPM_LIFECYCLE_SCRIPT NPM_EXECPATH 2>/dev/null; ';
38
+ /** Bash wrapper for npx in deferred restart (env -u strips inherited npm exec state). */
39
+ export const DEFERRED_RESTART_NPX = 'env -u npm_command -u npm_config_local_prefix -u npm_config_package -u npm_lifecycle_event ' +
40
+ '-u npm_lifecycle_script -u npm_lifecycle_inject_node -u npm_package_json -u npm_execpath npx';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "log-llm-config-staging",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "CLI helpers for logging hardware UUIDs and posting startup payloads to Optimus Security.",
5
5
  "type": "module",
6
6
  "bin": {