@algochad/archcoder 2.0.2
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/README.md +113 -0
- package/bin/cli-entry.js +55 -0
- package/bin/cli-output.js +145 -0
- package/bin/cli.js +5108 -0
- package/bin/cli.test.js +56 -0
- package/dist/apple-touch-icon-120x120.png +0 -0
- package/dist/apple-touch-icon-152x152.png +0 -0
- package/dist/apple-touch-icon-167x167.png +0 -0
- package/dist/apple-touch-icon-180x180.png +0 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/apple-touch-icon.svg +67 -0
- package/dist/assets/MultiRunWindow-BZp3MjJP.js +1 -0
- package/dist/assets/SettingsWindow-DoGYXpX7.js +1 -0
- package/dist/assets/TerminalView-BN7BR5Ff.js +3 -0
- package/dist/assets/TimelineDialog-ZQ33oVQR.js +1 -0
- package/dist/assets/ToolOutputDialog-Blv3pnug.js +16 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/assets/index-CtCEGYrr.css +1 -0
- package/dist/assets/index-o_d2wtWC.js +48 -0
- package/dist/assets/main-5QGBtzdq.css +1 -0
- package/dist/assets/main-B6oiMU86.js +8033 -0
- package/dist/assets/vendor--DbVqbJpV.css +1 -0
- package/dist/assets/vendor-.bun-HTKwyaEM.js +10086 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/worker-bqd4RMrj.js +155 -0
- package/dist/favicon-16.png +0 -0
- package/dist/favicon-32.png +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +67 -0
- package/dist/index.html +533 -0
- package/dist/logo-dark-192x192.png +0 -0
- package/dist/logo-dark-512x512.svg +16 -0
- package/dist/logo-light-192x192.png +0 -0
- package/dist/logo-light-512x512.svg +16 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/pwa-maskable-192.png +0 -0
- package/dist/pwa-maskable-512.png +0 -0
- package/dist/site.webmanifest +22 -0
- package/dist/sw.js +1 -0
- package/package.json +107 -0
- package/public/apple-touch-icon-120x120.png +0 -0
- package/public/apple-touch-icon-152x152.png +0 -0
- package/public/apple-touch-icon-167x167.png +0 -0
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/apple-touch-icon.svg +67 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +67 -0
- package/public/logo-dark-192x192.png +0 -0
- package/public/logo-dark-512x512.svg +16 -0
- package/public/logo-light-192x192.png +0 -0
- package/public/logo-light-512x512.svg +16 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/pwa-maskable-192.png +0 -0
- package/public/pwa-maskable-512.png +0 -0
- package/public/site.webmanifest +22 -0
- package/server/TERMINAL_INPUT_WS_PROTOCOL.md +44 -0
- package/server/index.d.ts +37 -0
- package/server/index.js +14694 -0
- package/server/lib/cloudflare-tunnel.js +650 -0
- package/server/lib/git/DOCUMENTATION.md +146 -0
- package/server/lib/git/credentials.js +74 -0
- package/server/lib/git/identity-storage.js +110 -0
- package/server/lib/git/index.js +6 -0
- package/server/lib/git/service.js +3117 -0
- package/server/lib/github/DOCUMENTATION.md +170 -0
- package/server/lib/github/auth.js +307 -0
- package/server/lib/github/device-flow.js +50 -0
- package/server/lib/github/index.js +24 -0
- package/server/lib/github/octokit.js +10 -0
- package/server/lib/github/pr-status.js +478 -0
- package/server/lib/github/repo/index.js +55 -0
- package/server/lib/installer/desktop.js +289 -0
- package/server/lib/installer/download.js +208 -0
- package/server/lib/installer/index.js +45 -0
- package/server/lib/installer/platform.js +100 -0
- package/server/lib/notifications/DOCUMENTATION.md +61 -0
- package/server/lib/notifications/index.js +1 -0
- package/server/lib/notifications/message.js +49 -0
- package/server/lib/notifications/message.test.js +59 -0
- package/server/lib/opencode/DOCUMENTATION.md +59 -0
- package/server/lib/opencode/agents.js +634 -0
- package/server/lib/opencode/auth.js +81 -0
- package/server/lib/opencode/commands.js +339 -0
- package/server/lib/opencode/index.js +66 -0
- package/server/lib/opencode/mcp.js +206 -0
- package/server/lib/opencode/providers.js +96 -0
- package/server/lib/opencode/shared.js +527 -0
- package/server/lib/opencode/skills.js +480 -0
- package/server/lib/opencode/tunnel-auth.js +591 -0
- package/server/lib/opencode/ui-auth.js +510 -0
- package/server/lib/package-manager.js +505 -0
- package/server/lib/quota/DOCUMENTATION.md +55 -0
- package/server/lib/quota/index.js +24 -0
- package/server/lib/quota/providers/claude.js +107 -0
- package/server/lib/quota/providers/codex.js +113 -0
- package/server/lib/quota/providers/copilot.js +165 -0
- package/server/lib/quota/providers/google/api.js +92 -0
- package/server/lib/quota/providers/google/auth.js +108 -0
- package/server/lib/quota/providers/google/index.js +124 -0
- package/server/lib/quota/providers/google/transforms.js +109 -0
- package/server/lib/quota/providers/index.js +152 -0
- package/server/lib/quota/providers/interface.js +55 -0
- package/server/lib/quota/providers/kimi.js +108 -0
- package/server/lib/quota/providers/minimax-cn-coding-plan.js +15 -0
- package/server/lib/quota/providers/minimax-coding-plan.js +15 -0
- package/server/lib/quota/providers/minimax-shared.js +136 -0
- package/server/lib/quota/providers/nanogpt.js +124 -0
- package/server/lib/quota/providers/ollama-cloud.js +112 -0
- package/server/lib/quota/providers/openai.js +91 -0
- package/server/lib/quota/providers/openrouter.js +92 -0
- package/server/lib/quota/providers/zai.js +91 -0
- package/server/lib/quota/utils/auth.js +46 -0
- package/server/lib/quota/utils/formatters.js +76 -0
- package/server/lib/quota/utils/index.js +10 -0
- package/server/lib/quota/utils/transformers.js +55 -0
- package/server/lib/skills-catalog/DOCUMENTATION.md +178 -0
- package/server/lib/skills-catalog/cache.js +32 -0
- package/server/lib/skills-catalog/clawdhub/api.js +158 -0
- package/server/lib/skills-catalog/clawdhub/index.js +30 -0
- package/server/lib/skills-catalog/clawdhub/install.js +238 -0
- package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
- package/server/lib/skills-catalog/curated-sources.js +21 -0
- package/server/lib/skills-catalog/git.js +77 -0
- package/server/lib/skills-catalog/index.js +42 -0
- package/server/lib/skills-catalog/install.js +294 -0
- package/server/lib/skills-catalog/scan.js +221 -0
- package/server/lib/skills-catalog/source.js +85 -0
- package/server/lib/terminal/DOCUMENTATION.md +114 -0
- package/server/lib/terminal/index.js +12 -0
- package/server/lib/terminal/input-ws-protocol.js +66 -0
- package/server/lib/terminal/input-ws-protocol.test.js +138 -0
- package/server/lib/tts/DOCUMENTATION.md +134 -0
- package/server/lib/tts/index.js +16 -0
- package/server/lib/tts/service.js +162 -0
- package/server/lib/tts/summarization.js +171 -0
- package/server/lib/tunnels/index.js +166 -0
- package/server/lib/tunnels/providers/cloudflare.js +260 -0
- package/server/lib/tunnels/registry.js +51 -0
- package/server/lib/tunnels/types.js +219 -0
- package/server/lib/utils/lru.js +107 -0
- package/server/lib/utils/sse.js +121 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import {
|
|
5
|
+
SKILL_DIR,
|
|
6
|
+
OPENCODE_CONFIG_DIR,
|
|
7
|
+
SKILL_SCOPE,
|
|
8
|
+
ensureDirs,
|
|
9
|
+
parseMdFile,
|
|
10
|
+
writeMdFile,
|
|
11
|
+
readConfigLayers,
|
|
12
|
+
readConfig,
|
|
13
|
+
walkSkillMdFiles,
|
|
14
|
+
addSkillFromMdFile,
|
|
15
|
+
resolveSkillSearchDirectories,
|
|
16
|
+
listSkillSupportingFiles,
|
|
17
|
+
readSkillSupportingFile,
|
|
18
|
+
writeSkillSupportingFile,
|
|
19
|
+
deleteSkillSupportingFile,
|
|
20
|
+
getAncestors,
|
|
21
|
+
findWorktreeRoot,
|
|
22
|
+
} from './shared.js';
|
|
23
|
+
|
|
24
|
+
function ensureProjectSkillDir(workingDirectory) {
|
|
25
|
+
const projectSkillDir = path.join(workingDirectory, '.opencode', 'skills');
|
|
26
|
+
if (!fs.existsSync(projectSkillDir)) {
|
|
27
|
+
fs.mkdirSync(projectSkillDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
const legacyProjectSkillDir = path.join(workingDirectory, '.opencode', 'skill');
|
|
30
|
+
if (!fs.existsSync(legacyProjectSkillDir)) {
|
|
31
|
+
fs.mkdirSync(legacyProjectSkillDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
return projectSkillDir;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getProjectSkillDir(workingDirectory, skillName) {
|
|
37
|
+
const pluralPath = path.join(workingDirectory, '.opencode', 'skills', skillName);
|
|
38
|
+
const legacyPath = path.join(workingDirectory, '.opencode', 'skill', skillName);
|
|
39
|
+
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
|
|
40
|
+
return pluralPath;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getProjectSkillPath(workingDirectory, skillName) {
|
|
44
|
+
const pluralPath = path.join(workingDirectory, '.opencode', 'skills', skillName, 'SKILL.md');
|
|
45
|
+
const legacyPath = path.join(workingDirectory, '.opencode', 'skill', skillName, 'SKILL.md');
|
|
46
|
+
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
|
|
47
|
+
return pluralPath;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getUserSkillDir(skillName) {
|
|
51
|
+
const pluralPath = path.join(SKILL_DIR, skillName);
|
|
52
|
+
const legacyPath = path.join(OPENCODE_CONFIG_DIR, 'skill', skillName);
|
|
53
|
+
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
|
|
54
|
+
return pluralPath;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getUserSkillPath(skillName) {
|
|
58
|
+
const pluralPath = path.join(SKILL_DIR, skillName, 'SKILL.md');
|
|
59
|
+
const legacyPath = path.join(OPENCODE_CONFIG_DIR, 'skill', skillName, 'SKILL.md');
|
|
60
|
+
if (fs.existsSync(legacyPath) && !fs.existsSync(pluralPath)) return legacyPath;
|
|
61
|
+
return pluralPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getClaudeSkillDir(workingDirectory, skillName) {
|
|
65
|
+
return path.join(workingDirectory, '.claude', 'skills', skillName);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getClaudeSkillPath(workingDirectory, skillName) {
|
|
69
|
+
return path.join(getClaudeSkillDir(workingDirectory, skillName), 'SKILL.md');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getUserAgentsSkillDir(skillName) {
|
|
73
|
+
return path.join(os.homedir(), '.agents', 'skills', skillName);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getUserAgentsSkillPath(skillName) {
|
|
77
|
+
return path.join(getUserAgentsSkillDir(skillName), 'SKILL.md');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getProjectAgentsSkillDir(workingDirectory, skillName) {
|
|
81
|
+
return path.join(workingDirectory, '.agents', 'skills', skillName);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getProjectAgentsSkillPath(workingDirectory, skillName) {
|
|
85
|
+
return path.join(getProjectAgentsSkillDir(workingDirectory, skillName), 'SKILL.md');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getSkillScope(skillName, workingDirectory) {
|
|
89
|
+
const discovered = discoverSkills(workingDirectory).find((skill) => skill.name === skillName);
|
|
90
|
+
if (discovered?.path) {
|
|
91
|
+
return { scope: discovered.scope || null, path: discovered.path, source: discovered.source || null };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (workingDirectory) {
|
|
95
|
+
const projectPath = getProjectSkillPath(workingDirectory, skillName);
|
|
96
|
+
if (fs.existsSync(projectPath)) {
|
|
97
|
+
return { scope: SKILL_SCOPE.PROJECT, path: projectPath, source: 'opencode' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const claudePath = getClaudeSkillPath(workingDirectory, skillName);
|
|
101
|
+
if (fs.existsSync(claudePath)) {
|
|
102
|
+
return { scope: SKILL_SCOPE.PROJECT, path: claudePath, source: 'claude' };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const userPath = getUserSkillPath(skillName);
|
|
107
|
+
if (fs.existsSync(userPath)) {
|
|
108
|
+
return { scope: SKILL_SCOPE.USER, path: userPath, source: 'opencode' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { scope: null, path: null, source: null };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getSkillWritePath(skillName, workingDirectory, requestedScope) {
|
|
115
|
+
const existing = getSkillScope(skillName, workingDirectory);
|
|
116
|
+
if (existing.path) {
|
|
117
|
+
return existing;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const scope = requestedScope || SKILL_SCOPE.USER;
|
|
121
|
+
if (scope === SKILL_SCOPE.PROJECT && workingDirectory) {
|
|
122
|
+
return {
|
|
123
|
+
scope: SKILL_SCOPE.PROJECT,
|
|
124
|
+
path: getProjectSkillPath(workingDirectory, skillName),
|
|
125
|
+
source: 'opencode'
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
scope: SKILL_SCOPE.USER,
|
|
131
|
+
path: getUserSkillPath(skillName),
|
|
132
|
+
source: 'opencode'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function discoverSkills(workingDirectory) {
|
|
137
|
+
const skills = new Map();
|
|
138
|
+
|
|
139
|
+
for (const externalRootName of ['.claude', '.agents']) {
|
|
140
|
+
const homeRoot = path.join(os.homedir(), externalRootName, 'skills');
|
|
141
|
+
const source = externalRootName === '.agents' ? 'agents' : 'claude';
|
|
142
|
+
for (const skillMdPath of walkSkillMdFiles(homeRoot)) {
|
|
143
|
+
addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.USER, source);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (workingDirectory) {
|
|
148
|
+
const worktreeRoot = findWorktreeRoot(workingDirectory) || path.resolve(workingDirectory);
|
|
149
|
+
const ancestors = getAncestors(workingDirectory, worktreeRoot);
|
|
150
|
+
for (const ancestor of ancestors) {
|
|
151
|
+
for (const externalRootName of ['.claude', '.agents']) {
|
|
152
|
+
const source = externalRootName === '.agents' ? 'agents' : 'claude';
|
|
153
|
+
const externalSkillsRoot = path.join(ancestor, externalRootName, 'skills');
|
|
154
|
+
for (const skillMdPath of walkSkillMdFiles(externalSkillsRoot)) {
|
|
155
|
+
addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.PROJECT, source);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const configDirectories = resolveSkillSearchDirectories(workingDirectory);
|
|
162
|
+
const homeOpencodeDir = path.resolve(path.join(os.homedir(), '.opencode'));
|
|
163
|
+
const customConfigDir = process.env.OPENCODE_CONFIG_DIR
|
|
164
|
+
? path.resolve(process.env.OPENCODE_CONFIG_DIR)
|
|
165
|
+
: null;
|
|
166
|
+
for (const dir of configDirectories) {
|
|
167
|
+
for (const subDir of ['skill', 'skills']) {
|
|
168
|
+
const root = path.join(dir, subDir);
|
|
169
|
+
for (const skillMdPath of walkSkillMdFiles(root)) {
|
|
170
|
+
const isUserConfigDir = dir === OPENCODE_CONFIG_DIR
|
|
171
|
+
|| dir === homeOpencodeDir
|
|
172
|
+
|| (customConfigDir && dir === customConfigDir);
|
|
173
|
+
const scope = isUserConfigDir ? SKILL_SCOPE.USER : SKILL_SCOPE.PROJECT;
|
|
174
|
+
addSkillFromMdFile(skills, skillMdPath, scope, 'opencode');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let configuredPaths = [];
|
|
180
|
+
try {
|
|
181
|
+
const config = readConfig(workingDirectory);
|
|
182
|
+
configuredPaths = Array.isArray(config?.skills?.paths) ? config.skills.paths : [];
|
|
183
|
+
} catch {
|
|
184
|
+
configuredPaths = [];
|
|
185
|
+
}
|
|
186
|
+
for (const skillPath of configuredPaths) {
|
|
187
|
+
if (typeof skillPath !== 'string' || !skillPath.trim()) continue;
|
|
188
|
+
const expanded = skillPath.startsWith('~/')
|
|
189
|
+
? path.join(os.homedir(), skillPath.slice(2))
|
|
190
|
+
: skillPath;
|
|
191
|
+
const resolved = path.isAbsolute(expanded)
|
|
192
|
+
? path.resolve(expanded)
|
|
193
|
+
: path.resolve(workingDirectory || process.cwd(), expanded);
|
|
194
|
+
for (const skillMdPath of walkSkillMdFiles(resolved)) {
|
|
195
|
+
addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.PROJECT, 'opencode');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const cacheCandidates = [];
|
|
200
|
+
if (process.env.XDG_CACHE_HOME) {
|
|
201
|
+
cacheCandidates.push(path.join(process.env.XDG_CACHE_HOME, 'opencode', 'skills'));
|
|
202
|
+
}
|
|
203
|
+
cacheCandidates.push(path.join(os.homedir(), '.cache', 'opencode', 'skills'));
|
|
204
|
+
cacheCandidates.push(path.join(os.homedir(), 'Library', 'Caches', 'opencode', 'skills'));
|
|
205
|
+
|
|
206
|
+
for (const cacheRoot of cacheCandidates) {
|
|
207
|
+
if (!fs.existsSync(cacheRoot)) continue;
|
|
208
|
+
const entries = fs.readdirSync(cacheRoot, { withFileTypes: true });
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
if (!entry.isDirectory()) continue;
|
|
211
|
+
const skillRoot = path.join(cacheRoot, entry.name);
|
|
212
|
+
for (const skillMdPath of walkSkillMdFiles(skillRoot)) {
|
|
213
|
+
addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.USER, 'opencode');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return Array.from(skills.values());
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getSkillSources(skillName, workingDirectory, discoveredSkill = null) {
|
|
222
|
+
const projectPath = workingDirectory ? getProjectSkillPath(workingDirectory, skillName) : null;
|
|
223
|
+
const projectExists = projectPath && fs.existsSync(projectPath);
|
|
224
|
+
const projectDir = projectExists ? path.dirname(projectPath) : null;
|
|
225
|
+
|
|
226
|
+
const claudePath = workingDirectory ? getClaudeSkillPath(workingDirectory, skillName) : null;
|
|
227
|
+
const claudeExists = claudePath && fs.existsSync(claudePath);
|
|
228
|
+
const claudeDir = claudeExists ? path.dirname(claudePath) : null;
|
|
229
|
+
|
|
230
|
+
const userPath = getUserSkillPath(skillName);
|
|
231
|
+
const userExists = fs.existsSync(userPath);
|
|
232
|
+
const userDir = userExists ? path.dirname(userPath) : null;
|
|
233
|
+
|
|
234
|
+
const matchedDiscovered = discoveredSkill && discoveredSkill.name === skillName
|
|
235
|
+
? discoveredSkill
|
|
236
|
+
: discoverSkills(workingDirectory).find((skill) => skill.name === skillName);
|
|
237
|
+
|
|
238
|
+
let mdPath = null;
|
|
239
|
+
let mdScope = null;
|
|
240
|
+
let mdSource = null;
|
|
241
|
+
let mdDir = null;
|
|
242
|
+
|
|
243
|
+
if (projectExists) {
|
|
244
|
+
mdPath = projectPath;
|
|
245
|
+
mdScope = SKILL_SCOPE.PROJECT;
|
|
246
|
+
mdSource = 'opencode';
|
|
247
|
+
mdDir = projectDir;
|
|
248
|
+
} else if (claudeExists) {
|
|
249
|
+
mdPath = claudePath;
|
|
250
|
+
mdScope = SKILL_SCOPE.PROJECT;
|
|
251
|
+
mdSource = 'claude';
|
|
252
|
+
mdDir = claudeDir;
|
|
253
|
+
} else if (userExists) {
|
|
254
|
+
mdPath = userPath;
|
|
255
|
+
mdScope = SKILL_SCOPE.USER;
|
|
256
|
+
mdSource = 'opencode';
|
|
257
|
+
mdDir = userDir;
|
|
258
|
+
} else if (matchedDiscovered?.path) {
|
|
259
|
+
mdPath = matchedDiscovered.path;
|
|
260
|
+
mdScope = matchedDiscovered.scope || null;
|
|
261
|
+
mdSource = matchedDiscovered.source || null;
|
|
262
|
+
mdDir = path.dirname(matchedDiscovered.path);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const mdExists = !!mdPath;
|
|
266
|
+
|
|
267
|
+
const sources = {
|
|
268
|
+
md: {
|
|
269
|
+
exists: mdExists,
|
|
270
|
+
path: mdPath,
|
|
271
|
+
dir: mdDir,
|
|
272
|
+
scope: mdScope,
|
|
273
|
+
source: mdSource,
|
|
274
|
+
fields: [],
|
|
275
|
+
supportingFiles: []
|
|
276
|
+
},
|
|
277
|
+
projectMd: {
|
|
278
|
+
exists: projectExists,
|
|
279
|
+
path: projectPath,
|
|
280
|
+
dir: projectDir
|
|
281
|
+
},
|
|
282
|
+
claudeMd: {
|
|
283
|
+
exists: claudeExists,
|
|
284
|
+
path: claudePath,
|
|
285
|
+
dir: claudeDir
|
|
286
|
+
},
|
|
287
|
+
userMd: {
|
|
288
|
+
exists: userExists,
|
|
289
|
+
path: userPath,
|
|
290
|
+
dir: userDir
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
if (mdExists && mdDir) {
|
|
295
|
+
const { frontmatter, body } = parseMdFile(mdPath);
|
|
296
|
+
sources.md.fields = Object.keys(frontmatter);
|
|
297
|
+
sources.md.description = frontmatter.description || '';
|
|
298
|
+
sources.md.name = frontmatter.name || skillName;
|
|
299
|
+
if (body) {
|
|
300
|
+
sources.md.fields.push('instructions');
|
|
301
|
+
sources.md.instructions = body;
|
|
302
|
+
} else {
|
|
303
|
+
sources.md.instructions = '';
|
|
304
|
+
}
|
|
305
|
+
sources.md.supportingFiles = listSkillSupportingFiles(mdDir);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return sources;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function createSkill(skillName, config, workingDirectory, scope) {
|
|
312
|
+
ensureDirs();
|
|
313
|
+
|
|
314
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(skillName) || skillName.length > 64) {
|
|
315
|
+
throw new Error(`Invalid skill name "${skillName}". Must be 1-64 lowercase alphanumeric characters with hyphens, cannot start or end with hyphen.`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const existing = getSkillScope(skillName, workingDirectory);
|
|
319
|
+
if (existing.path) {
|
|
320
|
+
throw new Error(`Skill ${skillName} already exists at ${existing.path}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let targetDir;
|
|
324
|
+
let targetPath;
|
|
325
|
+
let targetScope;
|
|
326
|
+
|
|
327
|
+
const requestedScope = scope === SKILL_SCOPE.PROJECT ? SKILL_SCOPE.PROJECT : SKILL_SCOPE.USER;
|
|
328
|
+
const requestedSource = config?.source === 'agents' ? 'agents' : 'opencode';
|
|
329
|
+
|
|
330
|
+
if (requestedScope === SKILL_SCOPE.PROJECT && workingDirectory) {
|
|
331
|
+
ensureProjectSkillDir(workingDirectory);
|
|
332
|
+
if (requestedSource === 'agents') {
|
|
333
|
+
targetDir = getProjectAgentsSkillDir(workingDirectory, skillName);
|
|
334
|
+
targetPath = getProjectAgentsSkillPath(workingDirectory, skillName);
|
|
335
|
+
} else {
|
|
336
|
+
targetDir = getProjectSkillDir(workingDirectory, skillName);
|
|
337
|
+
targetPath = getProjectSkillPath(workingDirectory, skillName);
|
|
338
|
+
}
|
|
339
|
+
targetScope = SKILL_SCOPE.PROJECT;
|
|
340
|
+
} else {
|
|
341
|
+
if (requestedSource === 'agents') {
|
|
342
|
+
targetDir = getUserAgentsSkillDir(skillName);
|
|
343
|
+
targetPath = getUserAgentsSkillPath(skillName);
|
|
344
|
+
} else {
|
|
345
|
+
targetDir = getUserSkillDir(skillName);
|
|
346
|
+
targetPath = getUserSkillPath(skillName);
|
|
347
|
+
}
|
|
348
|
+
targetScope = SKILL_SCOPE.USER;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
352
|
+
|
|
353
|
+
const { instructions, scope: _scopeFromConfig, source: _sourceFromConfig, supportingFiles, ...frontmatter } = config;
|
|
354
|
+
void _scopeFromConfig;
|
|
355
|
+
void _sourceFromConfig;
|
|
356
|
+
|
|
357
|
+
if (!frontmatter.name) {
|
|
358
|
+
frontmatter.name = skillName;
|
|
359
|
+
}
|
|
360
|
+
if (!frontmatter.description) {
|
|
361
|
+
throw new Error('Skill description is required');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
writeMdFile(targetPath, frontmatter, instructions || '');
|
|
365
|
+
|
|
366
|
+
if (supportingFiles && Array.isArray(supportingFiles)) {
|
|
367
|
+
for (const file of supportingFiles) {
|
|
368
|
+
if (file.path && file.content !== undefined) {
|
|
369
|
+
writeSkillSupportingFile(targetDir, file.path, file.content);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
console.log(`Created new skill: ${skillName} (scope: ${targetScope}, path: ${targetPath})`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function updateSkill(skillName, updates, workingDirectory) {
|
|
378
|
+
ensureDirs();
|
|
379
|
+
|
|
380
|
+
const existing = getSkillScope(skillName, workingDirectory);
|
|
381
|
+
if (!existing.path) {
|
|
382
|
+
throw new Error(`Skill "${skillName}" not found`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const mdPath = existing.path;
|
|
386
|
+
const mdDir = path.dirname(mdPath);
|
|
387
|
+
const mdData = parseMdFile(mdPath);
|
|
388
|
+
|
|
389
|
+
let mdModified = false;
|
|
390
|
+
|
|
391
|
+
for (const [field, value] of Object.entries(updates)) {
|
|
392
|
+
if (field === 'scope') {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (field === 'instructions') {
|
|
397
|
+
const normalizedValue = typeof value === 'string' ? value : (value == null ? '' : String(value));
|
|
398
|
+
mdData.body = normalizedValue;
|
|
399
|
+
mdModified = true;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (field === 'supportingFiles') {
|
|
404
|
+
if (Array.isArray(value)) {
|
|
405
|
+
for (const file of value) {
|
|
406
|
+
if (file.delete && file.path) {
|
|
407
|
+
deleteSkillSupportingFile(mdDir, file.path);
|
|
408
|
+
} else if (file.path && file.content !== undefined) {
|
|
409
|
+
writeSkillSupportingFile(mdDir, file.path, file.content);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
mdData.frontmatter[field] = value;
|
|
417
|
+
mdModified = true;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (mdModified) {
|
|
421
|
+
writeMdFile(mdPath, mdData.frontmatter, mdData.body);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
console.log(`Updated skill: ${skillName} (path: ${mdPath})`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function deleteSkill(skillName, workingDirectory) {
|
|
428
|
+
let deleted = false;
|
|
429
|
+
|
|
430
|
+
if (workingDirectory) {
|
|
431
|
+
const projectDir = getProjectSkillDir(workingDirectory, skillName);
|
|
432
|
+
if (fs.existsSync(projectDir)) {
|
|
433
|
+
fs.rmSync(projectDir, { recursive: true, force: true });
|
|
434
|
+
console.log(`Deleted project-level skill directory: ${projectDir}`);
|
|
435
|
+
deleted = true;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const claudeDir = getClaudeSkillDir(workingDirectory, skillName);
|
|
439
|
+
if (fs.existsSync(claudeDir)) {
|
|
440
|
+
fs.rmSync(claudeDir, { recursive: true, force: true });
|
|
441
|
+
console.log(`Deleted claude-compat skill directory: ${claudeDir}`);
|
|
442
|
+
deleted = true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const projectAgentsDir = getProjectAgentsSkillDir(workingDirectory, skillName);
|
|
446
|
+
if (fs.existsSync(projectAgentsDir)) {
|
|
447
|
+
fs.rmSync(projectAgentsDir, { recursive: true, force: true });
|
|
448
|
+
console.log(`Deleted project-level agents skill directory: ${projectAgentsDir}`);
|
|
449
|
+
deleted = true;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const userDir = getUserSkillDir(skillName);
|
|
454
|
+
if (fs.existsSync(userDir)) {
|
|
455
|
+
fs.rmSync(userDir, { recursive: true, force: true });
|
|
456
|
+
console.log(`Deleted user-level skill directory: ${userDir}`);
|
|
457
|
+
deleted = true;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const userAgentsDir = getUserAgentsSkillDir(skillName);
|
|
461
|
+
if (fs.existsSync(userAgentsDir)) {
|
|
462
|
+
fs.rmSync(userAgentsDir, { recursive: true, force: true });
|
|
463
|
+
console.log(`Deleted user-level agents skill directory: ${userAgentsDir}`);
|
|
464
|
+
deleted = true;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (!deleted) {
|
|
468
|
+
throw new Error(`Skill "${skillName}" not found`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export {
|
|
473
|
+
getSkillSources,
|
|
474
|
+
getSkillScope,
|
|
475
|
+
getSkillWritePath,
|
|
476
|
+
discoverSkills,
|
|
477
|
+
createSkill,
|
|
478
|
+
updateSkill,
|
|
479
|
+
deleteSkill,
|
|
480
|
+
};
|