@aisa-one/cli 0.1.7 → 0.2.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/README.md +10 -11
- package/dist/commands/configCmd.js +1 -1
- package/dist/commands/skills.d.ts +4 -8
- package/dist/commands/skills.js +214 -295
- package/dist/commands/twitter.d.ts +127 -3
- package/dist/commands/twitter.js +296 -36
- package/dist/config.js +2 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/index.js +213 -30
- package/dist/types.d.ts +0 -16
- package/dist/utils/file.d.ts +0 -4
- package/dist/utils/file.js +1 -26
- package/package.json +1 -1
package/dist/commands/skills.js
CHANGED
|
@@ -2,24 +2,86 @@ import ora from "ora";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { expandHome, ensureDir, writeSkillFiles, readSkillDir, removeDir, detectAgents } from "../utils/file.js";
|
|
5
|
+
import fetch from "node-fetch";
|
|
6
|
+
import { error, success, hint, truncate } from "../utils/display.js";
|
|
7
|
+
import { expandHome, ensureDir, writeSkillFiles, removeDir, detectAgents } from "../utils/file.js";
|
|
9
8
|
import { AGENT_DIRS } from "../constants.js";
|
|
10
|
-
// ---
|
|
9
|
+
// --- GitHub-backed skill registry ---
|
|
10
|
+
const SKILLS_REPO = "AIsa-team/OpenClaw-Skills";
|
|
11
|
+
const GH_API = `https://api.github.com/repos/${SKILLS_REPO}`;
|
|
12
|
+
const GH_RAW = `https://raw.githubusercontent.com/${SKILLS_REPO}/main`;
|
|
13
|
+
/** Fetch the repo tree and extract top-level skill folder names. */
|
|
14
|
+
async function fetchSkillSlugs() {
|
|
15
|
+
const res = await fetch(`${GH_API}/git/trees/main`);
|
|
16
|
+
if (!res.ok)
|
|
17
|
+
throw new Error(`GitHub API error: ${res.status}`);
|
|
18
|
+
const data = (await res.json());
|
|
19
|
+
// Top-level directories (excluding dotfiles, LICENSE, README) are skills
|
|
20
|
+
return data.tree
|
|
21
|
+
.filter((e) => e.type === "tree" &&
|
|
22
|
+
!e.path.startsWith(".") &&
|
|
23
|
+
e.path !== "node_modules")
|
|
24
|
+
.map((e) => e.path);
|
|
25
|
+
}
|
|
26
|
+
/** Fetch and parse SKILL.md frontmatter for a single skill. */
|
|
27
|
+
async function fetchSkillMeta(slug) {
|
|
28
|
+
const res = await fetch(`${GH_RAW}/${slug}/SKILL.md`);
|
|
29
|
+
if (!res.ok)
|
|
30
|
+
return null;
|
|
31
|
+
const text = await res.text();
|
|
32
|
+
return parseSkillFrontmatter(slug, text);
|
|
33
|
+
}
|
|
34
|
+
/** Parse YAML frontmatter from SKILL.md content. */
|
|
35
|
+
function parseSkillFrontmatter(slug, content) {
|
|
36
|
+
const info = { slug, name: slug, description: "", emoji: "" };
|
|
37
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
38
|
+
if (!match)
|
|
39
|
+
return info;
|
|
40
|
+
const frontmatter = match[1];
|
|
41
|
+
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
42
|
+
if (nameMatch)
|
|
43
|
+
info.name = nameMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
44
|
+
const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
45
|
+
if (descMatch)
|
|
46
|
+
info.description = descMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
47
|
+
const metaMatch = frontmatter.match(/^metadata:\s*(.+)$/m);
|
|
48
|
+
if (metaMatch) {
|
|
49
|
+
try {
|
|
50
|
+
const meta = JSON.parse(metaMatch[1]);
|
|
51
|
+
info.emoji = meta?.openclaw?.emoji || "";
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// ignore malformed metadata
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return info;
|
|
58
|
+
}
|
|
59
|
+
/** Fetch all files in a skill folder from GitHub. */
|
|
60
|
+
async function fetchSkillFiles(slug) {
|
|
61
|
+
const treeRes = await fetch(`${GH_API}/git/trees/main?recursive=1`);
|
|
62
|
+
if (!treeRes.ok)
|
|
63
|
+
throw new Error(`GitHub API error: ${treeRes.status}`);
|
|
64
|
+
const treeData = (await treeRes.json());
|
|
65
|
+
const prefix = `${slug}/`;
|
|
66
|
+
const blobs = treeData.tree.filter((e) => e.type === "blob" && e.path.startsWith(prefix));
|
|
67
|
+
const files = [];
|
|
68
|
+
await Promise.all(blobs.map(async (entry) => {
|
|
69
|
+
const res = await fetch(`${GH_RAW}/${entry.path}`);
|
|
70
|
+
if (!res.ok)
|
|
71
|
+
return;
|
|
72
|
+
const content = await res.text();
|
|
73
|
+
const relativePath = entry.path.slice(prefix.length);
|
|
74
|
+
files.push({ path: relativePath, content });
|
|
75
|
+
}));
|
|
76
|
+
return files;
|
|
77
|
+
}
|
|
78
|
+
// --- Skill Templates (for init) ---
|
|
11
79
|
const TEMPLATES = {
|
|
12
80
|
default: `---
|
|
13
81
|
name: my-skill
|
|
14
82
|
description: "Describe what this skill does."
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
metadata:
|
|
18
|
-
aisa:
|
|
19
|
-
apis: []
|
|
20
|
-
requires:
|
|
21
|
-
auth: true
|
|
22
|
-
env: [AISA_API_KEY]
|
|
83
|
+
homepage: https://openclaw.ai
|
|
84
|
+
metadata: {"openclaw":{"emoji":"","requires":{"bins":["curl"],"env":["AISA_API_KEY"]},"primaryEnv":"AISA_API_KEY"}}
|
|
23
85
|
---
|
|
24
86
|
|
|
25
87
|
# My Skill
|
|
@@ -41,14 +103,8 @@ aisa run <slug> <path> -q "param=value"
|
|
|
41
103
|
llm: `---
|
|
42
104
|
name: llm-assistant
|
|
43
105
|
description: "Use AISA's unified LLM gateway to chat with 70+ AI models."
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
metadata:
|
|
47
|
-
aisa:
|
|
48
|
-
apis: [chat/completions, models]
|
|
49
|
-
requires:
|
|
50
|
-
auth: true
|
|
51
|
-
env: [AISA_API_KEY]
|
|
106
|
+
homepage: https://openclaw.ai
|
|
107
|
+
metadata: {"openclaw":{"emoji":"🤖","requires":{"bins":["curl"],"env":["AISA_API_KEY"]},"primaryEnv":"AISA_API_KEY"}}
|
|
52
108
|
---
|
|
53
109
|
|
|
54
110
|
# LLM Assistant Skill
|
|
@@ -79,27 +135,12 @@ aisa chat "Explain quantum computing" --model claude-opus-4-6 --stream
|
|
|
79
135
|
aisa models
|
|
80
136
|
aisa models --provider anthropic
|
|
81
137
|
\`\`\`
|
|
82
|
-
|
|
83
|
-
## REST API (OpenAI-compatible)
|
|
84
|
-
|
|
85
|
-
\`\`\`bash
|
|
86
|
-
curl -X POST https://api.aisa.one/v1/chat/completions \\
|
|
87
|
-
-H "Authorization: Bearer $AISA_API_KEY" \\
|
|
88
|
-
-H "Content-Type: application/json" \\
|
|
89
|
-
-d '{"model": "gpt-4.1", "messages": [{"role": "user", "content": "Hello"}]}'
|
|
90
|
-
\`\`\`
|
|
91
138
|
`,
|
|
92
139
|
search: `---
|
|
93
140
|
name: web-search
|
|
94
141
|
description: "Search the web, YouTube, and academic papers via AISA APIs."
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
metadata:
|
|
98
|
-
aisa:
|
|
99
|
-
apis: [search/smart, search/full, youtube/search, scholar/search, tavily/search]
|
|
100
|
-
requires:
|
|
101
|
-
auth: true
|
|
102
|
-
env: [AISA_API_KEY]
|
|
142
|
+
homepage: https://openclaw.ai
|
|
143
|
+
metadata: {"openclaw":{"emoji":"🔍","requires":{"bins":["curl"],"env":["AISA_API_KEY"]},"primaryEnv":"AISA_API_KEY"}}
|
|
103
144
|
---
|
|
104
145
|
|
|
105
146
|
# Web Search Skill
|
|
@@ -129,30 +170,12 @@ aisa web-search "machine learning tutorial" --type youtube
|
|
|
129
170
|
\`\`\`bash
|
|
130
171
|
aisa scholar "transformer architecture"
|
|
131
172
|
\`\`\`
|
|
132
|
-
|
|
133
|
-
## Tavily Deep Search
|
|
134
|
-
|
|
135
|
-
\`\`\`bash
|
|
136
|
-
aisa run tavily /search -d '{"query": "NVIDIA earnings 2025", "search_depth": "advanced"}'
|
|
137
|
-
\`\`\`
|
|
138
|
-
|
|
139
|
-
## Tavily Extract (webpage content)
|
|
140
|
-
|
|
141
|
-
\`\`\`bash
|
|
142
|
-
aisa run tavily /extract -d '{"urls": ["https://example.com"]}'
|
|
143
|
-
\`\`\`
|
|
144
173
|
`,
|
|
145
174
|
finance: `---
|
|
146
175
|
name: finance-analyst
|
|
147
176
|
description: "Access stock prices, earnings, SEC filings, and financial data via AISA."
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
metadata:
|
|
151
|
-
aisa:
|
|
152
|
-
apis: [stock/price, analyst-estimates, financial-statements, filings, insider-trades, crypto/price]
|
|
153
|
-
requires:
|
|
154
|
-
auth: true
|
|
155
|
-
env: [AISA_API_KEY]
|
|
177
|
+
homepage: https://openclaw.ai
|
|
178
|
+
metadata: {"openclaw":{"emoji":"📊","requires":{"bins":["curl"],"env":["AISA_API_KEY"]},"primaryEnv":"AISA_API_KEY"}}
|
|
156
179
|
---
|
|
157
180
|
|
|
158
181
|
# Finance Analyst Skill
|
|
@@ -179,42 +202,12 @@ aisa stock TSLA --field filings
|
|
|
179
202
|
aisa crypto BTC
|
|
180
203
|
aisa crypto ETH --period 30d
|
|
181
204
|
\`\`\`
|
|
182
|
-
|
|
183
|
-
## Stock Screener
|
|
184
|
-
|
|
185
|
-
\`\`\`bash
|
|
186
|
-
aisa screener --sector Technology --limit 20
|
|
187
|
-
\`\`\`
|
|
188
|
-
|
|
189
|
-
## Financial Statements
|
|
190
|
-
|
|
191
|
-
\`\`\`bash
|
|
192
|
-
aisa run financial-statements -q "symbol=AAPL&period=annual"
|
|
193
|
-
\`\`\`
|
|
194
|
-
|
|
195
|
-
## SEC Filings
|
|
196
|
-
|
|
197
|
-
\`\`\`bash
|
|
198
|
-
aisa run filings -q "symbol=TSLA&type=10-K"
|
|
199
|
-
\`\`\`
|
|
200
|
-
|
|
201
|
-
## Insider Trades
|
|
202
|
-
|
|
203
|
-
\`\`\`bash
|
|
204
|
-
aisa stock NVDA --field insider
|
|
205
|
-
\`\`\`
|
|
206
205
|
`,
|
|
207
206
|
twitter: `---
|
|
208
207
|
name: twitter-manager
|
|
209
|
-
description: "
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
metadata:
|
|
213
|
-
aisa:
|
|
214
|
-
apis: [twitter/create-tweet-v2, twitter/tweet/advanced-search, twitter/user/info, twitter/trends]
|
|
215
|
-
requires:
|
|
216
|
-
auth: true
|
|
217
|
-
env: [AISA_API_KEY]
|
|
208
|
+
description: "Search Twitter, get user profiles and trends via AISA."
|
|
209
|
+
homepage: https://openclaw.ai
|
|
210
|
+
metadata: {"openclaw":{"emoji":"🐦","requires":{"bins":["curl"],"env":["AISA_API_KEY"]},"primaryEnv":"AISA_API_KEY"}}
|
|
218
211
|
---
|
|
219
212
|
|
|
220
213
|
# Twitter Manager Skill
|
|
@@ -227,12 +220,6 @@ Interact with Twitter/X through AISA's Twitter APIs.
|
|
|
227
220
|
export AISA_API_KEY=sk-your-key
|
|
228
221
|
\`\`\`
|
|
229
222
|
|
|
230
|
-
## Post a Tweet
|
|
231
|
-
|
|
232
|
-
\`\`\`bash
|
|
233
|
-
aisa tweet "Hello from AISA CLI!"
|
|
234
|
-
\`\`\`
|
|
235
|
-
|
|
236
223
|
## Search Tweets
|
|
237
224
|
|
|
238
225
|
\`\`\`bash
|
|
@@ -250,30 +237,12 @@ aisa twitter user elonmusk
|
|
|
250
237
|
\`\`\`bash
|
|
251
238
|
aisa twitter trends
|
|
252
239
|
\`\`\`
|
|
253
|
-
|
|
254
|
-
## Advanced: Get Tweet Replies
|
|
255
|
-
|
|
256
|
-
\`\`\`bash
|
|
257
|
-
aisa run twitter/tweet/replies -q "id=1234567890"
|
|
258
|
-
\`\`\`
|
|
259
|
-
|
|
260
|
-
## Advanced: Send DM
|
|
261
|
-
|
|
262
|
-
\`\`\`bash
|
|
263
|
-
aisa run twitter/send-dm-to-user -d '{"userId": "123", "text": "Hello!"}'
|
|
264
|
-
\`\`\`
|
|
265
240
|
`,
|
|
266
241
|
video: `---
|
|
267
242
|
name: video-generator
|
|
268
243
|
description: "Generate videos from text prompts using AISA's video synthesis API."
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
metadata:
|
|
272
|
-
aisa:
|
|
273
|
-
apis: [services/aigc/video-generation/video-synthesis, services/aigc/tasks]
|
|
274
|
-
requires:
|
|
275
|
-
auth: true
|
|
276
|
-
env: [AISA_API_KEY]
|
|
244
|
+
homepage: https://openclaw.ai
|
|
245
|
+
metadata: {"openclaw":{"emoji":"🎬","requires":{"bins":["curl"],"env":["AISA_API_KEY"]},"primaryEnv":"AISA_API_KEY"}}
|
|
277
246
|
---
|
|
278
247
|
|
|
279
248
|
# Video Generator Skill
|
|
@@ -306,120 +275,152 @@ aisa video status <task-id>
|
|
|
306
275
|
`,
|
|
307
276
|
};
|
|
308
277
|
// --- Commands ---
|
|
309
|
-
export async function skillsListAction(
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
278
|
+
export async function skillsListAction(_options) {
|
|
279
|
+
const spinner = ora("Fetching skills from GitHub...").start();
|
|
280
|
+
try {
|
|
281
|
+
const slugs = await fetchSkillSlugs();
|
|
282
|
+
// Fetch all SKILL.md frontmatters in parallel
|
|
283
|
+
const metas = await Promise.all(slugs.map((s) => fetchSkillMeta(s)));
|
|
284
|
+
const skills = metas.filter((m) => m !== null);
|
|
285
|
+
spinner.stop();
|
|
286
|
+
if (skills.length === 0) {
|
|
287
|
+
console.log(" No skills found.");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
console.log(chalk.bold(`\n ${skills.length} skills available\n`));
|
|
291
|
+
for (const s of skills) {
|
|
292
|
+
const emoji = s.emoji ? `${s.emoji} ` : "";
|
|
293
|
+
console.log(` ${emoji}${chalk.cyan.bold(s.name)} ${chalk.gray(s.slug)}`);
|
|
294
|
+
if (s.description) {
|
|
295
|
+
console.log(` ${chalk.gray(truncate(s.description, 80))}`);
|
|
296
|
+
}
|
|
297
|
+
console.log();
|
|
298
|
+
}
|
|
299
|
+
hint("Install: aisa skills install <slug>");
|
|
300
|
+
hint("Details: aisa skills show <slug>");
|
|
328
301
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
console.log(` ${chalk.gray(truncate(s.description, 60))}`);
|
|
333
|
-
console.log(` ${chalk.gray(`${s.installs} installs`)}`);
|
|
302
|
+
catch (err) {
|
|
303
|
+
spinner.fail("Failed to fetch skills");
|
|
304
|
+
error(err.message);
|
|
334
305
|
}
|
|
335
306
|
}
|
|
336
|
-
export async function skillsSearchAction(query,
|
|
337
|
-
const key = requireApiKey();
|
|
307
|
+
export async function skillsSearchAction(query, _options) {
|
|
338
308
|
const spinner = ora(`Searching skills: "${query}"...`).start();
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
309
|
+
try {
|
|
310
|
+
const slugs = await fetchSkillSlugs();
|
|
311
|
+
const metas = await Promise.all(slugs.map((s) => fetchSkillMeta(s)));
|
|
312
|
+
const skills = metas.filter((m) => m !== null);
|
|
313
|
+
const q = query.toLowerCase();
|
|
314
|
+
const matches = skills.filter((s) => s.name.toLowerCase().includes(q) ||
|
|
315
|
+
s.slug.toLowerCase().includes(q) ||
|
|
316
|
+
s.description.toLowerCase().includes(q));
|
|
317
|
+
spinner.stop();
|
|
318
|
+
if (matches.length === 0) {
|
|
319
|
+
console.log(` No skills found for "${query}".`);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
console.log(chalk.bold(`\n ${matches.length} result(s)\n`));
|
|
323
|
+
for (const s of matches) {
|
|
324
|
+
const emoji = s.emoji ? `${s.emoji} ` : "";
|
|
325
|
+
console.log(` ${emoji}${chalk.cyan.bold(s.name)} ${chalk.gray(s.slug)}`);
|
|
326
|
+
if (s.description) {
|
|
327
|
+
console.log(` ${chalk.gray(truncate(s.description, 80))}`);
|
|
328
|
+
}
|
|
329
|
+
console.log();
|
|
330
|
+
}
|
|
331
|
+
hint("Install: aisa skills install <slug>");
|
|
353
332
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
console.log(` ${chalk.gray(s.description)}`);
|
|
333
|
+
catch (err) {
|
|
334
|
+
spinner.fail("Search failed");
|
|
335
|
+
error(err.message);
|
|
358
336
|
}
|
|
359
|
-
console.log(chalk.gray("\n Run 'aisa skills add <owner/name>' to install"));
|
|
360
337
|
}
|
|
361
338
|
export async function skillsShowAction(slug) {
|
|
362
|
-
const key = requireApiKey();
|
|
363
339
|
const spinner = ora(`Loading ${slug}...`).start();
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
340
|
+
try {
|
|
341
|
+
// Fetch SKILL.md
|
|
342
|
+
const res = await fetch(`${GH_RAW}/${slug}/SKILL.md`);
|
|
343
|
+
if (!res.ok) {
|
|
344
|
+
spinner.fail("Skill not found");
|
|
345
|
+
error(`No skill "${slug}" in ${SKILLS_REPO}`);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const content = await res.text();
|
|
349
|
+
const meta = parseSkillFrontmatter(slug, content);
|
|
350
|
+
// Fetch file list
|
|
351
|
+
const treeRes = await fetch(`${GH_API}/git/trees/main?recursive=1`);
|
|
352
|
+
const treeData = (await treeRes.json());
|
|
353
|
+
const prefix = `${slug}/`;
|
|
354
|
+
const files = treeData.tree
|
|
355
|
+
.filter((e) => e.type === "blob" && e.path.startsWith(prefix))
|
|
356
|
+
.map((e) => e.path.slice(prefix.length));
|
|
357
|
+
spinner.stop();
|
|
358
|
+
const emoji = meta.emoji ? `${meta.emoji} ` : "";
|
|
359
|
+
console.log(`\n ${emoji}${chalk.cyan.bold(meta.name)}`);
|
|
360
|
+
console.log(` ${meta.description}`);
|
|
361
|
+
console.log(` Slug: ${chalk.gray(slug)}`);
|
|
362
|
+
console.log(` Files: ${files.join(", ")}`);
|
|
363
|
+
console.log(` Source: ${chalk.gray(`https://github.com/${SKILLS_REPO}/tree/main/${slug}`)}`);
|
|
364
|
+
// Print SKILL.md body (after frontmatter)
|
|
365
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "").trim();
|
|
366
|
+
if (body) {
|
|
367
|
+
console.log(`\n${chalk.gray("─".repeat(60))}`);
|
|
368
|
+
console.log(body);
|
|
369
|
+
}
|
|
370
|
+
console.log();
|
|
371
|
+
hint(`Install: aisa skills install ${slug}`);
|
|
369
372
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
console.log(`\n ${chalk.cyan.bold(s.name)}${verified}`);
|
|
374
|
-
console.log(` ${s.description}`);
|
|
375
|
-
console.log(` Category: ${badge(s.category)}`);
|
|
376
|
-
console.log(` Tags: ${s.tags.join(", ")}`);
|
|
377
|
-
console.log(` Installs: ${s.installs}`);
|
|
378
|
-
console.log(` Files: ${s.files.map((f) => f.path).join(", ")}`);
|
|
379
|
-
console.log(chalk.gray(`\n Install: aisa skills add ${s.slug}`));
|
|
380
|
-
}
|
|
381
|
-
export async function skillsAddAction(slug, options) {
|
|
382
|
-
const key = requireApiKey();
|
|
383
|
-
const spinner = ora(`Fetching skill '${slug}'...`).start();
|
|
384
|
-
const res = await apiRequest(key, `cli/skills/${slug}`);
|
|
385
|
-
if (!res.success || !res.data) {
|
|
386
|
-
spinner.fail("Failed to fetch skill");
|
|
387
|
-
error(res.error || "Unknown error");
|
|
388
|
-
return;
|
|
373
|
+
catch (err) {
|
|
374
|
+
spinner.fail("Failed to load skill");
|
|
375
|
+
error(err.message);
|
|
389
376
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (
|
|
396
|
-
|
|
377
|
+
}
|
|
378
|
+
export async function skillsInstallAction(slug, options) {
|
|
379
|
+
const spinner = ora(`Fetching skill '${slug}' from GitHub...`).start();
|
|
380
|
+
try {
|
|
381
|
+
const files = await fetchSkillFiles(slug);
|
|
382
|
+
if (files.length === 0) {
|
|
383
|
+
spinner.fail("Skill not found");
|
|
384
|
+
error(`No skill "${slug}" in ${SKILLS_REPO}`);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
spinner.stop();
|
|
388
|
+
// Determine targets
|
|
389
|
+
let targets;
|
|
390
|
+
if (options.agent) {
|
|
391
|
+
if (options.agent === "all") {
|
|
392
|
+
targets = Object.keys(AGENT_DIRS);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
if (!AGENT_DIRS[options.agent]) {
|
|
396
|
+
error(`Unknown agent: ${options.agent}. Valid: ${Object.keys(AGENT_DIRS).join(", ")}, all`);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
targets = [options.agent];
|
|
400
|
+
}
|
|
397
401
|
}
|
|
398
402
|
else {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
403
|
+
targets = detectAgents(AGENT_DIRS);
|
|
404
|
+
if (targets.length === 0) {
|
|
405
|
+
targets = Object.keys(AGENT_DIRS);
|
|
402
406
|
}
|
|
403
|
-
targets = [options.agent];
|
|
404
407
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
408
|
+
let installed = 0;
|
|
409
|
+
for (const agent of targets) {
|
|
410
|
+
const dir = expandHome(join(AGENT_DIRS[agent], slug));
|
|
411
|
+
ensureDir(dir);
|
|
412
|
+
writeSkillFiles(dir, files);
|
|
413
|
+
console.log(` ${chalk.green("✓")} ${AGENT_DIRS[agent]} (${agent})`);
|
|
414
|
+
installed++;
|
|
410
415
|
}
|
|
416
|
+
success(`Skill '${slug}' installed to ${installed} agent(s)`);
|
|
411
417
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
ensureDir(dir);
|
|
416
|
-
writeSkillFiles(dir, skill.files);
|
|
417
|
-
console.log(` ${chalk.green("✓")} ${AGENT_DIRS[agent]} (${agent})`);
|
|
418
|
-
installed++;
|
|
418
|
+
catch (err) {
|
|
419
|
+
spinner.fail("Failed to install skill");
|
|
420
|
+
error(err.message);
|
|
419
421
|
}
|
|
420
|
-
success(`Skill '${skill.name}' installed to ${installed} agent(s)`);
|
|
421
422
|
}
|
|
422
|
-
export
|
|
423
|
+
export function skillsRemoveAction(slug, options) {
|
|
423
424
|
let targets;
|
|
424
425
|
if (options.agent && options.agent !== "all") {
|
|
425
426
|
targets = [options.agent];
|
|
@@ -469,87 +470,5 @@ export function skillsInitAction(name, options) {
|
|
|
469
470
|
}
|
|
470
471
|
success(`Skill initialized: ${name}/`);
|
|
471
472
|
console.log(` ${chalk.gray(join(name, "SKILL.md"))}`);
|
|
472
|
-
hint(
|
|
473
|
-
}
|
|
474
|
-
export async function skillsSubmitAction(path) {
|
|
475
|
-
const key = requireApiKey();
|
|
476
|
-
const dir = resolve(path);
|
|
477
|
-
if (!existsSync(join(dir, "SKILL.md"))) {
|
|
478
|
-
error("No SKILL.md found. Run 'aisa skills init <name>' first.");
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
const spinner = ora("Submitting skill...").start();
|
|
482
|
-
const files = readSkillDir(dir);
|
|
483
|
-
const res = await apiRequest(key, "cli/skills", {
|
|
484
|
-
method: "POST",
|
|
485
|
-
body: { files },
|
|
486
|
-
});
|
|
487
|
-
if (!res.success || !res.data) {
|
|
488
|
-
spinner.fail("Failed to submit skill");
|
|
489
|
-
error(res.error || "Unknown error");
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
spinner.stop();
|
|
493
|
-
success(`Skill submitted: ${res.data.slug}`);
|
|
494
|
-
hint(`Request verification: aisa skills request-verification ${res.data.slug}`);
|
|
495
|
-
}
|
|
496
|
-
export async function skillsPushAction(slug) {
|
|
497
|
-
const key = requireApiKey();
|
|
498
|
-
const dir = resolve(".");
|
|
499
|
-
if (!existsSync(join(dir, "SKILL.md"))) {
|
|
500
|
-
error("No SKILL.md in current directory.");
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
const spinner = ora(`Pushing updates to ${slug}...`).start();
|
|
504
|
-
const files = readSkillDir(dir);
|
|
505
|
-
const res = await apiRequest(key, `cli/skills/${slug}`, {
|
|
506
|
-
method: "PUT",
|
|
507
|
-
body: { files },
|
|
508
|
-
});
|
|
509
|
-
if (!res.success) {
|
|
510
|
-
spinner.fail("Failed to push updates");
|
|
511
|
-
error(res.error || "Unknown error");
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
spinner.stop();
|
|
515
|
-
success(`Skill '${slug}' updated.`);
|
|
516
|
-
}
|
|
517
|
-
export async function skillsVerifyAction(slug) {
|
|
518
|
-
const key = requireApiKey();
|
|
519
|
-
const spinner = ora("Requesting verification...").start();
|
|
520
|
-
const res = await apiRequest(key, `cli/skills/${slug}/verify`, { method: "POST" });
|
|
521
|
-
if (!res.success) {
|
|
522
|
-
spinner.fail("Failed to request verification");
|
|
523
|
-
error(res.error || "Unknown error");
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
spinner.stop();
|
|
527
|
-
success("Verification requested. The AISA team will review your skill.");
|
|
528
|
-
}
|
|
529
|
-
export async function skillsUpdateAction(slug) {
|
|
530
|
-
const key = requireApiKey();
|
|
531
|
-
if (slug) {
|
|
532
|
-
// Update single skill
|
|
533
|
-
const spinner = ora(`Updating ${slug}...`).start();
|
|
534
|
-
const res = await apiRequest(key, `cli/skills/${slug}`);
|
|
535
|
-
if (!res.success || !res.data) {
|
|
536
|
-
spinner.fail("Failed to fetch skill");
|
|
537
|
-
error(res.error || "Unknown error");
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
spinner.stop();
|
|
541
|
-
const skill = res.data;
|
|
542
|
-
const targets = detectAgents(AGENT_DIRS);
|
|
543
|
-
for (const agent of targets) {
|
|
544
|
-
const dir = expandHome(join(AGENT_DIRS[agent], slug));
|
|
545
|
-
if (existsSync(dir)) {
|
|
546
|
-
writeSkillFiles(dir, skill.files);
|
|
547
|
-
console.log(` ${chalk.green("✓")} Updated in ${AGENT_DIRS[agent]}`);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
success(`Skill '${slug}' updated.`);
|
|
551
|
-
}
|
|
552
|
-
else {
|
|
553
|
-
console.log(" Specify a skill to update: aisa skills update <owner/name>");
|
|
554
|
-
}
|
|
473
|
+
hint(`Edit SKILL.md, then submit via PR: https://github.com/${SKILLS_REPO}`);
|
|
555
474
|
}
|