@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 +23 -0
- package/package.json +1 -1
- package/src/commands/skill.ts +204 -0
- package/src/index.ts +2 -0
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
|
@@ -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();
|