@agentskill.sh/cli 1.0.9 → 2.0.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/dist/agents.d.ts +10 -0
- package/dist/agents.js +347 -0
- package/dist/api.js +1 -1
- package/dist/commands/feedback.js +9 -9
- package/dist/commands/find.d.ts +1 -0
- package/dist/commands/find.js +183 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +82 -0
- package/dist/commands/install.js +118 -81
- package/dist/commands/list.js +105 -50
- package/dist/commands/remove.js +105 -25
- package/dist/commands/search.js +52 -27
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +99 -31
- package/dist/commands/update.js +75 -105
- package/dist/index.js +91 -59
- package/dist/installer.d.ts +33 -0
- package/dist/installer.js +145 -0
- package/dist/skill-lock.d.ts +19 -0
- package/dist/skill-lock.js +63 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +1 -0
- package/dist/ui.d.ts +17 -0
- package/dist/ui.js +84 -0
- package/package.json +5 -1
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AgentConfig, AgentType } from './types.js';
|
|
2
|
+
export declare const agents: Record<AgentType, AgentConfig>;
|
|
3
|
+
/** Detect all agents installed on the machine */
|
|
4
|
+
export declare function detectInstalledAgents(): Promise<AgentType[]>;
|
|
5
|
+
/** Agents that use the universal .agents/skills directory */
|
|
6
|
+
export declare function getUniversalAgents(): AgentType[];
|
|
7
|
+
/** Agents that need their own skill directory */
|
|
8
|
+
export declare function getNonUniversalAgents(): AgentType[];
|
|
9
|
+
export declare function isUniversalAgent(type: AgentType): boolean;
|
|
10
|
+
export declare function getAgentDisplayName(type: AgentType): string;
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
const home = homedir();
|
|
5
|
+
const configHome = process.env.XDG_CONFIG_HOME || join(home, '.config');
|
|
6
|
+
const codexHome = process.env.CODEX_HOME?.trim() || join(home, '.codex');
|
|
7
|
+
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, '.claude');
|
|
8
|
+
export const agents = {
|
|
9
|
+
// Universal agents (use .agents/skills)
|
|
10
|
+
amp: {
|
|
11
|
+
name: 'amp',
|
|
12
|
+
displayName: 'Amp',
|
|
13
|
+
skillsDir: '.agents/skills',
|
|
14
|
+
globalSkillsDir: join(configHome, 'agents/skills'),
|
|
15
|
+
detectInstalled: async () => existsSync(join(configHome, 'amp')),
|
|
16
|
+
},
|
|
17
|
+
antigravity: {
|
|
18
|
+
name: 'antigravity',
|
|
19
|
+
displayName: 'Antigravity',
|
|
20
|
+
skillsDir: '.agents/skills',
|
|
21
|
+
globalSkillsDir: join(home, '.gemini/antigravity/skills'),
|
|
22
|
+
detectInstalled: async () => existsSync(join(home, '.gemini/antigravity')),
|
|
23
|
+
},
|
|
24
|
+
cline: {
|
|
25
|
+
name: 'cline',
|
|
26
|
+
displayName: 'Cline',
|
|
27
|
+
skillsDir: '.agents/skills',
|
|
28
|
+
globalSkillsDir: join(home, '.agents/skills'),
|
|
29
|
+
detectInstalled: async () => existsSync(join(home, '.cline')),
|
|
30
|
+
},
|
|
31
|
+
codex: {
|
|
32
|
+
name: 'codex',
|
|
33
|
+
displayName: 'Codex',
|
|
34
|
+
skillsDir: '.agents/skills',
|
|
35
|
+
globalSkillsDir: join(codexHome, 'skills'),
|
|
36
|
+
detectInstalled: async () => existsSync(codexHome) || existsSync('/etc/codex'),
|
|
37
|
+
},
|
|
38
|
+
cursor: {
|
|
39
|
+
name: 'cursor',
|
|
40
|
+
displayName: 'Cursor',
|
|
41
|
+
skillsDir: '.agents/skills',
|
|
42
|
+
globalSkillsDir: join(home, '.cursor/skills'),
|
|
43
|
+
detectInstalled: async () => existsSync(join(home, '.cursor')),
|
|
44
|
+
},
|
|
45
|
+
deepagents: {
|
|
46
|
+
name: 'deepagents',
|
|
47
|
+
displayName: 'Deep Agents',
|
|
48
|
+
skillsDir: '.agents/skills',
|
|
49
|
+
globalSkillsDir: join(home, '.deepagents/agent/skills'),
|
|
50
|
+
detectInstalled: async () => existsSync(join(home, '.deepagents')),
|
|
51
|
+
},
|
|
52
|
+
firebender: {
|
|
53
|
+
name: 'firebender',
|
|
54
|
+
displayName: 'Firebender',
|
|
55
|
+
skillsDir: '.agents/skills',
|
|
56
|
+
globalSkillsDir: join(home, '.firebender/skills'),
|
|
57
|
+
detectInstalled: async () => existsSync(join(home, '.firebender')),
|
|
58
|
+
},
|
|
59
|
+
'gemini-cli': {
|
|
60
|
+
name: 'gemini-cli',
|
|
61
|
+
displayName: 'Gemini CLI',
|
|
62
|
+
skillsDir: '.agents/skills',
|
|
63
|
+
globalSkillsDir: join(home, '.gemini/skills'),
|
|
64
|
+
detectInstalled: async () => existsSync(join(home, '.gemini')),
|
|
65
|
+
},
|
|
66
|
+
'github-copilot': {
|
|
67
|
+
name: 'github-copilot',
|
|
68
|
+
displayName: 'GitHub Copilot',
|
|
69
|
+
skillsDir: '.agents/skills',
|
|
70
|
+
globalSkillsDir: join(home, '.copilot/skills'),
|
|
71
|
+
detectInstalled: async () => existsSync(join(home, '.copilot')),
|
|
72
|
+
},
|
|
73
|
+
'kimi-cli': {
|
|
74
|
+
name: 'kimi-cli',
|
|
75
|
+
displayName: 'Kimi Code CLI',
|
|
76
|
+
skillsDir: '.agents/skills',
|
|
77
|
+
globalSkillsDir: join(configHome, 'agents/skills'),
|
|
78
|
+
detectInstalled: async () => existsSync(join(home, '.kimi')),
|
|
79
|
+
},
|
|
80
|
+
opencode: {
|
|
81
|
+
name: 'opencode',
|
|
82
|
+
displayName: 'OpenCode',
|
|
83
|
+
skillsDir: '.agents/skills',
|
|
84
|
+
globalSkillsDir: join(configHome, 'opencode/skills'),
|
|
85
|
+
detectInstalled: async () => existsSync(join(configHome, 'opencode')),
|
|
86
|
+
},
|
|
87
|
+
warp: {
|
|
88
|
+
name: 'warp',
|
|
89
|
+
displayName: 'Warp',
|
|
90
|
+
skillsDir: '.agents/skills',
|
|
91
|
+
globalSkillsDir: join(home, '.agents/skills'),
|
|
92
|
+
detectInstalled: async () => existsSync(join(home, '.warp')),
|
|
93
|
+
},
|
|
94
|
+
// Non-universal agents (agent-specific skill dirs)
|
|
95
|
+
augment: {
|
|
96
|
+
name: 'augment',
|
|
97
|
+
displayName: 'Augment',
|
|
98
|
+
skillsDir: '.augment/skills',
|
|
99
|
+
globalSkillsDir: join(home, '.augment/skills'),
|
|
100
|
+
detectInstalled: async () => existsSync(join(home, '.augment')),
|
|
101
|
+
},
|
|
102
|
+
bob: {
|
|
103
|
+
name: 'bob',
|
|
104
|
+
displayName: 'IBM Bob',
|
|
105
|
+
skillsDir: '.bob/skills',
|
|
106
|
+
globalSkillsDir: join(home, '.bob/skills'),
|
|
107
|
+
detectInstalled: async () => existsSync(join(home, '.bob')),
|
|
108
|
+
},
|
|
109
|
+
'claude-code': {
|
|
110
|
+
name: 'claude-code',
|
|
111
|
+
displayName: 'Claude Code',
|
|
112
|
+
skillsDir: '.claude/skills',
|
|
113
|
+
globalSkillsDir: join(claudeHome, 'skills'),
|
|
114
|
+
detectInstalled: async () => existsSync(claudeHome),
|
|
115
|
+
},
|
|
116
|
+
openclaw: {
|
|
117
|
+
name: 'openclaw',
|
|
118
|
+
displayName: 'OpenClaw',
|
|
119
|
+
skillsDir: 'skills',
|
|
120
|
+
globalSkillsDir: join(home, '.openclaw/skills'),
|
|
121
|
+
detectInstalled: async () => existsSync(join(home, '.openclaw')) ||
|
|
122
|
+
existsSync(join(home, '.clawdbot')) ||
|
|
123
|
+
existsSync(join(home, '.moltbot')),
|
|
124
|
+
},
|
|
125
|
+
codebuddy: {
|
|
126
|
+
name: 'codebuddy',
|
|
127
|
+
displayName: 'CodeBuddy',
|
|
128
|
+
skillsDir: '.codebuddy/skills',
|
|
129
|
+
globalSkillsDir: join(home, '.codebuddy/skills'),
|
|
130
|
+
detectInstalled: async () => existsSync(join(home, '.codebuddy')),
|
|
131
|
+
},
|
|
132
|
+
'command-code': {
|
|
133
|
+
name: 'command-code',
|
|
134
|
+
displayName: 'Command Code',
|
|
135
|
+
skillsDir: '.commandcode/skills',
|
|
136
|
+
globalSkillsDir: join(home, '.commandcode/skills'),
|
|
137
|
+
detectInstalled: async () => existsSync(join(home, '.commandcode')),
|
|
138
|
+
},
|
|
139
|
+
continue: {
|
|
140
|
+
name: 'continue',
|
|
141
|
+
displayName: 'Continue',
|
|
142
|
+
skillsDir: '.continue/skills',
|
|
143
|
+
globalSkillsDir: join(home, '.continue/skills'),
|
|
144
|
+
detectInstalled: async () => existsSync(join(home, '.continue')),
|
|
145
|
+
},
|
|
146
|
+
cortex: {
|
|
147
|
+
name: 'cortex',
|
|
148
|
+
displayName: 'Cortex Code',
|
|
149
|
+
skillsDir: '.cortex/skills',
|
|
150
|
+
globalSkillsDir: join(home, '.snowflake/cortex/skills'),
|
|
151
|
+
detectInstalled: async () => existsSync(join(home, '.snowflake/cortex')),
|
|
152
|
+
},
|
|
153
|
+
crush: {
|
|
154
|
+
name: 'crush',
|
|
155
|
+
displayName: 'Crush',
|
|
156
|
+
skillsDir: '.crush/skills',
|
|
157
|
+
globalSkillsDir: join(configHome, 'crush/skills'),
|
|
158
|
+
detectInstalled: async () => existsSync(join(configHome, 'crush')),
|
|
159
|
+
},
|
|
160
|
+
droid: {
|
|
161
|
+
name: 'droid',
|
|
162
|
+
displayName: 'Droid',
|
|
163
|
+
skillsDir: '.factory/skills',
|
|
164
|
+
globalSkillsDir: join(home, '.factory/skills'),
|
|
165
|
+
detectInstalled: async () => existsSync(join(home, '.factory')),
|
|
166
|
+
},
|
|
167
|
+
goose: {
|
|
168
|
+
name: 'goose',
|
|
169
|
+
displayName: 'Goose',
|
|
170
|
+
skillsDir: '.goose/skills',
|
|
171
|
+
globalSkillsDir: join(configHome, 'goose/skills'),
|
|
172
|
+
detectInstalled: async () => existsSync(join(configHome, 'goose')),
|
|
173
|
+
},
|
|
174
|
+
junie: {
|
|
175
|
+
name: 'junie',
|
|
176
|
+
displayName: 'Junie',
|
|
177
|
+
skillsDir: '.junie/skills',
|
|
178
|
+
globalSkillsDir: join(home, '.junie/skills'),
|
|
179
|
+
detectInstalled: async () => existsSync(join(home, '.junie')),
|
|
180
|
+
},
|
|
181
|
+
'iflow-cli': {
|
|
182
|
+
name: 'iflow-cli',
|
|
183
|
+
displayName: 'iFlow CLI',
|
|
184
|
+
skillsDir: '.iflow/skills',
|
|
185
|
+
globalSkillsDir: join(home, '.iflow/skills'),
|
|
186
|
+
detectInstalled: async () => existsSync(join(home, '.iflow')),
|
|
187
|
+
},
|
|
188
|
+
kilo: {
|
|
189
|
+
name: 'kilo',
|
|
190
|
+
displayName: 'Kilo Code',
|
|
191
|
+
skillsDir: '.kilocode/skills',
|
|
192
|
+
globalSkillsDir: join(home, '.kilocode/skills'),
|
|
193
|
+
detectInstalled: async () => existsSync(join(home, '.kilocode')),
|
|
194
|
+
},
|
|
195
|
+
'kiro-cli': {
|
|
196
|
+
name: 'kiro-cli',
|
|
197
|
+
displayName: 'Kiro CLI',
|
|
198
|
+
skillsDir: '.kiro/skills',
|
|
199
|
+
globalSkillsDir: join(home, '.kiro/skills'),
|
|
200
|
+
detectInstalled: async () => existsSync(join(home, '.kiro')),
|
|
201
|
+
},
|
|
202
|
+
kode: {
|
|
203
|
+
name: 'kode',
|
|
204
|
+
displayName: 'Kode',
|
|
205
|
+
skillsDir: '.kode/skills',
|
|
206
|
+
globalSkillsDir: join(home, '.kode/skills'),
|
|
207
|
+
detectInstalled: async () => existsSync(join(home, '.kode')),
|
|
208
|
+
},
|
|
209
|
+
mcpjam: {
|
|
210
|
+
name: 'mcpjam',
|
|
211
|
+
displayName: 'MCPJam',
|
|
212
|
+
skillsDir: '.mcpjam/skills',
|
|
213
|
+
globalSkillsDir: join(home, '.mcpjam/skills'),
|
|
214
|
+
detectInstalled: async () => existsSync(join(home, '.mcpjam')),
|
|
215
|
+
},
|
|
216
|
+
'mistral-vibe': {
|
|
217
|
+
name: 'mistral-vibe',
|
|
218
|
+
displayName: 'Mistral Vibe',
|
|
219
|
+
skillsDir: '.vibe/skills',
|
|
220
|
+
globalSkillsDir: join(home, '.vibe/skills'),
|
|
221
|
+
detectInstalled: async () => existsSync(join(home, '.vibe')),
|
|
222
|
+
},
|
|
223
|
+
mux: {
|
|
224
|
+
name: 'mux',
|
|
225
|
+
displayName: 'Mux',
|
|
226
|
+
skillsDir: '.mux/skills',
|
|
227
|
+
globalSkillsDir: join(home, '.mux/skills'),
|
|
228
|
+
detectInstalled: async () => existsSync(join(home, '.mux')),
|
|
229
|
+
},
|
|
230
|
+
openhands: {
|
|
231
|
+
name: 'openhands',
|
|
232
|
+
displayName: 'OpenHands',
|
|
233
|
+
skillsDir: '.openhands/skills',
|
|
234
|
+
globalSkillsDir: join(home, '.openhands/skills'),
|
|
235
|
+
detectInstalled: async () => existsSync(join(home, '.openhands')),
|
|
236
|
+
},
|
|
237
|
+
pi: {
|
|
238
|
+
name: 'pi',
|
|
239
|
+
displayName: 'Pi',
|
|
240
|
+
skillsDir: '.pi/skills',
|
|
241
|
+
globalSkillsDir: join(home, '.pi/agent/skills'),
|
|
242
|
+
detectInstalled: async () => existsSync(join(home, '.pi/agent')),
|
|
243
|
+
},
|
|
244
|
+
qoder: {
|
|
245
|
+
name: 'qoder',
|
|
246
|
+
displayName: 'Qoder',
|
|
247
|
+
skillsDir: '.qoder/skills',
|
|
248
|
+
globalSkillsDir: join(home, '.qoder/skills'),
|
|
249
|
+
detectInstalled: async () => existsSync(join(home, '.qoder')),
|
|
250
|
+
},
|
|
251
|
+
'qwen-code': {
|
|
252
|
+
name: 'qwen-code',
|
|
253
|
+
displayName: 'Qwen Code',
|
|
254
|
+
skillsDir: '.qwen/skills',
|
|
255
|
+
globalSkillsDir: join(home, '.qwen/skills'),
|
|
256
|
+
detectInstalled: async () => existsSync(join(home, '.qwen')),
|
|
257
|
+
},
|
|
258
|
+
roo: {
|
|
259
|
+
name: 'roo',
|
|
260
|
+
displayName: 'Roo Code',
|
|
261
|
+
skillsDir: '.roo/skills',
|
|
262
|
+
globalSkillsDir: join(home, '.roo/skills'),
|
|
263
|
+
detectInstalled: async () => existsSync(join(home, '.roo')),
|
|
264
|
+
},
|
|
265
|
+
trae: {
|
|
266
|
+
name: 'trae',
|
|
267
|
+
displayName: 'Trae',
|
|
268
|
+
skillsDir: '.trae/skills',
|
|
269
|
+
globalSkillsDir: join(home, '.trae/skills'),
|
|
270
|
+
detectInstalled: async () => existsSync(join(home, '.trae')),
|
|
271
|
+
},
|
|
272
|
+
'trae-cn': {
|
|
273
|
+
name: 'trae-cn',
|
|
274
|
+
displayName: 'Trae CN',
|
|
275
|
+
skillsDir: '.trae/skills',
|
|
276
|
+
globalSkillsDir: join(home, '.trae-cn/skills'),
|
|
277
|
+
detectInstalled: async () => existsSync(join(home, '.trae-cn')),
|
|
278
|
+
},
|
|
279
|
+
windsurf: {
|
|
280
|
+
name: 'windsurf',
|
|
281
|
+
displayName: 'Windsurf',
|
|
282
|
+
skillsDir: '.windsurf/skills',
|
|
283
|
+
globalSkillsDir: join(home, '.codeium/windsurf/skills'),
|
|
284
|
+
detectInstalled: async () => existsSync(join(home, '.codeium/windsurf')),
|
|
285
|
+
},
|
|
286
|
+
zencoder: {
|
|
287
|
+
name: 'zencoder',
|
|
288
|
+
displayName: 'Zencoder',
|
|
289
|
+
skillsDir: '.zencoder/skills',
|
|
290
|
+
globalSkillsDir: join(home, '.zencoder/skills'),
|
|
291
|
+
detectInstalled: async () => existsSync(join(home, '.zencoder')),
|
|
292
|
+
},
|
|
293
|
+
neovate: {
|
|
294
|
+
name: 'neovate',
|
|
295
|
+
displayName: 'Neovate',
|
|
296
|
+
skillsDir: '.neovate/skills',
|
|
297
|
+
globalSkillsDir: join(home, '.neovate/skills'),
|
|
298
|
+
detectInstalled: async () => existsSync(join(home, '.neovate')),
|
|
299
|
+
},
|
|
300
|
+
pochi: {
|
|
301
|
+
name: 'pochi',
|
|
302
|
+
displayName: 'Pochi',
|
|
303
|
+
skillsDir: '.pochi/skills',
|
|
304
|
+
globalSkillsDir: join(home, '.pochi/skills'),
|
|
305
|
+
detectInstalled: async () => existsSync(join(home, '.pochi')),
|
|
306
|
+
},
|
|
307
|
+
adal: {
|
|
308
|
+
name: 'adal',
|
|
309
|
+
displayName: 'AdaL',
|
|
310
|
+
skillsDir: '.adal/skills',
|
|
311
|
+
globalSkillsDir: join(home, '.adal/skills'),
|
|
312
|
+
detectInstalled: async () => existsSync(join(home, '.adal')),
|
|
313
|
+
},
|
|
314
|
+
hermes: {
|
|
315
|
+
name: 'hermes',
|
|
316
|
+
displayName: 'Hermes',
|
|
317
|
+
skillsDir: '.hermes/skills',
|
|
318
|
+
globalSkillsDir: join(home, '.hermes/skills'),
|
|
319
|
+
detectInstalled: async () => existsSync(join(home, '.hermes')),
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
/** Detect all agents installed on the machine */
|
|
323
|
+
export async function detectInstalledAgents() {
|
|
324
|
+
const results = await Promise.all(Object.entries(agents).map(async ([type, config]) => ({
|
|
325
|
+
type,
|
|
326
|
+
installed: await config.detectInstalled(),
|
|
327
|
+
})));
|
|
328
|
+
return results.filter((r) => r.installed).map((r) => r.type);
|
|
329
|
+
}
|
|
330
|
+
/** Agents that use the universal .agents/skills directory */
|
|
331
|
+
export function getUniversalAgents() {
|
|
332
|
+
return Object.entries(agents)
|
|
333
|
+
.filter(([, config]) => config.skillsDir === '.agents/skills' && config.showInUniversalList !== false)
|
|
334
|
+
.map(([type]) => type);
|
|
335
|
+
}
|
|
336
|
+
/** Agents that need their own skill directory */
|
|
337
|
+
export function getNonUniversalAgents() {
|
|
338
|
+
return Object.entries(agents)
|
|
339
|
+
.filter(([, config]) => config.skillsDir !== '.agents/skills')
|
|
340
|
+
.map(([type]) => type);
|
|
341
|
+
}
|
|
342
|
+
export function isUniversalAgent(type) {
|
|
343
|
+
return agents[type]?.skillsDir === '.agents/skills';
|
|
344
|
+
}
|
|
345
|
+
export function getAgentDisplayName(type) {
|
|
346
|
+
return agents[type]?.displayName || type;
|
|
347
|
+
}
|
package/dist/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
1
2
|
import { apiFetch } from '../api.js';
|
|
2
|
-
import {
|
|
3
|
+
import { ORANGE } from '../ui.js';
|
|
3
4
|
export async function feedbackCommand(args) {
|
|
4
5
|
const jsonFlag = args.includes('--json');
|
|
5
6
|
const filteredArgs = args.filter((a) => !a.startsWith('--'));
|
|
@@ -19,19 +20,16 @@ export async function feedbackCommand(args) {
|
|
|
19
20
|
console.error('Score must be an integer between 1 and 5.');
|
|
20
21
|
process.exit(1);
|
|
21
22
|
}
|
|
22
|
-
// Normalize slug: strip leading @ if present
|
|
23
23
|
const cleanSlug = slug.startsWith('@') ? slug.slice(1) : slug;
|
|
24
|
-
const platform = detectPlatform();
|
|
25
24
|
const body = {
|
|
26
25
|
score,
|
|
27
|
-
platform,
|
|
28
|
-
agentName: '
|
|
26
|
+
platform: 'claude-code',
|
|
27
|
+
agentName: 'agentskill-sh-cli',
|
|
29
28
|
sessionId: `cli-${Date.now()}`,
|
|
30
29
|
autoRated: false,
|
|
31
30
|
};
|
|
32
31
|
if (comment)
|
|
33
32
|
body.comment = comment;
|
|
34
|
-
// If slug contains owner prefix, split it for the API
|
|
35
33
|
const apiSlug = cleanSlug.includes('/') ? cleanSlug.split('/').pop() : cleanSlug;
|
|
36
34
|
const owner = cleanSlug.includes('/') ? cleanSlug.split('/')[0] : undefined;
|
|
37
35
|
if (owner)
|
|
@@ -50,8 +48,10 @@ export async function feedbackCommand(args) {
|
|
|
50
48
|
}, null, 2));
|
|
51
49
|
return;
|
|
52
50
|
}
|
|
53
|
-
console.log(
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(pc.green('\u2713') + ` Feedback submitted for ${ORANGE(cleanSlug)}: ${pc.bold(`${score}/5`)}`);
|
|
54
53
|
if (comment)
|
|
55
|
-
console.log(`Comment: ${comment}`);
|
|
56
|
-
console.log(`Community average: ${data.averageScore}/5 (${data.ratingCount} ratings)`);
|
|
54
|
+
console.log(` Comment: ${comment}`);
|
|
55
|
+
console.log(` Community average: ${pc.bold(`${data.averageScore}/5`)} (${data.ratingCount} ratings)`);
|
|
56
|
+
console.log();
|
|
57
57
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function findCommand(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { apiFetch } from '../api.js';
|
|
4
|
+
import { truncate, ORANGE, DIM } from '../ui.js';
|
|
5
|
+
const DEBOUNCE_MS = 300;
|
|
6
|
+
const MAX_RESULTS = 10;
|
|
7
|
+
function scoreLabel(score) {
|
|
8
|
+
if (score == null)
|
|
9
|
+
return pc.dim('n/a');
|
|
10
|
+
if (score >= 70)
|
|
11
|
+
return pc.green(String(score));
|
|
12
|
+
if (score >= 30)
|
|
13
|
+
return pc.yellow(String(score));
|
|
14
|
+
return pc.red(String(score));
|
|
15
|
+
}
|
|
16
|
+
function renderResults(results, selectedIndex, query) {
|
|
17
|
+
const lines = [];
|
|
18
|
+
if (!results.length && query.length > 0) {
|
|
19
|
+
lines.push(pc.dim(' No results'));
|
|
20
|
+
return lines;
|
|
21
|
+
}
|
|
22
|
+
for (let i = 0; i < results.length; i++) {
|
|
23
|
+
const r = results[i];
|
|
24
|
+
const isSelected = i === selectedIndex;
|
|
25
|
+
const prefix = isSelected ? ORANGE('\u276f ') : ' ';
|
|
26
|
+
const name = isSelected ? pc.bold(ORANGE(r.name)) : r.name;
|
|
27
|
+
const owner = DIM(`@${r.owner}`);
|
|
28
|
+
const security = scoreLabel(r.securityScore);
|
|
29
|
+
const desc = truncate(r.description || '', 40);
|
|
30
|
+
lines.push(`${prefix}${name} ${owner} ${DIM('sec:')}${security} ${DIM(desc)}`);
|
|
31
|
+
}
|
|
32
|
+
return lines;
|
|
33
|
+
}
|
|
34
|
+
function clearLines(count) {
|
|
35
|
+
for (let i = 0; i < count; i++) {
|
|
36
|
+
process.stdout.write('\x1b[1A\x1b[2K');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function findCommand(args) {
|
|
40
|
+
// Non-interactive mode: just search and print
|
|
41
|
+
if (!process.stdin.isTTY) {
|
|
42
|
+
const query = args.filter((a) => !a.startsWith('--')).join(' ');
|
|
43
|
+
if (!query) {
|
|
44
|
+
console.error('Usage: ags find <query>');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const params = new URLSearchParams({ q: query, limit: String(MAX_RESULTS) });
|
|
48
|
+
const data = await apiFetch(`/agent/search?${params}`);
|
|
49
|
+
for (const r of data.results) {
|
|
50
|
+
console.log(`${r.slug}\t${r.owner}\t${r.securityScore ?? 'n/a'}\t${r.description || ''}`);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Interactive fzf-style search
|
|
55
|
+
const rl = readline.createInterface({
|
|
56
|
+
input: process.stdin,
|
|
57
|
+
output: process.stdout,
|
|
58
|
+
terminal: true,
|
|
59
|
+
});
|
|
60
|
+
// Enable raw mode for key-by-key input
|
|
61
|
+
if (process.stdin.setRawMode) {
|
|
62
|
+
process.stdin.setRawMode(true);
|
|
63
|
+
}
|
|
64
|
+
process.stdin.resume();
|
|
65
|
+
let query = args.filter((a) => !a.startsWith('--')).join(' ');
|
|
66
|
+
let results = [];
|
|
67
|
+
let selectedIndex = 0;
|
|
68
|
+
let lastRenderedLines = 0;
|
|
69
|
+
let debounceTimer = null;
|
|
70
|
+
let fetching = false;
|
|
71
|
+
function render() {
|
|
72
|
+
if (lastRenderedLines > 0) {
|
|
73
|
+
clearLines(lastRenderedLines);
|
|
74
|
+
}
|
|
75
|
+
const lines = [];
|
|
76
|
+
lines.push(`${ORANGE('\u276f')} ${pc.bold('Find a skill:')} ${query}${fetching ? DIM(' ...') : ''}`);
|
|
77
|
+
const resultLines = renderResults(results, selectedIndex, query);
|
|
78
|
+
lines.push(...resultLines);
|
|
79
|
+
if (results.length > 0) {
|
|
80
|
+
lines.push(DIM(' \u2191\u2193 navigate \u21b5 install esc quit'));
|
|
81
|
+
}
|
|
82
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
83
|
+
lastRenderedLines = lines.length;
|
|
84
|
+
}
|
|
85
|
+
async function search() {
|
|
86
|
+
if (!query.trim()) {
|
|
87
|
+
results = [];
|
|
88
|
+
selectedIndex = 0;
|
|
89
|
+
render();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
fetching = true;
|
|
93
|
+
render();
|
|
94
|
+
try {
|
|
95
|
+
const params = new URLSearchParams({ q: query, limit: String(MAX_RESULTS) });
|
|
96
|
+
const data = await apiFetch(`/agent/search?${params}`);
|
|
97
|
+
results = data.results;
|
|
98
|
+
selectedIndex = 0;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
results = [];
|
|
102
|
+
}
|
|
103
|
+
fetching = false;
|
|
104
|
+
render();
|
|
105
|
+
}
|
|
106
|
+
function scheduleSearch() {
|
|
107
|
+
if (debounceTimer)
|
|
108
|
+
clearTimeout(debounceTimer);
|
|
109
|
+
debounceTimer = setTimeout(search, DEBOUNCE_MS);
|
|
110
|
+
}
|
|
111
|
+
// Initial render
|
|
112
|
+
render();
|
|
113
|
+
// If there's an initial query, search immediately
|
|
114
|
+
if (query) {
|
|
115
|
+
await search();
|
|
116
|
+
}
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
process.stdin.on('data', async (data) => {
|
|
119
|
+
const key = data.toString();
|
|
120
|
+
// Escape or Ctrl+C: quit
|
|
121
|
+
if (key === '\x1b' || key === '\x03') {
|
|
122
|
+
if (lastRenderedLines > 0) {
|
|
123
|
+
clearLines(lastRenderedLines);
|
|
124
|
+
}
|
|
125
|
+
if (process.stdin.setRawMode) {
|
|
126
|
+
process.stdin.setRawMode(false);
|
|
127
|
+
}
|
|
128
|
+
rl.close();
|
|
129
|
+
resolve();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Enter: install selected
|
|
133
|
+
if (key === '\r' || key === '\n') {
|
|
134
|
+
if (results.length > 0 && results[selectedIndex]) {
|
|
135
|
+
const selected = results[selectedIndex];
|
|
136
|
+
if (lastRenderedLines > 0) {
|
|
137
|
+
clearLines(lastRenderedLines);
|
|
138
|
+
}
|
|
139
|
+
if (process.stdin.setRawMode) {
|
|
140
|
+
process.stdin.setRawMode(false);
|
|
141
|
+
}
|
|
142
|
+
rl.close();
|
|
143
|
+
console.log(`\nInstalling ${ORANGE(selected.name)}...\n`);
|
|
144
|
+
const { installCommand } = await import('./install.js');
|
|
145
|
+
await installCommand([selected.slug]);
|
|
146
|
+
resolve();
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Up arrow
|
|
151
|
+
if (key === '\x1b[A') {
|
|
152
|
+
if (selectedIndex > 0) {
|
|
153
|
+
selectedIndex--;
|
|
154
|
+
render();
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Down arrow
|
|
159
|
+
if (key === '\x1b[B') {
|
|
160
|
+
if (selectedIndex < results.length - 1) {
|
|
161
|
+
selectedIndex++;
|
|
162
|
+
render();
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Backspace
|
|
167
|
+
if (key === '\x7f' || key === '\b') {
|
|
168
|
+
if (query.length > 0) {
|
|
169
|
+
query = query.slice(0, -1);
|
|
170
|
+
scheduleSearch();
|
|
171
|
+
render();
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Regular character input
|
|
176
|
+
if (key.length === 1 && key >= ' ') {
|
|
177
|
+
query += key;
|
|
178
|
+
scheduleSearch();
|
|
179
|
+
render();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function initCommand(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { writeFileSync, existsSync } from 'fs';
|
|
4
|
+
import { join, basename } from 'path';
|
|
5
|
+
import { ORANGE } from '../ui.js';
|
|
6
|
+
export async function initCommand(args) {
|
|
7
|
+
const cwd = process.cwd();
|
|
8
|
+
const defaultName = basename(cwd);
|
|
9
|
+
// Get skill name from args or use directory name
|
|
10
|
+
let name = args.find((a) => !a.startsWith('--')) || '';
|
|
11
|
+
if (!name) {
|
|
12
|
+
const input = await p.text({
|
|
13
|
+
message: 'Skill name:',
|
|
14
|
+
placeholder: defaultName,
|
|
15
|
+
defaultValue: defaultName,
|
|
16
|
+
validate: (val) => {
|
|
17
|
+
if (!val || !val.trim())
|
|
18
|
+
return 'Skill name is required';
|
|
19
|
+
if (!/^[a-z0-9-]+$/.test(val.trim())) {
|
|
20
|
+
return 'Use lowercase letters, numbers, and hyphens only';
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
if (p.isCancel(input)) {
|
|
26
|
+
p.cancel('Cancelled.');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
name = input;
|
|
30
|
+
}
|
|
31
|
+
// Get description
|
|
32
|
+
const description = await p.text({
|
|
33
|
+
message: 'Description:',
|
|
34
|
+
placeholder: 'What does this skill do?',
|
|
35
|
+
validate: (val) => {
|
|
36
|
+
if (!val || !val.trim())
|
|
37
|
+
return 'Description is required';
|
|
38
|
+
return undefined;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
if (p.isCancel(description)) {
|
|
42
|
+
p.cancel('Cancelled.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const skillMdPath = join(cwd, 'SKILL.md');
|
|
46
|
+
if (existsSync(skillMdPath)) {
|
|
47
|
+
const overwrite = await p.confirm({
|
|
48
|
+
message: 'SKILL.md already exists. Overwrite?',
|
|
49
|
+
initialValue: false,
|
|
50
|
+
});
|
|
51
|
+
if (p.isCancel(overwrite) || !overwrite) {
|
|
52
|
+
p.cancel('Cancelled.');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const content = `---
|
|
57
|
+
name: ${name}
|
|
58
|
+
description: >
|
|
59
|
+
${description.trim()}
|
|
60
|
+
metadata:
|
|
61
|
+
author: ""
|
|
62
|
+
version: "1.0"
|
|
63
|
+
compatibility: Requires Node.js 18+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# ${name}
|
|
67
|
+
|
|
68
|
+
${description.trim()}
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
<!-- Describe how an AI agent should use this skill -->
|
|
73
|
+
|
|
74
|
+
## Examples
|
|
75
|
+
|
|
76
|
+
<!-- Provide examples of when and how to use this skill -->
|
|
77
|
+
`;
|
|
78
|
+
writeFileSync(skillMdPath, content, 'utf-8');
|
|
79
|
+
p.log.success(`Created ${ORANGE('SKILL.md')} for ${pc.bold(name)}`);
|
|
80
|
+
p.log.info(`Edit ${pc.dim(skillMdPath)} to customize your skill.`);
|
|
81
|
+
p.log.info(`Publish: ${pc.dim('Push to GitHub and submit at https://agentskill.sh/submit')}`);
|
|
82
|
+
}
|