@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
package/lib/installer.ts
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import type { InstallMode, InstallTarget, ManifestData, SyncResult } from './types';
|
|
6
|
+
|
|
7
|
+
export interface TargetDefinition {
|
|
8
|
+
id: InstallMode;
|
|
9
|
+
label: string;
|
|
10
|
+
description: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const TARGET_DEFINITIONS: readonly TargetDefinition[] = Object.freeze([
|
|
14
|
+
{ id: 'codex', label: 'Codex', description: '~/.codex/skills' },
|
|
15
|
+
{ id: 'openclaw', label: 'OpenClaw', description: '~/.openclaw/workspace*/skills' },
|
|
16
|
+
{ id: 'trae', label: 'Trae', description: '~/.trae/skills' },
|
|
17
|
+
{ id: 'agents', label: 'Agents', description: '~/.agents/skills' },
|
|
18
|
+
{ id: 'claude-code', label: 'Claude Code', description: '~/.claude/skills' },
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
export const VALID_MODES: readonly InstallMode[] = TARGET_DEFINITIONS.map(({ id }) => id);
|
|
22
|
+
const COPY_FILES = new Set(['AGENTS.md', 'CHANGELOG.md', 'LICENSE', 'README.md', 'package.json']);
|
|
23
|
+
const COPY_DIRS = new Set<string>();
|
|
24
|
+
export const MANIFEST_FILENAME = '.apollo-toolkit-manifest.json';
|
|
25
|
+
|
|
26
|
+
export function resolveHomeDirectory(env: NodeJS.ProcessEnv = process.env): string {
|
|
27
|
+
return env.HOME || env.USERPROFILE || os.homedir();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function expandUserPath(inputPath: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
31
|
+
if (!inputPath) return inputPath;
|
|
32
|
+
if (inputPath === '~') return resolveHomeDirectory(env);
|
|
33
|
+
if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
|
|
34
|
+
return path.join(resolveHomeDirectory(env), inputPath.slice(2));
|
|
35
|
+
}
|
|
36
|
+
return inputPath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resolveToolkitHome(env: NodeJS.ProcessEnv = process.env): string {
|
|
40
|
+
if (env.APOLLO_TOOLKIT_HOME) {
|
|
41
|
+
return path.resolve(expandUserPath(env.APOLLO_TOOLKIT_HOME, env));
|
|
42
|
+
}
|
|
43
|
+
return path.join(resolveHomeDirectory(env), '.apollo-toolkit');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function normalizeModes(inputModes: string[]): InstallMode[] {
|
|
47
|
+
const modes: InstallMode[] = [];
|
|
48
|
+
for (const rawMode of inputModes) {
|
|
49
|
+
const mode = String(rawMode).toLowerCase();
|
|
50
|
+
if (mode === 'all') {
|
|
51
|
+
for (const candidate of VALID_MODES) {
|
|
52
|
+
if (!modes.includes(candidate)) {
|
|
53
|
+
modes.push(candidate);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!(VALID_MODES as readonly string[]).includes(mode)) {
|
|
59
|
+
throw new Error(`Invalid mode: ${rawMode}`);
|
|
60
|
+
}
|
|
61
|
+
if (!modes.includes(mode as InstallMode)) {
|
|
62
|
+
modes.push(mode as InstallMode);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return modes;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function listSkillNames(rootDir: string, modes: InstallMode[] = []): Promise<string[]> {
|
|
69
|
+
const entries = await fsp.readdir(rootDir, { withFileTypes: true });
|
|
70
|
+
const skillNames = new Set<string>();
|
|
71
|
+
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (!entry.isDirectory()) continue;
|
|
74
|
+
if (fs.existsSync(path.join(rootDir, entry.name, 'SKILL.md'))) {
|
|
75
|
+
skillNames.add(entry.name);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (modes.includes('codex')) {
|
|
80
|
+
const codexDir = path.join(rootDir, 'codex');
|
|
81
|
+
if (fs.existsSync(codexDir)) {
|
|
82
|
+
const codexEntries = await fsp.readdir(codexDir, { withFileTypes: true });
|
|
83
|
+
for (const entry of codexEntries) {
|
|
84
|
+
if (entry.isDirectory() && fs.existsSync(path.join(codexDir, entry.name, 'SKILL.md'))) {
|
|
85
|
+
skillNames.add(entry.name);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return [...skillNames].sort();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function listCodexSkillNames(rootDir: string): Promise<string[]> {
|
|
95
|
+
const codexDir = path.join(rootDir, 'codex');
|
|
96
|
+
if (!fs.existsSync(codexDir)) return [];
|
|
97
|
+
|
|
98
|
+
const entries = await fsp.readdir(codexDir, { withFileTypes: true });
|
|
99
|
+
return entries
|
|
100
|
+
.filter((entry) => entry.isDirectory() && fs.existsSync(path.join(codexDir, entry.name, 'SKILL.md')))
|
|
101
|
+
.map((entry) => entry.name)
|
|
102
|
+
.sort();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function readManifest(targetRoot: string): Promise<ManifestData | null> {
|
|
106
|
+
const manifestPath = path.join(targetRoot, MANIFEST_FILENAME);
|
|
107
|
+
try {
|
|
108
|
+
const raw = await fsp.readFile(manifestPath, 'utf8');
|
|
109
|
+
return JSON.parse(raw);
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isSafeSkillName(skillName: string): boolean {
|
|
116
|
+
return typeof skillName === 'string'
|
|
117
|
+
&& skillName.length > 0
|
|
118
|
+
&& !skillName.includes('\0')
|
|
119
|
+
&& !skillName.includes('/')
|
|
120
|
+
&& !skillName.includes('\\')
|
|
121
|
+
&& !path.isAbsolute(skillName)
|
|
122
|
+
&& skillName !== '.'
|
|
123
|
+
&& skillName !== '..';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getManifestSkillNames(manifest: ManifestData): string[] {
|
|
127
|
+
return [...new Set([
|
|
128
|
+
...(Array.isArray(manifest.historicalSkills) ? manifest.historicalSkills : []),
|
|
129
|
+
...(Array.isArray(manifest.skills) ? manifest.skills : []),
|
|
130
|
+
])].filter(isSafeSkillName).sort();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function writeManifest(
|
|
134
|
+
targetRoot: string,
|
|
135
|
+
{ version, linkMode, skills, previousSkills = [] }: { version: string; linkMode: string; skills: string[]; previousSkills?: string[] },
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
const historicalSkills = [...new Set([...previousSkills, ...skills])].sort();
|
|
138
|
+
const manifest: ManifestData = {
|
|
139
|
+
version,
|
|
140
|
+
installedAt: new Date().toISOString(),
|
|
141
|
+
linkMode,
|
|
142
|
+
skills: [...skills].sort(),
|
|
143
|
+
historicalSkills,
|
|
144
|
+
};
|
|
145
|
+
await fsp.mkdir(targetRoot, { recursive: true });
|
|
146
|
+
await fsp.writeFile(
|
|
147
|
+
path.join(targetRoot, MANIFEST_FILENAME),
|
|
148
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
149
|
+
'utf8',
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function listAllKnownSkillNames({ toolkitHome, modes = [], env = process.env }: { toolkitHome: string; modes?: InstallMode[]; env?: NodeJS.ProcessEnv }): Promise<string[]> {
|
|
154
|
+
const allNames = new Set<string>();
|
|
155
|
+
const currentSkills = await listSkillNames(toolkitHome, modes).catch(() => []);
|
|
156
|
+
for (const name of currentSkills) allNames.add(name);
|
|
157
|
+
|
|
158
|
+
const targets = await getUninstallTargetRoots(modes, env);
|
|
159
|
+
for (const target of targets) {
|
|
160
|
+
if (!target.root) continue;
|
|
161
|
+
const manifest = await readManifest(target.root);
|
|
162
|
+
if (manifest && manifest.historicalSkills) {
|
|
163
|
+
for (const name of getManifestSkillNames(manifest)) {
|
|
164
|
+
allNames.add(name);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return [...allNames].sort();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getTargetSkillNames({ targetMode, sharedSkillNames, codexSkillNames, includeExclusiveSkills = false }: {
|
|
172
|
+
targetMode: string;
|
|
173
|
+
sharedSkillNames: string[];
|
|
174
|
+
codexSkillNames: string[];
|
|
175
|
+
includeExclusiveSkills?: boolean;
|
|
176
|
+
}): string[] {
|
|
177
|
+
const includeCodexSkills = targetMode === 'codex' || includeExclusiveSkills;
|
|
178
|
+
if (!includeCodexSkills || codexSkillNames.length === 0) return sharedSkillNames;
|
|
179
|
+
return [...new Set([...sharedSkillNames, ...codexSkillNames])].sort();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function resolveInstallSourcePath({ toolkitHome, targetMode, skillName, codexSkillNames }: {
|
|
183
|
+
toolkitHome: string;
|
|
184
|
+
targetMode: string;
|
|
185
|
+
skillName: string;
|
|
186
|
+
codexSkillNames: string[];
|
|
187
|
+
}): string {
|
|
188
|
+
if (targetMode === 'codex' && codexSkillNames.includes(skillName)) {
|
|
189
|
+
return path.join(toolkitHome, 'codex', skillName);
|
|
190
|
+
}
|
|
191
|
+
return path.join(toolkitHome, skillName);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function shouldCopyEntry(sourceRoot: string, entry: fs.Dirent): boolean {
|
|
195
|
+
if (entry.isFile()) return COPY_FILES.has(entry.name);
|
|
196
|
+
if (!entry.isDirectory()) return false;
|
|
197
|
+
if (COPY_DIRS.has(entry.name)) return true;
|
|
198
|
+
return fs.existsSync(path.join(sourceRoot, entry.name, 'SKILL.md'));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function shouldCopyCodexContainer({ sourceRoot, entry, modes = [] }: {
|
|
202
|
+
sourceRoot: string;
|
|
203
|
+
entry: fs.Dirent;
|
|
204
|
+
modes?: InstallMode[];
|
|
205
|
+
}): boolean {
|
|
206
|
+
if (entry.name !== 'codex' || !entry.isDirectory() || !modes.includes('codex')) return false;
|
|
207
|
+
const codexDir = path.join(sourceRoot, entry.name);
|
|
208
|
+
if (!fs.existsSync(codexDir)) return false;
|
|
209
|
+
const childNames = fs.readdirSync(codexDir);
|
|
210
|
+
return childNames.some((childName) => fs.existsSync(path.join(codexDir, childName, 'SKILL.md')));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function stageToolkitContents({ sourceRoot, destinationRoot, version, modes = [] }: {
|
|
214
|
+
sourceRoot: string;
|
|
215
|
+
destinationRoot: string;
|
|
216
|
+
version: string;
|
|
217
|
+
modes?: InstallMode[];
|
|
218
|
+
}): Promise<string[]> {
|
|
219
|
+
const entries = await fsp.readdir(sourceRoot, { withFileTypes: true });
|
|
220
|
+
const copiedEntries: string[] = [];
|
|
221
|
+
await fsp.mkdir(destinationRoot, { recursive: true });
|
|
222
|
+
|
|
223
|
+
for (const entry of entries) {
|
|
224
|
+
if (!shouldCopyEntry(sourceRoot, entry) && !shouldCopyCodexContainer({ sourceRoot, entry, modes })) continue;
|
|
225
|
+
const sourcePath = path.join(sourceRoot, entry.name);
|
|
226
|
+
const destinationPath = path.join(destinationRoot, entry.name);
|
|
227
|
+
await fsp.cp(sourcePath, destinationPath, { recursive: true, force: true });
|
|
228
|
+
copiedEntries.push(entry.name);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const metadata = { version, installedAt: new Date().toISOString(), source: 'npm-package' };
|
|
232
|
+
await fsp.writeFile(
|
|
233
|
+
path.join(destinationRoot, '.apollo-toolkit-install.json'),
|
|
234
|
+
`${JSON.stringify(metadata, null, 2)}\n`,
|
|
235
|
+
'utf8',
|
|
236
|
+
);
|
|
237
|
+
return copiedEntries.sort();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export async function syncToolkitHome({ sourceRoot, toolkitHome, version, modes = [] }: {
|
|
241
|
+
sourceRoot: string;
|
|
242
|
+
toolkitHome: string;
|
|
243
|
+
version: string;
|
|
244
|
+
modes?: InstallMode[];
|
|
245
|
+
}): Promise<SyncResult & { toolkitHome: string; skillNames: string[] }> {
|
|
246
|
+
const parentDir = path.dirname(toolkitHome);
|
|
247
|
+
const tempDir = path.join(parentDir, `.apollo-toolkit.tmp-${process.pid}-${Date.now()}`);
|
|
248
|
+
const previousSkillNames = await listSkillNames(toolkitHome, modes).catch(() => []);
|
|
249
|
+
|
|
250
|
+
await fsp.rm(tempDir, { recursive: true, force: true });
|
|
251
|
+
await stageToolkitContents({ sourceRoot, destinationRoot: tempDir, version, modes });
|
|
252
|
+
|
|
253
|
+
const stat = await fsp.lstat(toolkitHome).catch(() => null);
|
|
254
|
+
if (stat && !stat.isDirectory()) {
|
|
255
|
+
throw new Error(`Apollo Toolkit home exists but is not a directory: ${toolkitHome}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
await fsp.rm(toolkitHome, { recursive: true, force: true });
|
|
259
|
+
await fsp.mkdir(parentDir, { recursive: true });
|
|
260
|
+
await fsp.rename(tempDir, toolkitHome);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
toolkitHome,
|
|
264
|
+
previousSkillNames,
|
|
265
|
+
skillNames: await listSkillNames(toolkitHome, modes),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export async function getTargetRoots(modes: string[], env: NodeJS.ProcessEnv = process.env): Promise<InstallTarget[]> {
|
|
270
|
+
const homeDir = resolveHomeDirectory(env);
|
|
271
|
+
const targets: InstallTarget[] = [];
|
|
272
|
+
|
|
273
|
+
for (const mode of normalizeModes(modes)) {
|
|
274
|
+
if (mode === 'codex') {
|
|
275
|
+
targets.push({
|
|
276
|
+
id: mode,
|
|
277
|
+
label: 'Codex',
|
|
278
|
+
root: env.CODEX_SKILLS_DIR
|
|
279
|
+
? path.resolve(expandUserPath(env.CODEX_SKILLS_DIR, env))
|
|
280
|
+
: path.join(homeDir, '.codex', 'skills'),
|
|
281
|
+
});
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (mode === 'trae') {
|
|
285
|
+
targets.push({
|
|
286
|
+
id: mode,
|
|
287
|
+
label: 'Trae',
|
|
288
|
+
root: env.TRAE_SKILLS_DIR
|
|
289
|
+
? path.resolve(expandUserPath(env.TRAE_SKILLS_DIR, env))
|
|
290
|
+
: path.join(homeDir, '.trae', 'skills'),
|
|
291
|
+
});
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (mode === 'agents') {
|
|
295
|
+
targets.push({
|
|
296
|
+
id: mode,
|
|
297
|
+
label: 'Agents',
|
|
298
|
+
root: env.AGENTS_SKILLS_DIR
|
|
299
|
+
? path.resolve(expandUserPath(env.AGENTS_SKILLS_DIR, env))
|
|
300
|
+
: path.join(homeDir, '.agents', 'skills'),
|
|
301
|
+
});
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (mode === 'openclaw') {
|
|
305
|
+
const openclawHome = env.OPENCLAW_HOME
|
|
306
|
+
? path.resolve(expandUserPath(env.OPENCLAW_HOME, env))
|
|
307
|
+
: path.join(homeDir, '.openclaw');
|
|
308
|
+
const entries = await fsp.readdir(openclawHome, { withFileTypes: true }).catch(() => []);
|
|
309
|
+
const workspaceNames = entries
|
|
310
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith('workspace'))
|
|
311
|
+
.map((entry) => entry.name)
|
|
312
|
+
.sort();
|
|
313
|
+
if (workspaceNames.length === 0) {
|
|
314
|
+
throw new Error(`No workspace directories found under: ${openclawHome}`);
|
|
315
|
+
}
|
|
316
|
+
for (const workspaceName of workspaceNames) {
|
|
317
|
+
targets.push({
|
|
318
|
+
id: mode,
|
|
319
|
+
label: `OpenClaw (${workspaceName})`,
|
|
320
|
+
root: path.join(openclawHome, workspaceName, 'skills'),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (mode === 'claude-code') {
|
|
326
|
+
targets.push({
|
|
327
|
+
id: mode,
|
|
328
|
+
label: 'Claude Code',
|
|
329
|
+
root: env.CLAUDE_CODE_SKILLS_DIR
|
|
330
|
+
? path.resolve(expandUserPath(env.CLAUDE_CODE_SKILLS_DIR, env))
|
|
331
|
+
: path.join(homeDir, '.claude', 'skills'),
|
|
332
|
+
});
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return targets;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export async function getUninstallTargetRoots(modes: string[] = [...VALID_MODES], env: NodeJS.ProcessEnv = process.env): Promise<InstallTarget[]> {
|
|
340
|
+
const targets: InstallTarget[] = [];
|
|
341
|
+
for (const mode of normalizeModes(modes)) {
|
|
342
|
+
try {
|
|
343
|
+
targets.push(...await getTargetRoots([mode], env));
|
|
344
|
+
} catch {
|
|
345
|
+
// Uninstall is best-effort across agents
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return targets;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function ensureDirectory(dirPath: string): Promise<void> {
|
|
352
|
+
await fsp.mkdir(dirPath, { recursive: true });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function replaceWithCopy(sourcePath: string, targetPath: string): Promise<void> {
|
|
356
|
+
await fsp.rm(targetPath, { recursive: true, force: true });
|
|
357
|
+
await ensureDirectory(path.dirname(targetPath));
|
|
358
|
+
await fsp.cp(sourcePath, targetPath, { recursive: true, force: true });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function replaceWithSymlink(sourcePath: string, targetPath: string): Promise<void> {
|
|
362
|
+
await fsp.rm(targetPath, { recursive: true, force: true });
|
|
363
|
+
await ensureDirectory(path.dirname(targetPath));
|
|
364
|
+
await fsp.symlink(sourcePath, targetPath, process.platform === 'win32' ? 'junction' : 'dir');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export async function installLinks({ toolkitHome, modes, env = process.env, previousSkillNames = [], linkMode = 'copy', includeExclusiveSkills = false }: {
|
|
368
|
+
toolkitHome: string;
|
|
369
|
+
modes: InstallMode[];
|
|
370
|
+
env?: NodeJS.ProcessEnv;
|
|
371
|
+
previousSkillNames?: string[];
|
|
372
|
+
linkMode?: 'copy' | 'symlink';
|
|
373
|
+
includeExclusiveSkills?: boolean;
|
|
374
|
+
}): Promise<{ skillNames: string[]; targets: InstallTarget[]; copiedPaths: any[]; linkMode: string }> {
|
|
375
|
+
const normalizedModes = normalizeModes(modes);
|
|
376
|
+
const codexSkillNames = (normalizedModes.includes('codex') || includeExclusiveSkills)
|
|
377
|
+
? await listCodexSkillNames(toolkitHome)
|
|
378
|
+
: [];
|
|
379
|
+
const sharedSkillNames = await listSkillNames(toolkitHome);
|
|
380
|
+
const skillNames = normalizedModes.includes('codex')
|
|
381
|
+
? [...new Set([...sharedSkillNames, ...codexSkillNames])].sort()
|
|
382
|
+
: sharedSkillNames;
|
|
383
|
+
const targets = await getTargetRoots(normalizedModes, env);
|
|
384
|
+
const copiedPaths: any[] = [];
|
|
385
|
+
|
|
386
|
+
for (const target of targets) {
|
|
387
|
+
const targetSkillNames = getTargetSkillNames({
|
|
388
|
+
targetMode: target.id,
|
|
389
|
+
sharedSkillNames,
|
|
390
|
+
codexSkillNames,
|
|
391
|
+
includeExclusiveSkills,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const existingManifest = await readManifest(target.root!);
|
|
395
|
+
const allPreviousSkills = existingManifest
|
|
396
|
+
? [...new Set([...getManifestSkillNames(existingManifest), ...previousSkillNames.filter(isSafeSkillName)])]
|
|
397
|
+
: previousSkillNames.filter(isSafeSkillName);
|
|
398
|
+
|
|
399
|
+
const staleSkillNames = allPreviousSkills.filter(
|
|
400
|
+
(skillName) => !targetSkillNames.includes(skillName),
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
await ensureDirectory(target.root!);
|
|
404
|
+
for (const staleSkillName of staleSkillNames) {
|
|
405
|
+
await fsp.rm(path.join(target.root!, staleSkillName), { recursive: true, force: true });
|
|
406
|
+
}
|
|
407
|
+
for (const skillName of targetSkillNames) {
|
|
408
|
+
const sourcePath = resolveInstallSourcePath({
|
|
409
|
+
toolkitHome,
|
|
410
|
+
targetMode: target.id,
|
|
411
|
+
skillName,
|
|
412
|
+
codexSkillNames,
|
|
413
|
+
});
|
|
414
|
+
const targetPath = path.join(target.root!, skillName);
|
|
415
|
+
|
|
416
|
+
if (linkMode === 'symlink') {
|
|
417
|
+
await replaceWithSymlink(sourcePath, targetPath);
|
|
418
|
+
} else {
|
|
419
|
+
await replaceWithCopy(sourcePath, targetPath);
|
|
420
|
+
}
|
|
421
|
+
copiedPaths.push({ target: target.label, path: targetPath, skillName, linkMode });
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
await writeManifest(target.root!, {
|
|
425
|
+
version: existingManifest?.version || 'unknown',
|
|
426
|
+
linkMode,
|
|
427
|
+
skills: targetSkillNames,
|
|
428
|
+
previousSkills: allPreviousSkills,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return { skillNames, targets, copiedPaths, linkMode };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export async function uninstallSkills({ env = process.env, modes = null }: { env?: NodeJS.ProcessEnv; modes?: InstallMode[] | null } = {}): Promise<{ target: string; root: string; removedSkills: string[] }[]> {
|
|
436
|
+
const normalizedModes = modes ? normalizeModes(modes) : [...VALID_MODES];
|
|
437
|
+
const targets = await getUninstallTargetRoots(normalizedModes, env);
|
|
438
|
+
const results: { target: string; root: string; removedSkills: string[] }[] = [];
|
|
439
|
+
|
|
440
|
+
for (const target of targets) {
|
|
441
|
+
const manifest = await readManifest(target.root!);
|
|
442
|
+
if (!manifest) continue;
|
|
443
|
+
|
|
444
|
+
const skillNames = getManifestSkillNames(manifest);
|
|
445
|
+
const removedSkills: string[] = [];
|
|
446
|
+
for (const skillName of skillNames) {
|
|
447
|
+
const skillPath = path.join(target.root!, skillName);
|
|
448
|
+
try {
|
|
449
|
+
await fsp.rm(skillPath, { recursive: true, force: true });
|
|
450
|
+
removedSkills.push(skillName);
|
|
451
|
+
} catch {
|
|
452
|
+
// skip
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
await fsp.rm(path.join(target.root!, MANIFEST_FILENAME), { force: true });
|
|
458
|
+
} catch {
|
|
459
|
+
// ok
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
results.push({ target: target.label, root: target.root!, removedSkills });
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return results;
|
|
466
|
+
}
|