@orderful/droid 0.10.4 → 0.11.0
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/.claude/CLAUDE.md +13 -6
- package/CHANGELOG.md +21 -0
- package/README.md +23 -24
- package/dist/bin/droid.js +9 -9
- package/dist/bin/droid.js.map +1 -1
- package/dist/commands/install.d.ts +1 -1
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +24 -23
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/setup.d.ts +3 -3
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +32 -28
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +60 -53
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +213 -319
- package/dist/commands/tui.js.map +1 -1
- package/dist/commands/uninstall.d.ts +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +15 -6
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts +2 -2
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +9 -9
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/agents.d.ts +11 -10
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +52 -54
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +42 -5
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/platforms.d.ts +41 -0
- package/dist/lib/platforms.d.ts.map +1 -0
- package/dist/lib/platforms.js +52 -0
- package/dist/lib/platforms.js.map +1 -0
- package/dist/lib/skills.d.ts +19 -11
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +127 -106
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/tools.d.ts +30 -0
- package/dist/lib/tools.d.ts.map +1 -0
- package/dist/lib/tools.js +116 -0
- package/dist/lib/tools.js.map +1 -0
- package/dist/lib/types.d.ts +45 -2
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +24 -5
- package/dist/lib/types.js.map +1 -1
- package/dist/tools/brain/TOOL.yaml +27 -0
- package/dist/tools/coach/TOOL.yaml +21 -0
- package/dist/tools/code-review/TOOL.yaml +18 -0
- package/dist/{skills → tools}/code-review/commands/code-review.md +23 -1
- package/dist/tools/comments/TOOL.yaml +27 -0
- package/dist/tools/project/TOOL.yaml +26 -0
- package/package.json +2 -2
- package/src/bin/droid.ts +9 -9
- package/src/commands/install.ts +24 -23
- package/src/commands/setup.test.ts +2 -2
- package/src/commands/setup.ts +33 -29
- package/src/commands/skills.ts +63 -64
- package/src/commands/tui.tsx +432 -578
- package/src/commands/uninstall.ts +17 -6
- package/src/commands/update.ts +10 -10
- package/src/lib/agents.ts +58 -58
- package/src/lib/config.test.ts +0 -10
- package/src/lib/config.ts +47 -5
- package/src/lib/platforms.ts +59 -0
- package/src/lib/skills.test.ts +53 -28
- package/src/lib/skills.ts +136 -109
- package/src/lib/tools.ts +140 -0
- package/src/lib/types.test.ts +15 -7
- package/src/lib/types.ts +63 -2
- package/src/tools/brain/TOOL.yaml +27 -0
- package/src/tools/coach/TOOL.yaml +21 -0
- package/src/tools/code-review/TOOL.yaml +18 -0
- package/src/{skills → tools}/code-review/commands/code-review.md +23 -1
- package/src/tools/comments/TOOL.yaml +27 -0
- package/src/tools/project/TOOL.yaml +26 -0
- package/dist/agents/README.md +0 -137
- package/src/agents/README.md +0 -137
- /package/dist/{skills → tools}/README.md +0 -0
- /package/dist/{skills → tools}/brain/commands/README.md +0 -0
- /package/dist/{skills → tools}/brain/commands/brain.md +0 -0
- /package/dist/{skills → tools}/brain/commands/scratchpad.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/SKILL.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/SKILL.yaml +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/references/metadata.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/references/naming.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/references/templates.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/references/workflows.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain-obsidian/SKILL.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain-obsidian/SKILL.yaml +0 -0
- /package/dist/{skills → tools/brain/skills}/brain-obsidian/references/templates.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain-obsidian/references/workflows.md +0 -0
- /package/dist/{skills → tools}/coach/commands/README.md +0 -0
- /package/dist/{skills → tools}/coach/commands/coach.md +0 -0
- /package/dist/{skills → tools/coach/skills}/coach/SKILL.md +0 -0
- /package/dist/{skills → tools/coach/skills}/coach/SKILL.yaml +0 -0
- /package/dist/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.md +0 -0
- /package/dist/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -0
- /package/dist/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.md +0 -0
- /package/dist/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -0
- /package/dist/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.md +0 -0
- /package/dist/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -0
- /package/dist/{skills → tools}/code-review/agents/type-reviewer/AGENT.md +0 -0
- /package/dist/{skills → tools}/code-review/agents/type-reviewer/AGENT.yaml +0 -0
- /package/dist/{skills → tools/code-review/skills}/code-review/SKILL.md +0 -0
- /package/dist/{skills → tools/code-review/skills}/code-review/SKILL.yaml +0 -0
- /package/dist/{skills → tools}/comments/commands/README.md +0 -0
- /package/dist/{skills → tools}/comments/commands/comments.md +0 -0
- /package/dist/{skills → tools/comments/skills}/comments/SKILL.md +0 -0
- /package/dist/{skills → tools/comments/skills}/comments/SKILL.yaml +0 -0
- /package/dist/{skills → tools}/project/commands/README.md +0 -0
- /package/dist/{skills → tools}/project/commands/project.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/SKILL.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/SKILL.yaml +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/changelog.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/creating.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/loading.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/templates.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/updating.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/versioning.md +0 -0
- /package/src/{skills → tools}/README.md +0 -0
- /package/src/{skills → tools}/brain/commands/README.md +0 -0
- /package/src/{skills → tools}/brain/commands/brain.md +0 -0
- /package/src/{skills → tools}/brain/commands/scratchpad.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain/SKILL.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain/SKILL.yaml +0 -0
- /package/src/{skills → tools/brain/skills}/brain/references/metadata.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain/references/naming.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain/references/templates.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain/references/workflows.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain-obsidian/SKILL.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain-obsidian/SKILL.yaml +0 -0
- /package/src/{skills → tools/brain/skills}/brain-obsidian/references/templates.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain-obsidian/references/workflows.md +0 -0
- /package/src/{skills → tools}/coach/commands/README.md +0 -0
- /package/src/{skills → tools}/coach/commands/coach.md +0 -0
- /package/src/{skills → tools/coach/skills}/coach/SKILL.md +0 -0
- /package/src/{skills → tools/coach/skills}/coach/SKILL.yaml +0 -0
- /package/src/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.md +0 -0
- /package/src/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -0
- /package/src/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.md +0 -0
- /package/src/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -0
- /package/src/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.md +0 -0
- /package/src/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -0
- /package/src/{skills → tools}/code-review/agents/type-reviewer/AGENT.md +0 -0
- /package/src/{skills → tools}/code-review/agents/type-reviewer/AGENT.yaml +0 -0
- /package/src/{skills → tools/code-review/skills}/code-review/SKILL.md +0 -0
- /package/src/{skills → tools/code-review/skills}/code-review/SKILL.yaml +0 -0
- /package/src/{skills → tools}/comments/commands/README.md +0 -0
- /package/src/{skills → tools}/comments/commands/comments.md +0 -0
- /package/src/{skills → tools/comments/skills}/comments/SKILL.md +0 -0
- /package/src/{skills → tools/comments/skills}/comments/SKILL.yaml +0 -0
- /package/src/{skills → tools}/project/commands/README.md +0 -0
- /package/src/{skills → tools}/project/commands/project.md +0 -0
- /package/src/{skills → tools/project/skills}/project/SKILL.md +0 -0
- /package/src/{skills → tools/project/skills}/project/SKILL.yaml +0 -0
- /package/src/{skills → tools/project/skills}/project/references/changelog.md +0 -0
- /package/src/{skills → tools/project/skills}/project/references/creating.md +0 -0
- /package/src/{skills → tools/project/skills}/project/references/loading.md +0 -0
- /package/src/{skills → tools/project/skills}/project/references/templates.md +0 -0
- /package/src/{skills → tools/project/skills}/project/references/updating.md +0 -0
- /package/src/{skills → tools/project/skills}/project/references/versioning.md +0 -0
package/src/lib/skills.ts
CHANGED
|
@@ -1,67 +1,52 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
4
|
import YAML from 'yaml';
|
|
6
5
|
import { loadConfig, saveConfig } from './config.js';
|
|
7
|
-
import {
|
|
6
|
+
import { Platform, SkillStatus, type SkillManifest, type InstalledSkill, getPlatformTools, setPlatformTools } from './types.js';
|
|
8
7
|
import { getInstalledAgentsDir, installAgentFromPath, uninstallAgent, isAgentInstalled } from './agents.js';
|
|
8
|
+
import { getSkillsPath, getCommandsPath, getConfigPath } from './platforms.js';
|
|
9
9
|
|
|
10
10
|
// Marker comments for CLAUDE.md skill registration
|
|
11
11
|
const DROID_SKILLS_START = '<!-- droid-skills-start -->';
|
|
12
12
|
const DROID_SKILLS_END = '<!-- droid-skills-end -->';
|
|
13
13
|
|
|
14
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
const BUNDLED_SKILLS_DIR = join(__dirname, '../
|
|
15
|
+
const BUNDLED_SKILLS_DIR = join(__dirname, '../tools');
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Get the path to bundled
|
|
18
|
+
* Get the path to bundled tools directory
|
|
19
19
|
*/
|
|
20
20
|
export function getBundledSkillsDir(): string {
|
|
21
21
|
return BUNDLED_SKILLS_DIR;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Get the installation path for skills based on
|
|
25
|
+
* Get the installation path for skills based on platform
|
|
26
26
|
*/
|
|
27
|
-
export function getSkillsInstallPath(
|
|
28
|
-
|
|
29
|
-
case AITool.ClaudeCode:
|
|
30
|
-
return join(homedir(), '.claude', 'skills');
|
|
31
|
-
case AITool.OpenCode:
|
|
32
|
-
return join(homedir(), '.config', 'opencode', 'skills');
|
|
33
|
-
}
|
|
27
|
+
export function getSkillsInstallPath(platform: Platform): string {
|
|
28
|
+
return getSkillsPath(platform);
|
|
34
29
|
}
|
|
35
30
|
|
|
36
31
|
/**
|
|
37
|
-
* Get the commands installation path based on
|
|
32
|
+
* Get the commands installation path based on platform
|
|
38
33
|
*/
|
|
39
|
-
export function getCommandsInstallPath(
|
|
40
|
-
|
|
41
|
-
case AITool.ClaudeCode:
|
|
42
|
-
return join(homedir(), '.claude', 'commands');
|
|
43
|
-
case AITool.OpenCode:
|
|
44
|
-
return join(homedir(), '.config', 'opencode', 'command');
|
|
45
|
-
}
|
|
34
|
+
export function getCommandsInstallPath(platform: Platform): string {
|
|
35
|
+
return getCommandsPath(platform);
|
|
46
36
|
}
|
|
47
37
|
|
|
48
38
|
/**
|
|
49
|
-
* Get the path to the
|
|
39
|
+
* Get the path to the platform's main config markdown file
|
|
50
40
|
*/
|
|
51
|
-
export function
|
|
52
|
-
|
|
53
|
-
case AITool.ClaudeCode:
|
|
54
|
-
return join(homedir(), '.claude', 'CLAUDE.md');
|
|
55
|
-
case AITool.OpenCode:
|
|
56
|
-
return join(homedir(), '.config', 'opencode', 'AGENTS.md');
|
|
57
|
-
}
|
|
41
|
+
export function getPlatformConfigPath(platform: Platform): string {
|
|
42
|
+
return getConfigPath(platform);
|
|
58
43
|
}
|
|
59
44
|
|
|
60
45
|
/**
|
|
61
|
-
* Update the
|
|
46
|
+
* Update the platform's config file with skill registrations
|
|
62
47
|
*/
|
|
63
|
-
export function
|
|
64
|
-
const configPath =
|
|
48
|
+
export function updatePlatformConfigSkills(platform: Platform, installedSkills: string[]): void {
|
|
49
|
+
const configPath = getPlatformConfigPath(platform);
|
|
65
50
|
|
|
66
51
|
let content = '';
|
|
67
52
|
if (existsSync(configPath)) {
|
|
@@ -118,23 +103,61 @@ export function loadSkillManifest(skillDir: string): SkillManifest | null {
|
|
|
118
103
|
}
|
|
119
104
|
|
|
120
105
|
/**
|
|
121
|
-
*
|
|
106
|
+
* Find the path to a skill within the tools directory structure
|
|
107
|
+
* Returns { toolDir, skillDir } or null if not found
|
|
108
|
+
*/
|
|
109
|
+
export function findSkillPath(skillName: string): { toolDir: string; skillDir: string } | null {
|
|
110
|
+
if (!existsSync(BUNDLED_SKILLS_DIR)) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const toolDirs = readdirSync(BUNDLED_SKILLS_DIR, { withFileTypes: true })
|
|
115
|
+
.filter((dirent) => dirent.isDirectory())
|
|
116
|
+
.map((dirent) => dirent.name);
|
|
117
|
+
|
|
118
|
+
for (const toolName of toolDirs) {
|
|
119
|
+
const skillsDir = join(BUNDLED_SKILLS_DIR, toolName, 'skills');
|
|
120
|
+
if (!existsSync(skillsDir)) continue;
|
|
121
|
+
|
|
122
|
+
const skillDir = join(skillsDir, skillName);
|
|
123
|
+
if (existsSync(skillDir) && existsSync(join(skillDir, 'SKILL.yaml'))) {
|
|
124
|
+
return {
|
|
125
|
+
toolDir: join(BUNDLED_SKILLS_DIR, toolName),
|
|
126
|
+
skillDir,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get all bundled skills from all tools
|
|
122
136
|
*/
|
|
123
137
|
export function getBundledSkills(): SkillManifest[] {
|
|
124
138
|
if (!existsSync(BUNDLED_SKILLS_DIR)) {
|
|
125
139
|
return [];
|
|
126
140
|
}
|
|
127
141
|
|
|
128
|
-
const
|
|
142
|
+
const toolDirs = readdirSync(BUNDLED_SKILLS_DIR, { withFileTypes: true })
|
|
129
143
|
.filter((dirent) => dirent.isDirectory())
|
|
130
144
|
.map((dirent) => dirent.name);
|
|
131
145
|
|
|
132
146
|
const skills: SkillManifest[] = [];
|
|
133
147
|
|
|
134
|
-
for (const
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
137
|
-
|
|
148
|
+
for (const toolName of toolDirs) {
|
|
149
|
+
const skillsDir = join(BUNDLED_SKILLS_DIR, toolName, 'skills');
|
|
150
|
+
if (!existsSync(skillsDir)) continue;
|
|
151
|
+
|
|
152
|
+
const skillSubdirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
153
|
+
.filter((dirent) => dirent.isDirectory())
|
|
154
|
+
.map((dirent) => dirent.name);
|
|
155
|
+
|
|
156
|
+
for (const skillName of skillSubdirs) {
|
|
157
|
+
const manifest = loadSkillManifest(join(skillsDir, skillName));
|
|
158
|
+
if (manifest) {
|
|
159
|
+
skills.push(manifest);
|
|
160
|
+
}
|
|
138
161
|
}
|
|
139
162
|
}
|
|
140
163
|
|
|
@@ -146,7 +169,8 @@ export function getBundledSkills(): SkillManifest[] {
|
|
|
146
169
|
*/
|
|
147
170
|
export function isSkillInstalled(skillName: string): boolean {
|
|
148
171
|
const config = loadConfig();
|
|
149
|
-
|
|
172
|
+
const tools = getPlatformTools(config);
|
|
173
|
+
return skillName in tools;
|
|
150
174
|
}
|
|
151
175
|
|
|
152
176
|
/**
|
|
@@ -154,7 +178,8 @@ export function isSkillInstalled(skillName: string): boolean {
|
|
|
154
178
|
*/
|
|
155
179
|
export function getInstalledSkill(skillName: string): InstalledSkill | null {
|
|
156
180
|
const config = loadConfig();
|
|
157
|
-
|
|
181
|
+
const tools = getPlatformTools(config);
|
|
182
|
+
return tools[skillName] || null;
|
|
158
183
|
}
|
|
159
184
|
|
|
160
185
|
/**
|
|
@@ -166,8 +191,8 @@ export function getSkillUpdateStatus(skillName: string): {
|
|
|
166
191
|
bundledVersion: string | null;
|
|
167
192
|
} {
|
|
168
193
|
const installed = getInstalledSkill(skillName);
|
|
169
|
-
const
|
|
170
|
-
const manifest =
|
|
194
|
+
const skillPath = findSkillPath(skillName);
|
|
195
|
+
const manifest = skillPath ? loadSkillManifest(skillPath.skillDir) : null;
|
|
171
196
|
|
|
172
197
|
if (!installed || !manifest) {
|
|
173
198
|
return {
|
|
@@ -193,9 +218,10 @@ export function getSkillsWithUpdates(): Array<{
|
|
|
193
218
|
bundledVersion: string;
|
|
194
219
|
}> {
|
|
195
220
|
const config = loadConfig();
|
|
221
|
+
const tools = getPlatformTools(config);
|
|
196
222
|
const updates: Array<{ name: string; installedVersion: string; bundledVersion: string }> = [];
|
|
197
223
|
|
|
198
|
-
for (const skillName of Object.keys(
|
|
224
|
+
for (const skillName of Object.keys(tools)) {
|
|
199
225
|
const status = getSkillUpdateStatus(skillName);
|
|
200
226
|
if (status.hasUpdate && status.installedVersion && status.bundledVersion) {
|
|
201
227
|
updates.push({
|
|
@@ -248,13 +274,14 @@ export function updateAllSkills(): {
|
|
|
248
274
|
upToDate: number;
|
|
249
275
|
} {
|
|
250
276
|
const config = loadConfig();
|
|
277
|
+
const tools = getPlatformTools(config);
|
|
251
278
|
const result = {
|
|
252
279
|
updated: [] as Array<{ name: string; from: string; to: string }>,
|
|
253
280
|
failed: [] as Array<{ name: string; error: string }>,
|
|
254
281
|
upToDate: 0,
|
|
255
282
|
};
|
|
256
283
|
|
|
257
|
-
for (const skillName of Object.keys(
|
|
284
|
+
for (const skillName of Object.keys(tools)) {
|
|
258
285
|
const status = getSkillUpdateStatus(skillName);
|
|
259
286
|
|
|
260
287
|
if (!status.hasUpdate) {
|
|
@@ -285,13 +312,14 @@ export function updateAllSkills(): {
|
|
|
285
312
|
*/
|
|
286
313
|
export function installSkill(skillName: string): { success: boolean; message: string } {
|
|
287
314
|
const config = loadConfig();
|
|
288
|
-
const
|
|
315
|
+
const skillPath = findSkillPath(skillName);
|
|
289
316
|
|
|
290
|
-
if (!
|
|
317
|
+
if (!skillPath) {
|
|
291
318
|
return { success: false, message: `Skill '${skillName}' not found` };
|
|
292
319
|
}
|
|
293
320
|
|
|
294
|
-
const
|
|
321
|
+
const { toolDir, skillDir } = skillPath;
|
|
322
|
+
const manifest = loadSkillManifest(skillDir);
|
|
295
323
|
if (!manifest) {
|
|
296
324
|
return { success: false, message: `Invalid skill manifest for '${skillName}'` };
|
|
297
325
|
}
|
|
@@ -308,22 +336,20 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
308
336
|
}
|
|
309
337
|
}
|
|
310
338
|
|
|
311
|
-
const skillsPath = getSkillsInstallPath(config.
|
|
339
|
+
const skillsPath = getSkillsInstallPath(config.platform);
|
|
312
340
|
const targetSkillDir = join(skillsPath, skillName);
|
|
313
|
-
const commandsPath = getCommandsInstallPath(config.
|
|
341
|
+
const commandsPath = getCommandsInstallPath(config.platform);
|
|
342
|
+
const tools = getPlatformTools(config);
|
|
314
343
|
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (existsSync(targetSkillDir)) {
|
|
319
|
-
return {
|
|
320
|
-
success: false,
|
|
321
|
-
message: `Cannot install: skill folder '${skillName}' already exists at ${targetSkillDir}`,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
344
|
+
// Commands and agents are at the tool level, not skill level
|
|
345
|
+
const commandsSource = join(toolDir, 'commands');
|
|
346
|
+
const agentsSource = join(toolDir, 'agents');
|
|
324
347
|
|
|
325
|
-
|
|
326
|
-
|
|
348
|
+
// Check for collisions BEFORE installing (only if not already installed by droid)
|
|
349
|
+
// Note: If skill folder exists but skill isn't in config, we allow overwriting (stale state
|
|
350
|
+
// from platform switch or manual cleanup). Command/agent collisions still checked.
|
|
351
|
+
if (!tools[skillName]) {
|
|
352
|
+
// Check command file collisions (these could conflict with other skills)
|
|
327
353
|
if (existsSync(commandsSource)) {
|
|
328
354
|
const commandFiles = readdirSync(commandsSource).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
|
|
329
355
|
for (const file of commandFiles) {
|
|
@@ -339,7 +365,6 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
339
365
|
}
|
|
340
366
|
|
|
341
367
|
// Check bundled agent collisions
|
|
342
|
-
const agentsSource = join(bundledSkillDir, 'agents');
|
|
343
368
|
if (existsSync(agentsSource)) {
|
|
344
369
|
const agentDirs = readdirSync(agentsSource, { withFileTypes: true })
|
|
345
370
|
.filter(dirent => dirent.isDirectory())
|
|
@@ -361,7 +386,7 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
361
386
|
}
|
|
362
387
|
|
|
363
388
|
// Copy SKILL.md (the actual skill file for Claude Code / OpenCode)
|
|
364
|
-
const skillMdSource = join(
|
|
389
|
+
const skillMdSource = join(skillDir, 'SKILL.md');
|
|
365
390
|
if (existsSync(skillMdSource)) {
|
|
366
391
|
if (!existsSync(targetSkillDir)) {
|
|
367
392
|
mkdirSync(targetSkillDir, { recursive: true });
|
|
@@ -372,7 +397,7 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
372
397
|
}
|
|
373
398
|
|
|
374
399
|
// Copy references if present (skill documentation files)
|
|
375
|
-
const referencesSource = join(
|
|
400
|
+
const referencesSource = join(skillDir, 'references');
|
|
376
401
|
if (existsSync(referencesSource)) {
|
|
377
402
|
const targetReferencesDir = join(targetSkillDir, 'references');
|
|
378
403
|
if (!existsSync(targetReferencesDir)) {
|
|
@@ -387,8 +412,7 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
387
412
|
}
|
|
388
413
|
}
|
|
389
414
|
|
|
390
|
-
// Copy commands if present
|
|
391
|
-
const commandsSource = join(bundledSkillDir, 'commands');
|
|
415
|
+
// Copy commands if present (from tool level)
|
|
392
416
|
if (existsSync(commandsSource)) {
|
|
393
417
|
if (!existsSync(commandsPath)) {
|
|
394
418
|
mkdirSync(commandsPath, { recursive: true });
|
|
@@ -402,16 +426,14 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
402
426
|
}
|
|
403
427
|
}
|
|
404
428
|
|
|
405
|
-
// Install bundled agents if present
|
|
429
|
+
// Install bundled agents if present (from tool level)
|
|
406
430
|
const installedAgents: string[] = [];
|
|
407
|
-
const agentsSource = join(bundledSkillDir, 'agents');
|
|
408
431
|
if (existsSync(agentsSource)) {
|
|
409
432
|
const agentDirs = readdirSync(agentsSource, { withFileTypes: true })
|
|
410
433
|
.filter(dirent => dirent.isDirectory())
|
|
411
434
|
.map(dirent => dirent.name);
|
|
412
435
|
|
|
413
436
|
for (const agentName of agentDirs) {
|
|
414
|
-
// Use the skill's bundled agent path instead of global agents
|
|
415
437
|
const agentDir = join(agentsSource, agentName);
|
|
416
438
|
const result = installAgentFromPath(agentDir, agentName);
|
|
417
439
|
if (result.success) {
|
|
@@ -421,16 +443,20 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
421
443
|
}
|
|
422
444
|
|
|
423
445
|
// Update config
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
446
|
+
const updatedTools = {
|
|
447
|
+
...tools,
|
|
448
|
+
[skillName]: {
|
|
449
|
+
version: manifest.version,
|
|
450
|
+
installed_at: new Date().toISOString(),
|
|
451
|
+
...(installedAgents.length > 0 && { bundled_agents: installedAgents }),
|
|
452
|
+
},
|
|
428
453
|
};
|
|
454
|
+
setPlatformTools(config, updatedTools);
|
|
429
455
|
saveConfig(config);
|
|
430
456
|
|
|
431
|
-
// Update
|
|
432
|
-
const installedSkillNames = Object.keys(
|
|
433
|
-
|
|
457
|
+
// Update platform's config file with skill reference
|
|
458
|
+
const installedSkillNames = Object.keys(updatedTools);
|
|
459
|
+
updatePlatformConfigSkills(config.platform, installedSkillNames);
|
|
434
460
|
|
|
435
461
|
return { success: true, message: `Installed ${skillName} v${manifest.version}` };
|
|
436
462
|
}
|
|
@@ -440,23 +466,24 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
440
466
|
*/
|
|
441
467
|
export function uninstallSkill(skillName: string): { success: boolean; message: string } {
|
|
442
468
|
const config = loadConfig();
|
|
469
|
+
const tools = getPlatformTools(config);
|
|
443
470
|
|
|
444
471
|
if (!isSkillInstalled(skillName)) {
|
|
445
472
|
return { success: false, message: `Skill '${skillName}' is not installed` };
|
|
446
473
|
}
|
|
447
474
|
|
|
448
|
-
// Remove skill files from
|
|
449
|
-
const skillsPath = getSkillsInstallPath(config.
|
|
475
|
+
// Remove skill files from platform location
|
|
476
|
+
const skillsPath = getSkillsInstallPath(config.platform);
|
|
450
477
|
const skillDir = join(skillsPath, skillName);
|
|
451
478
|
if (existsSync(skillDir)) {
|
|
452
479
|
rmSync(skillDir, { recursive: true });
|
|
453
480
|
}
|
|
454
481
|
|
|
455
|
-
// Remove command files if they exist
|
|
456
|
-
const
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
if (existsSync(commandsSource)) {
|
|
482
|
+
// Remove command files if they exist (commands are at tool level)
|
|
483
|
+
const skillPath = findSkillPath(skillName);
|
|
484
|
+
const commandsPath = getCommandsInstallPath(config.platform);
|
|
485
|
+
const commandsSource = skillPath ? join(skillPath.toolDir, 'commands') : null;
|
|
486
|
+
if (commandsSource && existsSync(commandsSource)) {
|
|
460
487
|
const commandFiles = readdirSync(commandsSource).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
|
|
461
488
|
for (const file of commandFiles) {
|
|
462
489
|
const commandPath = join(commandsPath, file);
|
|
@@ -467,20 +494,22 @@ export function uninstallSkill(skillName: string): { success: boolean; message:
|
|
|
467
494
|
}
|
|
468
495
|
|
|
469
496
|
// Remove bundled agents if they were installed with this skill
|
|
470
|
-
const installedSkillInfo =
|
|
497
|
+
const installedSkillInfo = tools[skillName];
|
|
471
498
|
if (installedSkillInfo?.bundled_agents) {
|
|
472
499
|
for (const agentName of installedSkillInfo.bundled_agents) {
|
|
473
500
|
uninstallAgent(agentName);
|
|
474
501
|
}
|
|
475
502
|
}
|
|
476
503
|
|
|
477
|
-
// Remove from config
|
|
478
|
-
|
|
504
|
+
// Remove from config (destructure to omit the skill being removed)
|
|
505
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
506
|
+
const { [skillName]: removed, ...remainingTools } = tools;
|
|
507
|
+
setPlatformTools(config, remainingTools);
|
|
479
508
|
saveConfig(config);
|
|
480
509
|
|
|
481
|
-
// Update
|
|
482
|
-
const installedSkillNames = Object.keys(
|
|
483
|
-
|
|
510
|
+
// Update platform's config file to remove skill reference
|
|
511
|
+
const installedSkillNames = Object.keys(remainingTools);
|
|
512
|
+
updatePlatformConfigSkills(config.platform, installedSkillNames);
|
|
484
513
|
|
|
485
514
|
return { success: true, message: `Uninstalled ${skillName}` };
|
|
486
515
|
}
|
|
@@ -513,7 +542,7 @@ export function isCommandInstalled(commandName: string, skillName: string): bool
|
|
|
513
542
|
|
|
514
543
|
// Otherwise check if command was installed standalone
|
|
515
544
|
const config = loadConfig();
|
|
516
|
-
const commandsPath = getCommandsInstallPath(config.
|
|
545
|
+
const commandsPath = getCommandsInstallPath(config.platform);
|
|
517
546
|
|
|
518
547
|
// Command filename is derived from the command name after the skill prefix
|
|
519
548
|
// e.g., "comments check" from skill "comments" → "check.md"
|
|
@@ -534,31 +563,23 @@ export function installCommand(
|
|
|
534
563
|
): { success: boolean; message: string } {
|
|
535
564
|
const config = loadConfig();
|
|
536
565
|
|
|
566
|
+
// Find the skill to get its tool directory (commands are at tool level)
|
|
567
|
+
const skillPath = findSkillPath(skillName);
|
|
568
|
+
if (!skillPath) {
|
|
569
|
+
return { success: false, message: `Skill '${skillName}' not found` };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const commandsDir = join(skillPath.toolDir, 'commands');
|
|
573
|
+
if (!existsSync(commandsDir)) {
|
|
574
|
+
return { success: false, message: `No commands found for skill '${skillName}'` };
|
|
575
|
+
}
|
|
576
|
+
|
|
537
577
|
// Find the source command file
|
|
538
578
|
const cmdPart = commandName.startsWith(skillName + ' ')
|
|
539
579
|
? commandName.slice(skillName.length + 1)
|
|
540
580
|
: commandName;
|
|
541
|
-
const filename = cmdPart.replace(/\s+/g, '-') + '.md';
|
|
542
|
-
const sourcePath = join(BUNDLED_SKILLS_DIR, skillName, 'commands', filename);
|
|
543
|
-
|
|
544
|
-
if (!existsSync(sourcePath)) {
|
|
545
|
-
// Try without hyphen conversion (original filename)
|
|
546
|
-
const altFilename = cmdPart + '.md';
|
|
547
|
-
const altSourcePath = join(BUNDLED_SKILLS_DIR, skillName, 'commands', altFilename);
|
|
548
|
-
if (!existsSync(altSourcePath)) {
|
|
549
|
-
return { success: false, message: `Command file not found: ${filename}` };
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// Ensure commands directory exists
|
|
554
|
-
const commandsPath = getCommandsInstallPath(config.ai_tool);
|
|
555
|
-
if (!existsSync(commandsPath)) {
|
|
556
|
-
mkdirSync(commandsPath, { recursive: true });
|
|
557
|
-
}
|
|
558
581
|
|
|
559
|
-
|
|
560
|
-
const commandsDir = join(BUNDLED_SKILLS_DIR, skillName, 'commands');
|
|
561
|
-
const files = readdirSync(commandsDir);
|
|
582
|
+
const files = readdirSync(commandsDir).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
|
|
562
583
|
const sourceFile = files.find(f => {
|
|
563
584
|
const base = f.replace('.md', '');
|
|
564
585
|
return base === cmdPart || base === cmdPart.replace(/\s+/g, '-');
|
|
@@ -568,6 +589,12 @@ export function installCommand(
|
|
|
568
589
|
return { success: false, message: `Command file not found for: ${commandName}` };
|
|
569
590
|
}
|
|
570
591
|
|
|
592
|
+
// Ensure commands directory exists
|
|
593
|
+
const commandsPath = getCommandsInstallPath(config.platform);
|
|
594
|
+
if (!existsSync(commandsPath)) {
|
|
595
|
+
mkdirSync(commandsPath, { recursive: true });
|
|
596
|
+
}
|
|
597
|
+
|
|
571
598
|
const actualSourcePath = join(commandsDir, sourceFile);
|
|
572
599
|
const targetPath = join(commandsPath, sourceFile);
|
|
573
600
|
|
|
@@ -597,7 +624,7 @@ export function uninstallCommand(
|
|
|
597
624
|
};
|
|
598
625
|
}
|
|
599
626
|
|
|
600
|
-
const commandsPath = getCommandsInstallPath(config.
|
|
627
|
+
const commandsPath = getCommandsInstallPath(config.platform);
|
|
601
628
|
|
|
602
629
|
// Find the installed command file
|
|
603
630
|
const cmdPart = commandName.startsWith(skillName + ' ')
|
package/src/lib/tools.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import YAML from 'yaml';
|
|
5
|
+
import { loadConfig } from './config.js';
|
|
6
|
+
import { type ToolManifest, type ToolIncludes, getPlatformTools } from './types.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const BUNDLED_TOOLS_DIR = join(__dirname, '../tools');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the path to bundled tools directory
|
|
13
|
+
*/
|
|
14
|
+
export function getBundledToolsDir(): string {
|
|
15
|
+
return BUNDLED_TOOLS_DIR;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load a tool manifest from a tool directory
|
|
20
|
+
*/
|
|
21
|
+
export function loadToolManifest(toolDir: string): ToolManifest | null {
|
|
22
|
+
const manifestPath = join(toolDir, 'TOOL.yaml');
|
|
23
|
+
|
|
24
|
+
if (!existsSync(manifestPath)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const content = readFileSync(manifestPath, 'utf-8');
|
|
30
|
+
const parsed = YAML.parse(content);
|
|
31
|
+
|
|
32
|
+
// Normalize includes structure
|
|
33
|
+
const includes: ToolIncludes = {
|
|
34
|
+
skills: parsed.includes?.skills || [],
|
|
35
|
+
commands: parsed.includes?.commands || [],
|
|
36
|
+
agents: parsed.includes?.agents || [],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
...parsed,
|
|
41
|
+
includes,
|
|
42
|
+
} as ToolManifest;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get all bundled tools
|
|
50
|
+
*/
|
|
51
|
+
export function getBundledTools(): ToolManifest[] {
|
|
52
|
+
if (!existsSync(BUNDLED_TOOLS_DIR)) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const toolDirs = readdirSync(BUNDLED_TOOLS_DIR, { withFileTypes: true })
|
|
57
|
+
.filter((dirent) => dirent.isDirectory())
|
|
58
|
+
.map((dirent) => dirent.name);
|
|
59
|
+
|
|
60
|
+
const tools: ToolManifest[] = [];
|
|
61
|
+
|
|
62
|
+
for (const toolName of toolDirs) {
|
|
63
|
+
const manifest = loadToolManifest(join(BUNDLED_TOOLS_DIR, toolName));
|
|
64
|
+
if (manifest) {
|
|
65
|
+
tools.push(manifest);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return tools;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if a tool is installed (any of its required skills are installed)
|
|
74
|
+
*/
|
|
75
|
+
export function isToolInstalled(toolName: string): boolean {
|
|
76
|
+
const config = loadConfig();
|
|
77
|
+
const installedTools = getPlatformTools(config);
|
|
78
|
+
|
|
79
|
+
// A tool is installed if any of its required skills are in the installed tools
|
|
80
|
+
const tool = getBundledTools().find(t => t.name === toolName);
|
|
81
|
+
if (!tool) return false;
|
|
82
|
+
|
|
83
|
+
const requiredSkills = tool.includes.skills
|
|
84
|
+
.filter(s => s.required)
|
|
85
|
+
.map(s => s.name);
|
|
86
|
+
|
|
87
|
+
// Tool is installed if at least one required skill is installed
|
|
88
|
+
return requiredSkills.some(skillName => skillName in installedTools);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the installed version of a tool (from its primary skill)
|
|
93
|
+
*/
|
|
94
|
+
export function getInstalledToolVersion(toolName: string): string | null {
|
|
95
|
+
const config = loadConfig();
|
|
96
|
+
const installedTools = getPlatformTools(config);
|
|
97
|
+
|
|
98
|
+
const tool = getBundledTools().find(t => t.name === toolName);
|
|
99
|
+
if (!tool) return null;
|
|
100
|
+
|
|
101
|
+
// Get version from the first installed required skill
|
|
102
|
+
const requiredSkills = tool.includes.skills
|
|
103
|
+
.filter(s => s.required)
|
|
104
|
+
.map(s => s.name);
|
|
105
|
+
|
|
106
|
+
for (const skillName of requiredSkills) {
|
|
107
|
+
if (installedTools[skillName]) {
|
|
108
|
+
return installedTools[skillName].version;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if a tool has an update available
|
|
117
|
+
*/
|
|
118
|
+
export function getToolUpdateStatus(toolName: string): {
|
|
119
|
+
hasUpdate: boolean;
|
|
120
|
+
installedVersion: string | null;
|
|
121
|
+
bundledVersion: string | null;
|
|
122
|
+
} {
|
|
123
|
+
const installedVersion = getInstalledToolVersion(toolName);
|
|
124
|
+
const tool = getBundledTools().find(t => t.name === toolName);
|
|
125
|
+
const bundledVersion = tool?.version || null;
|
|
126
|
+
|
|
127
|
+
if (!installedVersion || !bundledVersion) {
|
|
128
|
+
return {
|
|
129
|
+
hasUpdate: false,
|
|
130
|
+
installedVersion,
|
|
131
|
+
bundledVersion,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
hasUpdate: bundledVersion !== installedVersion,
|
|
137
|
+
installedVersion,
|
|
138
|
+
bundledVersion,
|
|
139
|
+
};
|
|
140
|
+
}
|
package/src/lib/types.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'bun:test';
|
|
2
2
|
import {
|
|
3
|
+
Platform,
|
|
3
4
|
AITool,
|
|
4
5
|
BuiltInOutput,
|
|
5
6
|
SkillStatus,
|
|
@@ -7,21 +8,21 @@ import {
|
|
|
7
8
|
getAITag,
|
|
8
9
|
} from './types.js';
|
|
9
10
|
|
|
10
|
-
describe('
|
|
11
|
+
describe('Platform enum', () => {
|
|
11
12
|
it('should have correct values', () => {
|
|
12
|
-
expect(
|
|
13
|
-
expect(
|
|
13
|
+
expect(Platform.ClaudeCode).toBe('claude-code');
|
|
14
|
+
expect(Platform.OpenCode).toBe('opencode');
|
|
14
15
|
});
|
|
15
16
|
|
|
16
17
|
it('should be usable in switch statements', () => {
|
|
17
|
-
const
|
|
18
|
+
const platform = Platform.ClaudeCode;
|
|
18
19
|
let result = '';
|
|
19
20
|
|
|
20
|
-
switch (
|
|
21
|
-
case
|
|
21
|
+
switch (platform) {
|
|
22
|
+
case Platform.ClaudeCode:
|
|
22
23
|
result = 'claude';
|
|
23
24
|
break;
|
|
24
|
-
case
|
|
25
|
+
case Platform.OpenCode:
|
|
25
26
|
result = 'opencode';
|
|
26
27
|
break;
|
|
27
28
|
}
|
|
@@ -30,6 +31,13 @@ describe('AITool enum', () => {
|
|
|
30
31
|
});
|
|
31
32
|
});
|
|
32
33
|
|
|
34
|
+
describe('AITool backward compatibility', () => {
|
|
35
|
+
it('should be an alias for Platform', () => {
|
|
36
|
+
expect(AITool.ClaudeCode).toBe(Platform.ClaudeCode);
|
|
37
|
+
expect(AITool.OpenCode).toBe(Platform.OpenCode);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
33
41
|
describe('BuiltInOutput enum', () => {
|
|
34
42
|
it('should have correct values', () => {
|
|
35
43
|
expect(BuiltInOutput.Terminal).toBe('terminal');
|