@orderful/droid 0.10.5 → 0.11.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/.claude/CLAUDE.md +13 -6
- package/CHANGELOG.md +19 -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 +125 -99
- 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/tools/comments/TOOL.yaml +27 -0
- package/dist/{skills → tools/comments/skills}/comments/SKILL.md +12 -3
- package/dist/{skills → tools/comments/skills}/comments/SKILL.yaml +1 -1
- 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 +134 -101
- 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/tools/comments/TOOL.yaml +27 -0
- package/src/{skills → tools/comments/skills}/comments/SKILL.md +12 -3
- package/src/{skills → tools/comments/skills}/comments/SKILL.yaml +1 -1
- 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/commands/code-review.md +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}/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/commands/code-review.md +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}/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,16 +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);
|
|
343
|
+
|
|
344
|
+
// Commands and agents are at the tool level, not skill level
|
|
345
|
+
const commandsSource = join(toolDir, 'commands');
|
|
346
|
+
const agentsSource = join(toolDir, 'agents');
|
|
314
347
|
|
|
315
348
|
// Check for collisions BEFORE installing (only if not already installed by droid)
|
|
316
349
|
// Note: If skill folder exists but skill isn't in config, we allow overwriting (stale state
|
|
317
|
-
// from
|
|
318
|
-
if (!
|
|
350
|
+
// from platform switch or manual cleanup). Command/agent collisions still checked.
|
|
351
|
+
if (!tools[skillName]) {
|
|
319
352
|
// Check command file collisions (these could conflict with other skills)
|
|
320
|
-
const commandsSource = join(bundledSkillDir, 'commands');
|
|
321
353
|
if (existsSync(commandsSource)) {
|
|
322
354
|
const commandFiles = readdirSync(commandsSource).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
|
|
323
355
|
for (const file of commandFiles) {
|
|
@@ -333,7 +365,6 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
333
365
|
}
|
|
334
366
|
|
|
335
367
|
// Check bundled agent collisions
|
|
336
|
-
const agentsSource = join(bundledSkillDir, 'agents');
|
|
337
368
|
if (existsSync(agentsSource)) {
|
|
338
369
|
const agentDirs = readdirSync(agentsSource, { withFileTypes: true })
|
|
339
370
|
.filter(dirent => dirent.isDirectory())
|
|
@@ -355,7 +386,7 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
355
386
|
}
|
|
356
387
|
|
|
357
388
|
// Copy SKILL.md (the actual skill file for Claude Code / OpenCode)
|
|
358
|
-
const skillMdSource = join(
|
|
389
|
+
const skillMdSource = join(skillDir, 'SKILL.md');
|
|
359
390
|
if (existsSync(skillMdSource)) {
|
|
360
391
|
if (!existsSync(targetSkillDir)) {
|
|
361
392
|
mkdirSync(targetSkillDir, { recursive: true });
|
|
@@ -366,7 +397,7 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
366
397
|
}
|
|
367
398
|
|
|
368
399
|
// Copy references if present (skill documentation files)
|
|
369
|
-
const referencesSource = join(
|
|
400
|
+
const referencesSource = join(skillDir, 'references');
|
|
370
401
|
if (existsSync(referencesSource)) {
|
|
371
402
|
const targetReferencesDir = join(targetSkillDir, 'references');
|
|
372
403
|
if (!existsSync(targetReferencesDir)) {
|
|
@@ -381,8 +412,7 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
381
412
|
}
|
|
382
413
|
}
|
|
383
414
|
|
|
384
|
-
// Copy commands if present
|
|
385
|
-
const commandsSource = join(bundledSkillDir, 'commands');
|
|
415
|
+
// Copy commands if present (from tool level)
|
|
386
416
|
if (existsSync(commandsSource)) {
|
|
387
417
|
if (!existsSync(commandsPath)) {
|
|
388
418
|
mkdirSync(commandsPath, { recursive: true });
|
|
@@ -396,16 +426,14 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
396
426
|
}
|
|
397
427
|
}
|
|
398
428
|
|
|
399
|
-
// Install bundled agents if present
|
|
429
|
+
// Install bundled agents if present (from tool level)
|
|
400
430
|
const installedAgents: string[] = [];
|
|
401
|
-
const agentsSource = join(bundledSkillDir, 'agents');
|
|
402
431
|
if (existsSync(agentsSource)) {
|
|
403
432
|
const agentDirs = readdirSync(agentsSource, { withFileTypes: true })
|
|
404
433
|
.filter(dirent => dirent.isDirectory())
|
|
405
434
|
.map(dirent => dirent.name);
|
|
406
435
|
|
|
407
436
|
for (const agentName of agentDirs) {
|
|
408
|
-
// Use the skill's bundled agent path instead of global agents
|
|
409
437
|
const agentDir = join(agentsSource, agentName);
|
|
410
438
|
const result = installAgentFromPath(agentDir, agentName);
|
|
411
439
|
if (result.success) {
|
|
@@ -415,16 +443,20 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
415
443
|
}
|
|
416
444
|
|
|
417
445
|
// Update config
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
+
},
|
|
422
453
|
};
|
|
454
|
+
setPlatformTools(config, updatedTools);
|
|
423
455
|
saveConfig(config);
|
|
424
456
|
|
|
425
|
-
// Update
|
|
426
|
-
const installedSkillNames = Object.keys(
|
|
427
|
-
|
|
457
|
+
// Update platform's config file with skill reference
|
|
458
|
+
const installedSkillNames = Object.keys(updatedTools);
|
|
459
|
+
updatePlatformConfigSkills(config.platform, installedSkillNames);
|
|
428
460
|
|
|
429
461
|
return { success: true, message: `Installed ${skillName} v${manifest.version}` };
|
|
430
462
|
}
|
|
@@ -434,23 +466,24 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
434
466
|
*/
|
|
435
467
|
export function uninstallSkill(skillName: string): { success: boolean; message: string } {
|
|
436
468
|
const config = loadConfig();
|
|
469
|
+
const tools = getPlatformTools(config);
|
|
437
470
|
|
|
438
471
|
if (!isSkillInstalled(skillName)) {
|
|
439
472
|
return { success: false, message: `Skill '${skillName}' is not installed` };
|
|
440
473
|
}
|
|
441
474
|
|
|
442
|
-
// Remove skill files from
|
|
443
|
-
const skillsPath = getSkillsInstallPath(config.
|
|
475
|
+
// Remove skill files from platform location
|
|
476
|
+
const skillsPath = getSkillsInstallPath(config.platform);
|
|
444
477
|
const skillDir = join(skillsPath, skillName);
|
|
445
478
|
if (existsSync(skillDir)) {
|
|
446
479
|
rmSync(skillDir, { recursive: true });
|
|
447
480
|
}
|
|
448
481
|
|
|
449
|
-
// Remove command files if they exist
|
|
450
|
-
const
|
|
451
|
-
const
|
|
452
|
-
const
|
|
453
|
-
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)) {
|
|
454
487
|
const commandFiles = readdirSync(commandsSource).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
|
|
455
488
|
for (const file of commandFiles) {
|
|
456
489
|
const commandPath = join(commandsPath, file);
|
|
@@ -461,20 +494,22 @@ export function uninstallSkill(skillName: string): { success: boolean; message:
|
|
|
461
494
|
}
|
|
462
495
|
|
|
463
496
|
// Remove bundled agents if they were installed with this skill
|
|
464
|
-
const installedSkillInfo =
|
|
497
|
+
const installedSkillInfo = tools[skillName];
|
|
465
498
|
if (installedSkillInfo?.bundled_agents) {
|
|
466
499
|
for (const agentName of installedSkillInfo.bundled_agents) {
|
|
467
500
|
uninstallAgent(agentName);
|
|
468
501
|
}
|
|
469
502
|
}
|
|
470
503
|
|
|
471
|
-
// Remove from config
|
|
472
|
-
|
|
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);
|
|
473
508
|
saveConfig(config);
|
|
474
509
|
|
|
475
|
-
// Update
|
|
476
|
-
const installedSkillNames = Object.keys(
|
|
477
|
-
|
|
510
|
+
// Update platform's config file to remove skill reference
|
|
511
|
+
const installedSkillNames = Object.keys(remainingTools);
|
|
512
|
+
updatePlatformConfigSkills(config.platform, installedSkillNames);
|
|
478
513
|
|
|
479
514
|
return { success: true, message: `Uninstalled ${skillName}` };
|
|
480
515
|
}
|
|
@@ -507,7 +542,7 @@ export function isCommandInstalled(commandName: string, skillName: string): bool
|
|
|
507
542
|
|
|
508
543
|
// Otherwise check if command was installed standalone
|
|
509
544
|
const config = loadConfig();
|
|
510
|
-
const commandsPath = getCommandsInstallPath(config.
|
|
545
|
+
const commandsPath = getCommandsInstallPath(config.platform);
|
|
511
546
|
|
|
512
547
|
// Command filename is derived from the command name after the skill prefix
|
|
513
548
|
// e.g., "comments check" from skill "comments" → "check.md"
|
|
@@ -528,31 +563,23 @@ export function installCommand(
|
|
|
528
563
|
): { success: boolean; message: string } {
|
|
529
564
|
const config = loadConfig();
|
|
530
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
|
+
|
|
531
577
|
// Find the source command file
|
|
532
578
|
const cmdPart = commandName.startsWith(skillName + ' ')
|
|
533
579
|
? commandName.slice(skillName.length + 1)
|
|
534
580
|
: commandName;
|
|
535
|
-
const filename = cmdPart.replace(/\s+/g, '-') + '.md';
|
|
536
|
-
const sourcePath = join(BUNDLED_SKILLS_DIR, skillName, 'commands', filename);
|
|
537
|
-
|
|
538
|
-
if (!existsSync(sourcePath)) {
|
|
539
|
-
// Try without hyphen conversion (original filename)
|
|
540
|
-
const altFilename = cmdPart + '.md';
|
|
541
|
-
const altSourcePath = join(BUNDLED_SKILLS_DIR, skillName, 'commands', altFilename);
|
|
542
|
-
if (!existsSync(altSourcePath)) {
|
|
543
|
-
return { success: false, message: `Command file not found: ${filename}` };
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
581
|
|
|
547
|
-
|
|
548
|
-
const commandsPath = getCommandsInstallPath(config.ai_tool);
|
|
549
|
-
if (!existsSync(commandsPath)) {
|
|
550
|
-
mkdirSync(commandsPath, { recursive: true });
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// Find the actual source file
|
|
554
|
-
const commandsDir = join(BUNDLED_SKILLS_DIR, skillName, 'commands');
|
|
555
|
-
const files = readdirSync(commandsDir);
|
|
582
|
+
const files = readdirSync(commandsDir).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
|
|
556
583
|
const sourceFile = files.find(f => {
|
|
557
584
|
const base = f.replace('.md', '');
|
|
558
585
|
return base === cmdPart || base === cmdPart.replace(/\s+/g, '-');
|
|
@@ -562,6 +589,12 @@ export function installCommand(
|
|
|
562
589
|
return { success: false, message: `Command file not found for: ${commandName}` };
|
|
563
590
|
}
|
|
564
591
|
|
|
592
|
+
// Ensure commands directory exists
|
|
593
|
+
const commandsPath = getCommandsInstallPath(config.platform);
|
|
594
|
+
if (!existsSync(commandsPath)) {
|
|
595
|
+
mkdirSync(commandsPath, { recursive: true });
|
|
596
|
+
}
|
|
597
|
+
|
|
565
598
|
const actualSourcePath = join(commandsDir, sourceFile);
|
|
566
599
|
const targetPath = join(commandsPath, sourceFile);
|
|
567
600
|
|
|
@@ -591,7 +624,7 @@ export function uninstallCommand(
|
|
|
591
624
|
};
|
|
592
625
|
}
|
|
593
626
|
|
|
594
|
-
const commandsPath = getCommandsInstallPath(config.
|
|
627
|
+
const commandsPath = getCommandsInstallPath(config.platform);
|
|
595
628
|
|
|
596
629
|
// Find the installed command file
|
|
597
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');
|