simba-skills 0.6.0 → 0.6.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/README.md +6 -17
- package/package.json +1 -1
- package/src/commands/assign.ts +24 -5
- package/src/commands/bootstrap.ts +25 -4
- package/src/commands/detect.ts +12 -3
- package/src/commands/install.ts +1 -1
- package/src/core/agent-registry.ts +12 -7
- package/src/core/config-store.ts +416 -26
- package/src/core/registry-store.ts +42 -1
- package/src/core/types.ts +3 -0
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/simba-skills)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
|
|
10
|
-
AI skills manager with a central store and symlink-based distribution across
|
|
10
|
+
AI skills manager with a central store and symlink-based distribution across 17+ coding agents.
|
|
11
11
|
|
|
12
12
|
## Why Simba?
|
|
13
13
|
|
|
@@ -108,22 +108,11 @@ simba undo
|
|
|
108
108
|
|
|
109
109
|
## Supported Agents
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
| GitHub Copilot | `~/.copilot/skills` | `.github/skills` |
|
|
117
|
-
| Gemini CLI | `~/.gemini/skills` | `.gemini/skills` |
|
|
118
|
-
| Windsurf | `~/.codeium/windsurf/skills` | `.windsurf/skills` |
|
|
119
|
-
| Amp | `~/.config/agents/skills` | `.agents/skills` |
|
|
120
|
-
| Goose | `~/.config/goose/skills` | `.goose/skills` |
|
|
121
|
-
| OpenCode | `~/.config/opencode/skill` | `.opencode/skill` |
|
|
122
|
-
| Kilo Code | `~/.kilocode/skills` | `.kilocode/skills` |
|
|
123
|
-
| Roo Code | `~/.roo/skills` | `.roo/skills` |
|
|
124
|
-
| Antigravity | `~/.gemini/antigravity/skills` | `.agent/skills` |
|
|
125
|
-
| Clawdbot | `~/.clawdbot/skills` | `skills` |
|
|
126
|
-
| Droid | `~/.factory/skills` | `.factory/skills` |
|
|
111
|
+
Supports Claude Code, Codex, OpenCode, Cursor, Gemini CLI, GitHub Copilot, Amp, Kimi Code CLI, Replit, and 30+ others.
|
|
112
|
+
|
|
113
|
+
Includes agents using the `.agents/skills` universal standard, plus agent-specific paths.
|
|
114
|
+
|
|
115
|
+
See full agent definitions and paths in [`src/core/config-store.ts`](./src/core/config-store.ts).
|
|
127
116
|
|
|
128
117
|
## Architecture
|
|
129
118
|
|
package/package.json
CHANGED
package/src/commands/assign.ts
CHANGED
|
@@ -56,13 +56,32 @@ export default defineCommand({
|
|
|
56
56
|
// Get detected agents
|
|
57
57
|
const agentRegistry = new AgentRegistry(config.agents)
|
|
58
58
|
const detected = await agentRegistry.detectAgents()
|
|
59
|
-
|
|
59
|
+
|
|
60
|
+
const universalFallbackByProjectPath = new Map<string, string>()
|
|
61
|
+
for (const agent of Object.values(detected)) {
|
|
62
|
+
if (!agent.universal || !agent.detected) continue
|
|
63
|
+
if (!universalFallbackByProjectPath.has(agent.projectPath)) {
|
|
64
|
+
universalFallbackByProjectPath.set(agent.projectPath, expandPath(agent.globalPath))
|
|
65
|
+
}
|
|
66
|
+
}
|
|
60
67
|
|
|
61
68
|
const agentPaths: Record<string, string> = {}
|
|
62
|
-
for (const [id, agent] of
|
|
63
|
-
|
|
69
|
+
for (const [id, agent] of Object.entries(detected)) {
|
|
70
|
+
if (agent.detected) {
|
|
71
|
+
agentPaths[id] = expandPath(agent.globalPath)
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (agent.universal) {
|
|
76
|
+
const fallback = universalFallbackByProjectPath.get(agent.projectPath)
|
|
77
|
+
if (fallback) {
|
|
78
|
+
agentPaths[id] = fallback
|
|
79
|
+
}
|
|
80
|
+
}
|
|
64
81
|
}
|
|
65
82
|
|
|
83
|
+
const assignableAgents = Object.entries(detected).filter(([id]) => agentPaths[id])
|
|
84
|
+
|
|
66
85
|
// Interactive mode if args missing
|
|
67
86
|
let skill = args.skill as string | undefined
|
|
68
87
|
let agents: string[]
|
|
@@ -83,14 +102,14 @@ export default defineCommand({
|
|
|
83
102
|
}
|
|
84
103
|
|
|
85
104
|
if (!args.agents) {
|
|
86
|
-
if (
|
|
105
|
+
if (assignableAgents.length === 0) {
|
|
87
106
|
console.log("No agents detected.")
|
|
88
107
|
return
|
|
89
108
|
}
|
|
90
109
|
|
|
91
110
|
const result = await p.multiselect({
|
|
92
111
|
message: "Select agents to assign to",
|
|
93
|
-
options:
|
|
112
|
+
options: assignableAgents.map(([id, a]) => ({ value: id, label: a.name })),
|
|
94
113
|
required: true,
|
|
95
114
|
})
|
|
96
115
|
if (p.isCancel(result)) process.exit(0)
|
|
@@ -349,6 +349,28 @@ export interface AssignResult {
|
|
|
349
349
|
message?: string
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
function resolveAssignmentPath(
|
|
353
|
+
agentId: string,
|
|
354
|
+
detected: Record<string, import("../core/types").Agent>
|
|
355
|
+
): string | null {
|
|
356
|
+
const agent = detected[agentId]
|
|
357
|
+
if (!agent) return null
|
|
358
|
+
|
|
359
|
+
if (agent.detected) {
|
|
360
|
+
return expandPath(agent.globalPath)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!agent.universal) {
|
|
364
|
+
return null
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const fallback = Object.values(detected).find(
|
|
368
|
+
(candidate) => candidate.universal && candidate.detected && candidate.projectPath === agent.projectPath
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
return fallback ? expandPath(fallback.globalPath) : null
|
|
372
|
+
}
|
|
373
|
+
|
|
352
374
|
/** Detect agents and create symlinks for each skill's assignments */
|
|
353
375
|
export async function assignSkillsToAgents(
|
|
354
376
|
registry: { skills: Record<string, ManagedSkill> },
|
|
@@ -366,14 +388,13 @@ export async function assignSkillsToAgents(
|
|
|
366
388
|
|
|
367
389
|
const assignments = skill.assignments
|
|
368
390
|
for (const [agentId, assignment] of Object.entries(assignments)) {
|
|
369
|
-
const
|
|
370
|
-
if (!
|
|
391
|
+
const assignmentPath = resolveAssignmentPath(agentId, detected)
|
|
392
|
+
if (!assignmentPath) {
|
|
371
393
|
results.push({ skill: skillName, agent: agentId, status: "skipped", message: "agent not detected" })
|
|
372
394
|
continue
|
|
373
395
|
}
|
|
374
396
|
|
|
375
|
-
|
|
376
|
-
await skillsStore.assignSkill(skillName, agentSkillsDir, assignment)
|
|
397
|
+
await skillsStore.assignSkill(skillName, assignmentPath, assignment)
|
|
377
398
|
results.push({ skill: skillName, agent: agentId, status: "assigned" })
|
|
378
399
|
}
|
|
379
400
|
}
|
package/src/commands/detect.ts
CHANGED
|
@@ -51,9 +51,18 @@ export default defineCommand({
|
|
|
51
51
|
|
|
52
52
|
await configStore.save(config)
|
|
53
53
|
|
|
54
|
-
// Output results
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
// Output results grouped by universal/custom
|
|
55
|
+
const universal = Object.values(detected).filter(a => a.universal)
|
|
56
|
+
const custom = Object.values(detected).filter(a => !a.universal)
|
|
57
|
+
|
|
58
|
+
console.log("\nUniversal (.agents/skills):")
|
|
59
|
+
for (const agent of universal) {
|
|
60
|
+
const status = agent.detected ? "✓" : "─"
|
|
61
|
+
console.log(` ${status} ${agent.name}`)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log("\nCustom:")
|
|
65
|
+
for (const agent of custom) {
|
|
57
66
|
const status = agent.detected ? "✓" : "─"
|
|
58
67
|
console.log(` ${status} ${agent.name}`)
|
|
59
68
|
}
|
package/src/commands/install.ts
CHANGED
|
@@ -31,7 +31,7 @@ interface MarketplaceJson {
|
|
|
31
31
|
plugins?: MarketplacePlugin[]
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const SKILL_DIRS = ["skills", ".claude/skills", ".cursor/skills", ".codex/skills"]
|
|
34
|
+
const SKILL_DIRS = ["skills", ".agents/skills", ".claude/skills", ".cursor/skills", ".codex/skills"]
|
|
35
35
|
|
|
36
36
|
interface SubmoduleInfo {
|
|
37
37
|
path: string
|
|
@@ -11,15 +11,20 @@ export class AgentRegistry {
|
|
|
11
11
|
const results: Record<string, Agent> = {}
|
|
12
12
|
|
|
13
13
|
for (const [id, agent] of Object.entries(this.agents)) {
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const configuredPaths = agent.detectPaths && agent.detectPaths.length > 0
|
|
15
|
+
? agent.detectPaths
|
|
16
|
+
: [agent.detectPath ?? dirname(agent.globalPath)]
|
|
16
17
|
|
|
17
18
|
let detected = false
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
for (const path of configuredPaths) {
|
|
20
|
+
const detectionPath = expandPath(path)
|
|
21
|
+
try {
|
|
22
|
+
await access(detectionPath)
|
|
23
|
+
detected = true
|
|
24
|
+
break
|
|
25
|
+
} catch {
|
|
26
|
+
continue
|
|
27
|
+
}
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
results[id] = { ...agent, detected }
|
package/src/core/config-store.ts
CHANGED
|
@@ -3,32 +3,405 @@ import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import type { Config, Agent } from "./types";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
[
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
6
|
+
interface AgentDefinition {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
shortName: string;
|
|
10
|
+
globalPath: string;
|
|
11
|
+
projectPath: string;
|
|
12
|
+
detectPath?: string;
|
|
13
|
+
detectPaths?: string[];
|
|
14
|
+
universal: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const AGENT_DEFINITIONS: AgentDefinition[] = [
|
|
18
|
+
// Universal agents (use .agents/skills as project path)
|
|
19
|
+
{
|
|
20
|
+
id: "amp",
|
|
21
|
+
name: "Amp",
|
|
22
|
+
shortName: "Amp",
|
|
23
|
+
globalPath: "~/.config/agents/skills",
|
|
24
|
+
projectPath: ".agents/skills",
|
|
25
|
+
detectPath: "~/.config/amp",
|
|
26
|
+
universal: true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "codex",
|
|
30
|
+
name: "Codex",
|
|
31
|
+
shortName: "Codex",
|
|
32
|
+
globalPath: "~/.codex/skills",
|
|
33
|
+
projectPath: ".agents/skills",
|
|
34
|
+
detectPath: "~/.codex",
|
|
35
|
+
universal: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "copilot",
|
|
39
|
+
name: "GitHub Copilot",
|
|
40
|
+
shortName: "Copilot",
|
|
41
|
+
globalPath: "~/.copilot/skills",
|
|
42
|
+
projectPath: ".agents/skills",
|
|
43
|
+
detectPath: "~/.copilot",
|
|
44
|
+
universal: true,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "gemini",
|
|
48
|
+
name: "Gemini CLI",
|
|
49
|
+
shortName: "Gemini",
|
|
50
|
+
globalPath: "~/.gemini/skills",
|
|
51
|
+
projectPath: ".agents/skills",
|
|
52
|
+
detectPath: "~/.gemini",
|
|
53
|
+
universal: true,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "opencode",
|
|
57
|
+
name: "OpenCode",
|
|
58
|
+
shortName: "OpenCode",
|
|
59
|
+
globalPath: "~/.config/opencode/skills",
|
|
60
|
+
projectPath: ".agents/skills",
|
|
61
|
+
detectPath: "~/.config/opencode",
|
|
62
|
+
universal: true,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "kimi",
|
|
66
|
+
name: "Kimi Code CLI",
|
|
67
|
+
shortName: "Kimi",
|
|
68
|
+
globalPath: "~/.config/agents/skills",
|
|
69
|
+
projectPath: ".agents/skills",
|
|
70
|
+
detectPath: "~/.kimi",
|
|
71
|
+
universal: true,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "replit",
|
|
75
|
+
name: "Replit",
|
|
76
|
+
shortName: "Replit",
|
|
77
|
+
globalPath: "~/.config/agents/skills",
|
|
78
|
+
projectPath: ".agents/skills",
|
|
79
|
+
detectPath: ".replit",
|
|
80
|
+
universal: true,
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// Custom agents
|
|
84
|
+
{
|
|
85
|
+
id: "claude",
|
|
86
|
+
name: "Claude Code",
|
|
87
|
+
shortName: "Claude",
|
|
88
|
+
globalPath: "~/.claude/skills",
|
|
89
|
+
projectPath: ".claude/skills",
|
|
90
|
+
detectPath: "~/.claude",
|
|
91
|
+
universal: false,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "cursor",
|
|
95
|
+
name: "Cursor",
|
|
96
|
+
shortName: "Cursor",
|
|
97
|
+
globalPath: "~/.cursor/skills",
|
|
98
|
+
projectPath: ".cursor/skills",
|
|
99
|
+
detectPath: "~/.cursor",
|
|
100
|
+
universal: false,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: "windsurf",
|
|
104
|
+
name: "Windsurf",
|
|
105
|
+
shortName: "Windsurf",
|
|
106
|
+
globalPath: "~/.codeium/windsurf/skills",
|
|
107
|
+
projectPath: ".windsurf/skills",
|
|
108
|
+
detectPath: "~/.codeium/windsurf",
|
|
109
|
+
universal: false,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "goose",
|
|
113
|
+
name: "Goose",
|
|
114
|
+
shortName: "Goose",
|
|
115
|
+
globalPath: "~/.config/goose/skills",
|
|
116
|
+
projectPath: ".goose/skills",
|
|
117
|
+
detectPath: "~/.config/goose",
|
|
118
|
+
universal: false,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "kilo",
|
|
122
|
+
name: "Kilo Code",
|
|
123
|
+
shortName: "Kilo",
|
|
124
|
+
globalPath: "~/.kilocode/skills",
|
|
125
|
+
projectPath: ".kilocode/skills",
|
|
126
|
+
detectPath: "~/.kilocode",
|
|
127
|
+
universal: false,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "roo",
|
|
131
|
+
name: "Roo Code",
|
|
132
|
+
shortName: "Roo",
|
|
133
|
+
globalPath: "~/.roo/skills",
|
|
134
|
+
projectPath: ".roo/skills",
|
|
135
|
+
detectPath: "~/.roo",
|
|
136
|
+
universal: false,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "antigravity",
|
|
140
|
+
name: "Antigravity",
|
|
141
|
+
shortName: "Antigrav",
|
|
142
|
+
globalPath: "~/.gemini/antigravity/skills",
|
|
143
|
+
projectPath: ".agent/skills",
|
|
144
|
+
detectPath: "~/.gemini/antigravity",
|
|
145
|
+
universal: false,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "droid",
|
|
149
|
+
name: "Droid",
|
|
150
|
+
shortName: "Droid",
|
|
151
|
+
globalPath: "~/.factory/skills",
|
|
152
|
+
projectPath: ".factory/skills",
|
|
153
|
+
detectPath: "~/.factory",
|
|
154
|
+
universal: false,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: "pi",
|
|
158
|
+
name: "pi",
|
|
159
|
+
shortName: "pi",
|
|
160
|
+
globalPath: "~/.pi/agent/skills",
|
|
161
|
+
projectPath: ".pi/skills",
|
|
162
|
+
detectPath: "~/.pi/agent",
|
|
163
|
+
universal: false,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: "openclaw",
|
|
167
|
+
name: "OpenClaw",
|
|
168
|
+
shortName: "OpenClaw",
|
|
169
|
+
globalPath: "~/.openclaw/skills",
|
|
170
|
+
projectPath: "skills",
|
|
171
|
+
detectPath: "~/.openclaw",
|
|
172
|
+
universal: false,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "augment",
|
|
176
|
+
name: "Augment",
|
|
177
|
+
shortName: "Augment",
|
|
178
|
+
globalPath: "~/.augment/skills",
|
|
179
|
+
projectPath: ".augment/skills",
|
|
180
|
+
detectPath: "~/.augment",
|
|
181
|
+
universal: false,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "cline",
|
|
185
|
+
name: "Cline",
|
|
186
|
+
shortName: "Cline",
|
|
187
|
+
globalPath: "~/.cline/skills",
|
|
188
|
+
projectPath: ".cline/skills",
|
|
189
|
+
detectPath: "~/.cline",
|
|
190
|
+
universal: false,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: "codebuddy",
|
|
194
|
+
name: "CodeBuddy",
|
|
195
|
+
shortName: "CodeBuddy",
|
|
196
|
+
globalPath: "~/.codebuddy/skills",
|
|
197
|
+
projectPath: ".codebuddy/skills",
|
|
198
|
+
detectPaths: [".codebuddy", "~/.codebuddy"],
|
|
199
|
+
universal: false,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: "commandcode",
|
|
203
|
+
name: "Command Code",
|
|
204
|
+
shortName: "CmdCode",
|
|
205
|
+
globalPath: "~/.commandcode/skills",
|
|
206
|
+
projectPath: ".commandcode/skills",
|
|
207
|
+
detectPath: "~/.commandcode",
|
|
208
|
+
universal: false,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: "continue",
|
|
212
|
+
name: "Continue",
|
|
213
|
+
shortName: "Continue",
|
|
214
|
+
globalPath: "~/.continue/skills",
|
|
215
|
+
projectPath: ".continue/skills",
|
|
216
|
+
detectPaths: [".continue", "~/.continue"],
|
|
217
|
+
universal: false,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "cortex",
|
|
221
|
+
name: "Cortex Code",
|
|
222
|
+
shortName: "Cortex",
|
|
223
|
+
globalPath: "~/.snowflake/cortex/skills",
|
|
224
|
+
projectPath: ".cortex/skills",
|
|
225
|
+
detectPath: "~/.snowflake/cortex",
|
|
226
|
+
universal: false,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: "crush",
|
|
230
|
+
name: "Crush",
|
|
231
|
+
shortName: "Crush",
|
|
232
|
+
globalPath: "~/.config/crush/skills",
|
|
233
|
+
projectPath: ".crush/skills",
|
|
234
|
+
detectPath: "~/.config/crush",
|
|
235
|
+
universal: false,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: "junie",
|
|
239
|
+
name: "Junie",
|
|
240
|
+
shortName: "Junie",
|
|
241
|
+
globalPath: "~/.junie/skills",
|
|
242
|
+
projectPath: ".junie/skills",
|
|
243
|
+
detectPath: "~/.junie",
|
|
244
|
+
universal: false,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: "iflow",
|
|
248
|
+
name: "iFlow CLI",
|
|
249
|
+
shortName: "iFlow",
|
|
250
|
+
globalPath: "~/.iflow/skills",
|
|
251
|
+
projectPath: ".iflow/skills",
|
|
252
|
+
detectPath: "~/.iflow",
|
|
253
|
+
universal: false,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: "kiro",
|
|
257
|
+
name: "Kiro CLI",
|
|
258
|
+
shortName: "Kiro",
|
|
259
|
+
globalPath: "~/.kiro/skills",
|
|
260
|
+
projectPath: ".kiro/skills",
|
|
261
|
+
detectPath: "~/.kiro",
|
|
262
|
+
universal: false,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "kode",
|
|
266
|
+
name: "Kode",
|
|
267
|
+
shortName: "Kode",
|
|
268
|
+
globalPath: "~/.kode/skills",
|
|
269
|
+
projectPath: ".kode/skills",
|
|
270
|
+
detectPath: "~/.kode",
|
|
271
|
+
universal: false,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: "mcpjam",
|
|
275
|
+
name: "MCPJam",
|
|
276
|
+
shortName: "MCPJam",
|
|
277
|
+
globalPath: "~/.mcpjam/skills",
|
|
278
|
+
projectPath: ".mcpjam/skills",
|
|
279
|
+
detectPath: "~/.mcpjam",
|
|
280
|
+
universal: false,
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "mistralvibe",
|
|
284
|
+
name: "Mistral Vibe",
|
|
285
|
+
shortName: "Vibe",
|
|
286
|
+
globalPath: "~/.vibe/skills",
|
|
287
|
+
projectPath: ".vibe/skills",
|
|
288
|
+
detectPath: "~/.vibe",
|
|
289
|
+
universal: false,
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: "mux",
|
|
293
|
+
name: "Mux",
|
|
294
|
+
shortName: "Mux",
|
|
295
|
+
globalPath: "~/.mux/skills",
|
|
296
|
+
projectPath: ".mux/skills",
|
|
297
|
+
detectPath: "~/.mux",
|
|
298
|
+
universal: false,
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: "openhands",
|
|
302
|
+
name: "OpenHands",
|
|
303
|
+
shortName: "OpenHands",
|
|
304
|
+
globalPath: "~/.openhands/skills",
|
|
305
|
+
projectPath: ".openhands/skills",
|
|
306
|
+
detectPath: "~/.openhands",
|
|
307
|
+
universal: false,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "qoder",
|
|
311
|
+
name: "Qoder",
|
|
312
|
+
shortName: "Qoder",
|
|
313
|
+
globalPath: "~/.qoder/skills",
|
|
314
|
+
projectPath: ".qoder/skills",
|
|
315
|
+
detectPath: "~/.qoder",
|
|
316
|
+
universal: false,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: "qwen",
|
|
320
|
+
name: "Qwen Code",
|
|
321
|
+
shortName: "Qwen",
|
|
322
|
+
globalPath: "~/.qwen/skills",
|
|
323
|
+
projectPath: ".qwen/skills",
|
|
324
|
+
detectPath: "~/.qwen",
|
|
325
|
+
universal: false,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: "trae",
|
|
329
|
+
name: "Trae",
|
|
330
|
+
shortName: "Trae",
|
|
331
|
+
globalPath: "~/.trae/skills",
|
|
332
|
+
projectPath: ".trae/skills",
|
|
333
|
+
detectPath: "~/.trae",
|
|
334
|
+
universal: false,
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: "traecn",
|
|
338
|
+
name: "Trae CN",
|
|
339
|
+
shortName: "TraeCN",
|
|
340
|
+
globalPath: "~/.trae-cn/skills",
|
|
341
|
+
projectPath: ".trae/skills",
|
|
342
|
+
detectPath: "~/.trae-cn",
|
|
343
|
+
universal: false,
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: "zencoder",
|
|
347
|
+
name: "Zencoder",
|
|
348
|
+
shortName: "Zencoder",
|
|
349
|
+
globalPath: "~/.zencoder/skills",
|
|
350
|
+
projectPath: ".zencoder/skills",
|
|
351
|
+
detectPath: "~/.zencoder",
|
|
352
|
+
universal: false,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: "neovate",
|
|
356
|
+
name: "Neovate",
|
|
357
|
+
shortName: "Neovate",
|
|
358
|
+
globalPath: "~/.neovate/skills",
|
|
359
|
+
projectPath: ".neovate/skills",
|
|
360
|
+
detectPath: "~/.neovate",
|
|
361
|
+
universal: false,
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
id: "pochi",
|
|
365
|
+
name: "Pochi",
|
|
366
|
+
shortName: "Pochi",
|
|
367
|
+
globalPath: "~/.pochi/skills",
|
|
368
|
+
projectPath: ".pochi/skills",
|
|
369
|
+
detectPath: "~/.pochi",
|
|
370
|
+
universal: false,
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
id: "adal",
|
|
374
|
+
name: "AdaL",
|
|
375
|
+
shortName: "AdaL",
|
|
376
|
+
globalPath: "~/.adal/skills",
|
|
377
|
+
projectPath: ".adal/skills",
|
|
378
|
+
detectPath: "~/.adal",
|
|
379
|
+
universal: false,
|
|
380
|
+
},
|
|
23
381
|
];
|
|
24
382
|
|
|
25
383
|
const DEFAULT_AGENTS: Record<string, Agent> = Object.fromEntries(
|
|
26
|
-
AGENT_DEFINITIONS.map((
|
|
27
|
-
id,
|
|
28
|
-
{
|
|
384
|
+
AGENT_DEFINITIONS.map((definition) => [
|
|
385
|
+
definition.id,
|
|
386
|
+
{
|
|
387
|
+
id: definition.id,
|
|
388
|
+
name: definition.name,
|
|
389
|
+
shortName: definition.shortName,
|
|
390
|
+
globalPath: definition.globalPath,
|
|
391
|
+
projectPath: definition.projectPath,
|
|
392
|
+
detectPath: definition.detectPath,
|
|
393
|
+
detectPaths: definition.detectPaths,
|
|
394
|
+
universal: definition.universal,
|
|
395
|
+
detected: false,
|
|
396
|
+
},
|
|
29
397
|
]),
|
|
30
398
|
);
|
|
31
399
|
|
|
400
|
+
// Map old agent IDs to new ones for config migration
|
|
401
|
+
const AGENT_RENAMES: Record<string, string> = {
|
|
402
|
+
clawdbot: "openclaw",
|
|
403
|
+
};
|
|
404
|
+
|
|
32
405
|
function createDefaultConfig(): Config {
|
|
33
406
|
return {
|
|
34
407
|
agents: { ...DEFAULT_AGENTS },
|
|
@@ -69,17 +442,34 @@ export class ConfigStore {
|
|
|
69
442
|
private mergeWithDefaults(parsed: Partial<Config>): Config {
|
|
70
443
|
const defaults = createDefaultConfig();
|
|
71
444
|
|
|
72
|
-
// Merge agents and
|
|
445
|
+
// Merge agents, handling renames and ensuring shortName/universal exist
|
|
73
446
|
const mergedAgents = { ...defaults.agents };
|
|
74
447
|
for (const [id, agent] of Object.entries(parsed.agents ?? {})) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
448
|
+
const newId = AGENT_RENAMES[id] ?? id;
|
|
449
|
+
|
|
450
|
+
// Skip old ID if it was renamed and the new ID already has parsed data
|
|
451
|
+
if (AGENT_RENAMES[id] && parsed.agents?.[newId]) continue;
|
|
452
|
+
|
|
453
|
+
const isRenamed = id !== newId;
|
|
454
|
+
const def = defaults.agents[newId];
|
|
455
|
+
mergedAgents[newId] = {
|
|
456
|
+
...def,
|
|
457
|
+
...(isRenamed ? { detected: agent.detected } : agent),
|
|
458
|
+
id: newId,
|
|
459
|
+
// Defaults always win for display name and universal flag
|
|
460
|
+
name: def?.name ?? agent.name ?? newId,
|
|
461
|
+
shortName: def?.shortName ?? agent.shortName ?? agent.name?.split(" ")[0] ?? newId,
|
|
462
|
+
detectPath: def?.detectPath ?? agent.detectPath,
|
|
463
|
+
detectPaths: def?.detectPaths ?? agent.detectPaths,
|
|
464
|
+
universal: def?.universal ?? false,
|
|
80
465
|
};
|
|
81
466
|
}
|
|
82
467
|
|
|
468
|
+
// Remove stale renamed keys
|
|
469
|
+
for (const oldId of Object.keys(AGENT_RENAMES)) {
|
|
470
|
+
delete mergedAgents[oldId];
|
|
471
|
+
}
|
|
472
|
+
|
|
83
473
|
return {
|
|
84
474
|
agents: mergedAgents,
|
|
85
475
|
sync: { ...defaults.sync, ...parsed.sync },
|
|
@@ -2,17 +2,58 @@ import { readFile, writeFile, mkdir } from "node:fs/promises"
|
|
|
2
2
|
import { dirname } from "node:path"
|
|
3
3
|
import type { Registry } from "./types"
|
|
4
4
|
|
|
5
|
+
const ASSIGNMENT_RENAMES: Record<string, string> = {
|
|
6
|
+
clawdbot: "openclaw",
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
function createEmptyRegistry(): Registry {
|
|
6
10
|
return { version: 1, skills: {} }
|
|
7
11
|
}
|
|
8
12
|
|
|
13
|
+
function migrateAssignmentAgentIds(registry: Registry): Registry {
|
|
14
|
+
let changed = false
|
|
15
|
+
const migratedSkills: Registry["skills"] = {}
|
|
16
|
+
|
|
17
|
+
for (const [skillName, skill] of Object.entries(registry.skills)) {
|
|
18
|
+
const hasOpenClaw = Object.prototype.hasOwnProperty.call(skill.assignments, "openclaw")
|
|
19
|
+
let skillChanged = false
|
|
20
|
+
const migratedAssignments: typeof skill.assignments = {}
|
|
21
|
+
|
|
22
|
+
for (const [agentId, assignment] of Object.entries(skill.assignments)) {
|
|
23
|
+
const newAgentId = ASSIGNMENT_RENAMES[agentId] ?? agentId
|
|
24
|
+
|
|
25
|
+
if (newAgentId !== agentId) {
|
|
26
|
+
skillChanged = true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// If both old and new keys exist, keep explicit new key.
|
|
30
|
+
if (newAgentId === "openclaw" && hasOpenClaw && agentId !== "openclaw") {
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
migratedAssignments[newAgentId] = assignment
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (skillChanged) {
|
|
38
|
+
changed = true
|
|
39
|
+
migratedSkills[skillName] = { ...skill, assignments: migratedAssignments }
|
|
40
|
+
} else {
|
|
41
|
+
migratedSkills[skillName] = skill
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!changed) return registry
|
|
46
|
+
return { ...registry, skills: migratedSkills }
|
|
47
|
+
}
|
|
48
|
+
|
|
9
49
|
export class RegistryStore {
|
|
10
50
|
constructor(private registryPath: string) {}
|
|
11
51
|
|
|
12
52
|
async load(): Promise<Registry> {
|
|
13
53
|
try {
|
|
14
54
|
const content = await readFile(this.registryPath, "utf-8")
|
|
15
|
-
|
|
55
|
+
const parsed = JSON.parse(content) as Registry
|
|
56
|
+
return migrateAssignmentAgentIds(parsed)
|
|
16
57
|
} catch (err) {
|
|
17
58
|
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
18
59
|
return createEmptyRegistry()
|
package/src/core/types.ts
CHANGED
|
@@ -6,6 +6,9 @@ export interface Agent {
|
|
|
6
6
|
shortName: string // Short display name for matrix views (max 8 chars)
|
|
7
7
|
globalPath: string
|
|
8
8
|
projectPath: string
|
|
9
|
+
detectPath?: string // CLI-owned config path used for installation detection
|
|
10
|
+
detectPaths?: string[] // Multiple detection markers; detected when any path exists
|
|
11
|
+
universal: boolean // Uses .agents/skills as project path
|
|
9
12
|
detected: boolean
|
|
10
13
|
}
|
|
11
14
|
|