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
|
|
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
|
|
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';
|