@plosson/agentio 0.1.17 → 0.1.18

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/README.md CHANGED
@@ -141,6 +141,29 @@ agentio provides a plugin for [Claude Code](https://claude.com/claude-code) with
141
141
 
142
142
  Once installed, Claude Code can use the agentio CLI skills to help you manage emails, send Telegram messages, and more.
143
143
 
144
+ ### Install Skills Directly
145
+
146
+ You can also install skills directly without the plugin system:
147
+
148
+ ```bash
149
+ # List available skills
150
+ agentio skill list
151
+
152
+ # Install all skills to current directory
153
+ agentio skill install
154
+
155
+ # Install a specific skill
156
+ agentio skill install agentio-gmail
157
+
158
+ # Install to a specific directory
159
+ agentio skill install -d ~/myproject
160
+
161
+ # Skip confirmation prompts
162
+ agentio skill install -y
163
+ ```
164
+
165
+ Skills are installed to `.claude/skills/` in the target directory.
166
+
144
167
  ## Design
145
168
 
146
169
  agentio is designed for LLM consumption:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plosson/agentio",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "CLI for LLM agents to interact with communication and tracking services",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,204 @@
1
+ import { Command } from 'commander';
2
+ import { createInterface } from 'readline';
3
+ import { CliError, handleError } from '../utils/errors';
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+
7
+ const GITHUB_REPO = 'plosson/agentio';
8
+ const SKILLS_PATH = 'claude/skills';
9
+
10
+ interface GitHubContent {
11
+ name: string;
12
+ path: string;
13
+ type: 'file' | 'dir';
14
+ download_url: string | null;
15
+ }
16
+
17
+ function prompt(question: string): Promise<string> {
18
+ const rl = createInterface({
19
+ input: process.stdin,
20
+ output: process.stderr,
21
+ });
22
+
23
+ return new Promise((resolve) => {
24
+ rl.question(question, (answer) => {
25
+ rl.close();
26
+ resolve(answer.trim());
27
+ });
28
+ });
29
+ }
30
+
31
+ async function fetchGitHubContents(repoPath: string): Promise<GitHubContent[]> {
32
+ const url = `https://api.github.com/repos/${GITHUB_REPO}/contents/${repoPath}`;
33
+ const response = await fetch(url, {
34
+ headers: {
35
+ 'Accept': 'application/vnd.github.v3+json',
36
+ 'User-Agent': 'agentio-skill-manager',
37
+ },
38
+ });
39
+
40
+ if (!response.ok) {
41
+ if (response.status === 404) {
42
+ throw new CliError('NOT_FOUND', `Path not found: ${repoPath}`);
43
+ }
44
+ throw new CliError('API_ERROR', `GitHub API error: ${response.statusText}`);
45
+ }
46
+
47
+ return response.json();
48
+ }
49
+
50
+ async function fetchFileContent(downloadUrl: string): Promise<string> {
51
+ const response = await fetch(downloadUrl, {
52
+ headers: {
53
+ 'User-Agent': 'agentio-skill-manager',
54
+ },
55
+ });
56
+
57
+ if (!response.ok) {
58
+ throw new CliError('API_ERROR', `Failed to download file: ${response.statusText}`);
59
+ }
60
+
61
+ return response.text();
62
+ }
63
+
64
+ async function listAvailableSkills(): Promise<string[]> {
65
+ const contents = await fetchGitHubContents(SKILLS_PATH);
66
+ return contents
67
+ .filter((item) => item.type === 'dir')
68
+ .map((item) => item.name);
69
+ }
70
+
71
+ async function downloadSkillFolder(
72
+ skillName: string,
73
+ targetDir: string
74
+ ): Promise<void> {
75
+ const skillPath = `${SKILLS_PATH}/${skillName}`;
76
+ const contents = await fetchGitHubContents(skillPath);
77
+
78
+ // Create target directory
79
+ fs.mkdirSync(targetDir, { recursive: true });
80
+
81
+ for (const item of contents) {
82
+ const targetPath = path.join(targetDir, item.name);
83
+
84
+ if (item.type === 'file' && item.download_url) {
85
+ const content = await fetchFileContent(item.download_url);
86
+ fs.writeFileSync(targetPath, content);
87
+ } else if (item.type === 'dir') {
88
+ // Recursively download subdirectories
89
+ await downloadSkillFolder(`${skillName}/${item.name}`, targetPath);
90
+ }
91
+ }
92
+ }
93
+
94
+ async function installSkill(
95
+ skillName: string,
96
+ baseDir: string,
97
+ skipPrompt: boolean
98
+ ): Promise<boolean> {
99
+ const targetDir = path.join(baseDir, '.claude', 'skills', skillName);
100
+
101
+ // Check if skill already exists
102
+ if (fs.existsSync(targetDir)) {
103
+ if (!skipPrompt) {
104
+ const answer = await prompt(
105
+ `Skill '${skillName}' already exists at ${targetDir}. Update? [y/N] `
106
+ );
107
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
108
+ console.error(`Skipping '${skillName}'`);
109
+ return false;
110
+ }
111
+ }
112
+ // Remove existing skill directory before updating
113
+ fs.rmSync(targetDir, { recursive: true });
114
+ }
115
+
116
+ console.error(`Installing skill: ${skillName}...`);
117
+ await downloadSkillFolder(skillName, targetDir);
118
+ console.log(`Installed: ${skillName} -> ${targetDir}`);
119
+ return true;
120
+ }
121
+
122
+ export function registerSkillCommands(program: Command): void {
123
+ const skill = program
124
+ .command('skill')
125
+ .description('Manage Claude Code skills');
126
+
127
+ skill
128
+ .command('list')
129
+ .description('List available skills from the repository')
130
+ .action(async () => {
131
+ try {
132
+ console.error('Fetching available skills...');
133
+ const skills = await listAvailableSkills();
134
+
135
+ if (skills.length === 0) {
136
+ console.log('No skills found in repository');
137
+ return;
138
+ }
139
+
140
+ console.log('Available skills:');
141
+ for (const name of skills) {
142
+ console.log(` ${name}`);
143
+ }
144
+ } catch (error) {
145
+ handleError(error);
146
+ }
147
+ });
148
+
149
+ skill
150
+ .command('install')
151
+ .description('Install skills from the repository')
152
+ .argument('[skill-name]', 'Name of the skill to install (omit to install all)')
153
+ .option('-d, --dir <path>', 'Target directory (default: current directory)')
154
+ .option('-y, --yes', 'Skip confirmation prompts')
155
+ .action(async (skillName, options) => {
156
+ try {
157
+ const baseDir = options.dir ? path.resolve(options.dir) : process.cwd();
158
+
159
+ // Verify base directory exists
160
+ if (!fs.existsSync(baseDir)) {
161
+ throw new CliError(
162
+ 'INVALID_PARAMS',
163
+ `Directory does not exist: ${baseDir}`
164
+ );
165
+ }
166
+
167
+ console.error(`Target: ${path.join(baseDir, '.claude', 'skills')}`);
168
+
169
+ if (skillName) {
170
+ // Install specific skill
171
+ const skills = await listAvailableSkills();
172
+ if (!skills.includes(skillName)) {
173
+ throw new CliError(
174
+ 'NOT_FOUND',
175
+ `Skill '${skillName}' not found`,
176
+ `Available skills: ${skills.join(', ')}`
177
+ );
178
+ }
179
+ await installSkill(skillName, baseDir, options.yes);
180
+ } else {
181
+ // Install all skills
182
+ console.error('Fetching available skills...');
183
+ const skills = await listAvailableSkills();
184
+
185
+ if (skills.length === 0) {
186
+ console.log('No skills found in repository');
187
+ return;
188
+ }
189
+
190
+ console.error(`Found ${skills.length} skill(s)`);
191
+
192
+ let installed = 0;
193
+ for (const name of skills) {
194
+ const success = await installSkill(name, baseDir, options.yes);
195
+ if (success) installed++;
196
+ }
197
+
198
+ console.log(`\nInstalled ${installed} of ${skills.length} skill(s)`);
199
+ }
200
+ } catch (error) {
201
+ handleError(error);
202
+ }
203
+ });
204
+ }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import { registerJiraCommands } from './commands/jira';
7
7
  import { registerSlackCommands } from './commands/slack';
8
8
  import { registerUpdateCommand } from './commands/update';
9
9
  import { registerConfigCommands } from './commands/config';
10
+ import { registerSkillCommands } from './commands/skill';
10
11
 
11
12
  declare const BUILD_VERSION: string | undefined;
12
13
 
@@ -32,5 +33,6 @@ registerJiraCommands(program);
32
33
  registerSlackCommands(program);
33
34
  registerUpdateCommand(program);
34
35
  registerConfigCommands(program);
36
+ registerSkillCommands(program);
35
37
 
36
38
  program.parse();