agentskillsdk 0.4.5 → 0.5.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/package.json +1 -1
- package/src/commands/add.js +73 -8
- package/src/lib/detect-agent.js +18 -5
- package/src/lib/prompt.js +18 -2
- 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', detectFolder: '.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', detectFolder: '.cursor', universal: true }),
|
|
33
35
|
makeAgent('Droid', '.factory'),
|
|
34
|
-
makeAgent('
|
|
36
|
+
makeAgent('Gemini CLI', '.agents', { detectFolder: '.gemini', 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', { detectFolder: '.kimi', 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', detectFolder: '.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', { detectFolder: '.replit', 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/prompt.js
CHANGED
|
@@ -134,9 +134,10 @@ export function selectPrompt(question, choices, { defaultIndex = 0 } = {}) {
|
|
|
134
134
|
* @param {{ label: string, value: any }[]} choices
|
|
135
135
|
* @returns {Promise<any[]|null>} array of selected values, or null if Esc
|
|
136
136
|
*/
|
|
137
|
-
export function checkboxPrompt(question, choices) {
|
|
137
|
+
export function checkboxPrompt(question, choices, { pageSize = 10 } = {}) {
|
|
138
138
|
return new Promise((resolve) => {
|
|
139
139
|
let cursor = 0;
|
|
140
|
+
let scrollOffset = 0;
|
|
140
141
|
const checked = new Array(choices.length).fill(false);
|
|
141
142
|
const { stdin, stdout } = process;
|
|
142
143
|
const cols = stdout.columns || 80;
|
|
@@ -151,11 +152,20 @@ export function checkboxPrompt(question, choices) {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
function render() {
|
|
154
|
-
const
|
|
155
|
+
const visible = choices.slice(scrollOffset, scrollOffset + pageSize);
|
|
156
|
+
const lines = visible.map((c, vi) => {
|
|
157
|
+
const i = vi + scrollOffset;
|
|
155
158
|
const box = checked[i] ? orange('◼') : '◻';
|
|
156
159
|
const label = i === cursor ? orange(c.label) : c.label;
|
|
157
160
|
return ` ${box} ${label}`;
|
|
158
161
|
});
|
|
162
|
+
if (scrollOffset > 0) {
|
|
163
|
+
lines.unshift(chalk.dim(` ↑ ${scrollOffset} mere`));
|
|
164
|
+
}
|
|
165
|
+
const remaining = choices.length - (scrollOffset + pageSize);
|
|
166
|
+
if (remaining > 0) {
|
|
167
|
+
lines.push(chalk.dim(` ↓ ${remaining} mere`));
|
|
168
|
+
}
|
|
159
169
|
lines.push('');
|
|
160
170
|
lines.push(chalk.dim(' ↑↓ naviger · space skift · enter bekræft · esc tilbage'));
|
|
161
171
|
return lines;
|
|
@@ -224,14 +234,20 @@ export function checkboxPrompt(question, choices) {
|
|
|
224
234
|
// Up / Left
|
|
225
235
|
else if (key === '\x1b[A' || key === '\x1b[D') {
|
|
226
236
|
cursor = (cursor - 1 + choices.length) % choices.length;
|
|
237
|
+
if (cursor === choices.length - 1) scrollOffset = Math.max(0, choices.length - pageSize);
|
|
227
238
|
}
|
|
228
239
|
// Down / Right
|
|
229
240
|
else if (key === '\x1b[B' || key === '\x1b[C') {
|
|
230
241
|
cursor = (cursor + 1) % choices.length;
|
|
242
|
+
if (cursor === 0) scrollOffset = 0;
|
|
231
243
|
} else {
|
|
232
244
|
return;
|
|
233
245
|
}
|
|
234
246
|
|
|
247
|
+
// Keep cursor within the visible scroll window
|
|
248
|
+
if (cursor < scrollOffset) scrollOffset = cursor;
|
|
249
|
+
if (cursor >= scrollOffset + pageSize) scrollOffset = cursor - pageSize + 1;
|
|
250
|
+
|
|
235
251
|
// Redraw
|
|
236
252
|
const upCount = totalPhysicalLines(prevLines) - 1;
|
|
237
253
|
if (upCount > 0) stdout.write(`\x1b[${upCount}A`);
|
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.');
|