@laitszkin/apollo-toolkit 3.13.2 → 3.14.1
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/AGENTS.md +7 -7
- package/CHANGELOG.md +36 -0
- package/CLAUDE.md +8 -8
- package/analyse-app-logs/SKILL.md +3 -3
- package/bin/apollo-toolkit.ts +7 -0
- package/codex/codex-memory-manager/SKILL.md +2 -2
- package/codex/learn-skill-from-conversations/SKILL.md +3 -3
- package/dist/bin/apollo-toolkit.d.ts +2 -0
- package/dist/bin/apollo-toolkit.js +7 -0
- package/dist/lib/cli.d.ts +41 -0
- package/dist/lib/cli.js +655 -0
- package/dist/lib/installer.d.ts +59 -0
- package/dist/lib/installer.js +404 -0
- package/dist/lib/tool-runner.d.ts +19 -0
- package/dist/lib/tool-runner.js +536 -0
- package/dist/lib/tools/architecture.d.ts +2 -0
- package/dist/lib/tools/architecture.js +23 -0
- package/dist/lib/tools/create-specs.d.ts +2 -0
- package/dist/lib/tools/create-specs.js +175 -0
- package/dist/lib/tools/docs-to-voice.d.ts +2 -0
- package/dist/lib/tools/docs-to-voice.js +705 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.d.ts +2 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.js +312 -0
- package/dist/lib/tools/extract-conversations.d.ts +2 -0
- package/dist/lib/tools/extract-conversations.js +105 -0
- package/dist/lib/tools/extract-pdf-text.d.ts +2 -0
- package/dist/lib/tools/extract-pdf-text.js +92 -0
- package/dist/lib/tools/filter-logs.d.ts +2 -0
- package/dist/lib/tools/filter-logs.js +94 -0
- package/dist/lib/tools/find-github-issues.d.ts +2 -0
- package/dist/lib/tools/find-github-issues.js +176 -0
- package/dist/lib/tools/generate-storyboard-images.d.ts +2 -0
- package/dist/lib/tools/generate-storyboard-images.js +419 -0
- package/dist/lib/tools/log-cli-utils.d.ts +35 -0
- package/dist/lib/tools/log-cli-utils.js +233 -0
- package/dist/lib/tools/open-github-issue.d.ts +2 -0
- package/dist/lib/tools/open-github-issue.js +750 -0
- package/dist/lib/tools/read-github-issue.d.ts +2 -0
- package/dist/lib/tools/read-github-issue.js +134 -0
- package/dist/lib/tools/render-error-book.d.ts +2 -0
- package/dist/lib/tools/render-error-book.js +265 -0
- package/dist/lib/tools/render-katex.d.ts +2 -0
- package/dist/lib/tools/render-katex.js +294 -0
- package/dist/lib/tools/review-threads.d.ts +2 -0
- package/dist/lib/tools/review-threads.js +491 -0
- package/dist/lib/tools/search-logs.d.ts +2 -0
- package/dist/lib/tools/search-logs.js +164 -0
- package/dist/lib/tools/sync-memory-index.d.ts +2 -0
- package/dist/lib/tools/sync-memory-index.js +113 -0
- package/dist/lib/tools/validate-openai-agent-config.d.ts +2 -0
- package/dist/lib/tools/validate-openai-agent-config.js +190 -0
- package/dist/lib/tools/validate-skill-frontmatter.d.ts +2 -0
- package/dist/lib/tools/validate-skill-frontmatter.js +118 -0
- package/dist/lib/types.d.ts +82 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/updater.d.ts +34 -0
- package/dist/lib/updater.js +112 -0
- package/dist/lib/utils/format.d.ts +2 -0
- package/dist/lib/utils/format.js +6 -0
- package/dist/lib/utils/terminal.d.ts +12 -0
- package/dist/lib/utils/terminal.js +26 -0
- package/docs-to-voice/SKILL.md +0 -1
- package/generate-spec/SKILL.md +1 -1
- package/katex/SKILL.md +1 -2
- package/lib/cli.ts +780 -0
- package/lib/installer.ts +466 -0
- package/lib/tool-runner.ts +561 -0
- package/lib/tools/architecture.ts +20 -0
- package/lib/tools/create-specs.ts +204 -0
- package/lib/tools/docs-to-voice.ts +799 -0
- package/lib/tools/enforce-video-aspect-ratio.ts +368 -0
- package/lib/tools/extract-conversations.ts +114 -0
- package/lib/tools/extract-pdf-text.ts +99 -0
- package/lib/tools/filter-logs.ts +118 -0
- package/lib/tools/find-github-issues.ts +211 -0
- package/lib/tools/generate-storyboard-images.ts +455 -0
- package/lib/tools/log-cli-utils.ts +262 -0
- package/lib/tools/open-github-issue.ts +930 -0
- package/lib/tools/read-github-issue.ts +179 -0
- package/lib/tools/render-error-book.ts +300 -0
- package/lib/tools/render-katex.ts +325 -0
- package/lib/tools/review-threads.ts +590 -0
- package/lib/tools/search-logs.ts +200 -0
- package/lib/tools/sync-memory-index.ts +114 -0
- package/lib/tools/validate-openai-agent-config.ts +213 -0
- package/lib/tools/validate-skill-frontmatter.ts +124 -0
- package/lib/types.ts +90 -0
- package/lib/updater.ts +165 -0
- package/lib/utils/format.ts +7 -0
- package/lib/utils/terminal.ts +22 -0
- package/open-github-issue/SKILL.md +2 -2
- package/optimise-skill/SKILL.md +1 -1
- package/package.json +13 -4
- package/resources/project-architecture/assets/architecture.css +764 -0
- package/resources/project-architecture/assets/viewer.client.js +144 -0
- package/resources/project-architecture/index.html +42 -0
- package/review-spec-related-changes/SKILL.md +1 -1
- package/solve-issues-found-during-review/SKILL.md +2 -1
- package/tsconfig.json +28 -0
- package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/filter_logs_by_time.py +0 -64
- package/analyse-app-logs/scripts/log_cli_utils.py +0 -112
- package/analyse-app-logs/scripts/search_logs.py +0 -137
- package/analyse-app-logs/tests/test_filter_logs_by_time.py +0 -95
- package/analyse-app-logs/tests/test_search_logs.py +0 -100
- package/codex/codex-memory-manager/scripts/extract_recent_conversations.py +0 -369
- package/codex/codex-memory-manager/scripts/sync_memory_index.py +0 -130
- package/codex/codex-memory-manager/tests/test_extract_recent_conversations.py +0 -177
- package/codex/codex-memory-manager/tests/test_memory_template.py +0 -37
- package/codex/codex-memory-manager/tests/test_sync_memory_index.py +0 -84
- package/codex/learn-skill-from-conversations/scripts/extract_recent_conversations.py +0 -369
- package/codex/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +0 -177
- package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
- package/docs-to-voice/scripts/docs_to_voice.py +0 -1385
- package/docs-to-voice/scripts/docs_to_voice.sh +0 -11
- package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +0 -210
- package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +0 -115
- package/docs-to-voice/tests/test_docs_to_voice_settings.py +0 -43
- package/docs-to-voice/tests/test_docs_to_voice_shell_wrapper.py +0 -51
- package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +0 -57
- package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
- package/generate-spec/scripts/create-specs +0 -215
- package/generate-spec/tests/test_create_specs.py +0 -200
- package/init-project-html/scripts/architecture-bootstrap-render.js +0 -16
- package/init-project-html/scripts/architecture.js +0 -296
- package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
- package/katex/scripts/render_katex.py +0 -247
- package/katex/scripts/render_katex.sh +0 -11
- package/katex/tests/test_render_katex.py +0 -174
- package/learning-error-book/scripts/render_error_book_json_to_pdf.py +0 -590
- package/learning-error-book/tests/test_render_error_book_json_to_pdf.py +0 -134
- package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
- package/open-github-issue/scripts/open_github_issue.py +0 -705
- package/open-github-issue/tests/test_open_github_issue.py +0 -381
- package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +0 -763
- package/openai-text-to-image-storyboard/tests/test_generate_storyboard_images.py +0 -177
- package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/find_issues.py +0 -148
- package/read-github-issue/scripts/read_issue.py +0 -108
- package/read-github-issue/tests/test_find_issues.py +0 -127
- package/read-github-issue/tests/test_read_issue.py +0 -109
- package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
- package/resolve-review-comments/scripts/review_threads.py +0 -425
- package/resolve-review-comments/tests/test_review_threads.py +0 -74
- package/scripts/validate_openai_agent_config.py +0 -209
- package/scripts/validate_skill_frontmatter.py +0 -131
- package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
- package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +0 -350
- package/text-to-short-video/tests/test_enforce_video_aspect_ratio.py +0 -194
- package/weekly-financial-event-report/scripts/extract_pdf_text_pdfkit.swift +0 -99
- package/weekly-financial-event-report/tests/test_extract_pdf_text_pdfkit.py +0 -64
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.syncMemoryIndexHandler = syncMemoryIndexHandler;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const START_MARKER = '<!-- codex-memory-manager:start -->';
|
|
10
|
+
const END_MARKER = '<!-- codex-memory-manager:end -->';
|
|
11
|
+
const DEFAULT_SECTION_TITLE = '## User Memory Index';
|
|
12
|
+
const DEFAULT_INSTRUCTIONS = [
|
|
13
|
+
'Before starting work, review the index below and open any relevant user preference files.',
|
|
14
|
+
'When a new preference category appears, create or update the matching memory file and refresh this index.',
|
|
15
|
+
];
|
|
16
|
+
function titleFromMemoryFile(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
const content = node_fs_1.default.readFileSync(filePath, 'utf8');
|
|
19
|
+
for (const line of content.split('\n')) {
|
|
20
|
+
const stripped = line.trim();
|
|
21
|
+
if (stripped.startsWith('# ')) {
|
|
22
|
+
return stripped.slice(2).trim() || node_path_1.default.basename(filePath).replace(/\.md$/, '').replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// fall through
|
|
28
|
+
}
|
|
29
|
+
return node_path_1.default.basename(filePath).replace(/\.md$/, '').replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
30
|
+
}
|
|
31
|
+
function iterMemoryFiles(memoryDir) {
|
|
32
|
+
if (!node_fs_1.default.existsSync(memoryDir))
|
|
33
|
+
return [];
|
|
34
|
+
const entries = node_fs_1.default.readdirSync(memoryDir);
|
|
35
|
+
return entries
|
|
36
|
+
.filter((name) => name.endsWith('.md'))
|
|
37
|
+
.map((name) => node_path_1.default.join(memoryDir, name))
|
|
38
|
+
.filter((p) => node_fs_1.default.statSync(p).isFile())
|
|
39
|
+
.sort((a, b) => node_path_1.default.basename(a).toLowerCase().localeCompare(node_path_1.default.basename(b).toLowerCase()));
|
|
40
|
+
}
|
|
41
|
+
function renderSection(memoryFiles, sectionTitle, instructionLines) {
|
|
42
|
+
const lines = [START_MARKER, sectionTitle.trim(), ''];
|
|
43
|
+
const cleaned = instructionLines.filter((line) => line && line.trim());
|
|
44
|
+
for (const line of cleaned) {
|
|
45
|
+
lines.push(line.trim());
|
|
46
|
+
}
|
|
47
|
+
if (cleaned.length)
|
|
48
|
+
lines.push('');
|
|
49
|
+
if (memoryFiles.length) {
|
|
50
|
+
const entries = memoryFiles
|
|
51
|
+
.map((p) => ({ title: titleFromMemoryFile(p), resolved: node_path_1.default.resolve(p) }))
|
|
52
|
+
.sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase()) || a.resolved.localeCompare(b.resolved));
|
|
53
|
+
for (const { title, resolved } of entries) {
|
|
54
|
+
lines.push(`- [${title}](file://${resolved})`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
lines.push('- No memory files are currently indexed.');
|
|
59
|
+
}
|
|
60
|
+
lines.push(END_MARKER);
|
|
61
|
+
return lines.join('\n');
|
|
62
|
+
}
|
|
63
|
+
function removeExistingSection(content) {
|
|
64
|
+
const pattern = new RegExp(`\n*${escapeRegex(START_MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\n*`, 'g');
|
|
65
|
+
return content.replace(pattern, '\n\n').trimEnd();
|
|
66
|
+
}
|
|
67
|
+
function escapeRegex(str) {
|
|
68
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
69
|
+
}
|
|
70
|
+
function syncAgentsFile(agentsFile, sectionText) {
|
|
71
|
+
const dir = node_path_1.default.dirname(agentsFile);
|
|
72
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true });
|
|
73
|
+
let original = '';
|
|
74
|
+
try {
|
|
75
|
+
original = node_fs_1.default.readFileSync(agentsFile, 'utf8');
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// file doesn't exist
|
|
79
|
+
}
|
|
80
|
+
const base = removeExistingSection(original);
|
|
81
|
+
const updated = base ? `${base}\n\n${sectionText}\n` : `${sectionText}\n`;
|
|
82
|
+
node_fs_1.default.writeFileSync(agentsFile, updated, 'utf8');
|
|
83
|
+
}
|
|
84
|
+
function syncMemoryIndexHandler(args, context) {
|
|
85
|
+
try {
|
|
86
|
+
const homeDir = process.env.HOME || '';
|
|
87
|
+
let agentsFile = node_path_1.default.join(homeDir, '.codex', 'AGENTS.md');
|
|
88
|
+
let memoryDir = node_path_1.default.join(homeDir, '.codex', 'memory');
|
|
89
|
+
let sectionTitle = DEFAULT_SECTION_TITLE;
|
|
90
|
+
let instructionLines = [...DEFAULT_INSTRUCTIONS];
|
|
91
|
+
for (let i = 0; i < args.length; i++) {
|
|
92
|
+
if (args[i] === '--agents-file' && i + 1 < args.length)
|
|
93
|
+
agentsFile = args[++i];
|
|
94
|
+
else if (args[i] === '--memory-dir' && i + 1 < args.length)
|
|
95
|
+
memoryDir = args[++i];
|
|
96
|
+
else if (args[i] === '--section-title' && i + 1 < args.length)
|
|
97
|
+
sectionTitle = args[++i];
|
|
98
|
+
else if (args[i] === '--instruction-line' && i + 1 < args.length)
|
|
99
|
+
instructionLines.push(args[++i]);
|
|
100
|
+
}
|
|
101
|
+
const memoryFiles = iterMemoryFiles(memoryDir);
|
|
102
|
+
const sectionText = renderSection(memoryFiles, sectionTitle, instructionLines);
|
|
103
|
+
syncAgentsFile(agentsFile, sectionText);
|
|
104
|
+
context.stdout?.write(`SYNCED_AGENTS_FILE=${node_path_1.default.resolve(agentsFile)}\n`);
|
|
105
|
+
context.stdout?.write(`MEMORY_FILES_INDEXED=${memoryFiles.length}\n`);
|
|
106
|
+
return Promise.resolve(0);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
const stderr = context.stderr || process.stderr;
|
|
110
|
+
stderr.write(`Error: ${err.message}\n`);
|
|
111
|
+
return Promise.resolve(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateOpenaiAgentConfigHandler = validateOpenaiAgentConfigHandler;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
10
|
+
const TOP_LEVEL_ALLOWED_KEYS = new Set(['interface', 'dependencies', 'policy']);
|
|
11
|
+
const INTERFACE_REQUIRED_KEYS = new Set(['display_name', 'short_description', 'default_prompt']);
|
|
12
|
+
const INTERFACE_ALLOWED_KEYS = new Set([
|
|
13
|
+
'display_name', 'short_description', 'default_prompt',
|
|
14
|
+
'icon_small', 'icon_large', 'brand_color',
|
|
15
|
+
]);
|
|
16
|
+
const HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
|
|
17
|
+
function repoRoot(context) {
|
|
18
|
+
if (context?.sourceRoot)
|
|
19
|
+
return context.sourceRoot;
|
|
20
|
+
// __dirname is dist/lib/tools/; need to go up 3 levels to project root
|
|
21
|
+
const fromDirname = node_path_1.default.resolve(__dirname, '..', '..', '..');
|
|
22
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(fromDirname, 'package.json')))
|
|
23
|
+
return fromDirname;
|
|
24
|
+
return node_path_1.default.resolve(__dirname, '..', '..', '..');
|
|
25
|
+
}
|
|
26
|
+
function iterSkillDirs(root) {
|
|
27
|
+
return node_fs_1.default.readdirSync(root)
|
|
28
|
+
.filter((name) => {
|
|
29
|
+
const full = node_path_1.default.join(root, name);
|
|
30
|
+
return node_fs_1.default.statSync(full).isDirectory() && node_fs_1.default.existsSync(node_path_1.default.join(full, 'SKILL.md'));
|
|
31
|
+
})
|
|
32
|
+
.map((name) => node_path_1.default.join(root, name))
|
|
33
|
+
.sort();
|
|
34
|
+
}
|
|
35
|
+
function extractFrontmatter(content) {
|
|
36
|
+
const lines = content.split('\n');
|
|
37
|
+
if (!lines.length || lines[0].trim() !== '---') {
|
|
38
|
+
throw new Error("SKILL.md must start with YAML frontmatter delimiter '---'.");
|
|
39
|
+
}
|
|
40
|
+
for (let i = 1; i < lines.length; i++) {
|
|
41
|
+
if (lines[i].trim() === '---') {
|
|
42
|
+
const raw = lines.slice(1, i).join('\n');
|
|
43
|
+
const parsed = js_yaml_1.default.load(raw);
|
|
44
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
45
|
+
throw new Error('SKILL.md frontmatter must be a YAML mapping.');
|
|
46
|
+
}
|
|
47
|
+
return parsed;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw new Error("SKILL.md frontmatter is missing the closing '---' delimiter.");
|
|
51
|
+
}
|
|
52
|
+
function requireNonEmptyString(container, key, context, errors) {
|
|
53
|
+
const value = container[key];
|
|
54
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
55
|
+
errors.push(`${context}: '${key}' must be a non-empty string.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function validateDependencies(dependencies, context, errors) {
|
|
59
|
+
if (typeof dependencies !== 'object' || dependencies === null) {
|
|
60
|
+
errors.push(`${context}: 'dependencies' must be a mapping.`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const tools = dependencies['tools'];
|
|
64
|
+
if (tools === undefined)
|
|
65
|
+
return;
|
|
66
|
+
if (!Array.isArray(tools)) {
|
|
67
|
+
errors.push(`${context}: 'dependencies.tools' must be a list.`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
for (let i = 0; i < tools.length; i++) {
|
|
71
|
+
const itemContext = `${context}: dependencies.tools[${i}]`;
|
|
72
|
+
const item = tools[i];
|
|
73
|
+
if (typeof item !== 'object' || item === null) {
|
|
74
|
+
errors.push(`${itemContext} must be a mapping.`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
requireNonEmptyString(item, 'type', itemContext, errors);
|
|
78
|
+
requireNonEmptyString(item, 'value', itemContext, errors);
|
|
79
|
+
if (typeof item['type'] === 'string' && item['type'] !== 'mcp') {
|
|
80
|
+
errors.push(`${itemContext}: unsupported tool type '${item['type']}', only 'mcp' is allowed.`);
|
|
81
|
+
}
|
|
82
|
+
for (const optionalKey of ['description', 'transport', 'url']) {
|
|
83
|
+
const optionalValue = item[optionalKey];
|
|
84
|
+
if (optionalValue !== undefined && (typeof optionalValue !== 'string' || !optionalValue.trim())) {
|
|
85
|
+
errors.push(`${itemContext}: '${optionalKey}' must be a non-empty string when provided.`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function validatePolicy(policy, context, errors) {
|
|
91
|
+
if (typeof policy !== 'object' || policy === null) {
|
|
92
|
+
errors.push(`${context}: 'policy' must be a mapping.`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const allowImplicit = policy['allow_implicit_invocation'];
|
|
96
|
+
if (allowImplicit !== undefined && typeof allowImplicit !== 'boolean') {
|
|
97
|
+
errors.push(`${context}: 'policy.allow_implicit_invocation' must be a boolean when provided.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function validateSkill(skillDir) {
|
|
101
|
+
const errors = [];
|
|
102
|
+
const skillMd = node_path_1.default.join(skillDir, 'SKILL.md');
|
|
103
|
+
const openaiYaml = node_path_1.default.join(skillDir, 'agents', 'openai.yaml');
|
|
104
|
+
let skillFrontmatter;
|
|
105
|
+
try {
|
|
106
|
+
skillFrontmatter = extractFrontmatter(node_fs_1.default.readFileSync(skillMd, 'utf8'));
|
|
107
|
+
}
|
|
108
|
+
catch (exc) {
|
|
109
|
+
return [`${skillMd}: unable to read skill name for validation (${exc.message}).`];
|
|
110
|
+
}
|
|
111
|
+
const skillName = skillFrontmatter['name'];
|
|
112
|
+
if (typeof skillName !== 'string' || !skillName.trim()) {
|
|
113
|
+
return [`${skillMd}: frontmatter 'name' must be a non-empty string.`];
|
|
114
|
+
}
|
|
115
|
+
if (!node_fs_1.default.existsSync(openaiYaml)) {
|
|
116
|
+
return [`${openaiYaml}: file is required for every skill.`];
|
|
117
|
+
}
|
|
118
|
+
let parsed;
|
|
119
|
+
try {
|
|
120
|
+
parsed = js_yaml_1.default.load(node_fs_1.default.readFileSync(openaiYaml, 'utf8'));
|
|
121
|
+
}
|
|
122
|
+
catch (exc) {
|
|
123
|
+
return [`${openaiYaml}: invalid YAML (${exc.message}).`];
|
|
124
|
+
}
|
|
125
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
126
|
+
return [`${openaiYaml}: top-level structure must be a YAML mapping.`];
|
|
127
|
+
}
|
|
128
|
+
const topLevelKeys = new Set(Object.keys(parsed));
|
|
129
|
+
const unsupportedTopKeys = [...topLevelKeys].filter((k) => !TOP_LEVEL_ALLOWED_KEYS.has(k)).sort();
|
|
130
|
+
if (unsupportedTopKeys.length) {
|
|
131
|
+
errors.push(`${openaiYaml}: unsupported top-level keys: ${unsupportedTopKeys.join(', ')}.`);
|
|
132
|
+
}
|
|
133
|
+
const iface = parsed['interface'];
|
|
134
|
+
if (typeof iface !== 'object' || iface === null) {
|
|
135
|
+
errors.push(`${openaiYaml}: 'interface' must be a mapping.`);
|
|
136
|
+
return errors;
|
|
137
|
+
}
|
|
138
|
+
const missingInterfaceKeys = [...INTERFACE_REQUIRED_KEYS].filter((k) => !(k in iface)).sort();
|
|
139
|
+
if (missingInterfaceKeys.length) {
|
|
140
|
+
errors.push(`${openaiYaml}: missing required interface keys: ${missingInterfaceKeys.join(', ')}.`);
|
|
141
|
+
}
|
|
142
|
+
const unsupportedInterfaceKeys = Object.keys(iface).filter((k) => !INTERFACE_ALLOWED_KEYS.has(k)).sort();
|
|
143
|
+
if (unsupportedInterfaceKeys.length) {
|
|
144
|
+
errors.push(`${openaiYaml}: unsupported interface keys: ${unsupportedInterfaceKeys.join(', ')}.`);
|
|
145
|
+
}
|
|
146
|
+
for (const requiredKey of [...INTERFACE_REQUIRED_KEYS].sort()) {
|
|
147
|
+
requireNonEmptyString(iface, requiredKey, `${openaiYaml}`, errors);
|
|
148
|
+
}
|
|
149
|
+
const defaultPrompt = iface['default_prompt'];
|
|
150
|
+
const expectedSkillRef = `$${skillName.trim()}`;
|
|
151
|
+
if (typeof defaultPrompt === 'string' && !defaultPrompt.includes(expectedSkillRef)) {
|
|
152
|
+
errors.push(`${openaiYaml}: interface.default_prompt must reference '${expectedSkillRef}'.`);
|
|
153
|
+
}
|
|
154
|
+
const brandColor = iface['brand_color'];
|
|
155
|
+
if (brandColor !== undefined) {
|
|
156
|
+
if (typeof brandColor !== 'string' || !HEX_COLOR_PATTERN.test(brandColor)) {
|
|
157
|
+
errors.push(`${openaiYaml}: interface.brand_color must be a hex color like '#1A2B3C'.`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const dependencies = parsed['dependencies'];
|
|
161
|
+
if (dependencies !== undefined) {
|
|
162
|
+
validateDependencies(dependencies, `${openaiYaml}`, errors);
|
|
163
|
+
}
|
|
164
|
+
const policy = parsed['policy'];
|
|
165
|
+
if (policy !== undefined) {
|
|
166
|
+
validatePolicy(policy, `${openaiYaml}`, errors);
|
|
167
|
+
}
|
|
168
|
+
return errors;
|
|
169
|
+
}
|
|
170
|
+
function validateOpenaiAgentConfigHandler(args, context) {
|
|
171
|
+
const root = repoRoot(context);
|
|
172
|
+
const skillDirs = iterSkillDirs(root);
|
|
173
|
+
if (!skillDirs.length) {
|
|
174
|
+
context.stdout?.write('No top-level skill directories found.\n');
|
|
175
|
+
return Promise.resolve(1);
|
|
176
|
+
}
|
|
177
|
+
const allErrors = [];
|
|
178
|
+
for (const dir of skillDirs) {
|
|
179
|
+
allErrors.push(...validateSkill(dir));
|
|
180
|
+
}
|
|
181
|
+
if (allErrors.length) {
|
|
182
|
+
context.stdout?.write('agents/openai.yaml validation failed:\n');
|
|
183
|
+
for (const error of allErrors) {
|
|
184
|
+
context.stdout?.write(`- ${error}\n`);
|
|
185
|
+
}
|
|
186
|
+
return Promise.resolve(1);
|
|
187
|
+
}
|
|
188
|
+
context.stdout?.write(`agents/openai.yaml validation passed for ${skillDirs.length} skills.\n`);
|
|
189
|
+
return Promise.resolve(0);
|
|
190
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateSkillFrontmatterHandler = validateSkillFrontmatterHandler;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
10
|
+
const REQUIRED_KEYS = new Set(['name', 'description']);
|
|
11
|
+
const MAX_DESCRIPTION_LENGTH = 1024;
|
|
12
|
+
function repoRoot(context) {
|
|
13
|
+
if (context?.sourceRoot)
|
|
14
|
+
return context.sourceRoot;
|
|
15
|
+
// __dirname is dist/lib/tools/; need to go up 3 levels to project root
|
|
16
|
+
const fromDirname = node_path_1.default.resolve(__dirname, '..', '..', '..');
|
|
17
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(fromDirname, 'package.json')))
|
|
18
|
+
return fromDirname;
|
|
19
|
+
return node_path_1.default.resolve(__dirname, '..', '..', '..');
|
|
20
|
+
}
|
|
21
|
+
function iterSkillDirs(root) {
|
|
22
|
+
return node_fs_1.default.readdirSync(root)
|
|
23
|
+
.filter((name) => {
|
|
24
|
+
const full = node_path_1.default.join(root, name);
|
|
25
|
+
return node_fs_1.default.statSync(full).isDirectory() && node_fs_1.default.existsSync(node_path_1.default.join(full, 'SKILL.md'));
|
|
26
|
+
})
|
|
27
|
+
.map((name) => node_path_1.default.join(root, name))
|
|
28
|
+
.sort();
|
|
29
|
+
}
|
|
30
|
+
function extractFrontmatter(content) {
|
|
31
|
+
const lines = content.split('\n');
|
|
32
|
+
if (!lines.length || lines[0].trim() !== '---') {
|
|
33
|
+
throw new Error("SKILL.md must start with YAML frontmatter delimiter '---'.");
|
|
34
|
+
}
|
|
35
|
+
for (let i = 1; i < lines.length; i++) {
|
|
36
|
+
if (lines[i].trim() === '---') {
|
|
37
|
+
return lines.slice(1, i).join('\n');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw new Error("SKILL.md frontmatter is missing the closing '---' delimiter.");
|
|
41
|
+
}
|
|
42
|
+
function validateSkill(skillDir) {
|
|
43
|
+
const errors = [];
|
|
44
|
+
const skillMd = node_path_1.default.join(skillDir, 'SKILL.md');
|
|
45
|
+
let content;
|
|
46
|
+
try {
|
|
47
|
+
content = node_fs_1.default.readFileSync(skillMd, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
catch (exc) {
|
|
50
|
+
return [`${skillMd}: cannot read file (${exc.message}).`];
|
|
51
|
+
}
|
|
52
|
+
let frontmatterText;
|
|
53
|
+
try {
|
|
54
|
+
frontmatterText = extractFrontmatter(content);
|
|
55
|
+
}
|
|
56
|
+
catch (exc) {
|
|
57
|
+
return [`${skillMd}: ${exc.message}`];
|
|
58
|
+
}
|
|
59
|
+
// Simple YAML-like parsing for frontmatter (handles the common cases)
|
|
60
|
+
let frontmatter = {};
|
|
61
|
+
for (const line of frontmatterText.split('\n')) {
|
|
62
|
+
const match = line.match(/^(\w+):\s*(.*)$/);
|
|
63
|
+
if (match) {
|
|
64
|
+
frontmatter[match[1]] = match[2].trim();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const keys = new Set(Object.keys(frontmatter));
|
|
68
|
+
const missing = [...REQUIRED_KEYS].filter((k) => !keys.has(k));
|
|
69
|
+
const extra = [...keys].filter((k) => !REQUIRED_KEYS.has(k));
|
|
70
|
+
if (missing.length) {
|
|
71
|
+
errors.push(`${skillMd}: missing required frontmatter keys: ${missing.join(', ')}.`);
|
|
72
|
+
}
|
|
73
|
+
if (extra.length) {
|
|
74
|
+
errors.push(`${skillMd}: unsupported frontmatter keys: ${extra.join(', ')}.`);
|
|
75
|
+
}
|
|
76
|
+
const name = frontmatter['name'];
|
|
77
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
78
|
+
errors.push(`${skillMd}: 'name' must be a non-empty string.`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const normalizedName = name.trim();
|
|
82
|
+
if (!NAME_PATTERN.test(normalizedName)) {
|
|
83
|
+
errors.push(`${skillMd}: 'name' must be kebab-case (lowercase letters, digits, and hyphens).`);
|
|
84
|
+
}
|
|
85
|
+
if (normalizedName !== node_path_1.default.basename(skillDir)) {
|
|
86
|
+
errors.push(`${skillMd}: frontmatter name '${normalizedName}' must match folder name '${node_path_1.default.basename(skillDir)}'.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const description = frontmatter['description'];
|
|
90
|
+
if (typeof description !== 'string' || !description.trim()) {
|
|
91
|
+
errors.push(`${skillMd}: 'description' must be a non-empty string.`);
|
|
92
|
+
}
|
|
93
|
+
else if (description.length > MAX_DESCRIPTION_LENGTH) {
|
|
94
|
+
errors.push(`${skillMd}: invalid description: exceeds maximum length of ${MAX_DESCRIPTION_LENGTH} characters`);
|
|
95
|
+
}
|
|
96
|
+
return errors;
|
|
97
|
+
}
|
|
98
|
+
function validateSkillFrontmatterHandler(args, context) {
|
|
99
|
+
const root = repoRoot(context);
|
|
100
|
+
const skillDirs = iterSkillDirs(root);
|
|
101
|
+
if (!skillDirs.length) {
|
|
102
|
+
context.stdout?.write('No top-level skill directories found.\n');
|
|
103
|
+
return Promise.resolve(1);
|
|
104
|
+
}
|
|
105
|
+
const allErrors = [];
|
|
106
|
+
for (const dir of skillDirs) {
|
|
107
|
+
allErrors.push(...validateSkill(dir));
|
|
108
|
+
}
|
|
109
|
+
if (allErrors.length) {
|
|
110
|
+
context.stdout?.write('SKILL.md frontmatter validation failed:\n');
|
|
111
|
+
for (const error of allErrors) {
|
|
112
|
+
context.stdout?.write(`- ${error}\n`);
|
|
113
|
+
}
|
|
114
|
+
return Promise.resolve(1);
|
|
115
|
+
}
|
|
116
|
+
context.stdout?.write(`SKILL.md frontmatter validation passed for ${skillDirs.length} skills.\n`);
|
|
117
|
+
return Promise.resolve(0);
|
|
118
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export interface ToolHelp {
|
|
2
|
+
purpose: string;
|
|
3
|
+
useWhen: string[];
|
|
4
|
+
insteadOf?: string[];
|
|
5
|
+
examples?: ToolExample[];
|
|
6
|
+
}
|
|
7
|
+
export interface ToolExample {
|
|
8
|
+
command: string;
|
|
9
|
+
result: string;
|
|
10
|
+
}
|
|
11
|
+
export type RunnerKind = 'node' | 'python3' | 'swift';
|
|
12
|
+
export interface ToolDefinition {
|
|
13
|
+
name: string;
|
|
14
|
+
category: string;
|
|
15
|
+
skill?: string;
|
|
16
|
+
script?: string;
|
|
17
|
+
runner?: RunnerKind;
|
|
18
|
+
description: string;
|
|
19
|
+
aliases?: string[];
|
|
20
|
+
help?: ToolHelp;
|
|
21
|
+
/** Direct function reference for the tool handler (replaces spawn-based execution). */
|
|
22
|
+
handler?: (args: string[], context: ToolContext) => Promise<number>;
|
|
23
|
+
canonicalName?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ToolContext {
|
|
26
|
+
sourceRoot?: string;
|
|
27
|
+
stdout?: NodeJS.WriteStream;
|
|
28
|
+
stderr?: NodeJS.WriteStream;
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
spawnCommand?: Function;
|
|
31
|
+
cwd?: string;
|
|
32
|
+
stdio?: any;
|
|
33
|
+
}
|
|
34
|
+
export type InstallMode = 'codex' | 'openclaw' | 'trae' | 'agents' | 'claude-code';
|
|
35
|
+
export interface InstallTarget {
|
|
36
|
+
id: InstallMode;
|
|
37
|
+
label: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
root?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface InstallResult {
|
|
42
|
+
skillNames: string[];
|
|
43
|
+
linkMode: 'symlink' | 'copy';
|
|
44
|
+
targets: {
|
|
45
|
+
label: string;
|
|
46
|
+
root: string;
|
|
47
|
+
}[];
|
|
48
|
+
}
|
|
49
|
+
export interface ManifestData {
|
|
50
|
+
version: string;
|
|
51
|
+
installedAt: string;
|
|
52
|
+
linkMode: string;
|
|
53
|
+
skills: string[];
|
|
54
|
+
historicalSkills: string[];
|
|
55
|
+
}
|
|
56
|
+
export interface SyncResult {
|
|
57
|
+
previousSkillNames: string[];
|
|
58
|
+
}
|
|
59
|
+
export interface ParsedArguments {
|
|
60
|
+
command: 'install' | 'uninstall' | 'tool' | 'tools-help';
|
|
61
|
+
modes: InstallMode[];
|
|
62
|
+
showHelp: boolean;
|
|
63
|
+
showToolsHelp: boolean;
|
|
64
|
+
toolkitHome: string | null;
|
|
65
|
+
toolName: string | null;
|
|
66
|
+
toolArgs: string[];
|
|
67
|
+
linkMode: 'copy' | 'symlink' | null;
|
|
68
|
+
assumeYes: boolean;
|
|
69
|
+
explicitInstallCommand: boolean;
|
|
70
|
+
helpTopic: string;
|
|
71
|
+
}
|
|
72
|
+
export interface CliContext {
|
|
73
|
+
sourceRoot?: string;
|
|
74
|
+
stdout?: NodeJS.WriteStream;
|
|
75
|
+
stderr?: NodeJS.WriteStream;
|
|
76
|
+
stdin?: NodeJS.ReadStream;
|
|
77
|
+
env?: NodeJS.ProcessEnv;
|
|
78
|
+
execCommand?: Function;
|
|
79
|
+
confirmUpdate?: Function;
|
|
80
|
+
runTool?: Function;
|
|
81
|
+
spawnCommand?: Function;
|
|
82
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
interface ExecResult {
|
|
2
|
+
stdout: string;
|
|
3
|
+
stderr: string;
|
|
4
|
+
}
|
|
5
|
+
interface UpdateCheckResult {
|
|
6
|
+
checked: boolean;
|
|
7
|
+
updated: boolean;
|
|
8
|
+
latestVersion?: string;
|
|
9
|
+
error?: Error;
|
|
10
|
+
}
|
|
11
|
+
export declare function compareVersions(left: string, right: string): number;
|
|
12
|
+
export declare function execCommand(command: string, args: string[], { env, stdout, stderr }?: {
|
|
13
|
+
env?: NodeJS.ProcessEnv;
|
|
14
|
+
stdout?: NodeJS.WriteStream;
|
|
15
|
+
stderr?: NodeJS.WriteStream;
|
|
16
|
+
}): Promise<ExecResult>;
|
|
17
|
+
declare function defaultConfirmUpdate({ stdin, stdout, currentVersion, latestVersion, packageName }: {
|
|
18
|
+
stdin: NodeJS.ReadStream;
|
|
19
|
+
stdout: NodeJS.WriteStream;
|
|
20
|
+
currentVersion: string;
|
|
21
|
+
latestVersion: string;
|
|
22
|
+
packageName: string;
|
|
23
|
+
}): Promise<boolean>;
|
|
24
|
+
export declare function checkForPackageUpdate({ packageName, currentVersion, env, stdin, stdout, stderr, exec, confirmUpdate }: {
|
|
25
|
+
packageName: string;
|
|
26
|
+
currentVersion: string;
|
|
27
|
+
env?: NodeJS.ProcessEnv;
|
|
28
|
+
stdin?: NodeJS.ReadStream;
|
|
29
|
+
stdout?: NodeJS.WriteStream;
|
|
30
|
+
stderr?: NodeJS.WriteStream;
|
|
31
|
+
exec?: typeof execCommand;
|
|
32
|
+
confirmUpdate?: typeof defaultConfirmUpdate;
|
|
33
|
+
}): Promise<UpdateCheckResult>;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compareVersions = compareVersions;
|
|
4
|
+
exports.execCommand = execCommand;
|
|
5
|
+
exports.checkForPackageUpdate = checkForPackageUpdate;
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const promises_1 = require("node:readline/promises");
|
|
8
|
+
function normalizeVersion(version) {
|
|
9
|
+
return String(version || '')
|
|
10
|
+
.trim()
|
|
11
|
+
.replace(/^v/i, '');
|
|
12
|
+
}
|
|
13
|
+
function parseVersion(version) {
|
|
14
|
+
const normalized = normalizeVersion(version);
|
|
15
|
+
const [core, prerelease = ''] = normalized.split('-', 2);
|
|
16
|
+
const parts = core.split('.').map((part) => Number.parseInt(part, 10) || 0);
|
|
17
|
+
return { parts, prerelease };
|
|
18
|
+
}
|
|
19
|
+
function compareVersions(left, right) {
|
|
20
|
+
const leftVersion = parseVersion(left);
|
|
21
|
+
const rightVersion = parseVersion(right);
|
|
22
|
+
const leftParts = leftVersion.parts;
|
|
23
|
+
const rightParts = rightVersion.parts;
|
|
24
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
25
|
+
for (let index = 0; index < length; index += 1) {
|
|
26
|
+
const delta = (leftParts[index] || 0) - (rightParts[index] || 0);
|
|
27
|
+
if (delta !== 0)
|
|
28
|
+
return delta;
|
|
29
|
+
}
|
|
30
|
+
if (leftVersion.prerelease && !rightVersion.prerelease)
|
|
31
|
+
return -1;
|
|
32
|
+
if (!leftVersion.prerelease && rightVersion.prerelease)
|
|
33
|
+
return 1;
|
|
34
|
+
if (leftVersion.prerelease !== rightVersion.prerelease) {
|
|
35
|
+
return leftVersion.prerelease.localeCompare(rightVersion.prerelease);
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
function shouldSkipUpdateCheck({ env = process.env, stdin = process.stdin, stdout = process.stdout }) {
|
|
40
|
+
return env.APOLLO_TOOLKIT_SKIP_UPDATE_CHECK === '1' || !stdin.isTTY || !stdout.isTTY;
|
|
41
|
+
}
|
|
42
|
+
function execCommand(command, args, { env = process.env, stdout, stderr } = {}) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const child = (0, node_child_process_1.spawn)(command, args, {
|
|
45
|
+
env,
|
|
46
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
47
|
+
});
|
|
48
|
+
let capturedStdout = '';
|
|
49
|
+
let capturedStderr = '';
|
|
50
|
+
child.stdout.on('data', (chunk) => {
|
|
51
|
+
capturedStdout += chunk.toString('utf8');
|
|
52
|
+
if (stdout)
|
|
53
|
+
stdout.write(chunk);
|
|
54
|
+
});
|
|
55
|
+
child.stderr.on('data', (chunk) => {
|
|
56
|
+
capturedStderr += chunk.toString('utf8');
|
|
57
|
+
if (stderr)
|
|
58
|
+
stderr.write(chunk);
|
|
59
|
+
});
|
|
60
|
+
child.on('error', reject);
|
|
61
|
+
child.on('close', (code) => {
|
|
62
|
+
if (code !== 0) {
|
|
63
|
+
reject(new Error(capturedStderr.trim() || `${command} exited with code ${code}`));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
resolve({ stdout: capturedStdout, stderr: capturedStderr });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function defaultConfirmUpdate({ stdin, stdout, currentVersion, latestVersion, packageName }) {
|
|
71
|
+
const rl = (0, promises_1.createInterface)({ input: stdin, output: stdout });
|
|
72
|
+
try {
|
|
73
|
+
const answer = await rl.question(`A newer ${packageName} release is available (${currentVersion} -> ${latestVersion}). Update now? [Y/n] `);
|
|
74
|
+
const normalized = answer.trim().toLowerCase();
|
|
75
|
+
return normalized === '' || normalized === 'y';
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
rl.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function getLatestPublishedVersion({ packageName, env = process.env, exec = execCommand }) {
|
|
82
|
+
const result = await exec('npm', ['view', packageName, 'version', '--json'], { env });
|
|
83
|
+
const parsed = JSON.parse(result.stdout.trim());
|
|
84
|
+
if (Array.isArray(parsed)) {
|
|
85
|
+
return String(parsed[parsed.length - 1] || '').trim();
|
|
86
|
+
}
|
|
87
|
+
return String(parsed || '').trim();
|
|
88
|
+
}
|
|
89
|
+
async function checkForPackageUpdate({ packageName, currentVersion, env = process.env, stdin = process.stdin, stdout = process.stdout, stderr = process.stderr, exec = execCommand, confirmUpdate = defaultConfirmUpdate }) {
|
|
90
|
+
if (shouldSkipUpdateCheck({ env, stdin, stdout })) {
|
|
91
|
+
return { checked: false, updated: false };
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const latestVersion = await getLatestPublishedVersion({ packageName, env, exec });
|
|
95
|
+
if (!latestVersion || compareVersions(latestVersion, currentVersion) <= 0) {
|
|
96
|
+
return { checked: true, updated: false, latestVersion };
|
|
97
|
+
}
|
|
98
|
+
const approved = await confirmUpdate({ stdin, stdout, currentVersion, latestVersion, packageName });
|
|
99
|
+
if (!approved) {
|
|
100
|
+
stdout.write(`Continuing with ${packageName} ${currentVersion}.\n`);
|
|
101
|
+
return { checked: true, updated: false, latestVersion };
|
|
102
|
+
}
|
|
103
|
+
stdout.write(`Updating ${packageName} to ${latestVersion}...\n`);
|
|
104
|
+
await exec('npm', ['install', '-g', `${packageName}@latest`], { env, stdout, stderr });
|
|
105
|
+
stdout.write(`Update complete. Continuing with ${packageName} ${latestVersion}.\n`);
|
|
106
|
+
return { checked: true, updated: true, latestVersion };
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
stderr.write(`Warning: unable to check or install package updates: ${error.message}\n`);
|
|
110
|
+
return { checked: false, updated: false, error: error };
|
|
111
|
+
}
|
|
112
|
+
}
|