agentskillsdk 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/add.js +73 -8
- package/src/lib/detect-agent.js +18 -5
- package/src/lib/ui.js +78 -13
package/package.json
CHANGED
package/src/commands/add.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
+
import { symlinkSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { join, relative, dirname } from 'node:path';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
3
6
|
import { fetchSkill, fetchSkills } from '../lib/api.js';
|
|
4
|
-
import { detectAgents, AGENTS } from '../lib/detect-agent.js';
|
|
7
|
+
import { detectAgents, AGENTS, getUniversalAgents, getNonUniversalAgents } from '../lib/detect-agent.js';
|
|
5
8
|
import { downloadSkill } from '../lib/download.js';
|
|
6
9
|
import { parseGithubSource } from '../lib/parse-source.js';
|
|
7
10
|
import { selectPrompt, checkboxPrompt } from '../lib/prompt.js';
|
|
8
|
-
import { printBanner, printCompletionSummary, step, error } from '../lib/ui.js';
|
|
11
|
+
import { printBanner, printInstallPlan, printCompletionSummary, step, error } from '../lib/ui.js';
|
|
9
12
|
|
|
10
13
|
export async function addCommand(skillName, options) {
|
|
11
14
|
printBanner();
|
|
@@ -71,7 +74,7 @@ export async function addCommand(skillName, options) {
|
|
|
71
74
|
spinner.succeed(`fundet skill: ${chalk.bold(skill.name)}`);
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
// --- agent selection + scope
|
|
77
|
+
// --- agent selection + scope + confirmation (with back navigation) ---
|
|
75
78
|
const detected = detectAgents(cwd);
|
|
76
79
|
let agents;
|
|
77
80
|
let scope;
|
|
@@ -135,7 +138,34 @@ export async function addCommand(skillName, options) {
|
|
|
135
138
|
scope = result;
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
|
|
141
|
+
// --- confirmation screen ---
|
|
142
|
+
const universalAgents = getUniversalAgents(agents);
|
|
143
|
+
const nonUniversalAgents = getNonUniversalAgents(agents);
|
|
144
|
+
|
|
145
|
+
const source = isGithub
|
|
146
|
+
? `${skill.githubOwner}/${skill.githubRepo}`
|
|
147
|
+
: (skill.namespace || skill.name);
|
|
148
|
+
|
|
149
|
+
printInstallPlan({
|
|
150
|
+
skillName: installName,
|
|
151
|
+
source,
|
|
152
|
+
scope,
|
|
153
|
+
universalAgents,
|
|
154
|
+
symlinkAgents: nonUniversalAgents,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const confirm = await selectPrompt(chalk.bold('fortsæt med installation?'), [
|
|
158
|
+
{ label: 'ja, installer', value: 'yes' },
|
|
159
|
+
{ label: 'nej, annullér', value: 'no' },
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
if (confirm === null || confirm === 'no') {
|
|
163
|
+
// Esc or "nej" → back to scope selection (or exit if flags were set)
|
|
164
|
+
if (options.global || options.project) process.exit(0);
|
|
165
|
+
continue agentSelection;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
break; // selection + confirmation complete
|
|
139
169
|
}
|
|
140
170
|
|
|
141
171
|
// --- step message ---
|
|
@@ -145,12 +175,46 @@ export async function addCommand(skillName, options) {
|
|
|
145
175
|
step(`Agents: ${chalk.bold(agents.map(a => a.name).join(', '))}`);
|
|
146
176
|
}
|
|
147
177
|
|
|
148
|
-
// --- download to
|
|
178
|
+
// --- download to canonical .agents/skills/ then symlink ---
|
|
149
179
|
const spinner = ora({ text: 'downloader skill-filer...', indent: 2 }).start();
|
|
180
|
+
|
|
181
|
+
const symlinkResults = { canonical: [], symlinked: [], copied: [] };
|
|
182
|
+
|
|
150
183
|
try {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
184
|
+
// Canonical location: always .agents/skills/<name>/
|
|
185
|
+
const base = scope === 'global' ? homedir() : cwd;
|
|
186
|
+
const canonicalDir = join(base, '.agents', 'skills', installName);
|
|
187
|
+
|
|
188
|
+
// Download once to canonical
|
|
189
|
+
await downloadSkill(skill, canonicalDir);
|
|
190
|
+
|
|
191
|
+
// Track which agents got the canonical copy (universal agents using .agents/)
|
|
192
|
+
const universalAgents = getUniversalAgents(agents);
|
|
193
|
+
const nonUniversalAgents = getNonUniversalAgents(agents);
|
|
194
|
+
|
|
195
|
+
for (const a of universalAgents) {
|
|
196
|
+
symlinkResults.canonical.push(a);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// For each non-universal agent, symlink from their path → canonical
|
|
200
|
+
for (const agent of nonUniversalAgents) {
|
|
201
|
+
const agentDir = agent.path(installName, { cwd, scope });
|
|
202
|
+
|
|
203
|
+
if (agentDir === canonicalDir) {
|
|
204
|
+
symlinkResults.canonical.push(agent);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
mkdirSync(dirname(agentDir), { recursive: true });
|
|
209
|
+
try {
|
|
210
|
+
const rel = relative(dirname(agentDir), canonicalDir);
|
|
211
|
+
symlinkSync(rel, agentDir);
|
|
212
|
+
symlinkResults.symlinked.push(agent);
|
|
213
|
+
} catch {
|
|
214
|
+
// Fallback: download again
|
|
215
|
+
await downloadSkill(skill, agentDir);
|
|
216
|
+
symlinkResults.copied.push(agent);
|
|
217
|
+
}
|
|
154
218
|
}
|
|
155
219
|
} catch (err) {
|
|
156
220
|
spinner.fail('download fejlede');
|
|
@@ -166,5 +230,6 @@ export async function addCommand(skillName, options) {
|
|
|
166
230
|
agents,
|
|
167
231
|
isGithub,
|
|
168
232
|
namespace: skill.namespace,
|
|
233
|
+
symlinkResults,
|
|
169
234
|
});
|
|
170
235
|
}
|
package/src/lib/detect-agent.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
|
|
5
|
-
function makeAgent(name, folder, { globalFolder, detectFolder } = {}) {
|
|
5
|
+
function makeAgent(name, folder, { globalFolder, detectFolder, universal } = {}) {
|
|
6
6
|
const gFolder = globalFolder || folder;
|
|
7
7
|
const dFolder = detectFolder || folder;
|
|
8
8
|
return {
|
|
@@ -10,6 +10,7 @@ function makeAgent(name, folder, { globalFolder, detectFolder } = {}) {
|
|
|
10
10
|
folder,
|
|
11
11
|
globalFolder: gFolder,
|
|
12
12
|
detectFolder: dFolder,
|
|
13
|
+
universal: !!universal,
|
|
13
14
|
path: (skill, { cwd, scope }) => {
|
|
14
15
|
if (scope === 'global') return join(homedir(), gFolder, 'skills', skill);
|
|
15
16
|
return join(cwd, folder, 'skills', skill);
|
|
@@ -22,28 +23,32 @@ function makeAgent(name, folder, { globalFolder, detectFolder } = {}) {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export const AGENTS = [
|
|
26
|
+
makeAgent('Amp', '.agents', { detectFolder: '.config/amp', universal: true }),
|
|
25
27
|
makeAgent('Cline', '.cline'),
|
|
26
28
|
makeAgent('Claude Code', '.claude'),
|
|
27
29
|
makeAgent('CodeBuddy', '.codebuddy'),
|
|
28
|
-
makeAgent('Codex CLI', '.agents', { globalFolder: '.codex' }),
|
|
30
|
+
makeAgent('Codex CLI', '.agents', { globalFolder: '.codex', universal: true }),
|
|
29
31
|
makeAgent('Command Code', '.commandcode'),
|
|
30
32
|
makeAgent('Continue', '.continue'),
|
|
31
33
|
makeAgent('Crush', '.crush'),
|
|
32
|
-
makeAgent('Cursor', '.cursor'),
|
|
34
|
+
makeAgent('Cursor', '.agents', { globalFolder: '.cursor', universal: true }),
|
|
33
35
|
makeAgent('Droid', '.factory'),
|
|
34
|
-
makeAgent('
|
|
36
|
+
makeAgent('Gemini CLI', '.agents', { universal: true }),
|
|
37
|
+
makeAgent('GitHub Copilot', '.agents', { detectFolder: '.github/skills', globalFolder: '.github', universal: true }),
|
|
35
38
|
makeAgent('Goose', '.goose'),
|
|
36
39
|
makeAgent('Kilo Code', '.kilocode'),
|
|
40
|
+
makeAgent('Kimi CLI', '.agents', { universal: true }),
|
|
37
41
|
makeAgent('Kiro CLI', '.kiro'),
|
|
38
42
|
makeAgent('MCPJam', '.mcpjam'),
|
|
39
43
|
makeAgent('Mux', '.mux'),
|
|
40
44
|
makeAgent('Neovate', '.neovate'),
|
|
41
45
|
makeAgent('OpenClaw', '.openclaw'),
|
|
42
|
-
makeAgent('OpenCode', '.opencode'),
|
|
46
|
+
makeAgent('OpenCode', '.agents', { globalFolder: '.opencode', universal: true }),
|
|
43
47
|
makeAgent('OpenHands', '.openhands'),
|
|
44
48
|
makeAgent('Pi', '.pi'),
|
|
45
49
|
makeAgent('Qoder', '.qoder'),
|
|
46
50
|
makeAgent('Qwen Code', '.qwen'),
|
|
51
|
+
makeAgent('Replit', '.agents', { universal: true }),
|
|
47
52
|
makeAgent('Roo Code', '.roo'),
|
|
48
53
|
makeAgent('Trae', '.trae'),
|
|
49
54
|
makeAgent('Windsurf', '.windsurf'),
|
|
@@ -53,3 +58,11 @@ export const AGENTS = [
|
|
|
53
58
|
export function detectAgents(cwd) {
|
|
54
59
|
return AGENTS.filter(agent => existsSync(join(cwd, agent.detectFolder)));
|
|
55
60
|
}
|
|
61
|
+
|
|
62
|
+
export function getUniversalAgents(agentList) {
|
|
63
|
+
return agentList.filter(a => a.universal);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getNonUniversalAgents(agentList) {
|
|
67
|
+
return agentList.filter(a => !a.universal);
|
|
68
|
+
}
|
package/src/lib/ui.js
CHANGED
|
@@ -43,16 +43,48 @@ export function printBanner() {
|
|
|
43
43
|
console.log('');
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// ---
|
|
46
|
+
// --- install plan ---
|
|
47
47
|
|
|
48
|
-
export function
|
|
48
|
+
export function printInstallPlan({ skillName, source, scope, universalAgents, symlinkAgents }) {
|
|
49
49
|
const scopeLabel = scope === 'global'
|
|
50
50
|
? 'globalt (alle projekter)'
|
|
51
51
|
: 'projekt (lokalt)';
|
|
52
52
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const lines = [
|
|
54
|
+
chalk.bold('installationsplan:'),
|
|
55
|
+
'',
|
|
56
|
+
` ${chalk.dim('skill:')} ${skillName}`,
|
|
57
|
+
` ${chalk.dim('kilde:')} ${source}`,
|
|
58
|
+
` ${chalk.dim('omfang:')} ${scopeLabel}`,
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
if (universalAgents.length > 0) {
|
|
62
|
+
lines.push('');
|
|
63
|
+
lines.push(chalk.dim(' ── Universal (.agents/skills/) ──'));
|
|
64
|
+
for (const a of universalAgents) {
|
|
65
|
+
lines.push(` ${chalk.green('\u2713')} ${a.name}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (symlinkAgents.length > 0) {
|
|
70
|
+
lines.push('');
|
|
71
|
+
lines.push(chalk.dim(' ── Symlink \u2192 .agents/skills/ ──'));
|
|
72
|
+
for (const a of symlinkAgents) {
|
|
73
|
+
lines.push(` ${chalk.green('\u2713')} ${a.name} ${chalk.dim(a.displayPath(skillName, scope))}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(box(lines));
|
|
79
|
+
console.log('');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --- completion summary ---
|
|
83
|
+
|
|
84
|
+
export function printCompletionSummary({ skillName, scope, agents, isGithub, namespace, symlinkResults }) {
|
|
85
|
+
const scopeLabel = scope === 'global'
|
|
86
|
+
? 'globalt (alle projekter)'
|
|
87
|
+
: 'projekt (lokalt)';
|
|
56
88
|
|
|
57
89
|
const lines = [
|
|
58
90
|
chalk.bold.green('skill installeret!'),
|
|
@@ -61,19 +93,52 @@ export function printCompletionSummary({ skillName, scope, agents, isGithub, nam
|
|
|
61
93
|
` ${chalk.dim('omfang:')} ${scopeLabel}`,
|
|
62
94
|
];
|
|
63
95
|
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
96
|
+
if (symlinkResults) {
|
|
97
|
+
// Group by installation method
|
|
98
|
+
const { canonical, symlinked, copied } = symlinkResults;
|
|
99
|
+
|
|
100
|
+
if (canonical.length > 0) {
|
|
101
|
+
lines.push('');
|
|
102
|
+
lines.push(chalk.dim(' canonical (.agents/skills/):'));
|
|
103
|
+
for (const a of canonical) {
|
|
104
|
+
lines.push(` ${chalk.green('\u2713')} ${a.name}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (symlinked.length > 0) {
|
|
109
|
+
lines.push('');
|
|
110
|
+
lines.push(chalk.dim(' symlink:'));
|
|
111
|
+
for (const a of symlinked) {
|
|
112
|
+
lines.push(` ${chalk.green('\u2713')} ${a.name} ${chalk.dim(a.displayPath(skillName, scope) + ' \u2192 .agents/skills/')}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (copied.length > 0) {
|
|
117
|
+
lines.push('');
|
|
118
|
+
lines.push(chalk.dim(' kopi (symlink fejlede):'));
|
|
119
|
+
for (const a of copied) {
|
|
120
|
+
lines.push(` ${chalk.green('\u2713')} ${a.name} ${chalk.dim(a.displayPath(skillName, scope))}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
67
123
|
} else {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
124
|
+
// Fallback: old-style summary
|
|
125
|
+
const agentNames = agents.map(a => a.name).join(', ');
|
|
126
|
+
const paths = agents.map(a => a.displayPath(skillName, scope));
|
|
127
|
+
|
|
128
|
+
if (agents.length === 1) {
|
|
129
|
+
lines.push(` ${chalk.dim('agent:')} ${agentNames}`);
|
|
130
|
+
lines.push(` ${chalk.dim('sti:')} ${paths[0]}`);
|
|
131
|
+
} else {
|
|
132
|
+
lines.push(` ${chalk.dim('agenter:')} ${agentNames}`);
|
|
133
|
+
lines.push(` ${chalk.dim('stier:')} ${paths[0]}`);
|
|
134
|
+
for (let i = 1; i < paths.length; i++) {
|
|
135
|
+
lines.push(` ${paths[i]}`);
|
|
136
|
+
}
|
|
72
137
|
}
|
|
73
138
|
}
|
|
74
139
|
|
|
75
140
|
lines.push('');
|
|
76
|
-
if (
|
|
141
|
+
if (agents.length === 1) {
|
|
77
142
|
lines.push(`start en ny ${agents[0].name}-session for at bruge den.`);
|
|
78
143
|
} else {
|
|
79
144
|
lines.push('start en ny session i en af disse agenter for at bruge den.');
|