@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.
@@ -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 { requireApiKey } from "../config.js";
6
- import { apiRequest } from "../api.js";
7
- import { error, success, badge, hint, truncate } from "../utils/display.js";
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
- // --- Skill Templates ---
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
- category: general
16
- tags: []
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
- category: llm
45
- tags: [llm, chat, openai, claude, gemini]
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
- category: search
96
- tags: [search, web, youtube, scholar, tavily]
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
- category: finance
149
- tags: [stocks, earnings, sec-filings, crypto, financial-analysis]
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: "Post tweets, search Twitter, get user profiles and trends via AISA."
210
- category: twitter
211
- tags: [twitter, tweets, social-media, trends]
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
- category: video
270
- tags: [video, generation, aigc, synthesis]
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(options) {
310
- const key = requireApiKey();
311
- const spinner = ora("Fetching skills...").start();
312
- const query = {};
313
- if (options.category)
314
- query.category = options.category;
315
- if (options.limit)
316
- query.limit = options.limit;
317
- const res = await apiRequest(key, "cli/skills", { query });
318
- if (!res.success || !res.data) {
319
- spinner.fail("Failed to fetch skills");
320
- error(res.error || "Unknown error");
321
- return;
322
- }
323
- spinner.stop();
324
- const { skills } = res.data;
325
- if (skills.length === 0) {
326
- console.log(" No skills found.");
327
- return;
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
- for (const s of skills) {
330
- const verified = s.verified ? chalk.green(" ✓") : "";
331
- console.log(`\n ${chalk.cyan.bold(s.name)}${verified} ${chalk.gray(s.slug)} ${badge(s.category)}`);
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, options) {
337
- const key = requireApiKey();
307
+ export async function skillsSearchAction(query, _options) {
338
308
  const spinner = ora(`Searching skills: "${query}"...`).start();
339
- const res = await apiRequest(key, "cli/skills/search", {
340
- method: "POST",
341
- body: { query, limit: parseInt(options.limit || "10") },
342
- });
343
- if (!res.success || !res.data) {
344
- spinner.fail("Search failed");
345
- error(res.error || "Unknown error");
346
- return;
347
- }
348
- spinner.stop();
349
- const { skills } = res.data;
350
- if (skills.length === 0) {
351
- console.log(` No skills found for "${query}".`);
352
- return;
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
- for (const s of skills) {
355
- const verified = s.verified ? chalk.green(" ✓") : "";
356
- console.log(`\n ${chalk.cyan.bold(s.name)}${verified} ${chalk.gray(s.slug)} ${badge(s.category)}`);
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
- const res = await apiRequest(key, `cli/skills/${slug}`);
365
- if (!res.success || !res.data) {
366
- spinner.fail("Skill not found");
367
- error(res.error || "Unknown error");
368
- return;
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
- spinner.stop();
371
- const s = res.data;
372
- const verified = s.verified ? chalk.green(" ✓ verified") : "";
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
- spinner.stop();
391
- const skill = res.data;
392
- // Determine targets
393
- let targets;
394
- if (options.agent) {
395
- if (options.agent === "all") {
396
- targets = Object.keys(AGENT_DIRS);
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
- if (!AGENT_DIRS[options.agent]) {
400
- error(`Unknown agent: ${options.agent}. Valid: ${Object.keys(AGENT_DIRS).join(", ")}, all`);
401
- return;
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
- else {
407
- targets = detectAgents(AGENT_DIRS);
408
- if (targets.length === 0) {
409
- targets = Object.keys(AGENT_DIRS);
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
- let installed = 0;
413
- for (const agent of targets) {
414
- const dir = expandHome(join(AGENT_DIRS[agent], slug));
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 async function skillsRemoveAction(slug, options) {
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("Edit SKILL.md, then run 'aisa skills submit ./" + name + "'");
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
  }