@aisa-one/cli 0.2.1 → 0.2.3
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/CHANGELOG.md +144 -1
- package/README.md +249 -102
- package/dist/api.d.ts +46 -1
- package/dist/api.js +77 -8
- package/dist/cache.d.ts +18 -0
- package/dist/cache.js +65 -0
- package/dist/catalog.d.ts +90 -0
- package/dist/catalog.js +101 -0
- package/dist/commands/account.d.ts +4 -1
- package/dist/commands/account.js +36 -6
- package/dist/commands/api.d.ts +18 -4
- package/dist/commands/api.js +357 -24
- package/dist/commands/cacheCmd.d.ts +2 -0
- package/dist/commands/cacheCmd.js +10 -0
- package/dist/commands/completionCmd.d.ts +7 -0
- package/dist/commands/completionCmd.js +37 -0
- package/dist/commands/configCmd.js +19 -1
- package/dist/commands/finance.d.ts +4 -0
- package/dist/commands/finance.js +212 -29
- package/dist/commands/models.js +6 -0
- package/dist/commands/run.d.ts +21 -0
- package/dist/commands/run.js +113 -6
- package/dist/commands/search.js +8 -3
- package/dist/commands/skills.d.ts +6 -2
- package/dist/commands/skills.js +189 -134
- package/dist/commands/twitter.js +2 -3
- package/dist/commands/video.d.ts +40 -0
- package/dist/commands/video.js +272 -55
- package/dist/completion.d.ts +26 -0
- package/dist/completion.js +293 -0
- package/dist/constants.d.ts +55 -5
- package/dist/constants.js +136 -11
- package/dist/index.js +86 -23
- package/dist/skills-registry.d.ts +64 -0
- package/dist/skills-registry.js +219 -0
- package/dist/types.d.ts +32 -1
- package/dist/utils/file.d.ts +5 -1
- package/dist/utils/file.js +5 -1
- package/dist/utils/pool.d.ts +8 -0
- package/dist/utils/pool.js +27 -0
- package/package.json +9 -3
- package/dist/agent/env-setup.d.ts +0 -5
- package/dist/agent/env-setup.js +0 -49
- package/dist/agent/index-client.d.ts +0 -7
- package/dist/agent/index-client.js +0 -22
- package/dist/agent/installer.d.ts +0 -19
- package/dist/agent/installer.js +0 -70
- package/dist/agent/setup-python.d.ts +0 -15
- package/dist/agent/setup-python.js +0 -51
- package/dist/commands/agent.d.ts +0 -37
- package/dist/commands/agent.js +0 -129
- package/tests/streaming.test.ts +0 -73
package/dist/commands/skills.js
CHANGED
|
@@ -1,80 +1,11 @@
|
|
|
1
1
|
import ora from "ora";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { error, success, hint, truncate } from "../utils/display.js";
|
|
6
6
|
import { expandHome, ensureDir, writeSkillFiles, removeDir, detectAgents } from "../utils/file.js";
|
|
7
7
|
import { AGENT_DIRS } from "../constants.js";
|
|
8
|
-
|
|
9
|
-
const SKILLS_REPO = "AIsa-team/agent-skills";
|
|
10
|
-
const GH_API = `https://api.github.com/repos/${SKILLS_REPO}`;
|
|
11
|
-
const GH_RAW = `https://raw.githubusercontent.com/${SKILLS_REPO}/main`;
|
|
12
|
-
/** Fetch the repo tree and extract top-level skill folder names. */
|
|
13
|
-
async function fetchSkillSlugs() {
|
|
14
|
-
const res = await fetch(`${GH_API}/git/trees/main`);
|
|
15
|
-
if (!res.ok)
|
|
16
|
-
throw new Error(`GitHub API error: ${res.status}`);
|
|
17
|
-
const data = (await res.json());
|
|
18
|
-
// Top-level directories (excluding dotfiles, LICENSE, README) are skills
|
|
19
|
-
return data.tree
|
|
20
|
-
.filter((e) => e.type === "tree" &&
|
|
21
|
-
!e.path.startsWith(".") &&
|
|
22
|
-
e.path !== "node_modules")
|
|
23
|
-
.map((e) => e.path);
|
|
24
|
-
}
|
|
25
|
-
/** Fetch and parse SKILL.md frontmatter for a single skill. */
|
|
26
|
-
async function fetchSkillMeta(slug) {
|
|
27
|
-
const res = await fetch(`${GH_RAW}/${slug}/SKILL.md`);
|
|
28
|
-
if (!res.ok)
|
|
29
|
-
return null;
|
|
30
|
-
const text = await res.text();
|
|
31
|
-
return parseSkillFrontmatter(slug, text);
|
|
32
|
-
}
|
|
33
|
-
/** Parse YAML frontmatter from SKILL.md content. */
|
|
34
|
-
function parseSkillFrontmatter(slug, content) {
|
|
35
|
-
const info = { slug, name: slug, description: "", emoji: "" };
|
|
36
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
37
|
-
if (!match)
|
|
38
|
-
return info;
|
|
39
|
-
const frontmatter = match[1];
|
|
40
|
-
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
41
|
-
if (nameMatch)
|
|
42
|
-
info.name = nameMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
43
|
-
const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
44
|
-
if (descMatch)
|
|
45
|
-
info.description = descMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
46
|
-
const metaMatch = frontmatter.match(/^metadata:\s*(.+)$/m);
|
|
47
|
-
if (metaMatch) {
|
|
48
|
-
try {
|
|
49
|
-
const meta = JSON.parse(metaMatch[1]);
|
|
50
|
-
// agent-skills repo uses metadata.aisa; legacy skills used metadata.openclaw.
|
|
51
|
-
info.emoji = meta?.aisa?.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
|
-
}
|
|
8
|
+
import { SKILLS_REPO, getSkillIndex, listSkills, resolveSlug, leafName, fetchSkillFiles, fetchSkillMarkdown, parseSkillFrontmatter, } from "../skills-registry.js";
|
|
78
9
|
// --- Skill Templates (for init) ---
|
|
79
10
|
const TEMPLATES = {
|
|
80
11
|
default: `---
|
|
@@ -156,7 +87,7 @@ export AISA_API_KEY=sk-your-key
|
|
|
156
87
|
## Smart Search
|
|
157
88
|
|
|
158
89
|
\`\`\`bash
|
|
159
|
-
aisa web-search "latest AI research" --type
|
|
90
|
+
aisa web-search "latest AI research" --type tavily
|
|
160
91
|
\`\`\`
|
|
161
92
|
|
|
162
93
|
## YouTube Search
|
|
@@ -275,27 +206,40 @@ aisa video status <task-id>
|
|
|
275
206
|
`,
|
|
276
207
|
};
|
|
277
208
|
// --- Commands ---
|
|
278
|
-
|
|
209
|
+
function printSkill(s) {
|
|
210
|
+
const emoji = s.emoji ? `${s.emoji} ` : "";
|
|
211
|
+
console.log(` ${emoji}${chalk.cyan.bold(s.name)} ${chalk.gray(s.slug)}`);
|
|
212
|
+
if (s.description) {
|
|
213
|
+
console.log(` ${chalk.gray(truncate(s.description, 80))}`);
|
|
214
|
+
}
|
|
215
|
+
console.log();
|
|
216
|
+
}
|
|
217
|
+
export async function skillsListAction(options) {
|
|
279
218
|
const spinner = ora("Fetching skills from GitHub...").start();
|
|
280
219
|
try {
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
220
|
+
const index = await getSkillIndex({ refresh: options.refresh });
|
|
221
|
+
let skills = await listSkills(index);
|
|
222
|
+
if (options.category) {
|
|
223
|
+
const cat = options.category.toLowerCase();
|
|
224
|
+
skills = skills.filter((s) => s.slug.split("/")[0].toLowerCase() === cat);
|
|
225
|
+
}
|
|
226
|
+
const total = skills.length;
|
|
227
|
+
if (options.limit)
|
|
228
|
+
skills = skills.slice(0, parseInt(options.limit));
|
|
285
229
|
spinner.stop();
|
|
286
230
|
if (skills.length === 0) {
|
|
287
|
-
console.log(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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))}`);
|
|
231
|
+
console.log(options.category
|
|
232
|
+
? ` No skills in category "${options.category}".`
|
|
233
|
+
: " No skills found.");
|
|
234
|
+
if (options.category) {
|
|
235
|
+
const categories = [...new Set(index.slugs.map((s) => s.split("/")[0]))];
|
|
236
|
+
hint(`Categories: ${categories.join(", ")}`);
|
|
296
237
|
}
|
|
297
|
-
|
|
238
|
+
return;
|
|
298
239
|
}
|
|
240
|
+
console.log(chalk.bold(`\n ${skills.length}${skills.length < total ? ` of ${total}` : ""} skills available\n`));
|
|
241
|
+
for (const s of skills)
|
|
242
|
+
printSkill(s);
|
|
299
243
|
hint("Install: aisa skills install <slug>");
|
|
300
244
|
hint("Details: aisa skills show <slug>");
|
|
301
245
|
}
|
|
@@ -304,30 +248,26 @@ export async function skillsListAction(_options) {
|
|
|
304
248
|
error(err.message);
|
|
305
249
|
}
|
|
306
250
|
}
|
|
307
|
-
export async function skillsSearchAction(query,
|
|
251
|
+
export async function skillsSearchAction(query, options) {
|
|
308
252
|
const spinner = ora(`Searching skills: "${query}"...`).start();
|
|
309
253
|
try {
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
const skills = metas.filter((m) => m !== null);
|
|
254
|
+
const index = await getSkillIndex({ refresh: options.refresh });
|
|
255
|
+
const skills = await listSkills(index);
|
|
313
256
|
const q = query.toLowerCase();
|
|
314
|
-
|
|
257
|
+
let matches = skills.filter((s) => s.name.toLowerCase().includes(q) ||
|
|
315
258
|
s.slug.toLowerCase().includes(q) ||
|
|
316
259
|
s.description.toLowerCase().includes(q));
|
|
260
|
+
const total = matches.length;
|
|
261
|
+
if (options.limit)
|
|
262
|
+
matches = matches.slice(0, parseInt(options.limit));
|
|
317
263
|
spinner.stop();
|
|
318
264
|
if (matches.length === 0) {
|
|
319
265
|
console.log(` No skills found for "${query}".`);
|
|
320
266
|
return;
|
|
321
267
|
}
|
|
322
|
-
console.log(chalk.bold(`\n ${matches.length} result(s)\n`));
|
|
323
|
-
for (const s of matches)
|
|
324
|
-
|
|
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
|
-
}
|
|
268
|
+
console.log(chalk.bold(`\n ${matches.length}${matches.length < total ? ` of ${total}` : ""} result(s)\n`));
|
|
269
|
+
for (const s of matches)
|
|
270
|
+
printSkill(s);
|
|
331
271
|
hint("Install: aisa skills install <slug>");
|
|
332
272
|
}
|
|
333
273
|
catch (err) {
|
|
@@ -338,29 +278,19 @@ export async function skillsSearchAction(query, _options) {
|
|
|
338
278
|
export async function skillsShowAction(slug) {
|
|
339
279
|
const spinner = ora(`Loading ${slug}...`).start();
|
|
340
280
|
try {
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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));
|
|
281
|
+
const index = await getSkillIndex();
|
|
282
|
+
const canonical = resolveSlug(slug, index);
|
|
283
|
+
const content = await fetchSkillMarkdown(canonical);
|
|
284
|
+
const meta = parseSkillFrontmatter(canonical, content);
|
|
285
|
+
const prefix = `${canonical}/`;
|
|
286
|
+
const files = (index.blobs[canonical] || []).map((b) => b.path.slice(prefix.length));
|
|
357
287
|
spinner.stop();
|
|
358
288
|
const emoji = meta.emoji ? `${meta.emoji} ` : "";
|
|
359
289
|
console.log(`\n ${emoji}${chalk.cyan.bold(meta.name)}`);
|
|
360
290
|
console.log(` ${meta.description}`);
|
|
361
|
-
console.log(` Slug: ${chalk.gray(
|
|
291
|
+
console.log(` Slug: ${chalk.gray(canonical)}`);
|
|
362
292
|
console.log(` Files: ${files.join(", ")}`);
|
|
363
|
-
console.log(` Source: ${chalk.gray(`https://github.com/${SKILLS_REPO}/tree/main/${
|
|
293
|
+
console.log(` Source: ${chalk.gray(`https://github.com/${SKILLS_REPO}/tree/main/${canonical}`)}`);
|
|
364
294
|
// Print SKILL.md body (after frontmatter)
|
|
365
295
|
const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "").trim();
|
|
366
296
|
if (body) {
|
|
@@ -368,20 +298,83 @@ export async function skillsShowAction(slug) {
|
|
|
368
298
|
console.log(body);
|
|
369
299
|
}
|
|
370
300
|
console.log();
|
|
371
|
-
hint(`Install: aisa skills install ${
|
|
301
|
+
hint(`Install: aisa skills install ${leafName(canonical)}`);
|
|
372
302
|
}
|
|
373
303
|
catch (err) {
|
|
374
304
|
spinner.fail("Failed to load skill");
|
|
375
305
|
error(err.message);
|
|
376
306
|
}
|
|
377
307
|
}
|
|
308
|
+
/** Agent skill loaders expect one directory level, named to match SKILL.md's `name:`. */
|
|
309
|
+
function installedName(canonical) {
|
|
310
|
+
return leafName(canonical);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Marker recording which skill owns an installed directory.
|
|
314
|
+
*
|
|
315
|
+
* Frontmatter `name:` is not an identity: two skills in different categories can
|
|
316
|
+
* share both a leaf name and a `name:`, and comparing those would let the second
|
|
317
|
+
* install silently overwrite the first. The canonical slug is the only unique
|
|
318
|
+
* key, and nothing in SKILL.md carries it.
|
|
319
|
+
*/
|
|
320
|
+
const MARKER_FILE = ".aisa-skill.json";
|
|
321
|
+
function readMarker(dir) {
|
|
322
|
+
try {
|
|
323
|
+
const parsed = JSON.parse(readFileSync(join(dir, MARKER_FILE), "utf-8"));
|
|
324
|
+
return typeof parsed?.slug === "string" ? parsed : undefined;
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Identify the skill currently installed at `dir`. Falls back to frontmatter for
|
|
332
|
+
* directories written before markers existed, where the name is all we have.
|
|
333
|
+
*/
|
|
334
|
+
function occupant(dir) {
|
|
335
|
+
const marker = readMarker(dir);
|
|
336
|
+
if (marker)
|
|
337
|
+
return { slug: marker.slug };
|
|
338
|
+
const skillMd = join(dir, "SKILL.md");
|
|
339
|
+
if (!existsSync(skillMd))
|
|
340
|
+
return undefined;
|
|
341
|
+
try {
|
|
342
|
+
return { name: parseSkillFrontmatter("", readFileSync(skillMd, "utf-8")).name };
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Why replacing what is already installed would be unsafe, or undefined when it
|
|
350
|
+
* is the same skill.
|
|
351
|
+
*
|
|
352
|
+
* Frontmatter `name:` cannot settle ownership: two skills in different
|
|
353
|
+
* categories may share both a leaf name and a `name:`. That ambiguity is only
|
|
354
|
+
* reachable when the caller named a category — a bare name would have been
|
|
355
|
+
* rejected as ambiguous before we got here — so an unmarked directory is
|
|
356
|
+
* treated as unverifiable exactly in that case.
|
|
357
|
+
*/
|
|
358
|
+
function describeConflict(present, canonical, expectedName, requestedCanonical) {
|
|
359
|
+
if (!present)
|
|
360
|
+
return undefined;
|
|
361
|
+
if (present.slug) {
|
|
362
|
+
return present.slug === canonical ? undefined : `holds a different skill (${present.slug})`;
|
|
363
|
+
}
|
|
364
|
+
if (requestedCanonical) {
|
|
365
|
+
return `holds an unmarked install that cannot be verified as ${canonical}`;
|
|
366
|
+
}
|
|
367
|
+
return present.name === expectedName ? undefined : `holds a different skill ("${present.name}")`;
|
|
368
|
+
}
|
|
378
369
|
export async function skillsInstallAction(slug, options) {
|
|
379
370
|
const spinner = ora(`Fetching skill '${slug}' from GitHub...`).start();
|
|
380
371
|
try {
|
|
381
|
-
const
|
|
372
|
+
const index = await getSkillIndex();
|
|
373
|
+
const canonical = resolveSlug(slug, index);
|
|
374
|
+
const files = await fetchSkillFiles(canonical, index);
|
|
382
375
|
if (files.length === 0) {
|
|
383
376
|
spinner.fail("Skill not found");
|
|
384
|
-
error(`No
|
|
377
|
+
error(`No files found for "${canonical}" in ${SKILLS_REPO}`);
|
|
385
378
|
return;
|
|
386
379
|
}
|
|
387
380
|
spinner.stop();
|
|
@@ -405,15 +398,49 @@ export async function skillsInstallAction(slug, options) {
|
|
|
405
398
|
targets = Object.keys(AGENT_DIRS);
|
|
406
399
|
}
|
|
407
400
|
}
|
|
401
|
+
const dirName = installedName(canonical);
|
|
402
|
+
const expectedName = parseSkillFrontmatter(canonical, files.find((f) => f.path === "SKILL.md")?.content.toString("utf-8") || "").name;
|
|
403
|
+
const marker = { slug: canonical, installedAt: new Date().toISOString() };
|
|
404
|
+
// A bare name is only resolvable when the leaf is unique across the repo —
|
|
405
|
+
// resolveSlug rejects it otherwise — so whatever occupies the directory must
|
|
406
|
+
// be this skill. A canonical slug bypasses that check, which is exactly when
|
|
407
|
+
// an unmarked directory becomes unverifiable.
|
|
408
|
+
const requestedCanonical = slug.includes("/");
|
|
408
409
|
let installed = 0;
|
|
409
410
|
for (const agent of targets) {
|
|
410
|
-
const dir = expandHome(join(AGENT_DIRS[agent],
|
|
411
|
+
const dir = expandHome(join(AGENT_DIRS[agent], dirName));
|
|
412
|
+
// A different skill already occupying this directory name means the
|
|
413
|
+
// install would replace it. Compare canonical slugs where we have them —
|
|
414
|
+
// names collide across categories.
|
|
415
|
+
const exists = existsSync(dir);
|
|
416
|
+
const present = exists ? occupant(dir) : undefined;
|
|
417
|
+
const conflict = exists && !present
|
|
418
|
+
? // No marker and no readable SKILL.md: an interrupted install, or a
|
|
419
|
+
// directory the user put there. Neither is safe to delete silently.
|
|
420
|
+
"exists but is not a recognizable skill install"
|
|
421
|
+
: describeConflict(present, canonical, expectedName, requestedCanonical);
|
|
422
|
+
if (conflict && !options.force) {
|
|
423
|
+
error(`${AGENT_DIRS[agent]}${dirName}/ ${conflict} — pass --force to replace`);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
// Replace rather than merge: writing over a directory only overwrites
|
|
427
|
+
// files that happen to share a name, leaving the previous skill's scripts
|
|
428
|
+
// and assets behind for an agent to keep loading. Keyed on the directory
|
|
429
|
+
// existing, not on identifying its occupant — an unidentifiable directory
|
|
430
|
+
// still must not be merged into.
|
|
431
|
+
if (exists)
|
|
432
|
+
removeDir(dir);
|
|
411
433
|
ensureDir(dir);
|
|
412
434
|
writeSkillFiles(dir, files);
|
|
413
|
-
|
|
435
|
+
writeFileSync(join(dir, MARKER_FILE), JSON.stringify(marker, null, 2) + "\n", "utf-8");
|
|
436
|
+
console.log(` ${chalk.green("✓")} ${AGENT_DIRS[agent]}${dirName} (${agent})`);
|
|
414
437
|
installed++;
|
|
415
438
|
}
|
|
416
|
-
|
|
439
|
+
if (installed === 0) {
|
|
440
|
+
error("Nothing installed.");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
success(`Skill '${canonical}' installed to ${installed} agent(s)`);
|
|
417
444
|
}
|
|
418
445
|
catch (err) {
|
|
419
446
|
spinner.fail("Failed to install skill");
|
|
@@ -421,6 +448,12 @@ export async function skillsInstallAction(slug, options) {
|
|
|
421
448
|
}
|
|
422
449
|
}
|
|
423
450
|
export function skillsRemoveAction(slug, options) {
|
|
451
|
+
// Installed directories use the leaf name, so a bare name is what's on disk.
|
|
452
|
+
const requested = slug.replace(/^\/+|\/+$/g, "");
|
|
453
|
+
const dirName = leafName(requested);
|
|
454
|
+
// A canonical slug names one specific skill; a bare name names whatever holds
|
|
455
|
+
// that directory.
|
|
456
|
+
const wantsCanonical = requested.includes("/");
|
|
424
457
|
let targets;
|
|
425
458
|
if (options.agent && options.agent !== "all") {
|
|
426
459
|
targets = [options.agent];
|
|
@@ -429,20 +462,42 @@ export function skillsRemoveAction(slug, options) {
|
|
|
429
462
|
targets = Object.keys(AGENT_DIRS);
|
|
430
463
|
}
|
|
431
464
|
let removed = 0;
|
|
465
|
+
let refused = 0;
|
|
432
466
|
for (const agent of targets) {
|
|
433
|
-
const dir = expandHome(join(AGENT_DIRS[agent],
|
|
434
|
-
if (existsSync(dir))
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
467
|
+
const dir = expandHome(join(AGENT_DIRS[agent], dirName));
|
|
468
|
+
if (!existsSync(dir))
|
|
469
|
+
continue;
|
|
470
|
+
const marker = readMarker(dir);
|
|
471
|
+
// Leaf names collide across categories, so deleting by name alone can take
|
|
472
|
+
// out a different skill than the one asked for.
|
|
473
|
+
if (wantsCanonical && !options.force) {
|
|
474
|
+
if (marker && marker.slug !== requested) {
|
|
475
|
+
error(`${AGENT_DIRS[agent]}${dirName}/ holds ${marker.slug}, not ${requested} — skipped`);
|
|
476
|
+
refused++;
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
// Pre-marker installs carry no slug, and a same-named skill from another
|
|
480
|
+
// category is indistinguishable from the requested one.
|
|
481
|
+
if (!marker) {
|
|
482
|
+
error(`${AGENT_DIRS[agent]}${dirName}/ has no install marker — cannot verify it is ${requested} — skipped`);
|
|
483
|
+
refused++;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (!marker) {
|
|
488
|
+
hint(`${AGENT_DIRS[agent]}${dirName}/ has no install marker — removing by directory name`);
|
|
438
489
|
}
|
|
490
|
+
removeDir(dir);
|
|
491
|
+
console.log(` ${chalk.green("✓")} Removed from ${AGENT_DIRS[agent]}`);
|
|
492
|
+
removed++;
|
|
439
493
|
}
|
|
440
494
|
if (removed === 0) {
|
|
441
|
-
console.log(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
495
|
+
console.log(refused > 0
|
|
496
|
+
? " Nothing removed — pass --force to remove regardless of what is installed."
|
|
497
|
+
: " Skill not found in any agent directory.");
|
|
498
|
+
return;
|
|
445
499
|
}
|
|
500
|
+
success(`Removed '${dirName}' from ${removed} agent(s)`);
|
|
446
501
|
}
|
|
447
502
|
export function skillsInitAction(name, options) {
|
|
448
503
|
const dir = resolve(name);
|
package/dist/commands/twitter.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import ora from "ora";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { requireApiKey, getConfig, setConfig } from "../config.js";
|
|
4
|
-
import { apiRequest } from "../api.js";
|
|
5
|
-
import { APIS_BASE_URL } from "../constants.js";
|
|
4
|
+
import { apiRequest, resolveBases } from "../api.js";
|
|
6
5
|
import { error, success, formatJson } from "../utils/display.js";
|
|
7
6
|
function requireCookies() {
|
|
8
7
|
const cookies = getConfig("twitterCookies");
|
|
@@ -344,7 +343,7 @@ export async function twitterUploadMediaAction(filePath, options) {
|
|
|
344
343
|
}
|
|
345
344
|
formData.append("login_cookies", auth.login_cookies);
|
|
346
345
|
formData.append("proxy", auth.proxy);
|
|
347
|
-
const url = `${
|
|
346
|
+
const url = `${resolveBases().domain}/twitter/upload_media_v2`;
|
|
348
347
|
const res = await fetch(url, {
|
|
349
348
|
method: "POST",
|
|
350
349
|
headers: {
|
package/dist/commands/video.d.ts
CHANGED
|
@@ -1,9 +1,49 @@
|
|
|
1
|
+
import { type MediaType } from "../constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Each upstream nests the finished video somewhere different (DashScope puts it
|
|
4
|
+
* at result.output.video_url, Seedance under result.data[].content[].url), so
|
|
5
|
+
* search rather than hardcode a path.
|
|
6
|
+
*
|
|
7
|
+
* Every pass covers the whole tree before the next one starts, and each is
|
|
8
|
+
* narrower than a plain "ends in url" match would be. Responses ship thumbnails
|
|
9
|
+
* and cover images beside the clip, and `thumbnail_url` both ends in "url" and
|
|
10
|
+
* tends to sit closer to the root than the video does — matching it would make
|
|
11
|
+
* `--output` download a still image.
|
|
12
|
+
*/
|
|
13
|
+
export declare function findVideoUrl(value: unknown): string | undefined;
|
|
14
|
+
export interface MediaInput {
|
|
15
|
+
type: MediaType;
|
|
16
|
+
url: string;
|
|
17
|
+
}
|
|
18
|
+
/** Parse `--media first_frame=https://…`, and bare URLs from `--image`. */
|
|
19
|
+
export declare function parseMedia(specs?: string[], images?: string[]): MediaInput[];
|
|
20
|
+
/**
|
|
21
|
+
* Build the request body for a model's vendor. The gateway rewrites only the
|
|
22
|
+
* top-level `model` and forwards the rest verbatim, so a Bailian body sent to a
|
|
23
|
+
* Seedance model is rejected with a 400.
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildVideoBody(model: string, prompt: string, media: MediaInput[], options: {
|
|
26
|
+
resolution?: string;
|
|
27
|
+
duration?: string;
|
|
28
|
+
}): Record<string, unknown>;
|
|
29
|
+
/**
|
|
30
|
+
* Parse a `--body` payload. The option means "send this verbatim", so the model
|
|
31
|
+
* it names wins over the CLI's default — only an explicit `--model` overrides
|
|
32
|
+
* it. Anything else would silently redirect a valid request to another model.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseBodyOverride(raw: string, explicitModel?: string): Record<string, unknown>;
|
|
1
35
|
export declare function videoCreateAction(prompt: string, options: {
|
|
2
36
|
model?: string;
|
|
3
37
|
wait?: boolean;
|
|
4
38
|
output?: string;
|
|
5
39
|
raw?: boolean;
|
|
40
|
+
resolution?: string;
|
|
41
|
+
duration?: string;
|
|
42
|
+
image?: string[];
|
|
43
|
+
media?: string[];
|
|
44
|
+
body?: string;
|
|
6
45
|
}): Promise<void>;
|
|
7
46
|
export declare function videoStatusAction(taskId: string, options: {
|
|
8
47
|
raw?: boolean;
|
|
48
|
+
output?: string;
|
|
9
49
|
}): Promise<void>;
|