agent-root 0.1.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.
Files changed (2) hide show
  1. package/bin/agentroot.js +432 -0
  2. package/package.json +33 -0
@@ -0,0 +1,432 @@
1
+ #!/usr/bin/env node
2
+
3
+ // AgentRoot CLI — zero-dependency, runs via npx
4
+ // npx agentroot install stripe.com/payments --tool claude
5
+
6
+ const https = require('https');
7
+ const http = require('http');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const {
12
+ API_BASE, MANIFEST_FILE, TOOL_DIRS, TOOL_NAMES,
13
+ detectTools, resolveToolDir, hashContent, scanInstalled, writeSkill,
14
+ } = require('@agent-root/core');
15
+
16
+ // ─── Colors (ANSI) ────────────────────────────────────────────────────────
17
+
18
+ const c = {
19
+ reset: '\x1b[0m',
20
+ bold: '\x1b[1m',
21
+ dim: '\x1b[2m',
22
+ green: '\x1b[32m',
23
+ cyan: '\x1b[36m',
24
+ yellow:'\x1b[33m',
25
+ red: '\x1b[31m',
26
+ magenta:'\x1b[35m',
27
+ };
28
+
29
+ // ─── Helpers ───────────────────────────────────────────────────────────────
30
+
31
+ function fetch(url) {
32
+ return new Promise((resolve, reject) => {
33
+ const mod = url.startsWith('https') ? https : http;
34
+ mod.get(url, { headers: { 'User-Agent': 'agentroot-cli/0.1.0' } }, (res) => {
35
+ // Follow redirects
36
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
37
+ return fetch(res.headers.location).then(resolve, reject);
38
+ }
39
+ if (res.statusCode !== 200) {
40
+ return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
41
+ }
42
+ let data = '';
43
+ res.on('data', chunk => data += chunk);
44
+ res.on('end', () => resolve(data));
45
+ }).on('error', reject);
46
+ });
47
+ }
48
+
49
+ function fetchJSON(url) {
50
+ return fetch(url).then(d => JSON.parse(d));
51
+ }
52
+
53
+ function fatal(msg) {
54
+ console.error(`${c.red}error${c.reset} ${msg}`);
55
+ process.exit(1);
56
+ }
57
+
58
+ // ─── Argument Parsing ──────────────────────────────────────────────────────
59
+
60
+ function parseArgs(argv) {
61
+ const args = argv.slice(2);
62
+ const cmd = args[0];
63
+ const positional = [];
64
+ const flags = {};
65
+
66
+ for (let i = 1; i < args.length; i++) {
67
+ if (args[i].startsWith('--')) {
68
+ const key = args[i].slice(2);
69
+ // Boolean flags
70
+ if (key === 'all' || key === 'project' || key === 'help' || key === 'json') {
71
+ flags[key] = true;
72
+ } else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
73
+ flags[key] = args[i + 1];
74
+ i++;
75
+ } else {
76
+ flags[key] = true;
77
+ }
78
+ } else {
79
+ positional.push(args[i]);
80
+ }
81
+ }
82
+ return { cmd, positional, flags };
83
+ }
84
+
85
+ // ─── Commands ──────────────────────────────────────────────────────────────
86
+
87
+ async function cmdInstall(positional, flags) {
88
+ if (positional.length === 0) {
89
+ fatal('Usage: agentroot install <domain>/<skill-id> [--tool claude|codex|gemini|cursor|agents] [--project]');
90
+ }
91
+
92
+ const input = positional[0];
93
+ const isProject = !!flags.project;
94
+ const installAll = !!flags.all;
95
+
96
+ // Parse domain/skill-id
97
+ const slashIdx = input.indexOf('/');
98
+ if (slashIdx === -1 && !installAll) {
99
+ fatal('Expected format: <domain>/<skill-id> or <domain> --all');
100
+ }
101
+
102
+ const domain = installAll ? input : input.slice(0, slashIdx);
103
+ const skillId = installAll ? null : input.slice(slashIdx + 1);
104
+
105
+ if (!domain) fatal('Missing domain');
106
+ if (!installAll && !skillId) fatal('Missing skill ID');
107
+
108
+ // Resolve target tool
109
+ let tools = [];
110
+ if (flags.tool) {
111
+ tools = [flags.tool];
112
+ } else {
113
+ tools = detectTools();
114
+ if (tools.length === 0) {
115
+ tools = ['agents']; // fallback to cross-tool standard
116
+ console.log(`${c.dim}No AI tools detected, using cross-tool .agents/skills/ directory${c.reset}`);
117
+ } else {
118
+ console.log(`${c.dim}Detected tools: ${tools.join(', ')}${c.reset}`);
119
+ }
120
+ }
121
+
122
+ // Fetch skill(s) info
123
+ let skillsToInstall = [];
124
+
125
+ if (installAll) {
126
+ // Get all skills for a domain
127
+ console.log(`${c.cyan}Fetching skills for ${domain}...${c.reset}`);
128
+ const data = await fetchJSON(`${API_BASE}/api/skills/${encodeURIComponent(domain)}`);
129
+ if (!data.skill || !data.skill.skills || data.skill.skills.length === 0) {
130
+ fatal(`No skills found for domain: ${domain}`);
131
+ }
132
+ skillsToInstall = data.skill.skills.map(s => ({
133
+ id: s.skill_id,
134
+ name: s.name,
135
+ description: s.description,
136
+ url: s.skill_md_url,
137
+ domain: domain,
138
+ }));
139
+ } else {
140
+ // Get single skill
141
+ console.log(`${c.cyan}Fetching ${domain}/${skillId}...${c.reset}`);
142
+ const data = await fetchJSON(`${API_BASE}/api/skills/${encodeURIComponent(domain)}/item/${encodeURIComponent(skillId)}`);
143
+ if (!data.skill) {
144
+ fatal(`Skill not found: ${domain}/${skillId}`);
145
+ }
146
+ const s = data.skill;
147
+ skillsToInstall = [{
148
+ id: s.skill_id,
149
+ name: s.name,
150
+ description: s.description,
151
+ url: s.skill_md_url,
152
+ domain: domain,
153
+ }];
154
+ }
155
+
156
+ // Install each skill for each tool
157
+ let installed = 0;
158
+ for (const skill of skillsToInstall) {
159
+ if (!skill.url) {
160
+ console.log(`${c.yellow}skip${c.reset} ${skill.id} — no SKILL.md URL`);
161
+ continue;
162
+ }
163
+
164
+ // Fetch SKILL.md content
165
+ let content;
166
+ try {
167
+ content = await fetch(skill.url);
168
+ } catch (err) {
169
+ console.log(`${c.red}fail${c.reset} ${skill.id} — could not fetch SKILL.md: ${err.message}`);
170
+ continue;
171
+ }
172
+
173
+ for (const tool of tools) {
174
+ const baseDir = resolveToolDir(tool, isProject);
175
+ const skillDir = path.join(baseDir, skill.id);
176
+ const skillPath = path.join(skillDir, 'SKILL.md');
177
+
178
+ const manifest = {
179
+ source_domain: skill.domain,
180
+ skill_id: skill.id,
181
+ name: skill.name,
182
+ description: skill.description,
183
+ skill_md_url: skill.url,
184
+ installed_at: new Date().toISOString(),
185
+ version_hash: hashContent(content),
186
+ tool: tool,
187
+ };
188
+ writeSkill(skillDir, content, manifest);
189
+
190
+ console.log(`${c.green}installed${c.reset} ${c.bold}${skill.id}${c.reset} → ${skillPath}`);
191
+ installed++;
192
+ }
193
+ }
194
+
195
+ if (installed > 0) {
196
+ console.log(`\n${c.green}✓${c.reset} ${installed} skill(s) installed successfully`);
197
+ }
198
+ }
199
+
200
+ async function cmdSearch(positional, flags) {
201
+ const query = positional.join(' ');
202
+ if (!query) {
203
+ fatal('Usage: agentroot search <query>');
204
+ }
205
+
206
+ console.log(`${c.cyan}Searching for "${query}"...${c.reset}\n`);
207
+ const data = await fetchJSON(`${API_BASE}/api/find-skills?q=${encodeURIComponent(query)}`);
208
+
209
+ if (!data.skills || data.skills.length === 0) {
210
+ console.log('No skills found.');
211
+ return;
212
+ }
213
+
214
+ if (flags.json) {
215
+ console.log(JSON.stringify(data.skills, null, 2));
216
+ return;
217
+ }
218
+
219
+ for (const s of data.skills) {
220
+ const id = `${s.domain}/${s.skill_id}`;
221
+ console.log(` ${c.bold}${s.name || s.skill_id}${c.reset} ${c.dim}(${id})${c.reset}`);
222
+ if (s.description) {
223
+ console.log(` ${c.dim}${s.description}${c.reset}`);
224
+ }
225
+ console.log(` ${c.cyan}npx agentroot install ${id}${c.reset}`);
226
+ console.log();
227
+ }
228
+
229
+ console.log(`${c.dim}${data.count} result(s)${c.reset}`);
230
+ }
231
+
232
+ async function cmdList() {
233
+ const results = scanInstalled();
234
+
235
+ if (results.length === 0) {
236
+ console.log('No AgentRoot skills installed.');
237
+ console.log(`${c.dim}Install one: npx agentroot install <domain>/<skill-id>${c.reset}`);
238
+ return;
239
+ }
240
+
241
+ console.log(`${c.bold}Installed AgentRoot Skills${c.reset}\n`);
242
+ for (const r of results) {
243
+ console.log(` ${c.bold}${r.skill_id}${c.reset} ${c.dim}(${r.domain})${c.reset}`);
244
+ console.log(` ${c.dim}tool:${c.reset} ${r.tool} ${c.dim}scope:${c.reset} ${r.scope} ${c.dim}installed:${c.reset} ${r.installed_at ? r.installed_at.split('T')[0] : 'unknown'}`);
245
+ console.log(` ${c.dim}path:${c.reset} ${r.path}`);
246
+ console.log();
247
+ }
248
+
249
+ console.log(`${c.dim}${results.length} skill(s) total${c.reset}`);
250
+ }
251
+
252
+ async function cmdUpdate(positional, flags) {
253
+ if (positional.length === 0) {
254
+ fatal('Usage: agentroot update <domain>/<skill-id> [--tool claude|codex|gemini|cursor|agents]');
255
+ }
256
+
257
+ const input = positional[0];
258
+ const slashIdx = input.indexOf('/');
259
+ if (slashIdx === -1) fatal('Expected format: <domain>/<skill-id>');
260
+
261
+ const domain = input.slice(0, slashIdx);
262
+ const skillId = input.slice(slashIdx + 1);
263
+
264
+ // Find all installed copies
265
+ const updated = [];
266
+ for (const [tool, dirs] of Object.entries(TOOL_DIRS)) {
267
+ if (flags.tool && flags.tool !== tool) continue;
268
+
269
+ const paths = [dirs.global, dirs.project].filter(Boolean);
270
+ for (const baseDir of paths) {
271
+ const manifestPath = path.join(baseDir, skillId, MANIFEST_FILE);
272
+ if (!fs.existsSync(manifestPath)) continue;
273
+
274
+ let manifest;
275
+ try {
276
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
277
+ } catch { continue; }
278
+
279
+ if (manifest.source_domain !== domain) continue;
280
+
281
+ // Re-fetch SKILL.md
282
+ const url = manifest.skill_md_url;
283
+ if (!url) {
284
+ console.log(`${c.yellow}skip${c.reset} ${tool}:${skillId} — no SKILL.md URL in manifest`);
285
+ continue;
286
+ }
287
+
288
+ console.log(`${c.cyan}Updating ${tool}:${skillId}...${c.reset}`);
289
+ let content;
290
+ try {
291
+ content = await fetch(url);
292
+ } catch (err) {
293
+ console.log(`${c.red}fail${c.reset} ${tool}:${skillId} — ${err.message}`);
294
+ continue;
295
+ }
296
+
297
+ const newHash = hashContent(content);
298
+ if (newHash === manifest.version_hash) {
299
+ console.log(`${c.dim}no changes${c.reset} ${tool}:${skillId}`);
300
+ continue;
301
+ }
302
+
303
+ const skillPath = path.join(baseDir, skillId, 'SKILL.md');
304
+ fs.writeFileSync(skillPath, content, 'utf-8');
305
+
306
+ manifest.version_hash = newHash;
307
+ manifest.updated_at = new Date().toISOString();
308
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
309
+
310
+ console.log(`${c.green}updated${c.reset} ${c.bold}${tool}:${skillId}${c.reset}`);
311
+ updated.push(`${tool}:${skillId}`);
312
+ }
313
+ }
314
+
315
+ if (updated.length === 0) {
316
+ console.log(`No installed copies found for ${domain}/${skillId}.`);
317
+ console.log(`${c.dim}Install it first: npx agentroot install ${domain}/${skillId}${c.reset}`);
318
+ } else {
319
+ console.log(`\n${c.green}✓${c.reset} ${updated.length} skill(s) updated`);
320
+ }
321
+ }
322
+
323
+ async function cmdUninstall(positional, flags) {
324
+ if (positional.length === 0) {
325
+ fatal('Usage: agentroot uninstall <skill-id> [--tool claude|codex|gemini|cursor|agents]');
326
+ }
327
+
328
+ const skillId = positional[0];
329
+ // Remove domain prefix if provided
330
+ const actualId = skillId.includes('/') ? skillId.split('/').pop() : skillId;
331
+ let removed = 0;
332
+
333
+ for (const [tool, dirs] of Object.entries(TOOL_DIRS)) {
334
+ if (flags.tool && flags.tool !== tool) continue;
335
+
336
+ const paths = [dirs.global, dirs.project].filter(Boolean);
337
+ for (const baseDir of paths) {
338
+ const skillDir = path.join(baseDir, actualId);
339
+ const manifestPath = path.join(skillDir, MANIFEST_FILE);
340
+
341
+ // Only remove if it has our manifest (don't delete non-agentroot skills)
342
+ if (!fs.existsSync(manifestPath)) continue;
343
+
344
+ fs.rmSync(skillDir, { recursive: true, force: true });
345
+ console.log(`${c.green}removed${c.reset} ${tool}:${actualId} (${skillDir})`);
346
+ removed++;
347
+ }
348
+ }
349
+
350
+ if (removed === 0) {
351
+ console.log(`No installed AgentRoot skill found with id: ${actualId}`);
352
+ } else {
353
+ console.log(`\n${c.green}✓${c.reset} ${removed} skill(s) removed`);
354
+ }
355
+ }
356
+
357
+ // ─── Help ──────────────────────────────────────────────────────────────────
358
+
359
+ function showHelp() {
360
+ console.log(`
361
+ ${c.bold}agentroot${c.reset} — Install AI agent skills in one command
362
+
363
+ ${c.bold}USAGE${c.reset}
364
+ npx agentroot <command> [options]
365
+
366
+ ${c.bold}COMMANDS${c.reset}
367
+ ${c.cyan}install${c.reset} <domain>/<skill-id> Download and install a skill
368
+ ${c.cyan}search${c.reset} <query> Search the AgentRoot registry
369
+ ${c.cyan}list${c.reset} Show installed AgentRoot skills
370
+ ${c.cyan}update${c.reset} <domain>/<skill-id> Re-fetch a skill from source
371
+ ${c.cyan}uninstall${c.reset} <skill-id> Remove an installed skill
372
+
373
+ ${c.bold}OPTIONS${c.reset}
374
+ --tool <name> Target tool: claude, codex, gemini, cursor, agents
375
+ --project Install to project directory (not global)
376
+ --all Install all skills from a domain
377
+ --json Output search results as JSON
378
+
379
+ ${c.bold}EXAMPLES${c.reset}
380
+ npx agentroot install stripe.com/payments --tool claude
381
+ npx agentroot install stripe.com --all
382
+ npx agentroot search "github actions"
383
+ npx agentroot list
384
+ npx agentroot update stripe.com/payments
385
+ npx agentroot uninstall payments
386
+
387
+ ${c.bold}AUTO-DETECT${c.reset}
388
+ When --tool is omitted, agentroot detects which AI tools are installed
389
+ by checking for ~/.claude/, ~/.codex/, ~/.gemini/, .cursor/ directories.
390
+ Falls back to .agents/skills/ (cross-tool standard) if none found.
391
+ `);
392
+ }
393
+
394
+ // ─── Main ──────────────────────────────────────────────────────────────────
395
+
396
+ async function main() {
397
+ const { cmd, positional, flags } = parseArgs(process.argv);
398
+
399
+ if (!cmd || cmd === 'help' || flags.help) {
400
+ showHelp();
401
+ return;
402
+ }
403
+
404
+ try {
405
+ switch (cmd) {
406
+ case 'install': case 'i':
407
+ await cmdInstall(positional, flags);
408
+ break;
409
+ case 'search': case 's':
410
+ await cmdSearch(positional, flags);
411
+ break;
412
+ case 'list': case 'ls':
413
+ await cmdList(positional, flags);
414
+ break;
415
+ case 'update': case 'up':
416
+ await cmdUpdate(positional, flags);
417
+ break;
418
+ case 'uninstall': case 'rm': case 'remove':
419
+ await cmdUninstall(positional, flags);
420
+ break;
421
+ default:
422
+ fatal(`Unknown command: ${cmd}. Run "agentroot help" for usage.`);
423
+ }
424
+ } catch (err) {
425
+ if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED') {
426
+ fatal('Could not connect to agentroot.io. Check your internet connection.');
427
+ }
428
+ fatal(err.message);
429
+ }
430
+ }
431
+
432
+ main();
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "agent-root",
3
+ "version": "0.1.0",
4
+ "description": "Install AI agent skills in one command. Works with Claude Code, Codex CLI, Gemini CLI, Cursor, and more.",
5
+ "bin": {
6
+ "agentroot": "./bin/agentroot.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "lib/"
11
+ ],
12
+ "keywords": [
13
+ "ai",
14
+ "agent",
15
+ "skills",
16
+ "claude",
17
+ "codex",
18
+ "gemini",
19
+ "cursor",
20
+ "cli"
21
+ ],
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/d3-inc/agentroot"
26
+ },
27
+ "dependencies": {
28
+ "@agent-root/core": "^0.1.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=16"
32
+ }
33
+ }