@almyty/skills 1.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/__tests__/client.test.d.ts +1 -0
- package/dist/__tests__/client.test.js +145 -0
- package/dist/agents.d.ts +37 -0
- package/dist/agents.js +123 -0
- package/dist/auth.d.ts +41 -0
- package/dist/auth.js +117 -0
- package/dist/client.d.ts +34 -0
- package/dist/client.js +128 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +52 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +502 -0
- package/dist/installer.d.ts +24 -0
- package/dist/installer.js +55 -0
- package/dist/meta-skill.d.ts +2 -0
- package/dist/meta-skill.js +53 -0
- package/package.json +40 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join, resolve } from 'path';
|
|
4
|
+
import { detectAgents, getDefaultTargets, filterAgents } from './agents.js';
|
|
5
|
+
export function loadConfig(projectDir) {
|
|
6
|
+
const envDir = process.env.APIFAI_SKILLS_DIR;
|
|
7
|
+
if (envDir) {
|
|
8
|
+
return { skillsDir: envDir };
|
|
9
|
+
}
|
|
10
|
+
const cwd = projectDir || process.cwd();
|
|
11
|
+
const projectRc = join(cwd, '.almytyrc');
|
|
12
|
+
if (existsSync(projectRc)) {
|
|
13
|
+
return parseRcFile(projectRc);
|
|
14
|
+
}
|
|
15
|
+
const homeRc = join(homedir(), '.almytyrc');
|
|
16
|
+
if (existsSync(homeRc)) {
|
|
17
|
+
return parseRcFile(homeRc);
|
|
18
|
+
}
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
function parseRcFile(path) {
|
|
22
|
+
try {
|
|
23
|
+
const data = readFileSync(path, 'utf-8');
|
|
24
|
+
const parsed = JSON.parse(data);
|
|
25
|
+
return {
|
|
26
|
+
skillsDir: parsed.skillsDir,
|
|
27
|
+
agents: parsed.agents,
|
|
28
|
+
url: parsed.url,
|
|
29
|
+
interval: parsed.interval,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function resolveTargets(projectDir, config) {
|
|
37
|
+
if (config.skillsDir) {
|
|
38
|
+
return [{
|
|
39
|
+
name: 'custom',
|
|
40
|
+
configDir: config.skillsDir,
|
|
41
|
+
skillsDir: resolve(config.skillsDir),
|
|
42
|
+
}];
|
|
43
|
+
}
|
|
44
|
+
let targets = detectAgents(projectDir);
|
|
45
|
+
if (targets.length === 0) {
|
|
46
|
+
targets = getDefaultTargets(projectDir);
|
|
47
|
+
}
|
|
48
|
+
if (config.agents) {
|
|
49
|
+
targets = filterAgents(targets, config.agents);
|
|
50
|
+
}
|
|
51
|
+
return targets;
|
|
52
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { login, logout, resolveAuth } from './auth.js';
|
|
3
|
+
import { AlmytyClient, parseRef } from './client.js';
|
|
4
|
+
import { getAllTargets } from './agents.js';
|
|
5
|
+
import { installSkills, removeSkills, listInstalledSkills } from './installer.js';
|
|
6
|
+
import { loadConfig, resolveTargets } from './config.js';
|
|
7
|
+
import { generateMetaSkill } from './meta-skill.js';
|
|
8
|
+
const VERSION = '1.0.0';
|
|
9
|
+
function printHelp() {
|
|
10
|
+
console.log(`
|
|
11
|
+
almyty Skills CLI v${VERSION}
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
npx @almyty/skills <command> [options]
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
login Authenticate with almyty
|
|
18
|
+
logout Remove stored credentials
|
|
19
|
+
daemon Start skill daemon (syncs all skills)
|
|
20
|
+
install <ref> Install skills
|
|
21
|
+
list [ref] List available skills
|
|
22
|
+
search <query> Search for skills
|
|
23
|
+
run <ref> [--key value ...] Execute a skill
|
|
24
|
+
installed Show locally installed skills
|
|
25
|
+
remove Remove all installed skills
|
|
26
|
+
gateways List your gateways
|
|
27
|
+
|
|
28
|
+
References:
|
|
29
|
+
@org/gateway All skills from a gateway
|
|
30
|
+
@org/gateway/skill A specific skill
|
|
31
|
+
skill-name Search by name
|
|
32
|
+
<uuid> Direct ID reference
|
|
33
|
+
|
|
34
|
+
Config:
|
|
35
|
+
.almytyrc JSON config file (project or home dir)
|
|
36
|
+
APIFAI_SKILLS_DIR Override skill installation directory
|
|
37
|
+
APIFAI_URL Override API URL
|
|
38
|
+
APIFAI_TOKEN Override auth token
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
--interval, -i <seconds> Daemon poll interval in seconds (default: 60)
|
|
42
|
+
--url <url> almyty API URL (default: https://api.almyty.com)
|
|
43
|
+
--dir <path> Project directory (default: current directory)
|
|
44
|
+
--help, -h Show help
|
|
45
|
+
--version, -v Show version
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
npx @almyty/skills daemon
|
|
49
|
+
npx @almyty/skills install @myorg/petstore/get-pet
|
|
50
|
+
npx @almyty/skills search "weather"
|
|
51
|
+
npx @almyty/skills run @myorg/petstore/get-pet --petId 123
|
|
52
|
+
`);
|
|
53
|
+
}
|
|
54
|
+
function parseArgs(argv) {
|
|
55
|
+
const result = { positional: [], flags: {} };
|
|
56
|
+
let i = 0;
|
|
57
|
+
while (i < argv.length) {
|
|
58
|
+
const arg = argv[i];
|
|
59
|
+
if (arg === '--gateway' || arg === '-g') {
|
|
60
|
+
result.flags.gateway = argv[++i] || '';
|
|
61
|
+
}
|
|
62
|
+
else if (arg === '--url') {
|
|
63
|
+
result.flags.url = argv[++i] || '';
|
|
64
|
+
}
|
|
65
|
+
else if (arg === '--dir') {
|
|
66
|
+
result.flags.dir = argv[++i] || '';
|
|
67
|
+
}
|
|
68
|
+
else if (arg === '--interval' || arg === '-i') {
|
|
69
|
+
result.flags.interval = argv[++i] || '60';
|
|
70
|
+
}
|
|
71
|
+
else if (arg === '--help' || arg === '-h') {
|
|
72
|
+
result.flags.help = true;
|
|
73
|
+
}
|
|
74
|
+
else if (arg === '--version' || arg === '-v') {
|
|
75
|
+
result.flags.version = true;
|
|
76
|
+
}
|
|
77
|
+
else if (arg.startsWith('@')) {
|
|
78
|
+
result.ref = arg;
|
|
79
|
+
}
|
|
80
|
+
else if (arg.startsWith('--')) {
|
|
81
|
+
const key = arg.slice(2);
|
|
82
|
+
const next = argv[i + 1];
|
|
83
|
+
if (next && !next.startsWith('--')) {
|
|
84
|
+
result.flags[key] = next;
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
result.flags[key] = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (!result.command) {
|
|
92
|
+
result.command = arg;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
result.positional.push(arg);
|
|
96
|
+
}
|
|
97
|
+
i++;
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
function getRef(args) {
|
|
102
|
+
if (args.ref)
|
|
103
|
+
return args.ref;
|
|
104
|
+
if (args.flags.gateway)
|
|
105
|
+
return args.flags.gateway;
|
|
106
|
+
if (args.positional.length > 0)
|
|
107
|
+
return args.positional[0];
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function requireRef(args, command) {
|
|
111
|
+
const ref = getRef(args);
|
|
112
|
+
if (!ref) {
|
|
113
|
+
console.error('Error: reference required');
|
|
114
|
+
console.error(` npx @almyty/skills ${command} @<org>/<gateway>`);
|
|
115
|
+
console.error(` npx @almyty/skills ${command} <skill-name>`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
return ref;
|
|
119
|
+
}
|
|
120
|
+
function parseRunParams(args) {
|
|
121
|
+
const params = {};
|
|
122
|
+
const entries = Object.entries(args.flags);
|
|
123
|
+
for (const [key, value] of entries) {
|
|
124
|
+
if (['url', 'dir', 'help', 'version', 'interval', 'gateway'].includes(key))
|
|
125
|
+
continue;
|
|
126
|
+
params[key] = value;
|
|
127
|
+
}
|
|
128
|
+
return params;
|
|
129
|
+
}
|
|
130
|
+
async function main() {
|
|
131
|
+
const args = parseArgs(process.argv.slice(2));
|
|
132
|
+
if (args.flags.version) {
|
|
133
|
+
console.log(VERSION);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (args.flags.help || !args.command) {
|
|
137
|
+
printHelp();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const command = args.command;
|
|
141
|
+
const projectDir = args.flags.dir || process.cwd();
|
|
142
|
+
const config = loadConfig(projectDir);
|
|
143
|
+
const urlOverride = args.flags.url || config.url;
|
|
144
|
+
switch (command) {
|
|
145
|
+
case 'login': {
|
|
146
|
+
const url = urlOverride || process.env.APIFAI_URL || 'https://api.almyty.com';
|
|
147
|
+
await login(url);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case 'logout': {
|
|
151
|
+
logout();
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case 'gateways': {
|
|
155
|
+
const { url, token } = resolveAuth();
|
|
156
|
+
const client = new AlmytyClient(urlOverride || url, token);
|
|
157
|
+
const gateways = await client.listGateways();
|
|
158
|
+
if (gateways.length === 0) {
|
|
159
|
+
console.log('No gateways found. Create one at https://app.almyty.com/gateways');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
console.log('\nYour gateways:\n');
|
|
163
|
+
for (const gw of gateways) {
|
|
164
|
+
const slug = gw.name.toLowerCase().replace(/\s+/g, '-');
|
|
165
|
+
console.log(` ${gw.name}`);
|
|
166
|
+
console.log(` Type: ${gw.type}`);
|
|
167
|
+
console.log(` Use: npx @almyty/skills install @<org>/${slug}`);
|
|
168
|
+
console.log('');
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case 'list': {
|
|
173
|
+
const ref = getRef(args);
|
|
174
|
+
const { url, token } = resolveAuth();
|
|
175
|
+
const client = new AlmytyClient(urlOverride || url, token);
|
|
176
|
+
if (!ref) {
|
|
177
|
+
const allSkills = await client.fetchAllSkills();
|
|
178
|
+
if (!allSkills || allSkills.length === 0) {
|
|
179
|
+
console.log('No skills available. Assign tools to your gateways first.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
console.log(`\n${allSkills.length} skills available:\n`);
|
|
183
|
+
for (const skill of allSkills) {
|
|
184
|
+
const label = skill.gateway ? `@${skill.orgSlug}/${skill.gatewaySlug}/${skill.name}` : skill.name;
|
|
185
|
+
const desc = skill.description ? ` — ${skill.description}` : '';
|
|
186
|
+
console.log(` ${label}${desc}`);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const parsed = parseRef(ref);
|
|
191
|
+
if (parsed.type === 'gateway' || parsed.type === 'uuid') {
|
|
192
|
+
const skills = await client.fetchSkills(ref);
|
|
193
|
+
if (skills.length === 0) {
|
|
194
|
+
console.log('No skills available. Assign tools to your gateway first.');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
console.log(`\n${skills.length} skills available:\n`);
|
|
198
|
+
for (const skill of skills) {
|
|
199
|
+
console.log(` ${skill.name}`);
|
|
200
|
+
}
|
|
201
|
+
console.log(`\nInstall: npx @almyty/skills install ${ref}`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const allSkills = await client.fetchAllSkills();
|
|
205
|
+
if (!allSkills || allSkills.length === 0) {
|
|
206
|
+
console.log('No skills available.');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
console.log(`\n${allSkills.length} skills available:\n`);
|
|
210
|
+
for (const skill of allSkills) {
|
|
211
|
+
const label = skill.gateway ? `@${skill.orgSlug}/${skill.gatewaySlug}/${skill.name}` : skill.name;
|
|
212
|
+
const desc = skill.description ? ` — ${skill.description}` : '';
|
|
213
|
+
console.log(` ${label}${desc}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case 'search': {
|
|
219
|
+
const query = getRef(args) || args.positional[0];
|
|
220
|
+
if (!query) {
|
|
221
|
+
console.error('Error: search query required');
|
|
222
|
+
console.error(' npx @almyty/skills search <query>');
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
const { url, token } = resolveAuth();
|
|
226
|
+
const client = new AlmytyClient(urlOverride || url, token);
|
|
227
|
+
const results = await client.searchSkills(query);
|
|
228
|
+
if (!results || results.length === 0) {
|
|
229
|
+
console.log(`No skills found for "${query}".`);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
console.log(`\nFound ${results.length} skill(s):\n`);
|
|
233
|
+
for (const skill of results) {
|
|
234
|
+
const label = skill.skillRef || skill.toolName || skill.name;
|
|
235
|
+
const desc = skill.toolDescription ? ` — ${skill.toolDescription}` : '';
|
|
236
|
+
console.log(` ${label}${desc}`);
|
|
237
|
+
}
|
|
238
|
+
console.log(`\nInstall: npx @almyty/skills install <ref>`);
|
|
239
|
+
console.log(`Run: npx @almyty/skills run <ref>`);
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
case 'install': {
|
|
243
|
+
const ref = requireRef(args, 'install');
|
|
244
|
+
const { url, token } = resolveAuth();
|
|
245
|
+
const client = new AlmytyClient(urlOverride || url, token);
|
|
246
|
+
const parsed = parseRef(ref);
|
|
247
|
+
let skills = [];
|
|
248
|
+
let gwName = ref;
|
|
249
|
+
if (parsed.type === 'gateway' || parsed.type === 'uuid') {
|
|
250
|
+
console.log('Fetching skills...');
|
|
251
|
+
const [gateway, fetched] = await Promise.all([
|
|
252
|
+
client.fetchGateway(ref).catch(() => null),
|
|
253
|
+
client.fetchSkills(ref),
|
|
254
|
+
]);
|
|
255
|
+
skills = fetched;
|
|
256
|
+
gwName = gateway?.name || ref;
|
|
257
|
+
}
|
|
258
|
+
else if (parsed.type === 'skill') {
|
|
259
|
+
console.log('Fetching skill...');
|
|
260
|
+
const gatewayRef = `@${parsed.orgSlug}/${parsed.gatewaySlug}`;
|
|
261
|
+
const fetched = await client.fetchSkills(gatewayRef);
|
|
262
|
+
const match = fetched.find(s => s.name === parsed.skillName ||
|
|
263
|
+
s.fileName === `almyty-${parsed.skillName}`);
|
|
264
|
+
if (!match) {
|
|
265
|
+
console.error(`Skill "${parsed.skillName}" not found in ${gatewayRef}`);
|
|
266
|
+
const available = fetched.map(s => s.name).join(', ');
|
|
267
|
+
if (available)
|
|
268
|
+
console.error(`Available: ${available}`);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
skills = [match];
|
|
272
|
+
gwName = `${gatewayRef}/${parsed.skillName}`;
|
|
273
|
+
}
|
|
274
|
+
else if (parsed.type === 'search') {
|
|
275
|
+
console.log(`Searching for "${ref}"...`);
|
|
276
|
+
const results = await client.searchSkills(ref);
|
|
277
|
+
if (!results || results.length === 0) {
|
|
278
|
+
console.error(`No skills found for "${ref}".`);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
if (results.length === 1) {
|
|
282
|
+
const match = results[0];
|
|
283
|
+
const fetched = await client.fetchSkills(match.gatewayId);
|
|
284
|
+
const toolSlug = match.toolName?.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
285
|
+
const skill = fetched.find((s) => s.name === toolSlug || s.name === match.toolName);
|
|
286
|
+
if (skill) {
|
|
287
|
+
skills = [skill];
|
|
288
|
+
gwName = match.skillRef || match.toolName;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
skills = fetched;
|
|
292
|
+
gwName = match.gatewayName;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
console.log(`\nMultiple matches for "${ref}":\n`);
|
|
297
|
+
for (const r of results) {
|
|
298
|
+
const label = r.skillRef || r.toolName;
|
|
299
|
+
const desc = r.toolDescription ? ` — ${r.toolDescription}` : '';
|
|
300
|
+
console.log(` ${label}${desc}`);
|
|
301
|
+
}
|
|
302
|
+
console.log(`\nBe more specific: npx @almyty/skills install @org/gateway/skill`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (skills.length === 0) {
|
|
307
|
+
console.log('No skills found.');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
console.log(`\n${gwName} (${skills.length} skill(s))`);
|
|
311
|
+
const targets = resolveTargets(projectDir, config);
|
|
312
|
+
const results = targets.map(target => installSkills(skills, target));
|
|
313
|
+
console.log('');
|
|
314
|
+
for (const result of results) {
|
|
315
|
+
console.log(` ${result.agent}: ${result.installed} skills -> ${result.skillsDir}`);
|
|
316
|
+
}
|
|
317
|
+
const totalInstalled = results.reduce((sum, r) => sum + r.installed, 0);
|
|
318
|
+
console.log(`\nInstalled ${totalInstalled} skill files across ${results.length} agent(s).`);
|
|
319
|
+
console.log('Skills will be automatically loaded by your AI coding agent.');
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case 'run': {
|
|
323
|
+
const ref = requireRef(args, 'run');
|
|
324
|
+
const { url, token } = resolveAuth();
|
|
325
|
+
const client = new AlmytyClient(urlOverride || url, token);
|
|
326
|
+
const parsed = parseRef(ref);
|
|
327
|
+
let gatewayId;
|
|
328
|
+
let toolId;
|
|
329
|
+
if (parsed.type === 'skill' && parsed.orgSlug && parsed.gatewaySlug && parsed.skillName) {
|
|
330
|
+
const gateway = await client.resolveGateway(parsed.orgSlug, parsed.gatewaySlug);
|
|
331
|
+
gatewayId = gateway.id;
|
|
332
|
+
toolId = parsed.skillName;
|
|
333
|
+
}
|
|
334
|
+
else if (parsed.type === 'search') {
|
|
335
|
+
const results = await client.searchSkills(ref);
|
|
336
|
+
if (!results || results.length === 0) {
|
|
337
|
+
console.error(`No skill found for "${ref}".`);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
if (results.length > 1) {
|
|
341
|
+
console.error(`Multiple matches for "${ref}". Be more specific:`);
|
|
342
|
+
for (const r of results) {
|
|
343
|
+
console.error(` ${r.skillRef || r.toolName}`);
|
|
344
|
+
}
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
gatewayId = results[0].gatewayId;
|
|
348
|
+
toolId = results[0].toolId;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
console.error('Error: run requires a skill reference (@org/gateway/skill or skill-name)');
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
const params = parseRunParams(args);
|
|
355
|
+
const result = await client.executeSkill(gatewayId, toolId, params);
|
|
356
|
+
console.log(JSON.stringify(result, null, 2));
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case 'daemon': {
|
|
360
|
+
const intervalSec = parseInt(args.flags.interval, 10) || config.interval || 60;
|
|
361
|
+
const { url, token } = resolveAuth();
|
|
362
|
+
const client = new AlmytyClient(urlOverride || url, token);
|
|
363
|
+
const targets = resolveTargets(projectDir, config);
|
|
364
|
+
if (targets.length === 0) {
|
|
365
|
+
console.error('No agent targets found.');
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
console.log(`almyty skill daemon (every ${intervalSec}s)`);
|
|
369
|
+
console.log(`Syncing to ${targets.length} agent target(s):`);
|
|
370
|
+
for (const t of targets) {
|
|
371
|
+
console.log(` ${t.name}: ${t.skillsDir}`);
|
|
372
|
+
}
|
|
373
|
+
console.log('\nPress Ctrl+C to stop.\n');
|
|
374
|
+
let lastHash = '';
|
|
375
|
+
const sync = async () => {
|
|
376
|
+
try {
|
|
377
|
+
const allSkills = await client.fetchAllSkills();
|
|
378
|
+
const metaSkill = generateMetaSkill();
|
|
379
|
+
const skills = [metaSkill, ...(allSkills || [])];
|
|
380
|
+
const currentHash = skills.map(s => `${s.name}:${s.content.length}`).join('|');
|
|
381
|
+
if (currentHash !== lastHash) {
|
|
382
|
+
lastHash = currentHash;
|
|
383
|
+
const ts = new Date().toLocaleTimeString();
|
|
384
|
+
for (const target of targets) {
|
|
385
|
+
installSkills(skills, target);
|
|
386
|
+
}
|
|
387
|
+
console.log(`[${ts}] Synced ${skills.length} skills to ${targets.length} agent(s).`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
const ts = new Date().toLocaleTimeString();
|
|
392
|
+
console.error(`[${ts}] Sync error: ${err.message}`);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
await sync();
|
|
396
|
+
const interval = setInterval(sync, intervalSec * 1000);
|
|
397
|
+
const shutdown = () => {
|
|
398
|
+
clearInterval(interval);
|
|
399
|
+
console.log('\nDaemon stopped.');
|
|
400
|
+
process.exit(0);
|
|
401
|
+
};
|
|
402
|
+
process.on('SIGINT', shutdown);
|
|
403
|
+
process.on('SIGTERM', shutdown);
|
|
404
|
+
await new Promise(() => { });
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
case 'installed': {
|
|
408
|
+
const targets = resolveTargets(projectDir, config);
|
|
409
|
+
let totalFound = 0;
|
|
410
|
+
for (const target of targets) {
|
|
411
|
+
const installed = listInstalledSkills(target);
|
|
412
|
+
if (installed.length > 0) {
|
|
413
|
+
console.log(`\n${target.name} (${target.skillsDir}):`);
|
|
414
|
+
for (const name of installed) {
|
|
415
|
+
console.log(` ${name}`);
|
|
416
|
+
}
|
|
417
|
+
totalFound += installed.length;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (totalFound === 0) {
|
|
421
|
+
console.log('No almyty skills installed in this directory.');
|
|
422
|
+
console.log('Install: npx @almyty/skills install @<org>/<gateway>');
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case 'remove': {
|
|
427
|
+
const targets = resolveTargets(projectDir, config);
|
|
428
|
+
let totalRemoved = 0;
|
|
429
|
+
for (const target of targets) {
|
|
430
|
+
const removed = removeSkills(target);
|
|
431
|
+
if (removed > 0) {
|
|
432
|
+
console.log(` Removed ${removed} skills from ${target.skillsDir}`);
|
|
433
|
+
totalRemoved += removed;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (totalRemoved === 0) {
|
|
437
|
+
console.log('No almyty skills found to remove.');
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
console.log(`\nRemoved ${totalRemoved} skill(s) total.`);
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
case 'watch': {
|
|
445
|
+
const ref = requireRef(args, 'watch');
|
|
446
|
+
const intervalSec = parseInt(args.flags.interval, 10) || config.interval || 60;
|
|
447
|
+
const { url, token } = resolveAuth();
|
|
448
|
+
const client = new AlmytyClient(urlOverride || url, token);
|
|
449
|
+
const gateway = await client.fetchGateway(ref).catch(() => null);
|
|
450
|
+
const gwName = gateway?.name || ref;
|
|
451
|
+
const targets = getAllTargets(projectDir);
|
|
452
|
+
console.log(`Watching ${gwName} (every ${intervalSec}s)`);
|
|
453
|
+
console.log(`Syncing to ${targets.length} agent target(s):`);
|
|
454
|
+
for (const t of targets) {
|
|
455
|
+
console.log(` ${t.name}: ${t.skillsDir}`);
|
|
456
|
+
}
|
|
457
|
+
console.log('\nPress Ctrl+C to stop.\n');
|
|
458
|
+
let lastHash = '';
|
|
459
|
+
const sync = async () => {
|
|
460
|
+
try {
|
|
461
|
+
const skills = await client.fetchSkills(ref);
|
|
462
|
+
const currentHash = skills.map(s => `${s.name}:${s.content.length}`).join('|');
|
|
463
|
+
if (currentHash !== lastHash) {
|
|
464
|
+
lastHash = currentHash;
|
|
465
|
+
const ts = new Date().toLocaleTimeString();
|
|
466
|
+
if (skills.length === 0) {
|
|
467
|
+
console.log(`[${ts}] No skills available.`);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
for (const target of targets) {
|
|
471
|
+
installSkills(skills, target);
|
|
472
|
+
}
|
|
473
|
+
console.log(`[${ts}] Synced ${skills.length} skills to ${targets.length} agent(s).`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch (err) {
|
|
477
|
+
const ts = new Date().toLocaleTimeString();
|
|
478
|
+
console.error(`[${ts}] Sync error: ${err.message}`);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
await sync();
|
|
482
|
+
const interval = setInterval(sync, intervalSec * 1000);
|
|
483
|
+
const shutdown = () => {
|
|
484
|
+
clearInterval(interval);
|
|
485
|
+
console.log('\nWatch stopped.');
|
|
486
|
+
process.exit(0);
|
|
487
|
+
};
|
|
488
|
+
process.on('SIGINT', shutdown);
|
|
489
|
+
process.on('SIGTERM', shutdown);
|
|
490
|
+
await new Promise(() => { });
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
default:
|
|
494
|
+
console.error(`Unknown command: ${command}`);
|
|
495
|
+
printHelp();
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
main().catch((err) => {
|
|
500
|
+
console.error(`Error: ${err.message}`);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill installer — writes SKILL.md files to agent directories.
|
|
3
|
+
*/
|
|
4
|
+
import type { SkillFile } from './client.js';
|
|
5
|
+
import type { AgentTarget } from './agents.js';
|
|
6
|
+
export interface InstallResult {
|
|
7
|
+
agent: string;
|
|
8
|
+
skillsDir: string;
|
|
9
|
+
installed: number;
|
|
10
|
+
files: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Install skill files into an agent's skills directory.
|
|
14
|
+
* Each skill gets its own directory: <skillsDir>/almyty-<name>/SKILL.md
|
|
15
|
+
*/
|
|
16
|
+
export declare function installSkills(skills: SkillFile[], target: AgentTarget): InstallResult;
|
|
17
|
+
/**
|
|
18
|
+
* Remove all almyty-installed skills from an agent's skills directory.
|
|
19
|
+
*/
|
|
20
|
+
export declare function removeSkills(target: AgentTarget): number;
|
|
21
|
+
/**
|
|
22
|
+
* List installed almyty skills in an agent's skills directory.
|
|
23
|
+
*/
|
|
24
|
+
export declare function listInstalledSkills(target: AgentTarget): string[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill installer — writes SKILL.md files to agent directories.
|
|
3
|
+
*/
|
|
4
|
+
import { writeFileSync, mkdirSync, existsSync, readdirSync, rmSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
const SKILL_PREFIX = 'almyty-';
|
|
7
|
+
/**
|
|
8
|
+
* Install skill files into an agent's skills directory.
|
|
9
|
+
* Each skill gets its own directory: <skillsDir>/almyty-<name>/SKILL.md
|
|
10
|
+
*/
|
|
11
|
+
export function installSkills(skills, target) {
|
|
12
|
+
const files = [];
|
|
13
|
+
mkdirSync(target.skillsDir, { recursive: true });
|
|
14
|
+
for (const skill of skills) {
|
|
15
|
+
const dirName = `${SKILL_PREFIX}${skill.name}`;
|
|
16
|
+
const skillDir = join(target.skillsDir, dirName);
|
|
17
|
+
const skillFile = join(skillDir, 'SKILL.md');
|
|
18
|
+
mkdirSync(skillDir, { recursive: true });
|
|
19
|
+
writeFileSync(skillFile, skill.content, 'utf-8');
|
|
20
|
+
files.push(skillFile);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
agent: target.name,
|
|
24
|
+
skillsDir: target.skillsDir,
|
|
25
|
+
installed: skills.length,
|
|
26
|
+
files,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Remove all almyty-installed skills from an agent's skills directory.
|
|
31
|
+
*/
|
|
32
|
+
export function removeSkills(target) {
|
|
33
|
+
if (!existsSync(target.skillsDir))
|
|
34
|
+
return 0;
|
|
35
|
+
const entries = readdirSync(target.skillsDir, { withFileTypes: true });
|
|
36
|
+
let removed = 0;
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (entry.isDirectory() && entry.name.startsWith(SKILL_PREFIX)) {
|
|
39
|
+
rmSync(join(target.skillsDir, entry.name), { recursive: true, force: true });
|
|
40
|
+
removed++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return removed;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* List installed almyty skills in an agent's skills directory.
|
|
47
|
+
*/
|
|
48
|
+
export function listInstalledSkills(target) {
|
|
49
|
+
if (!existsSync(target.skillsDir))
|
|
50
|
+
return [];
|
|
51
|
+
const entries = readdirSync(target.skillsDir, { withFileTypes: true });
|
|
52
|
+
return entries
|
|
53
|
+
.filter(e => e.isDirectory() && e.name.startsWith(SKILL_PREFIX))
|
|
54
|
+
.map(e => e.name.replace(SKILL_PREFIX, ''));
|
|
55
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function generateMetaSkill() {
|
|
2
|
+
const content = `---
|
|
3
|
+
name: almyty-skills
|
|
4
|
+
description: Discover, search, install, and run API skills from almyty. Use when the user wants to find or use API tools.
|
|
5
|
+
metadata:
|
|
6
|
+
author: almyty
|
|
7
|
+
type: meta
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# almyty Skills Manager
|
|
11
|
+
|
|
12
|
+
Manage API skills powered by almyty — a universal API-to-AI tool gateway.
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- User wants to discover available API tools
|
|
17
|
+
- User wants to find a specific API capability
|
|
18
|
+
- User wants to run an API tool directly
|
|
19
|
+
- User needs to list what skills are installed
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
### List all available skills
|
|
24
|
+
\`\`\`bash
|
|
25
|
+
npx @almyty/skills list
|
|
26
|
+
\`\`\`
|
|
27
|
+
|
|
28
|
+
### Search for skills
|
|
29
|
+
\`\`\`bash
|
|
30
|
+
npx @almyty/skills search <query>
|
|
31
|
+
\`\`\`
|
|
32
|
+
|
|
33
|
+
### Install a specific skill
|
|
34
|
+
\`\`\`bash
|
|
35
|
+
npx @almyty/skills install @<org>/<gateway>/<skill-name>
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
### Run a skill directly
|
|
39
|
+
\`\`\`bash
|
|
40
|
+
npx @almyty/skills run @<org>/<gateway>/<skill-name> --param1 value1 --param2 value2
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
### Start the skill daemon (auto-syncs all skills)
|
|
44
|
+
\`\`\`bash
|
|
45
|
+
npx @almyty/skills daemon
|
|
46
|
+
\`\`\`
|
|
47
|
+
`;
|
|
48
|
+
return {
|
|
49
|
+
name: 'skills',
|
|
50
|
+
fileName: 'almyty-skills',
|
|
51
|
+
content,
|
|
52
|
+
};
|
|
53
|
+
}
|