@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
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { uninstallSkill
|
|
2
|
+
import { uninstallSkill } from '../lib/skills.js';
|
|
3
|
+
import { getBundledTools, isToolInstalled } from '../lib/tools.js';
|
|
3
4
|
|
|
4
|
-
export async function uninstallCommand(
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
export async function uninstallCommand(toolName: string): Promise<void> {
|
|
6
|
+
const tools = getBundledTools();
|
|
7
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
8
|
+
|
|
9
|
+
if (!tool) {
|
|
10
|
+
console.error(chalk.red(`\n✗ Tool '${toolName}' not found`));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!isToolInstalled(toolName)) {
|
|
15
|
+
console.error(chalk.red(`\n✗ Tool '${toolName}' is not installed`));
|
|
7
16
|
process.exit(1);
|
|
8
17
|
}
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
// Uninstall via primary skill
|
|
20
|
+
const primarySkill = tool.includes.skills.find(s => s.required)?.name || toolName;
|
|
21
|
+
const result = uninstallSkill(primarySkill);
|
|
11
22
|
|
|
12
23
|
if (result.success) {
|
|
13
|
-
console.log(chalk.green(`\n✓ ${
|
|
24
|
+
console.log(chalk.green(`\n✓ Uninstalled ${toolName}`));
|
|
14
25
|
} else {
|
|
15
26
|
console.error(chalk.red(`\n✗ ${result.message}`));
|
|
16
27
|
process.exit(1);
|
package/src/commands/update.ts
CHANGED
|
@@ -3,22 +3,22 @@ import { execSync } from 'child_process';
|
|
|
3
3
|
import { getVersion } from '../lib/version.js';
|
|
4
4
|
|
|
5
5
|
interface UpdateOptions {
|
|
6
|
-
|
|
6
|
+
tools?: boolean;
|
|
7
7
|
cli?: boolean;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export async function updateCommand(
|
|
11
|
-
// If specific
|
|
12
|
-
if (
|
|
13
|
-
console.log(chalk.yellow('\n⚠ Per-
|
|
14
|
-
console.log(chalk.gray('
|
|
10
|
+
export async function updateCommand(tool?: string, options?: UpdateOptions): Promise<void> {
|
|
11
|
+
// If specific tool specified, update just that tool
|
|
12
|
+
if (tool) {
|
|
13
|
+
console.log(chalk.yellow('\n⚠ Per-tool updates not implemented yet'));
|
|
14
|
+
console.log(chalk.gray('Tools are bundled with the CLI - run `droid update` to update all.'));
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// If --
|
|
19
|
-
if (options?.
|
|
20
|
-
console.log(chalk.yellow('\n⚠
|
|
21
|
-
console.log(chalk.gray('
|
|
18
|
+
// If --tools flag, update tools only
|
|
19
|
+
if (options?.tools) {
|
|
20
|
+
console.log(chalk.yellow('\n⚠ Tool-only updates not implemented yet'));
|
|
21
|
+
console.log(chalk.gray('Tools are bundled with the CLI - run `droid update` to update all.'));
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
|
package/src/lib/agents.ts
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'fs';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { homedir } from 'os';
|
|
5
4
|
import YAML from 'yaml';
|
|
6
5
|
import { loadConfig } from './config.js';
|
|
7
|
-
import {
|
|
6
|
+
import { Platform } from './types.js';
|
|
7
|
+
import { getAgentsPath } from './platforms.js';
|
|
8
8
|
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
const
|
|
10
|
+
const BUNDLED_TOOLS_DIR = join(__dirname, '../tools');
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Get the installation path for agents based on
|
|
13
|
+
* Get the installation path for agents based on platform
|
|
14
14
|
*/
|
|
15
|
-
export function getAgentsInstallPath(
|
|
16
|
-
|
|
17
|
-
case AITool.ClaudeCode:
|
|
18
|
-
return join(homedir(), '.claude', 'agents');
|
|
19
|
-
case AITool.OpenCode:
|
|
20
|
-
return join(homedir(), '.config', 'opencode', 'agent');
|
|
21
|
-
}
|
|
15
|
+
export function getAgentsInstallPath(platform: Platform): string {
|
|
16
|
+
return getAgentsPath(platform);
|
|
22
17
|
}
|
|
23
18
|
|
|
24
19
|
/**
|
|
@@ -37,13 +32,6 @@ export interface AgentManifest {
|
|
|
37
32
|
persona?: string;
|
|
38
33
|
}
|
|
39
34
|
|
|
40
|
-
/**
|
|
41
|
-
* Get the path to bundled agents directory
|
|
42
|
-
*/
|
|
43
|
-
export function getBundledAgentsDir(): string {
|
|
44
|
-
return BUNDLED_AGENTS_DIR;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
35
|
/**
|
|
48
36
|
* Load an agent manifest from an agent directory
|
|
49
37
|
*/
|
|
@@ -63,20 +51,31 @@ export function loadAgentManifest(agentDir: string): AgentManifest | null {
|
|
|
63
51
|
}
|
|
64
52
|
|
|
65
53
|
/**
|
|
66
|
-
* Get all bundled agents
|
|
54
|
+
* Get all bundled agents from tools
|
|
67
55
|
*/
|
|
68
56
|
export function getBundledAgents(): AgentManifest[] {
|
|
69
57
|
const agents: AgentManifest[] = [];
|
|
70
58
|
const seenNames = new Set<string>();
|
|
71
59
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
60
|
+
if (!existsSync(BUNDLED_TOOLS_DIR)) {
|
|
61
|
+
return agents;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Get agents from tools/*/agents/
|
|
65
|
+
const toolDirs = readdirSync(BUNDLED_TOOLS_DIR, { withFileTypes: true })
|
|
66
|
+
.filter((dirent) => dirent.isDirectory())
|
|
67
|
+
.map((dirent) => dirent.name);
|
|
68
|
+
|
|
69
|
+
for (const toolName of toolDirs) {
|
|
70
|
+
const toolAgentsDir = join(BUNDLED_TOOLS_DIR, toolName, 'agents');
|
|
71
|
+
if (!existsSync(toolAgentsDir)) continue;
|
|
72
|
+
|
|
73
|
+
const agentDirs = readdirSync(toolAgentsDir, { withFileTypes: true })
|
|
75
74
|
.filter((dirent) => dirent.isDirectory())
|
|
76
75
|
.map((dirent) => dirent.name);
|
|
77
76
|
|
|
78
77
|
for (const agentDir of agentDirs) {
|
|
79
|
-
const manifest = loadAgentManifest(join(
|
|
78
|
+
const manifest = loadAgentManifest(join(toolAgentsDir, agentDir));
|
|
80
79
|
if (manifest && !seenNames.has(manifest.name)) {
|
|
81
80
|
agents.push(manifest);
|
|
82
81
|
seenNames.add(manifest.name);
|
|
@@ -84,36 +83,12 @@ export function getBundledAgents(): AgentManifest[] {
|
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
// Get skill-bundled agents from src/skills/*/agents/
|
|
88
|
-
const skillsDir = join(__dirname, '../skills');
|
|
89
|
-
if (existsSync(skillsDir)) {
|
|
90
|
-
const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
91
|
-
.filter((dirent) => dirent.isDirectory())
|
|
92
|
-
.map((dirent) => dirent.name);
|
|
93
|
-
|
|
94
|
-
for (const skillName of skillDirs) {
|
|
95
|
-
const skillAgentsDir = join(skillsDir, skillName, 'agents');
|
|
96
|
-
if (!existsSync(skillAgentsDir)) continue;
|
|
97
|
-
|
|
98
|
-
const agentDirs = readdirSync(skillAgentsDir, { withFileTypes: true })
|
|
99
|
-
.filter((dirent) => dirent.isDirectory())
|
|
100
|
-
.map((dirent) => dirent.name);
|
|
101
|
-
|
|
102
|
-
for (const agentDir of agentDirs) {
|
|
103
|
-
const manifest = loadAgentManifest(join(skillAgentsDir, agentDir));
|
|
104
|
-
if (manifest && !seenNames.has(manifest.name)) {
|
|
105
|
-
agents.push(manifest);
|
|
106
|
-
seenNames.add(manifest.name);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
86
|
return agents;
|
|
113
87
|
}
|
|
114
88
|
|
|
115
89
|
/**
|
|
116
90
|
* Get agent status display string
|
|
91
|
+
* Note: Not currently used, kept for future TUI agent status display
|
|
117
92
|
*/
|
|
118
93
|
export function getAgentStatusDisplay(status?: string): string {
|
|
119
94
|
switch (status) {
|
|
@@ -128,11 +103,11 @@ export function getAgentStatusDisplay(status?: string): string {
|
|
|
128
103
|
}
|
|
129
104
|
|
|
130
105
|
/**
|
|
131
|
-
* Get installed agents directory for the configured
|
|
106
|
+
* Get installed agents directory for the configured platform
|
|
132
107
|
*/
|
|
133
108
|
export function getInstalledAgentsDir(): string {
|
|
134
109
|
const config = loadConfig();
|
|
135
|
-
return getAgentsInstallPath(config.
|
|
110
|
+
return getAgentsInstallPath(config.platform);
|
|
136
111
|
}
|
|
137
112
|
|
|
138
113
|
/**
|
|
@@ -140,7 +115,7 @@ export function getInstalledAgentsDir(): string {
|
|
|
140
115
|
*/
|
|
141
116
|
export function isAgentInstalled(agentName: string): boolean {
|
|
142
117
|
const config = loadConfig();
|
|
143
|
-
const agentsDir = getAgentsInstallPath(config.
|
|
118
|
+
const agentsDir = getAgentsInstallPath(config.platform);
|
|
144
119
|
const agentPath = join(agentsDir, `${agentName}.md`);
|
|
145
120
|
return existsSync(agentPath);
|
|
146
121
|
}
|
|
@@ -221,13 +196,13 @@ export function installAgentFromPath(agentDir: string, agentName: string): { suc
|
|
|
221
196
|
agentContent = manifest.persona;
|
|
222
197
|
}
|
|
223
198
|
|
|
224
|
-
// Generate format based on
|
|
225
|
-
const installedContent = config.
|
|
199
|
+
// Generate format based on platform
|
|
200
|
+
const installedContent = config.platform === Platform.ClaudeCode
|
|
226
201
|
? generateClaudeCodeAgent(manifest, agentContent)
|
|
227
202
|
: generateOpenCodeAgent(manifest, agentContent);
|
|
228
203
|
|
|
229
204
|
// Ensure agents directory exists
|
|
230
|
-
const agentsDir = getAgentsInstallPath(config.
|
|
205
|
+
const agentsDir = getAgentsInstallPath(config.platform);
|
|
231
206
|
if (!existsSync(agentsDir)) {
|
|
232
207
|
mkdirSync(agentsDir, { recursive: true });
|
|
233
208
|
}
|
|
@@ -236,7 +211,7 @@ export function installAgentFromPath(agentDir: string, agentName: string): { suc
|
|
|
236
211
|
const outputPath = join(agentsDir, `${agentName}.md`);
|
|
237
212
|
writeFileSync(outputPath, installedContent);
|
|
238
213
|
|
|
239
|
-
const targetDir = config.
|
|
214
|
+
const targetDir = config.platform === Platform.ClaudeCode
|
|
240
215
|
? '~/.claude/agents/'
|
|
241
216
|
: '~/.config/opencode/agent/';
|
|
242
217
|
|
|
@@ -247,11 +222,36 @@ export function installAgentFromPath(agentDir: string, agentName: string): { suc
|
|
|
247
222
|
}
|
|
248
223
|
|
|
249
224
|
/**
|
|
250
|
-
*
|
|
225
|
+
* Find the path to an agent within the tools directory structure
|
|
226
|
+
*/
|
|
227
|
+
export function findAgentPath(agentName: string): string | null {
|
|
228
|
+
if (!existsSync(BUNDLED_TOOLS_DIR)) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const toolDirs = readdirSync(BUNDLED_TOOLS_DIR, { withFileTypes: true })
|
|
233
|
+
.filter((dirent) => dirent.isDirectory())
|
|
234
|
+
.map((dirent) => dirent.name);
|
|
235
|
+
|
|
236
|
+
for (const toolName of toolDirs) {
|
|
237
|
+
const agentDir = join(BUNDLED_TOOLS_DIR, toolName, 'agents', agentName);
|
|
238
|
+
if (existsSync(agentDir) && existsSync(join(agentDir, 'AGENT.yaml'))) {
|
|
239
|
+
return agentDir;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Install an agent from the tools directory
|
|
251
248
|
* Combines AGENT.yaml metadata with AGENT.md content into a single .md file
|
|
252
249
|
*/
|
|
253
250
|
export function installAgent(agentName: string): { success: boolean; message: string } {
|
|
254
|
-
const agentDir =
|
|
251
|
+
const agentDir = findAgentPath(agentName);
|
|
252
|
+
if (!agentDir) {
|
|
253
|
+
return { success: false, message: `Agent '${agentName}' not found` };
|
|
254
|
+
}
|
|
255
255
|
return installAgentFromPath(agentDir, agentName);
|
|
256
256
|
}
|
|
257
257
|
|
|
@@ -260,7 +260,7 @@ export function installAgent(agentName: string): { success: boolean; message: st
|
|
|
260
260
|
*/
|
|
261
261
|
export function uninstallAgent(agentName: string): { success: boolean; message: string } {
|
|
262
262
|
const config = loadConfig();
|
|
263
|
-
const agentsDir = getAgentsInstallPath(config.
|
|
263
|
+
const agentsDir = getAgentsInstallPath(config.platform);
|
|
264
264
|
const agentPath = join(agentsDir, `${agentName}.md`);
|
|
265
265
|
|
|
266
266
|
if (!existsSync(agentPath)) {
|
package/src/lib/config.test.ts
CHANGED
|
@@ -3,16 +3,6 @@ import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'fs';
|
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
5
|
import YAML from 'yaml';
|
|
6
|
-
import { AITool, BuiltInOutput } from './types.js';
|
|
7
|
-
|
|
8
|
-
describe('config types', () => {
|
|
9
|
-
it('should have correct enum values', () => {
|
|
10
|
-
expect(AITool.ClaudeCode).toBe('claude-code');
|
|
11
|
-
expect(AITool.OpenCode).toBe('opencode');
|
|
12
|
-
expect(BuiltInOutput.Terminal).toBe('terminal');
|
|
13
|
-
expect(BuiltInOutput.Editor).toBe('editor');
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
6
|
|
|
17
7
|
describe('config value parsing', () => {
|
|
18
8
|
it('should parse dot notation keys correctly', () => {
|
package/src/lib/config.ts
CHANGED
|
@@ -2,19 +2,49 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import YAML from 'yaml';
|
|
5
|
-
import {
|
|
5
|
+
import { Platform, BuiltInOutput, type DroidConfig, type LegacyDroidConfig, type SkillOverrides } from './types.js';
|
|
6
6
|
|
|
7
7
|
const CONFIG_DIR = join(homedir(), '.droid');
|
|
8
8
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.yaml');
|
|
9
9
|
|
|
10
10
|
const DEFAULT_CONFIG: DroidConfig = {
|
|
11
|
-
|
|
11
|
+
platform: Platform.ClaudeCode,
|
|
12
12
|
user_mention: '@user',
|
|
13
13
|
output_preference: BuiltInOutput.Terminal,
|
|
14
14
|
git_username: '',
|
|
15
|
-
|
|
15
|
+
platforms: {},
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Migrate legacy config (v1) to new config format (v2)
|
|
20
|
+
* v1: ai_tool + skills flat
|
|
21
|
+
* v2: platform + platforms map with per-platform tools
|
|
22
|
+
*/
|
|
23
|
+
function migrateConfig(config: Partial<DroidConfig> & Partial<LegacyDroidConfig>): DroidConfig {
|
|
24
|
+
// Check if this is a legacy config (has ai_tool, no platform)
|
|
25
|
+
if ('ai_tool' in config && !('platform' in config)) {
|
|
26
|
+
const legacyConfig = config as LegacyDroidConfig;
|
|
27
|
+
return {
|
|
28
|
+
platform: legacyConfig.ai_tool,
|
|
29
|
+
user_mention: legacyConfig.user_mention ?? DEFAULT_CONFIG.user_mention,
|
|
30
|
+
output_preference: legacyConfig.output_preference ?? DEFAULT_CONFIG.output_preference,
|
|
31
|
+
git_username: legacyConfig.git_username ?? DEFAULT_CONFIG.git_username,
|
|
32
|
+
platforms: {
|
|
33
|
+
[legacyConfig.ai_tool]: {
|
|
34
|
+
tools: legacyConfig.skills ?? {},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Already new format, just ensure defaults
|
|
41
|
+
return {
|
|
42
|
+
...DEFAULT_CONFIG,
|
|
43
|
+
...config,
|
|
44
|
+
platforms: config.platforms ?? {},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
18
48
|
/**
|
|
19
49
|
* Ensure the config directory exists
|
|
20
50
|
*/
|
|
@@ -33,6 +63,7 @@ export function configExists(): boolean {
|
|
|
33
63
|
|
|
34
64
|
/**
|
|
35
65
|
* Load the global config, or return defaults if none exists
|
|
66
|
+
* Automatically migrates legacy config format to new format
|
|
36
67
|
*/
|
|
37
68
|
export function loadConfig(): DroidConfig {
|
|
38
69
|
ensureConfigDir();
|
|
@@ -43,8 +74,19 @@ export function loadConfig(): DroidConfig {
|
|
|
43
74
|
|
|
44
75
|
try {
|
|
45
76
|
const content = readFileSync(CONFIG_FILE, 'utf-8');
|
|
46
|
-
const
|
|
47
|
-
|
|
77
|
+
const rawConfig = YAML.parse(content) as Partial<DroidConfig> & Partial<LegacyDroidConfig>;
|
|
78
|
+
|
|
79
|
+
// Check if migration is needed
|
|
80
|
+
// TODO: Remove after v0.14.0 (target: late Jan 2025)
|
|
81
|
+
const needsMigration = 'ai_tool' in rawConfig && !('platform' in rawConfig);
|
|
82
|
+
const config = migrateConfig(rawConfig);
|
|
83
|
+
|
|
84
|
+
// Save migrated config if it was migrated
|
|
85
|
+
if (needsMigration) {
|
|
86
|
+
saveConfig(config);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return config;
|
|
48
90
|
} catch (error) {
|
|
49
91
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
50
92
|
console.error(`Error reading config: ${message}`);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { Platform } from './types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Platform-specific paths configuration
|
|
7
|
+
* Single source of truth for all platform-specific directories
|
|
8
|
+
*/
|
|
9
|
+
export const PLATFORM_PATHS = {
|
|
10
|
+
[Platform.ClaudeCode]: {
|
|
11
|
+
skills: join(homedir(), '.claude', 'skills'),
|
|
12
|
+
commands: join(homedir(), '.claude', 'commands'),
|
|
13
|
+
agents: join(homedir(), '.claude', 'agents'),
|
|
14
|
+
config: join(homedir(), '.claude', 'CLAUDE.md'),
|
|
15
|
+
},
|
|
16
|
+
[Platform.OpenCode]: {
|
|
17
|
+
skills: join(homedir(), '.config', 'opencode', 'skills'),
|
|
18
|
+
commands: join(homedir(), '.config', 'opencode', 'command'),
|
|
19
|
+
agents: join(homedir(), '.config', 'opencode', 'agent'),
|
|
20
|
+
config: join(homedir(), '.config', 'opencode', 'AGENTS.md'),
|
|
21
|
+
},
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
export type PlatformPaths = typeof PLATFORM_PATHS[Platform];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get all paths for a platform
|
|
28
|
+
*/
|
|
29
|
+
export function getPlatformPaths(platform: Platform): PlatformPaths {
|
|
30
|
+
return PLATFORM_PATHS[platform];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get skills install path for a platform
|
|
35
|
+
*/
|
|
36
|
+
export function getSkillsPath(platform: Platform): string {
|
|
37
|
+
return PLATFORM_PATHS[platform].skills;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get commands install path for a platform
|
|
42
|
+
*/
|
|
43
|
+
export function getCommandsPath(platform: Platform): string {
|
|
44
|
+
return PLATFORM_PATHS[platform].commands;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get agents install path for a platform
|
|
49
|
+
*/
|
|
50
|
+
export function getAgentsPath(platform: Platform): string {
|
|
51
|
+
return PLATFORM_PATHS[platform].agents;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get platform config file path (CLAUDE.md or AGENTS.md)
|
|
56
|
+
*/
|
|
57
|
+
export function getConfigPath(platform: Platform): string {
|
|
58
|
+
return PLATFORM_PATHS[platform].config;
|
|
59
|
+
}
|
package/src/lib/skills.test.ts
CHANGED
|
@@ -4,11 +4,11 @@ import { join } from 'path';
|
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import YAML from 'yaml';
|
|
7
|
-
import {
|
|
7
|
+
import { Platform, SkillStatus } from './types.js';
|
|
8
8
|
import {
|
|
9
9
|
getSkillsInstallPath,
|
|
10
10
|
getCommandsInstallPath,
|
|
11
|
-
|
|
11
|
+
getPlatformConfigPath,
|
|
12
12
|
getSkillStatusDisplay,
|
|
13
13
|
getBundledSkillsDir,
|
|
14
14
|
} from './skills.js';
|
|
@@ -28,44 +28,44 @@ function parseFrontmatter(content: string): Record<string, unknown> | null {
|
|
|
28
28
|
|
|
29
29
|
describe('getSkillsInstallPath', () => {
|
|
30
30
|
it('should return Claude Code path', () => {
|
|
31
|
-
const path = getSkillsInstallPath(
|
|
31
|
+
const path = getSkillsInstallPath(Platform.ClaudeCode);
|
|
32
32
|
expect(path).toBe(join(homedir(), '.claude', 'skills'));
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
it('should return OpenCode path', () => {
|
|
36
|
-
const path = getSkillsInstallPath(
|
|
36
|
+
const path = getSkillsInstallPath(Platform.OpenCode);
|
|
37
37
|
expect(path).toBe(join(homedir(), '.config', 'opencode', 'skills'));
|
|
38
38
|
});
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
describe('getCommandsInstallPath', () => {
|
|
42
42
|
it('should return Claude Code commands path', () => {
|
|
43
|
-
const path = getCommandsInstallPath(
|
|
43
|
+
const path = getCommandsInstallPath(Platform.ClaudeCode);
|
|
44
44
|
expect(path).toBe(join(homedir(), '.claude', 'commands'));
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
it('should return OpenCode commands path', () => {
|
|
48
|
-
const path = getCommandsInstallPath(
|
|
48
|
+
const path = getCommandsInstallPath(Platform.OpenCode);
|
|
49
49
|
expect(path).toBe(join(homedir(), '.config', 'opencode', 'command'));
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
describe('
|
|
53
|
+
describe('getPlatformConfigPath', () => {
|
|
54
54
|
it('should return Claude Code CLAUDE.md path', () => {
|
|
55
|
-
const path =
|
|
55
|
+
const path = getPlatformConfigPath(Platform.ClaudeCode);
|
|
56
56
|
expect(path).toBe(join(homedir(), '.claude', 'CLAUDE.md'));
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
it('should return OpenCode AGENTS.md path', () => {
|
|
60
|
-
const path =
|
|
60
|
+
const path = getPlatformConfigPath(Platform.OpenCode);
|
|
61
61
|
expect(path).toBe(join(homedir(), '.config', 'opencode', 'AGENTS.md'));
|
|
62
62
|
});
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
describe('getBundledSkillsDir', () => {
|
|
66
|
-
it('should return a path ending in
|
|
66
|
+
it('should return a path ending in tools', () => {
|
|
67
67
|
const dir = getBundledSkillsDir();
|
|
68
|
-
expect(dir.endsWith('
|
|
68
|
+
expect(dir.endsWith('tools')).toBe(true);
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
it('should return an existing directory', () => {
|
|
@@ -150,15 +150,44 @@ describe('skill manifest parsing', () => {
|
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Helper to get all skill directories in the new tools/{tool}/skills/{skill} structure
|
|
155
|
+
*/
|
|
156
|
+
function getAllSkillPaths(toolsDir: string): Array<{ skillName: string; skillDir: string }> {
|
|
157
|
+
const skillPaths: Array<{ skillName: string; skillDir: string }> = [];
|
|
158
|
+
|
|
159
|
+
const toolDirs = readdirSync(toolsDir, { withFileTypes: true })
|
|
160
|
+
.filter((d) => d.isDirectory())
|
|
161
|
+
.map((d) => d.name);
|
|
162
|
+
|
|
163
|
+
for (const toolName of toolDirs) {
|
|
164
|
+
const skillsDir = join(toolsDir, toolName, 'skills');
|
|
165
|
+
if (!existsSync(skillsDir)) continue;
|
|
166
|
+
|
|
156
167
|
const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
157
168
|
.filter((d) => d.isDirectory())
|
|
158
169
|
.map((d) => d.name);
|
|
159
170
|
|
|
160
171
|
for (const skillName of skillDirs) {
|
|
161
|
-
|
|
172
|
+
skillPaths.push({
|
|
173
|
+
skillName,
|
|
174
|
+
skillDir: join(skillsDir, skillName),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return skillPaths;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
describe('bundled skills validation', () => {
|
|
183
|
+
it('all skills should have SKILL.md with valid frontmatter', () => {
|
|
184
|
+
const toolsDir = getBundledSkillsDir();
|
|
185
|
+
const skillPaths = getAllSkillPaths(toolsDir);
|
|
186
|
+
|
|
187
|
+
expect(skillPaths.length).toBeGreaterThan(0);
|
|
188
|
+
|
|
189
|
+
for (const { skillName, skillDir } of skillPaths) {
|
|
190
|
+
const skillMdPath = join(skillDir, 'SKILL.md');
|
|
162
191
|
expect(existsSync(skillMdPath)).toBe(true);
|
|
163
192
|
|
|
164
193
|
const content = readFileSync(skillMdPath, 'utf-8');
|
|
@@ -171,13 +200,11 @@ describe('bundled skills validation', () => {
|
|
|
171
200
|
});
|
|
172
201
|
|
|
173
202
|
it('all skills should have SKILL.yaml manifest', () => {
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
.filter((d) => d.isDirectory())
|
|
177
|
-
.map((d) => d.name);
|
|
203
|
+
const toolsDir = getBundledSkillsDir();
|
|
204
|
+
const skillPaths = getAllSkillPaths(toolsDir);
|
|
178
205
|
|
|
179
|
-
for (const skillName of
|
|
180
|
-
const yamlPath = join(
|
|
206
|
+
for (const { skillName, skillDir } of skillPaths) {
|
|
207
|
+
const yamlPath = join(skillDir, 'SKILL.yaml');
|
|
181
208
|
expect(existsSync(yamlPath)).toBe(true);
|
|
182
209
|
|
|
183
210
|
const content = readFileSync(yamlPath, 'utf-8');
|
|
@@ -190,14 +217,12 @@ describe('bundled skills validation', () => {
|
|
|
190
217
|
});
|
|
191
218
|
|
|
192
219
|
it('SKILL.md frontmatter should match SKILL.yaml', () => {
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
.filter((d) => d.isDirectory())
|
|
196
|
-
.map((d) => d.name);
|
|
220
|
+
const toolsDir = getBundledSkillsDir();
|
|
221
|
+
const skillPaths = getAllSkillPaths(toolsDir);
|
|
197
222
|
|
|
198
|
-
for (const
|
|
199
|
-
const mdPath = join(
|
|
200
|
-
const yamlPath = join(
|
|
223
|
+
for (const { skillDir } of skillPaths) {
|
|
224
|
+
const mdPath = join(skillDir, 'SKILL.md');
|
|
225
|
+
const yamlPath = join(skillDir, 'SKILL.yaml');
|
|
201
226
|
|
|
202
227
|
const mdContent = readFileSync(mdPath, 'utf-8');
|
|
203
228
|
const yamlContent = readFileSync(yamlPath, 'utf-8');
|