@rbbtsn0w/adg 0.1.0-alpha.1 → 0.1.0-beta.2

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.
Files changed (110) hide show
  1. package/dist/bin/adg.js +703 -0
  2. package/dist/src/adapters/anthropic.js +54 -0
  3. package/dist/src/adapters/index.js +10 -0
  4. package/dist/src/adapters/openai.js +30 -0
  5. package/dist/src/adapters/reverse.js +53 -0
  6. package/dist/src/agents/claude.js +118 -0
  7. package/dist/src/agents/codex.js +61 -0
  8. package/{src/agents/index.ts → dist/src/agents/index.js} +6 -8
  9. package/dist/src/agents/registry.js +24 -0
  10. package/dist/src/agents/types.js +1 -0
  11. package/dist/src/commands/adapt.js +26 -0
  12. package/dist/src/commands/import.js +51 -0
  13. package/dist/src/commands/init.js +104 -0
  14. package/dist/src/commands/install.js +257 -0
  15. package/dist/src/commands/link.js +34 -0
  16. package/dist/src/commands/list.js +19 -0
  17. package/dist/src/commands/marketplace.js +124 -0
  18. package/dist/src/commands/migrate.js +60 -0
  19. package/dist/src/commands/multiselect-skills.js +103 -0
  20. package/dist/src/commands/remove.js +102 -0
  21. package/dist/src/commands/select-agents.js +40 -0
  22. package/dist/src/commands/select-components.js +61 -0
  23. package/dist/src/commands/select-plugins.js +25 -0
  24. package/dist/src/commands/select-scope.js +20 -0
  25. package/dist/src/commands/update.js +50 -0
  26. package/dist/src/commands/validate.js +50 -0
  27. package/dist/src/components.js +90 -0
  28. package/dist/src/deps.js +46 -0
  29. package/dist/src/fsutil.js +32 -0
  30. package/dist/src/hash.js +51 -0
  31. package/dist/src/lock.js +51 -0
  32. package/dist/src/manifest.js +110 -0
  33. package/dist/src/marketplace.js +39 -0
  34. package/{src/package.ts → dist/src/package.js} +37 -42
  35. package/{src/paths.ts → dist/src/paths.js} +54 -60
  36. package/dist/src/semver.js +55 -0
  37. package/dist/src/skills.js +79 -0
  38. package/dist/src/sources.js +122 -0
  39. package/dist/src/types.js +19 -0
  40. package/dist/vendor/skills/package.json +143 -0
  41. package/dist/vendor/skills/src/add.js +1663 -0
  42. package/dist/vendor/skills/src/agents.js +729 -0
  43. package/dist/vendor/skills/src/blob.js +436 -0
  44. package/dist/vendor/skills/src/cli.js +340 -0
  45. package/dist/vendor/skills/src/constants.js +3 -0
  46. package/dist/vendor/skills/src/detect-agent.js +56 -0
  47. package/dist/vendor/skills/src/find.js +294 -0
  48. package/dist/vendor/skills/src/frontmatter.js +13 -0
  49. package/dist/vendor/skills/src/git-tree.js +32 -0
  50. package/dist/vendor/skills/src/git.js +235 -0
  51. package/dist/vendor/skills/src/install.js +75 -0
  52. package/dist/vendor/skills/src/installer.js +924 -0
  53. package/dist/vendor/skills/src/list.js +201 -0
  54. package/dist/vendor/skills/src/local-lock.js +109 -0
  55. package/dist/vendor/skills/src/plugin-manifest.js +152 -0
  56. package/dist/vendor/skills/src/prompts/search-multiselect.js +312 -0
  57. package/dist/vendor/skills/src/providers/index.js +4 -0
  58. package/dist/vendor/skills/src/providers/registry.js +42 -0
  59. package/dist/vendor/skills/src/providers/types.js +1 -0
  60. package/dist/vendor/skills/src/providers/wellknown.js +625 -0
  61. package/dist/vendor/skills/src/remove.js +263 -0
  62. package/dist/vendor/skills/src/sanitize.js +57 -0
  63. package/dist/vendor/skills/src/self-cli.js +15 -0
  64. package/dist/vendor/skills/src/skill-lock.js +237 -0
  65. package/dist/vendor/skills/src/skills.js +264 -0
  66. package/dist/vendor/skills/src/source-parser.js +367 -0
  67. package/dist/vendor/skills/src/sync.js +404 -0
  68. package/dist/vendor/skills/src/telemetry.js +101 -0
  69. package/dist/vendor/skills/src/test-utils.js +59 -0
  70. package/dist/vendor/skills/src/types.js +1 -0
  71. package/dist/vendor/skills/src/update-source.js +76 -0
  72. package/dist/vendor/skills/src/update.js +590 -0
  73. package/dist/vendor/skills/src/use.js +505 -0
  74. package/package.json +15 -7
  75. package/bin/adg.ts +0 -758
  76. package/src/adapters/anthropic.ts +0 -54
  77. package/src/adapters/index.ts +0 -24
  78. package/src/adapters/openai.ts +0 -37
  79. package/src/adapters/reverse.ts +0 -60
  80. package/src/agents/claude.ts +0 -124
  81. package/src/agents/codex.ts +0 -67
  82. package/src/agents/registry.ts +0 -30
  83. package/src/agents/types.ts +0 -47
  84. package/src/commands/adapt.ts +0 -36
  85. package/src/commands/import.ts +0 -69
  86. package/src/commands/init.ts +0 -146
  87. package/src/commands/install.ts +0 -411
  88. package/src/commands/link.ts +0 -61
  89. package/src/commands/list.ts +0 -28
  90. package/src/commands/marketplace.ts +0 -198
  91. package/src/commands/migrate.ts +0 -84
  92. package/src/commands/multiselect-skills.ts +0 -137
  93. package/src/commands/remove.ts +0 -136
  94. package/src/commands/select-agents.ts +0 -45
  95. package/src/commands/select-components.ts +0 -66
  96. package/src/commands/select-plugins.ts +0 -28
  97. package/src/commands/select-scope.ts +0 -21
  98. package/src/commands/update.ts +0 -85
  99. package/src/commands/validate.ts +0 -57
  100. package/src/components.ts +0 -90
  101. package/src/deps.ts +0 -64
  102. package/src/fsutil.ts +0 -38
  103. package/src/hash.ts +0 -61
  104. package/src/lock.ts +0 -57
  105. package/src/manifest.ts +0 -113
  106. package/src/marketplace.ts +0 -41
  107. package/src/semver.ts +0 -67
  108. package/src/skills.ts +0 -88
  109. package/src/sources.ts +0 -159
  110. package/src/types.ts +0 -140
@@ -0,0 +1,340 @@
1
+ #!/usr/bin/env node
2
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
3
+ import { basename, join, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { runAdd, parseAddOptions, initTelemetry } from "./add.js";
6
+ import { runFind } from "./find.js";
7
+ import { runInstallFromLock } from "./install.js";
8
+ import { runList } from "./list.js";
9
+ import { removeCommand, parseRemoveOptions } from "./remove.js";
10
+ import { runSync, parseSyncOptions } from "./sync.js";
11
+ import { flushTelemetry } from "./telemetry.js";
12
+ import { isRunningInAgent } from "./detect-agent.js";
13
+ import { runUpdate } from "./update.js";
14
+ import { runUse, parseUseOptions } from "./use.js";
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ function getVersion() {
17
+ try {
18
+ const pkgPath = join(__dirname, '..', 'package.json');
19
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
20
+ return pkg.version;
21
+ }
22
+ catch {
23
+ return '0.0.0';
24
+ }
25
+ }
26
+ const VERSION = getVersion();
27
+ initTelemetry(VERSION);
28
+ const RESET = '\x1b[0m';
29
+ const BOLD = '\x1b[1m';
30
+ // 256-color grays - visible on both light and dark backgrounds
31
+ const DIM = '\x1b[38;5;102m'; // darker gray for secondary text
32
+ const TEXT = '\x1b[38;5;145m'; // lighter gray for primary text
33
+ const LOGO_LINES = [
34
+ '███████╗██╗ ██╗██╗██╗ ██╗ ███████╗',
35
+ '██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝',
36
+ '███████╗█████╔╝ ██║██║ ██║ ███████╗',
37
+ '╚════██║██╔═██╗ ██║██║ ██║ ╚════██║',
38
+ '███████║██║ ██╗██║███████╗███████╗███████║',
39
+ '╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝',
40
+ ];
41
+ // 256-color middle grays - visible on both light and dark backgrounds
42
+ const GRAYS = [
43
+ '\x1b[38;5;250m', // lighter gray
44
+ '\x1b[38;5;248m',
45
+ '\x1b[38;5;245m', // mid gray
46
+ '\x1b[38;5;243m',
47
+ '\x1b[38;5;240m',
48
+ '\x1b[38;5;238m', // darker gray
49
+ ];
50
+ function showLogo() {
51
+ console.log();
52
+ LOGO_LINES.forEach((line, i) => {
53
+ console.log(`${GRAYS[i]}${line}${RESET}`);
54
+ });
55
+ }
56
+ function showBanner() {
57
+ showLogo();
58
+ console.log();
59
+ console.log(`${DIM}The open agent skills ecosystem${RESET}`);
60
+ console.log();
61
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills add ${DIM}<package>${RESET} ${DIM}Add a new skill${RESET}`);
62
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills use ${DIM}<package>@<skill>${RESET} ${DIM}Use a skill without installing${RESET}`);
63
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills remove${RESET} ${DIM}Remove installed skills${RESET}`);
64
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills list${RESET} ${DIM}List installed skills${RESET}`);
65
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills find ${DIM}[query]${RESET} ${DIM}Search for skills${RESET}`);
66
+ console.log();
67
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills update${RESET} ${DIM}Update installed skills${RESET}`);
68
+ console.log();
69
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_install${RESET} ${DIM}Restore from skills-lock.json${RESET}`);
70
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills init ${DIM}[name]${RESET} ${DIM}Create a new skill${RESET}`);
71
+ console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_sync${RESET} ${DIM}Sync skills from node_modules${RESET}`);
72
+ console.log();
73
+ console.log(`${DIM}try:${RESET} npx skills add vercel-labs/agent-skills`);
74
+ console.log();
75
+ console.log(`Discover more skills at ${TEXT}https://skills.sh/${RESET}`);
76
+ console.log();
77
+ }
78
+ function showHelp() {
79
+ console.log(`
80
+ ${BOLD}Usage:${RESET} skills <command> [options]
81
+
82
+ ${BOLD}Manage Skills:${RESET}
83
+ add <package> Add a skill package (alias: a)
84
+ e.g. vercel-labs/agent-skills
85
+ https://github.com/vercel-labs/agent-skills
86
+ use <package>@<skill>
87
+ Generate a prompt for using one skill without installing it
88
+ remove [skills] Remove installed skills
89
+ list, ls List installed skills
90
+ find [query] Search for skills interactively
91
+
92
+ ${BOLD}Updates:${RESET}
93
+ update [skills...] Update skills to latest versions (alias: upgrade)
94
+
95
+ ${BOLD}Update Options:${RESET}
96
+ -g, --global Update global skills only
97
+ -p, --project Update project skills only
98
+ -y, --yes Skip scope prompt (auto-detect: project if in a project, else global)
99
+
100
+ ${BOLD}Project:${RESET}
101
+ experimental_install Restore skills from skills-lock.json
102
+ init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
103
+ experimental_sync Sync skills from node_modules into agent directories
104
+
105
+ ${BOLD}Add Options:${RESET}
106
+ -g, --global Install skill globally (user-level) instead of project-level
107
+ -a, --agent <agents> Specify agents to install to (use '*' for all agents)
108
+ -s, --skill <skills> Specify skill names to install (use '*' for all skills)
109
+ -l, --list List available skills in the repository without installing
110
+ -y, --yes Skip confirmation prompts
111
+ --copy Copy files instead of symlinking to agent directories
112
+ --all Shorthand for --skill '*' --agent '*' -y
113
+ --full-depth Search all subdirectories even when a root SKILL.md exists
114
+
115
+ ${BOLD}Use Options:${RESET}
116
+ -s, --skill <skill> Specify the skill to use
117
+ -a, --agent <agent> Start one supported agent interactively
118
+ --full-depth Search all subdirectories even when a root SKILL.md exists
119
+ --dangerously-accept-openclaw-risks
120
+ Allow unverified OpenClaw community skills
121
+
122
+ ${BOLD}Remove Options:${RESET}
123
+ -g, --global Remove from global scope
124
+ -a, --agent <agents> Remove from specific agents (use '*' for all agents)
125
+ -s, --skill <skills> Specify skills to remove (use '*' for all skills)
126
+ -y, --yes Skip confirmation prompts
127
+ --all Shorthand for --skill '*' --agent '*' -y
128
+
129
+ ${BOLD}Experimental Sync Options:${RESET}
130
+ -a, --agent <agents> Specify agents to install to (use '*' for all agents)
131
+ -y, --yes Skip confirmation prompts
132
+
133
+ ${BOLD}List Options:${RESET}
134
+ -g, --global List global skills (default: project)
135
+ -a, --agent <agents> Filter by specific agents
136
+ --json Output as JSON (machine-readable, no ANSI codes)
137
+
138
+ ${BOLD}Options:${RESET}
139
+ --help, -h Show this help message
140
+ --version, -v Show version number
141
+
142
+ ${BOLD}Examples:${RESET}
143
+ ${DIM}$${RESET} skills add vercel-labs/agent-skills
144
+ ${DIM}$${RESET} skills use vercel-labs/agent-skills@vercel-optimize | claude
145
+ ${DIM}$${RESET} skills use vercel-labs/agent-skills --skill vercel-optimize --agent claude-code
146
+ ${DIM}$${RESET} skills add vercel-labs/agent-skills -g
147
+ ${DIM}$${RESET} skills add vercel-labs/agent-skills --agent claude-code cursor
148
+ ${DIM}$${RESET} skills add vercel-labs/agent-skills --skill pr-review commit
149
+ ${DIM}$${RESET} skills remove ${DIM}# interactive remove${RESET}
150
+ ${DIM}$${RESET} skills remove web-design ${DIM}# remove by name${RESET}
151
+ ${DIM}$${RESET} skills rm --global frontend-design
152
+ ${DIM}$${RESET} skills list ${DIM}# list project skills${RESET}
153
+ ${DIM}$${RESET} skills ls -g ${DIM}# list global skills${RESET}
154
+ ${DIM}$${RESET} skills ls -a claude-code ${DIM}# filter by agent${RESET}
155
+ ${DIM}$${RESET} skills ls --json ${DIM}# JSON output${RESET}
156
+ ${DIM}$${RESET} skills find ${DIM}# interactive search${RESET}
157
+ ${DIM}$${RESET} skills find typescript ${DIM}# search by keyword${RESET}
158
+ ${DIM}$${RESET} skills update
159
+ ${DIM}$${RESET} skills update my-skill ${DIM}# update a single skill${RESET}
160
+ ${DIM}$${RESET} skills update -g ${DIM}# update global skills only${RESET}
161
+ ${DIM}$${RESET} skills experimental_install ${DIM}# restore from skills-lock.json${RESET}
162
+ ${DIM}$${RESET} skills init my-skill
163
+ ${DIM}$${RESET} skills experimental_sync ${DIM}# sync from node_modules${RESET}
164
+ ${DIM}$${RESET} skills experimental_sync -y ${DIM}# sync without prompts${RESET}
165
+
166
+ Discover more skills at ${TEXT}https://skills.sh/${RESET}
167
+ `);
168
+ }
169
+ function showRemoveHelp() {
170
+ console.log(`
171
+ ${BOLD}Usage:${RESET} skills remove [skills...] [options]
172
+
173
+ ${BOLD}Description:${RESET}
174
+ Remove installed skills from agents. If no skill names are provided,
175
+ an interactive selection menu will be shown.
176
+
177
+ ${BOLD}Arguments:${RESET}
178
+ skills Optional skill names to remove (space-separated)
179
+
180
+ ${BOLD}Options:${RESET}
181
+ -g, --global Remove from global scope (~/) instead of project scope
182
+ -a, --agent Remove from specific agents (use '*' for all agents)
183
+ -s, --skill Specify skills to remove (use '*' for all skills)
184
+ -y, --yes Skip confirmation prompts
185
+ --all Shorthand for --skill '*' --agent '*' -y
186
+
187
+ ${BOLD}Examples:${RESET}
188
+ ${DIM}$${RESET} skills remove ${DIM}# interactive selection${RESET}
189
+ ${DIM}$${RESET} skills remove my-skill ${DIM}# remove specific skill${RESET}
190
+ ${DIM}$${RESET} skills remove skill1 skill2 -y ${DIM}# remove multiple skills${RESET}
191
+ ${DIM}$${RESET} skills remove --global my-skill ${DIM}# remove from global scope${RESET}
192
+ ${DIM}$${RESET} skills rm --agent claude-code my-skill ${DIM}# remove from specific agent${RESET}
193
+ ${DIM}$${RESET} skills remove --all ${DIM}# remove all skills${RESET}
194
+ ${DIM}$${RESET} skills remove --skill '*' -a cursor ${DIM}# remove all skills from cursor${RESET}
195
+
196
+ Discover more skills at ${TEXT}https://skills.sh/${RESET}
197
+ `);
198
+ }
199
+ function runInit(args) {
200
+ const cwd = process.cwd();
201
+ const skillName = args[0] || basename(cwd);
202
+ const hasName = args[0] !== undefined;
203
+ const skillDir = hasName ? join(cwd, skillName) : cwd;
204
+ const skillFile = join(skillDir, 'SKILL.md');
205
+ const displayPath = hasName ? `${skillName}/SKILL.md` : 'SKILL.md';
206
+ if (existsSync(skillFile)) {
207
+ console.log(`${TEXT}Skill already exists at ${DIM}${displayPath}${RESET}`);
208
+ return;
209
+ }
210
+ if (hasName) {
211
+ mkdirSync(skillDir, { recursive: true });
212
+ }
213
+ const skillContent = `---
214
+ name: ${skillName}
215
+ description: A brief description of what this skill does
216
+ ---
217
+
218
+ # ${skillName}
219
+
220
+ Instructions for the agent to follow when this skill is activated.
221
+
222
+ ## When to use
223
+
224
+ Describe when this skill should be used.
225
+
226
+ ## Instructions
227
+
228
+ 1. First step
229
+ 2. Second step
230
+ 3. Additional steps as needed
231
+ `;
232
+ writeFileSync(skillFile, skillContent);
233
+ console.log(`${TEXT}Initialized skill: ${DIM}${skillName}${RESET}`);
234
+ console.log();
235
+ console.log(`${DIM}Created:${RESET}`);
236
+ console.log(` ${displayPath}`);
237
+ console.log();
238
+ console.log(`${DIM}Next steps:${RESET}`);
239
+ console.log(` 1. Edit ${TEXT}${displayPath}${RESET} to define your skill instructions`);
240
+ console.log(` 2. Update the ${TEXT}name${RESET} and ${TEXT}description${RESET} in the frontmatter`);
241
+ console.log();
242
+ console.log(`${DIM}Publishing:${RESET}`);
243
+ console.log(` ${DIM}GitHub:${RESET} Push to a repo, then ${TEXT}npx skills add <owner>/<repo>${RESET}`);
244
+ console.log(` ${DIM}URL:${RESET} Host the file, then ${TEXT}npx skills add https://example.com/${displayPath}${RESET}`);
245
+ console.log();
246
+ console.log(`Browse existing skills for inspiration at ${TEXT}https://skills.sh/${RESET}`);
247
+ console.log();
248
+ }
249
+ // ============================================
250
+ // Main
251
+ // ============================================
252
+ async function main() {
253
+ const args = process.argv.slice(2);
254
+ const inAgent = await isRunningInAgent();
255
+ if (args.length === 0) {
256
+ if (!inAgent) {
257
+ showBanner();
258
+ }
259
+ return;
260
+ }
261
+ const command = args[0];
262
+ const restArgs = args.slice(1);
263
+ switch (command) {
264
+ case 'find':
265
+ case 'search':
266
+ case 'f':
267
+ case 's':
268
+ if (!inAgent)
269
+ showLogo();
270
+ console.log();
271
+ await runFind(restArgs);
272
+ break;
273
+ case 'init':
274
+ if (!inAgent)
275
+ showLogo();
276
+ console.log();
277
+ runInit(restArgs);
278
+ break;
279
+ case 'experimental_install': {
280
+ if (!inAgent)
281
+ showLogo();
282
+ await runInstallFromLock(restArgs);
283
+ break;
284
+ }
285
+ case 'i':
286
+ case 'install':
287
+ case 'a':
288
+ case 'add': {
289
+ if (!inAgent)
290
+ showLogo();
291
+ const { source: addSource, options: addOpts } = parseAddOptions(restArgs);
292
+ await runAdd(addSource, addOpts);
293
+ break;
294
+ }
295
+ case 'use': {
296
+ const { source: useSource, options: useOptions, errors: useErrors, } = parseUseOptions(restArgs);
297
+ await runUse(useSource, useOptions, useErrors);
298
+ break;
299
+ }
300
+ case 'remove':
301
+ case 'rm':
302
+ case 'r':
303
+ // Check for --help or -h flag
304
+ if (restArgs.includes('--help') || restArgs.includes('-h')) {
305
+ showRemoveHelp();
306
+ break;
307
+ }
308
+ const { skills, options: removeOptions } = parseRemoveOptions(restArgs);
309
+ await removeCommand(skills, removeOptions);
310
+ break;
311
+ case 'experimental_sync': {
312
+ if (!inAgent)
313
+ showLogo();
314
+ const { options: syncOptions } = parseSyncOptions(restArgs);
315
+ await runSync(restArgs, syncOptions);
316
+ break;
317
+ }
318
+ case 'list':
319
+ case 'ls':
320
+ await runList(restArgs);
321
+ break;
322
+ case 'check':
323
+ case 'update':
324
+ case 'upgrade':
325
+ await runUpdate(restArgs);
326
+ break;
327
+ case '--help':
328
+ case '-h':
329
+ showHelp();
330
+ break;
331
+ case '--version':
332
+ case '-v':
333
+ console.log(VERSION);
334
+ break;
335
+ default:
336
+ console.log(`Unknown command: ${command}`);
337
+ console.log(`Run ${BOLD}skills --help${RESET} for usage.`);
338
+ }
339
+ }
340
+ main().finally(() => flushTelemetry().then(() => process.exit(0)));
@@ -0,0 +1,3 @@
1
+ export const AGENTS_DIR = '.agents';
2
+ export const SKILLS_SUBDIR = 'skills';
3
+ export const UNIVERSAL_SKILLS_DIR = '.agents/skills';
@@ -0,0 +1,56 @@
1
+ import { determineAgent } from '@vercel/detect-agent';
2
+ import { setDetectedAgent } from "./telemetry.js";
3
+ let cachedResult = null;
4
+ /**
5
+ * Map from @vercel/detect-agent names to skills-cli AgentType identifiers.
6
+ * Only includes agents that exist in both systems.
7
+ */
8
+ const agentNameToType = {
9
+ cursor: 'cursor',
10
+ 'cursor-cli': 'cursor',
11
+ claude: 'claude-code',
12
+ cowork: 'claude-code',
13
+ devin: 'universal', // Devin not in skills-cli agent list, use universal
14
+ replit: 'replit',
15
+ gemini: 'gemini-cli',
16
+ codex: 'codex',
17
+ antigravity: 'antigravity',
18
+ 'augment-cli': 'augment',
19
+ opencode: 'opencode',
20
+ 'github-copilot': 'github-copilot',
21
+ };
22
+ /**
23
+ * Detect if the CLI is being run inside an AI agent environment.
24
+ * Results are cached after the first call. Also updates telemetry with the agent name.
25
+ */
26
+ export async function detectAgent() {
27
+ if (cachedResult)
28
+ return cachedResult;
29
+ cachedResult = await determineAgent();
30
+ if (cachedResult.isAgent) {
31
+ setDetectedAgent(cachedResult.agent.name);
32
+ }
33
+ return cachedResult;
34
+ }
35
+ /**
36
+ * Returns true if the CLI is running inside a detected AI agent.
37
+ * When true, the CLI should skip interactive prompts and use sensible defaults.
38
+ */
39
+ export async function isRunningInAgent() {
40
+ const result = await detectAgent();
41
+ return result.isAgent;
42
+ }
43
+ /**
44
+ * Returns the name of the detected agent, or null if not running in an agent.
45
+ */
46
+ export async function getAgentName() {
47
+ const result = await detectAgent();
48
+ return result.isAgent ? result.agent.name : null;
49
+ }
50
+ /**
51
+ * Maps a detected agent name to the corresponding skills-cli AgentType.
52
+ * Returns null if the agent can't be mapped to a specific skills-cli agent.
53
+ */
54
+ export function getAgentType(agentName) {
55
+ return agentNameToType[agentName] ?? null;
56
+ }
@@ -0,0 +1,294 @@
1
+ import * as readline from 'readline';
2
+ import { runAdd, parseAddOptions } from "./add.js";
3
+ import { sanitizeMetadata } from "./sanitize.js";
4
+ import { track } from "./telemetry.js";
5
+ import { isRepoPrivate } from "./source-parser.js";
6
+ import { isRunningInAgent } from "./detect-agent.js";
7
+ const RESET = '\x1b[0m';
8
+ const BOLD = '\x1b[1m';
9
+ const DIM = '\x1b[38;5;102m';
10
+ const TEXT = '\x1b[38;5;145m';
11
+ const CYAN = '\x1b[36m';
12
+ const MAGENTA = '\x1b[35m';
13
+ const YELLOW = '\x1b[33m';
14
+ // API endpoint for skills search
15
+ const SEARCH_API_BASE = process.env.SKILLS_API_URL || 'https://skills.sh';
16
+ function formatInstalls(count) {
17
+ if (!count || count <= 0)
18
+ return '';
19
+ if (count >= 1_000_000)
20
+ return `${(count / 1_000_000).toFixed(1).replace(/\.0$/, '')}M installs`;
21
+ if (count >= 1_000)
22
+ return `${(count / 1_000).toFixed(1).replace(/\.0$/, '')}K installs`;
23
+ return `${count} install${count === 1 ? '' : 's'}`;
24
+ }
25
+ // Search via API
26
+ export async function searchSkillsAPI(query) {
27
+ try {
28
+ const url = `${SEARCH_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=10`;
29
+ const res = await fetch(url);
30
+ if (!res.ok)
31
+ return [];
32
+ const data = (await res.json());
33
+ return data.skills
34
+ .map((skill) => ({
35
+ name: sanitizeMetadata(skill.name),
36
+ slug: sanitizeMetadata(skill.id),
37
+ source: sanitizeMetadata(skill.source || ''),
38
+ installs: skill.installs,
39
+ }))
40
+ .sort((a, b) => (b.installs || 0) - (a.installs || 0));
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ }
46
+ // ANSI escape codes for terminal control
47
+ const HIDE_CURSOR = '\x1b[?25l';
48
+ const SHOW_CURSOR = '\x1b[?25h';
49
+ const CLEAR_DOWN = '\x1b[J';
50
+ const MOVE_UP = (n) => `\x1b[${n}A`;
51
+ const MOVE_TO_COL = (n) => `\x1b[${n}G`;
52
+ // Custom fzf-style search prompt using raw readline
53
+ async function runSearchPrompt(initialQuery = '') {
54
+ let results = [];
55
+ let selectedIndex = 0;
56
+ let query = initialQuery;
57
+ let loading = false;
58
+ let debounceTimer = null;
59
+ let lastRenderedLines = 0;
60
+ // Enable raw mode for keypress events
61
+ if (process.stdin.isTTY) {
62
+ process.stdin.setRawMode(true);
63
+ }
64
+ // Setup readline for keypress events but don't let it echo
65
+ readline.emitKeypressEvents(process.stdin);
66
+ // Resume stdin to start receiving events
67
+ process.stdin.resume();
68
+ // Hide cursor during selection
69
+ process.stdout.write(HIDE_CURSOR);
70
+ function render() {
71
+ // Move cursor up to overwrite previous render
72
+ if (lastRenderedLines > 0) {
73
+ process.stdout.write(MOVE_UP(lastRenderedLines) + MOVE_TO_COL(1));
74
+ }
75
+ // Clear from cursor to end of screen (removes ghost trails)
76
+ process.stdout.write(CLEAR_DOWN);
77
+ const lines = [];
78
+ // Search input line with cursor
79
+ const cursor = `${BOLD}_${RESET}`;
80
+ lines.push(`${TEXT}Search skills:${RESET} ${query}${cursor}`);
81
+ lines.push('');
82
+ // Results - keep showing existing results while loading new ones
83
+ if (!query || query.length < 2) {
84
+ lines.push(`${DIM}Start typing to search (min 2 chars)${RESET}`);
85
+ }
86
+ else if (results.length === 0 && loading) {
87
+ lines.push(`${DIM}Searching...${RESET}`);
88
+ }
89
+ else if (results.length === 0) {
90
+ lines.push(`${DIM}No skills found${RESET}`);
91
+ }
92
+ else {
93
+ const maxVisible = 8;
94
+ const visible = results.slice(0, maxVisible);
95
+ for (let i = 0; i < visible.length; i++) {
96
+ const skill = visible[i];
97
+ const isSelected = i === selectedIndex;
98
+ const arrow = isSelected ? `${BOLD}>${RESET}` : ' ';
99
+ const name = isSelected ? `${BOLD}${skill.name}${RESET}` : `${TEXT}${skill.name}${RESET}`;
100
+ const source = skill.source ? ` ${DIM}${skill.source}${RESET}` : '';
101
+ const installs = formatInstalls(skill.installs);
102
+ const installsBadge = installs ? ` ${CYAN}${installs}${RESET}` : '';
103
+ const loadingIndicator = loading && i === 0 ? ` ${DIM}...${RESET}` : '';
104
+ lines.push(` ${arrow} ${name}${source}${installsBadge}${loadingIndicator}`);
105
+ }
106
+ }
107
+ lines.push('');
108
+ lines.push(`${DIM}up/down navigate | enter select | esc cancel${RESET}`);
109
+ // Write each line
110
+ for (const line of lines) {
111
+ process.stdout.write(line + '\n');
112
+ }
113
+ lastRenderedLines = lines.length;
114
+ }
115
+ function triggerSearch(q) {
116
+ // Always clear any pending debounce timer
117
+ if (debounceTimer) {
118
+ clearTimeout(debounceTimer);
119
+ debounceTimer = null;
120
+ }
121
+ // Always reset loading state when starting a new search
122
+ loading = false;
123
+ if (!q || q.length < 2) {
124
+ results = [];
125
+ selectedIndex = 0;
126
+ render();
127
+ return;
128
+ }
129
+ // Use API search for all queries (debounced)
130
+ loading = true;
131
+ render();
132
+ // Adaptive debounce: shorter queries = longer wait (user still typing)
133
+ // 2 chars: 250ms, 3 chars: 200ms, 4 chars: 150ms, 5+ chars: 150ms
134
+ const debounceMs = Math.max(150, 350 - q.length * 50);
135
+ debounceTimer = setTimeout(async () => {
136
+ try {
137
+ results = await searchSkillsAPI(q);
138
+ selectedIndex = 0;
139
+ }
140
+ catch {
141
+ results = [];
142
+ }
143
+ finally {
144
+ loading = false;
145
+ debounceTimer = null;
146
+ render();
147
+ }
148
+ }, debounceMs);
149
+ }
150
+ // Trigger initial search if there's a query, then render
151
+ if (initialQuery) {
152
+ triggerSearch(initialQuery);
153
+ }
154
+ render();
155
+ return new Promise((resolve) => {
156
+ function cleanup() {
157
+ process.stdin.removeListener('keypress', handleKeypress);
158
+ if (process.stdin.isTTY) {
159
+ process.stdin.setRawMode(false);
160
+ }
161
+ process.stdout.write(SHOW_CURSOR);
162
+ // Pause stdin to fully release it for child processes
163
+ process.stdin.pause();
164
+ }
165
+ function handleKeypress(_ch, key) {
166
+ if (!key)
167
+ return;
168
+ if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
169
+ // Cancel
170
+ cleanup();
171
+ resolve(null);
172
+ return;
173
+ }
174
+ if (key.name === 'return') {
175
+ // Submit
176
+ cleanup();
177
+ resolve(results[selectedIndex] || null);
178
+ return;
179
+ }
180
+ if (key.name === 'up') {
181
+ selectedIndex = Math.max(0, selectedIndex - 1);
182
+ render();
183
+ return;
184
+ }
185
+ if (key.name === 'down') {
186
+ selectedIndex = Math.min(Math.max(0, results.length - 1), selectedIndex + 1);
187
+ render();
188
+ return;
189
+ }
190
+ if (key.name === 'backspace') {
191
+ if (query.length > 0) {
192
+ query = query.slice(0, -1);
193
+ triggerSearch(query);
194
+ }
195
+ return;
196
+ }
197
+ // Regular character input
198
+ if (key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
199
+ const char = key.sequence;
200
+ if (char >= ' ' && char <= '~') {
201
+ query += char;
202
+ triggerSearch(query);
203
+ }
204
+ }
205
+ }
206
+ process.stdin.on('keypress', handleKeypress);
207
+ });
208
+ }
209
+ // Parse owner/repo from a package string (for the find command)
210
+ function getOwnerRepoFromString(pkg) {
211
+ // Handle owner/repo or owner/repo@skill
212
+ const atIndex = pkg.lastIndexOf('@');
213
+ const repoPath = atIndex > 0 ? pkg.slice(0, atIndex) : pkg;
214
+ const match = repoPath.match(/^([^/]+)\/([^/]+)$/);
215
+ if (match) {
216
+ return { owner: match[1], repo: match[2] };
217
+ }
218
+ return null;
219
+ }
220
+ async function isRepoPublic(owner, repo) {
221
+ const isPrivate = await isRepoPrivate(owner, repo);
222
+ // Return true only if we know it's public (isPrivate === false)
223
+ // Return false if private or unable to determine
224
+ return isPrivate === false;
225
+ }
226
+ export async function runFind(args) {
227
+ const query = args.join(' ');
228
+ const isNonInteractive = !process.stdin.isTTY;
229
+ const agentTip = `${DIM}Tip: if running in a coding agent, follow these steps:${RESET}
230
+ ${DIM} 1) npx skills find [query]${RESET}
231
+ ${DIM} 2) npx skills add <owner/repo@skill>${RESET}`;
232
+ // Non-interactive mode: just print results and exit
233
+ if (query) {
234
+ const results = await searchSkillsAPI(query);
235
+ // Track telemetry for non-interactive search
236
+ track({
237
+ event: 'find',
238
+ query,
239
+ resultCount: String(results.length),
240
+ });
241
+ if (results.length === 0) {
242
+ console.log(`${DIM}No skills found for "${query}"${RESET}`);
243
+ return;
244
+ }
245
+ console.log(`${DIM}Install with${RESET} npx skills add <owner/repo@skill>`);
246
+ console.log();
247
+ for (const skill of results.slice(0, 6)) {
248
+ const pkg = skill.source || skill.slug;
249
+ const installs = formatInstalls(skill.installs);
250
+ console.log(`${TEXT}${pkg}@${skill.name}${RESET}${installs ? ` ${CYAN}${installs}${RESET}` : ''}`);
251
+ console.log(`${DIM}└ https://skills.sh/${skill.slug}${RESET}`);
252
+ console.log();
253
+ }
254
+ return;
255
+ }
256
+ // Skip interactive search when running inside an AI agent or non-TTY
257
+ if (isNonInteractive || (await isRunningInAgent())) {
258
+ console.log(agentTip);
259
+ console.log();
260
+ console.log(`${DIM}Usage: npx skills find <query>${RESET}`);
261
+ return;
262
+ }
263
+ const selected = await runSearchPrompt();
264
+ // Track telemetry for interactive search
265
+ track({
266
+ event: 'find',
267
+ query: '',
268
+ resultCount: selected ? '1' : '0',
269
+ interactive: '1',
270
+ });
271
+ if (!selected) {
272
+ console.log(`${DIM}Search cancelled${RESET}`);
273
+ console.log();
274
+ return;
275
+ }
276
+ // Use source (owner/repo) and skill name for installation
277
+ const pkg = selected.source || selected.slug;
278
+ const skillName = selected.name;
279
+ console.log();
280
+ console.log(`${TEXT}Installing ${BOLD}${skillName}${RESET} from ${DIM}${pkg}${RESET}...`);
281
+ console.log();
282
+ // Run add directly since we're in the same CLI
283
+ const { source, options } = parseAddOptions([pkg, '--skill', skillName]);
284
+ await runAdd(source, options);
285
+ console.log();
286
+ const info = getOwnerRepoFromString(pkg);
287
+ if (info && (await isRepoPublic(info.owner, info.repo))) {
288
+ console.log(`${DIM}View the skill at${RESET} ${TEXT}https://skills.sh/${selected.slug}${RESET}`);
289
+ }
290
+ else {
291
+ console.log(`${DIM}Discover more skills at${RESET} ${TEXT}https://skills.sh${RESET}`);
292
+ }
293
+ console.log();
294
+ }