ali-skills 0.0.13 → 0.0.14
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { r as __toESM } from "./_chunks/rolldown-runtime.mjs";
|
|
2
|
+
import { n as __require, r as __toESM } from "./_chunks/rolldown-runtime.mjs";
|
|
3
3
|
import { l as pD, u as require_picocolors } from "./_chunks/libs/@clack/core.mjs";
|
|
4
|
-
import { a as Y, c as
|
|
4
|
+
import { a as Y, c as ge, d as ye, i as Se, l as ve, n as M, o as be, r as Me, s as fe, t as Ie, u as xe } from "./_chunks/libs/@clack/prompts.mjs";
|
|
5
5
|
import "./_chunks/libs/@kwsites/file-exists.mjs";
|
|
6
6
|
import "./_chunks/libs/@kwsites/promise-deferred.mjs";
|
|
7
7
|
import { t as esm_default } from "./_chunks/libs/simple-git.mjs";
|
|
@@ -9,15 +9,16 @@ import { t as require_gray_matter } from "./_chunks/libs/gray-matter.mjs";
|
|
|
9
9
|
import "./_chunks/libs/extend-shallow.mjs";
|
|
10
10
|
import "./_chunks/libs/esprima.mjs";
|
|
11
11
|
import { t as xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
|
|
12
|
-
import { execSync, spawn
|
|
12
|
+
import { execSync, spawn } from "child_process";
|
|
13
13
|
import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
14
14
|
import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
|
15
15
|
import { homedir, platform, tmpdir } from "os";
|
|
16
16
|
import { createHash } from "crypto";
|
|
17
|
-
import { fileURLToPath } from "url";
|
|
17
|
+
import { URL as URL$1, fileURLToPath } from "url";
|
|
18
18
|
import * as readline from "readline";
|
|
19
19
|
import { Writable } from "stream";
|
|
20
20
|
import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
21
|
+
import * as http from "http";
|
|
21
22
|
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
22
23
|
function getOwnerRepo(parsed) {
|
|
23
24
|
if (parsed.type === "local") return null;
|
|
@@ -1977,6 +1978,26 @@ async function fetchSkillFolderHash(ownerRepo, skillPath, token) {
|
|
|
1977
1978
|
}
|
|
1978
1979
|
return null;
|
|
1979
1980
|
}
|
|
1981
|
+
async function fetchAliInternalCommitHash(source, skillPath = "", token) {
|
|
1982
|
+
const branches = ["main", "master"];
|
|
1983
|
+
const privateToken = token || process.env.GITLAB_PRIVATE_TOKEN || process.env.CODE_ALIBABA_TOKEN || GITLAB_ACCOUNT.token;
|
|
1984
|
+
for (const branch of branches) try {
|
|
1985
|
+
let url = `https://code.alibaba-inc.com/api/v4/projects/${encodeURIComponent(source)}/repository/commits?ref_name=${branch}&per_page=1`;
|
|
1986
|
+
if (skillPath) url += `&path=${encodeURIComponent(skillPath)}`;
|
|
1987
|
+
const headers = {
|
|
1988
|
+
Accept: "application/json",
|
|
1989
|
+
"User-Agent": "skills-cli",
|
|
1990
|
+
"Private-Token": privateToken
|
|
1991
|
+
};
|
|
1992
|
+
const response = await fetch(url, { headers });
|
|
1993
|
+
if (!response.ok) continue;
|
|
1994
|
+
const data = await response.json();
|
|
1995
|
+
if (data && data.length > 0) return data[0].id;
|
|
1996
|
+
} catch {
|
|
1997
|
+
continue;
|
|
1998
|
+
}
|
|
1999
|
+
return null;
|
|
2000
|
+
}
|
|
1980
2001
|
async function addSkillToLock(skillName, entry) {
|
|
1981
2002
|
const lock = await readSkillLock$1();
|
|
1982
2003
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2101,7 +2122,7 @@ function logSkillParseFailures(failures) {
|
|
|
2101
2122
|
if (f.hint) M.message(import_picocolors.default.yellow(` 提示: ${f.hint}`));
|
|
2102
2123
|
}
|
|
2103
2124
|
}
|
|
2104
|
-
var version$1 = "2.0.
|
|
2125
|
+
var version$1 = "2.0.14";
|
|
2105
2126
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
2106
2127
|
async function isSourcePrivate(source) {
|
|
2107
2128
|
const ownerRepo = parseOwnerRepo(source);
|
|
@@ -3108,11 +3129,25 @@ async function runAdd(args, options = {}) {
|
|
|
3108
3129
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
3109
3130
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
3110
3131
|
const computedHash = await computeSkillFolderHash(skill.path);
|
|
3111
|
-
|
|
3132
|
+
const skillFilePath = skillFiles[skill.name];
|
|
3133
|
+
const lockEntry = {
|
|
3112
3134
|
source: lockSource || parsed.url,
|
|
3113
3135
|
sourceType: effectiveSourceType,
|
|
3114
3136
|
computedHash
|
|
3115
|
-
}
|
|
3137
|
+
};
|
|
3138
|
+
if (effectiveSourceType === "github" && skillFilePath && normalizedSource) {
|
|
3139
|
+
const folderHash = await fetchSkillFolderHash(normalizedSource, skillFilePath, getGitHubToken());
|
|
3140
|
+
if (folderHash) lockEntry.skillFolderHash = folderHash;
|
|
3141
|
+
lockEntry.skillPath = skillFilePath.replace(/\/?SKILL\.md$/i, "").replace(/\/+$/, "");
|
|
3142
|
+
}
|
|
3143
|
+
if (effectiveSourceType === "internal") {
|
|
3144
|
+
const source = normalizedSource || getOwnerRepo(parsed) || parsed.url;
|
|
3145
|
+
const skillFolder = skillFilePath ? skillFilePath.replace(/\/?SKILL\.md$/i, "").replace(/\/+$/, "") : "";
|
|
3146
|
+
const commitHash = await fetchAliInternalCommitHash(source, skillFolder, null);
|
|
3147
|
+
if (commitHash) lockEntry.commitHash = commitHash;
|
|
3148
|
+
lockEntry.skillPath = skillFolder;
|
|
3149
|
+
}
|
|
3150
|
+
await addSkillToLocalLock(skill.name, lockEntry, cwd);
|
|
3116
3151
|
} catch {}
|
|
3117
3152
|
}
|
|
3118
3153
|
}
|
|
@@ -3254,12 +3289,12 @@ function parseAddOptions(args) {
|
|
|
3254
3289
|
options
|
|
3255
3290
|
};
|
|
3256
3291
|
}
|
|
3257
|
-
const RESET$
|
|
3292
|
+
const RESET$5 = "\x1B[0m";
|
|
3258
3293
|
const BOLD$2 = "\x1B[1m";
|
|
3259
|
-
const DIM$
|
|
3260
|
-
const TEXT$
|
|
3294
|
+
const DIM$5 = "\x1B[38;5;102m";
|
|
3295
|
+
const TEXT$4 = "\x1B[38;5;145m";
|
|
3261
3296
|
const CYAN$1 = "\x1B[36m";
|
|
3262
|
-
const YELLOW$
|
|
3297
|
+
const YELLOW$4 = "\x1B[33m";
|
|
3263
3298
|
const SKILLS_API_URL = process.env.SKILLS_API_URL;
|
|
3264
3299
|
const SEARCH_ENDPOINT = SKILLS_API_URL ? `${SKILLS_API_URL.replace(/\/+$/, "")}/search` : "https://next-ai-base.pre-fn.alibaba-inc.com/api/skills/search";
|
|
3265
3300
|
function formatInstalls(count) {
|
|
@@ -3313,29 +3348,29 @@ async function runSearchPrompt(initialQuery = "") {
|
|
|
3313
3348
|
if (lastRenderedLines > 0) process.stdout.write(MOVE_UP(lastRenderedLines) + MOVE_TO_COL(1));
|
|
3314
3349
|
process.stdout.write(CLEAR_DOWN);
|
|
3315
3350
|
const lines = [];
|
|
3316
|
-
const cursor = `${BOLD$2}_${RESET$
|
|
3317
|
-
lines.push(`${TEXT$
|
|
3351
|
+
const cursor = `${BOLD$2}_${RESET$5}`;
|
|
3352
|
+
lines.push(`${TEXT$4}Search skills:${RESET$5} ${query}${cursor}`);
|
|
3318
3353
|
lines.push("");
|
|
3319
|
-
if (!query || query.length < 2) lines.push(`${DIM$
|
|
3320
|
-
else if (results.length === 0 && loading) lines.push(`${DIM$
|
|
3321
|
-
else if (results.length === 0) lines.push(`${DIM$
|
|
3354
|
+
if (!query || query.length < 2) lines.push(`${DIM$5}Start typing to search (min 2 chars)${RESET$5}`);
|
|
3355
|
+
else if (results.length === 0 && loading) lines.push(`${DIM$5}Searching...${RESET$5}`);
|
|
3356
|
+
else if (results.length === 0) lines.push(`${DIM$5}No skills found${RESET$5}`);
|
|
3322
3357
|
else {
|
|
3323
3358
|
const visible = results.slice(0, 8);
|
|
3324
3359
|
for (let i = 0; i < visible.length; i++) {
|
|
3325
3360
|
const skill = visible[i];
|
|
3326
3361
|
const isSelected = i === selectedIndex;
|
|
3327
|
-
const arrow = isSelected ? `${BOLD$2}>${RESET$
|
|
3328
|
-
const name = isSelected ? `${BOLD$2}${skill.name}${RESET$
|
|
3329
|
-
const source = skill.source ? ` ${DIM$
|
|
3362
|
+
const arrow = isSelected ? `${BOLD$2}>${RESET$5}` : " ";
|
|
3363
|
+
const name = isSelected ? `${BOLD$2}${skill.name}${RESET$5}` : `${TEXT$4}${skill.name}${RESET$5}`;
|
|
3364
|
+
const source = skill.source ? ` ${DIM$5}${skill.source}${RESET$5}` : "";
|
|
3330
3365
|
const installs = formatInstalls(skill.installs);
|
|
3331
|
-
const installsBadge = installs ? ` ${CYAN$1}${installs}${RESET$
|
|
3332
|
-
const githubBadge = skill.sourceType === "github" ? ` ${YELLOW$
|
|
3333
|
-
const loadingIndicator = loading && i === 0 ? ` ${DIM$
|
|
3366
|
+
const installsBadge = installs ? ` ${CYAN$1}${installs}${RESET$5}` : "";
|
|
3367
|
+
const githubBadge = skill.sourceType === "github" ? ` ${YELLOW$4}GitHub${RESET$5}` : "";
|
|
3368
|
+
const loadingIndicator = loading && i === 0 ? ` ${DIM$5}...${RESET$5}` : "";
|
|
3334
3369
|
lines.push(` ${arrow} ${name}${source}${githubBadge}${installsBadge}${loadingIndicator}`);
|
|
3335
3370
|
}
|
|
3336
3371
|
}
|
|
3337
3372
|
lines.push("");
|
|
3338
|
-
lines.push(`${DIM$
|
|
3373
|
+
lines.push(`${DIM$5}up/down navigate | enter select | esc cancel${RESET$5}`);
|
|
3339
3374
|
for (const line of lines) process.stdout.write(line + "\n");
|
|
3340
3375
|
lastRenderedLines = lines.length;
|
|
3341
3376
|
}
|
|
@@ -3419,9 +3454,9 @@ async function runSearchPrompt(initialQuery = "") {
|
|
|
3419
3454
|
async function runFind(args) {
|
|
3420
3455
|
const query = args.join(" ");
|
|
3421
3456
|
const isNonInteractive = !process.stdin.isTTY;
|
|
3422
|
-
const agentTip = `${DIM$
|
|
3423
|
-
${DIM$
|
|
3424
|
-
${DIM$
|
|
3457
|
+
const agentTip = `${DIM$5}Tip: if running in a coding agent, follow these steps:${RESET$5}
|
|
3458
|
+
${DIM$5} 1) npx @ali/cli-skills find [query]${RESET$5}
|
|
3459
|
+
${DIM$5} 2) npx @ali/cli-skills add <owner/repo@skill>${RESET$5}`;
|
|
3425
3460
|
if (query) {
|
|
3426
3461
|
const results = await searchSkillsAPI(query);
|
|
3427
3462
|
track({
|
|
@@ -3430,18 +3465,18 @@ ${DIM$2} 2) npx @ali/cli-skills add <owner/repo@skill>${RESET$2}`;
|
|
|
3430
3465
|
resultCount: String(results.length)
|
|
3431
3466
|
});
|
|
3432
3467
|
if (results.length === 0) {
|
|
3433
|
-
console.log(`${DIM$
|
|
3468
|
+
console.log(`${DIM$5}No skills found for "${query}"${RESET$5}`);
|
|
3434
3469
|
return;
|
|
3435
3470
|
}
|
|
3436
|
-
console.log(`${DIM$
|
|
3471
|
+
console.log(`${DIM$5}Install with${RESET$5} npx ali-skills add <owner/repo>`);
|
|
3437
3472
|
console.log();
|
|
3438
3473
|
for (const skill of results.slice(0, 6)) {
|
|
3439
3474
|
const pkg = skill.source || skill.slug;
|
|
3440
3475
|
const installs = formatInstalls(skill.installs);
|
|
3441
|
-
const githubBadge = skill.sourceType === "github" ? ` ${YELLOW$
|
|
3442
|
-
console.log(`${TEXT$
|
|
3476
|
+
const githubBadge = skill.sourceType === "github" ? ` ${YELLOW$4}GitHub${RESET$5}` : "";
|
|
3477
|
+
console.log(`${TEXT$4}${pkg}@${skill.name}${RESET$5}${githubBadge}${installs ? ` ${CYAN$1}${installs}${RESET$5}` : ""}`);
|
|
3443
3478
|
const skillUrl = buildSkillUrl(skill);
|
|
3444
|
-
console.log(`${DIM$
|
|
3479
|
+
console.log(`${DIM$5}└ ${skillUrl}${RESET$5}`);
|
|
3445
3480
|
console.log();
|
|
3446
3481
|
}
|
|
3447
3482
|
return;
|
|
@@ -3458,14 +3493,14 @@ ${DIM$2} 2) npx @ali/cli-skills add <owner/repo@skill>${RESET$2}`;
|
|
|
3458
3493
|
interactive: "1"
|
|
3459
3494
|
});
|
|
3460
3495
|
if (!selected) {
|
|
3461
|
-
console.log(`${DIM$
|
|
3496
|
+
console.log(`${DIM$5}Search cancelled${RESET$5}`);
|
|
3462
3497
|
console.log();
|
|
3463
3498
|
return;
|
|
3464
3499
|
}
|
|
3465
3500
|
const pkg = selected.source || selected.slug;
|
|
3466
3501
|
const skillName = selected.name;
|
|
3467
3502
|
console.log();
|
|
3468
|
-
console.log(`${TEXT$
|
|
3503
|
+
console.log(`${TEXT$4}Installing ${BOLD$2}${skillName}${RESET$5} from ${DIM$5}${pkg}${RESET$5}...`);
|
|
3469
3504
|
console.log();
|
|
3470
3505
|
const { source, options } = parseAddOptions([
|
|
3471
3506
|
pkg,
|
|
@@ -3475,7 +3510,7 @@ ${DIM$2} 2) npx @ali/cli-skills add <owner/repo@skill>${RESET$2}`;
|
|
|
3475
3510
|
await runAdd(source, options);
|
|
3476
3511
|
console.log();
|
|
3477
3512
|
const skillUrl = buildSkillUrl(selected);
|
|
3478
|
-
console.log(`${DIM$
|
|
3513
|
+
console.log(`${DIM$5}View the skill at${RESET$5} ${TEXT$4}${skillUrl}${RESET$5}`);
|
|
3479
3514
|
console.log();
|
|
3480
3515
|
}
|
|
3481
3516
|
const isCancelled = (value) => typeof value === "symbol";
|
|
@@ -3813,11 +3848,11 @@ async function runInstallFromLock(args) {
|
|
|
3813
3848
|
}
|
|
3814
3849
|
}
|
|
3815
3850
|
}
|
|
3816
|
-
const RESET$
|
|
3851
|
+
const RESET$4 = "\x1B[0m";
|
|
3817
3852
|
const BOLD$1 = "\x1B[1m";
|
|
3818
|
-
const DIM$
|
|
3853
|
+
const DIM$4 = "\x1B[38;5;102m";
|
|
3819
3854
|
const CYAN = "\x1B[36m";
|
|
3820
|
-
const YELLOW = "\x1B[33m";
|
|
3855
|
+
const YELLOW$3 = "\x1B[33m";
|
|
3821
3856
|
function shortenPath(fullPath, cwd) {
|
|
3822
3857
|
const home = homedir();
|
|
3823
3858
|
if (fullPath.startsWith(home)) return fullPath.replace(home, "~");
|
|
@@ -3851,8 +3886,8 @@ async function runList(args) {
|
|
|
3851
3886
|
const validAgents = Object.keys(agents);
|
|
3852
3887
|
const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
|
|
3853
3888
|
if (invalidAgents.length > 0) {
|
|
3854
|
-
console.log(`${YELLOW}Invalid agents: ${invalidAgents.join(", ")}${RESET$
|
|
3855
|
-
console.log(`${DIM$
|
|
3889
|
+
console.log(`${YELLOW$3}Invalid agents: ${invalidAgents.join(", ")}${RESET$4}`);
|
|
3890
|
+
console.log(`${DIM$4}Valid agents: ${validAgents.join(", ")}${RESET$4}`);
|
|
3856
3891
|
process.exit(1);
|
|
3857
3892
|
}
|
|
3858
3893
|
agentFilter = options.agent;
|
|
@@ -3879,20 +3914,20 @@ async function runList(args) {
|
|
|
3879
3914
|
console.log("[]");
|
|
3880
3915
|
return;
|
|
3881
3916
|
}
|
|
3882
|
-
console.log(`${DIM$
|
|
3883
|
-
if (scope) console.log(`${DIM$
|
|
3884
|
-
else console.log(`${DIM$
|
|
3917
|
+
console.log(`${DIM$4}No ${scopeLabel.toLowerCase()} skills found.${RESET$4}`);
|
|
3918
|
+
if (scope) console.log(`${DIM$4}Try listing project skills without -g${RESET$4}`);
|
|
3919
|
+
else console.log(`${DIM$4}Try listing global skills with -g${RESET$4}`);
|
|
3885
3920
|
return;
|
|
3886
3921
|
}
|
|
3887
3922
|
function printSkill(skill, indent = false) {
|
|
3888
3923
|
const prefix = indent ? " " : "";
|
|
3889
3924
|
const shortPath = shortenPath(skill.canonicalPath, cwd);
|
|
3890
3925
|
const agentNames = skill.agents.map((a) => agents[a].displayName);
|
|
3891
|
-
const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$
|
|
3892
|
-
console.log(`${prefix}${CYAN}${skill.name}${RESET$
|
|
3893
|
-
console.log(`${prefix} ${DIM$
|
|
3926
|
+
const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW$3}not linked${RESET$4}`;
|
|
3927
|
+
console.log(`${prefix}${CYAN}${skill.name}${RESET$4} ${DIM$4}${shortPath}${RESET$4}`);
|
|
3928
|
+
console.log(`${prefix} ${DIM$4}Agents:${RESET$4} ${agentInfo}`);
|
|
3894
3929
|
}
|
|
3895
|
-
console.log(`${BOLD$1}${scopeLabel} Skills${RESET$
|
|
3930
|
+
console.log(`${BOLD$1}${scopeLabel} Skills${RESET$4}`);
|
|
3896
3931
|
console.log();
|
|
3897
3932
|
const groupedSkills = {};
|
|
3898
3933
|
const ungroupedSkills = [];
|
|
@@ -3908,13 +3943,13 @@ async function runList(args) {
|
|
|
3908
3943
|
const sortedGroups = Object.keys(groupedSkills).sort();
|
|
3909
3944
|
for (const group of sortedGroups) {
|
|
3910
3945
|
const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3911
|
-
console.log(`${BOLD$1}${title}${RESET$
|
|
3946
|
+
console.log(`${BOLD$1}${title}${RESET$4}`);
|
|
3912
3947
|
const skills = groupedSkills[group];
|
|
3913
3948
|
if (skills) for (const skill of skills) printSkill(skill, true);
|
|
3914
3949
|
console.log();
|
|
3915
3950
|
}
|
|
3916
3951
|
if (ungroupedSkills.length > 0) {
|
|
3917
|
-
console.log(`${BOLD$1}General${RESET$
|
|
3952
|
+
console.log(`${BOLD$1}General${RESET$4}`);
|
|
3918
3953
|
for (const skill of ungroupedSkills) printSkill(skill, true);
|
|
3919
3954
|
console.log();
|
|
3920
3955
|
}
|
|
@@ -4397,6 +4432,758 @@ function parsePublishOptions(args) {
|
|
|
4397
4432
|
}
|
|
4398
4433
|
return { options };
|
|
4399
4434
|
}
|
|
4435
|
+
const CONFIG_DIR = join(homedir(), ".ali-skills");
|
|
4436
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
4437
|
+
const AUTH_FILE = join(CONFIG_DIR, "auth.json");
|
|
4438
|
+
function ensureConfigDir() {
|
|
4439
|
+
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, {
|
|
4440
|
+
recursive: true,
|
|
4441
|
+
mode: 448
|
|
4442
|
+
});
|
|
4443
|
+
}
|
|
4444
|
+
function safeWriteFile(path, data) {
|
|
4445
|
+
ensureConfigDir();
|
|
4446
|
+
writeFileSync(path, data, { mode: 384 });
|
|
4447
|
+
}
|
|
4448
|
+
function readConfig() {
|
|
4449
|
+
try {
|
|
4450
|
+
if (!existsSync(CONFIG_FILE)) return {
|
|
4451
|
+
version: 1,
|
|
4452
|
+
gatewayUrl: getGatewayUrl(),
|
|
4453
|
+
defaultTimeout: 3e4
|
|
4454
|
+
};
|
|
4455
|
+
const content = readFileSync(CONFIG_FILE, "utf-8");
|
|
4456
|
+
return JSON.parse(content);
|
|
4457
|
+
} catch {
|
|
4458
|
+
return {
|
|
4459
|
+
version: 1,
|
|
4460
|
+
gatewayUrl: getGatewayUrl(),
|
|
4461
|
+
defaultTimeout: 3e4
|
|
4462
|
+
};
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
function saveConfig(config) {
|
|
4466
|
+
safeWriteFile(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
4467
|
+
}
|
|
4468
|
+
function readAuth() {
|
|
4469
|
+
try {
|
|
4470
|
+
if (!existsSync(AUTH_FILE)) return null;
|
|
4471
|
+
const content = readFileSync(AUTH_FILE, "utf-8");
|
|
4472
|
+
return JSON.parse(content);
|
|
4473
|
+
} catch {
|
|
4474
|
+
return null;
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
function saveAuth(auth) {
|
|
4478
|
+
const data = {
|
|
4479
|
+
version: 1,
|
|
4480
|
+
...auth
|
|
4481
|
+
};
|
|
4482
|
+
safeWriteFile(AUTH_FILE, JSON.stringify(data, null, 2));
|
|
4483
|
+
}
|
|
4484
|
+
function removeAuth() {
|
|
4485
|
+
try {
|
|
4486
|
+
if (existsSync(AUTH_FILE)) {
|
|
4487
|
+
const { unlinkSync } = __require("fs");
|
|
4488
|
+
unlinkSync(AUTH_FILE);
|
|
4489
|
+
}
|
|
4490
|
+
} catch {}
|
|
4491
|
+
}
|
|
4492
|
+
function isAuthExpired(auth) {
|
|
4493
|
+
return Date.now() >= auth.expiresAt - 10080 * 60 * 1e3;
|
|
4494
|
+
}
|
|
4495
|
+
function getPrivateToken() {
|
|
4496
|
+
return readAuth()?.privateToken || null;
|
|
4497
|
+
}
|
|
4498
|
+
function getGatewayUrl() {
|
|
4499
|
+
const envUrl = process.env.SKILLS_API_URL || process.env.ALI_GATEWAY_URL;
|
|
4500
|
+
if (envUrl) switch (envUrl.toLowerCase()) {
|
|
4501
|
+
case "local": return "http://localhost:3000";
|
|
4502
|
+
case "daily": return "https://next-ai-base.pre-fn.alibaba-inc.com";
|
|
4503
|
+
case "pre": return "https://ali-skills.pre.alibaba-inc.com";
|
|
4504
|
+
case "prod": return "https://ali-skills.alibaba-inc.com";
|
|
4505
|
+
default: return envUrl;
|
|
4506
|
+
}
|
|
4507
|
+
return "https://ali-skills.alibaba-inc.com";
|
|
4508
|
+
}
|
|
4509
|
+
function getConfigFilePath() {
|
|
4510
|
+
return CONFIG_FILE;
|
|
4511
|
+
}
|
|
4512
|
+
function setPlatformToken(platform, data) {
|
|
4513
|
+
const config = readConfig();
|
|
4514
|
+
if (!config.platforms) config.platforms = {};
|
|
4515
|
+
config.platforms[platform] = data;
|
|
4516
|
+
saveConfig(config);
|
|
4517
|
+
}
|
|
4518
|
+
function removePlatformToken(platform) {
|
|
4519
|
+
const config = readConfig();
|
|
4520
|
+
if (config.platforms && config.platforms[platform]) {
|
|
4521
|
+
delete config.platforms[platform];
|
|
4522
|
+
saveConfig(config);
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
4525
|
+
const DIM$3 = "\x1B[38;5;102m";
|
|
4526
|
+
const TEXT$3 = "\x1B[38;5;145m";
|
|
4527
|
+
const RESET$3 = "\x1B[0m";
|
|
4528
|
+
const GREEN$2 = "\x1B[32m";
|
|
4529
|
+
const YELLOW$2 = "\x1B[33m";
|
|
4530
|
+
function startLocalServer() {
|
|
4531
|
+
return new Promise((resolve) => {
|
|
4532
|
+
let resolveCallback;
|
|
4533
|
+
const callbackPromise = new Promise((resolve) => {
|
|
4534
|
+
resolveCallback = resolve;
|
|
4535
|
+
});
|
|
4536
|
+
const server = http.createServer((req, res) => {
|
|
4537
|
+
const url = new URL$1(req.url || "/", `http://localhost`);
|
|
4538
|
+
if (url.pathname === "/callback") {
|
|
4539
|
+
const empId = url.searchParams.get("emp_id") || "";
|
|
4540
|
+
const account = url.searchParams.get("account") || "";
|
|
4541
|
+
const name = url.searchParams.get("name") || "";
|
|
4542
|
+
const privateToken = url.searchParams.get("private_token") || "";
|
|
4543
|
+
const ssoRefreshToken = url.searchParams.get("sso_refresh_token") || "";
|
|
4544
|
+
const expiresAt = url.searchParams.get("expires_at") || "0";
|
|
4545
|
+
if (!empId || !privateToken) {
|
|
4546
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
4547
|
+
res.end(`
|
|
4548
|
+
<html>
|
|
4549
|
+
<body style="font-family: sans-serif; text-align: center; padding: 50px;">
|
|
4550
|
+
<h1 style="color: #ff4d4f;">登录失败</h1>
|
|
4551
|
+
<p>缺少必要的参数</p>
|
|
4552
|
+
</body>
|
|
4553
|
+
</html>
|
|
4554
|
+
`);
|
|
4555
|
+
return;
|
|
4556
|
+
}
|
|
4557
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
4558
|
+
res.end(`
|
|
4559
|
+
<html>
|
|
4560
|
+
<head><meta charset='UTF-8'><title>登录成功</title>
|
|
4561
|
+
<style>
|
|
4562
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; text-align: center; padding: 50px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; min-height: 100vh; margin: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; }
|
|
4563
|
+
.card { background: rgba(255, 255, 255, 0.95); padding: 40px; border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); color: #333; max-width: 400px; }
|
|
4564
|
+
h1 { margin: 0 0 16px 0; color: #52c41a; }
|
|
4565
|
+
.icon { font-size: 64px; margin-bottom: 16px; }
|
|
4566
|
+
.user-info { background: #f6ffed; border: 1px solid #b7eb8f; border-radius: 8px; padding: 16px; margin: 16px 0; text-align: left; }
|
|
4567
|
+
.user-info p { margin: 8px 0; }
|
|
4568
|
+
.hint { color: #666; font-size: 14px; margin-top: 24px; }
|
|
4569
|
+
</style>
|
|
4570
|
+
</head>
|
|
4571
|
+
<body>
|
|
4572
|
+
<div class="card">
|
|
4573
|
+
<div class="icon">✓</div>
|
|
4574
|
+
<h1>登录成功</h1>
|
|
4575
|
+
<div class="user-info">
|
|
4576
|
+
<p><strong>姓名:</strong> ${name}</p>
|
|
4577
|
+
<p><strong>账号:</strong> ${account}</p>
|
|
4578
|
+
<p><strong>工号:</strong> ${empId}</p>
|
|
4579
|
+
</div>
|
|
4580
|
+
<p class="hint">您可以关闭此页面并返回 CLI</p>
|
|
4581
|
+
</div>
|
|
4582
|
+
</body>
|
|
4583
|
+
</html>
|
|
4584
|
+
`);
|
|
4585
|
+
resolveCallback({
|
|
4586
|
+
empId,
|
|
4587
|
+
account,
|
|
4588
|
+
name,
|
|
4589
|
+
privateToken,
|
|
4590
|
+
ssoRefreshToken,
|
|
4591
|
+
expiresAt: parseInt(expiresAt, 10)
|
|
4592
|
+
});
|
|
4593
|
+
setTimeout(() => server.close(), 1e3);
|
|
4594
|
+
} else {
|
|
4595
|
+
res.writeHead(404);
|
|
4596
|
+
res.end("Not Found");
|
|
4597
|
+
}
|
|
4598
|
+
});
|
|
4599
|
+
server.listen(0, "localhost", () => {
|
|
4600
|
+
const port = server.address().port;
|
|
4601
|
+
resolve({
|
|
4602
|
+
port,
|
|
4603
|
+
waitForCallback: () => callbackPromise,
|
|
4604
|
+
close: () => server.close()
|
|
4605
|
+
});
|
|
4606
|
+
});
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4609
|
+
function openBrowser$2(url) {
|
|
4610
|
+
const platform = process.platform;
|
|
4611
|
+
spawn(platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open", [url], {
|
|
4612
|
+
detached: true,
|
|
4613
|
+
stdio: "ignore"
|
|
4614
|
+
});
|
|
4615
|
+
}
|
|
4616
|
+
async function login() {
|
|
4617
|
+
const existingAuth = readAuth();
|
|
4618
|
+
if (existingAuth && !isAuthExpired(existingAuth)) {
|
|
4619
|
+
console.log(`${TEXT$3}您已经登录为: ${existingAuth.name} (${existingAuth.account})${RESET$3}`);
|
|
4620
|
+
console.log(`${DIM$3}如需重新登录,请先执行: npx ali-skills logout${RESET$3}`);
|
|
4621
|
+
return;
|
|
4622
|
+
}
|
|
4623
|
+
console.log(`${TEXT$3}正在启动本地服务...${RESET$3}`);
|
|
4624
|
+
const { port, waitForCallback, close } = await startLocalServer();
|
|
4625
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
4626
|
+
const gatewayUrl = getGatewayUrl();
|
|
4627
|
+
const loginUrl = `${gatewayUrl}/api/gateway/login?callback_url=${encodeURIComponent(callbackUrl)}&type=cli`;
|
|
4628
|
+
console.log(`${TEXT$3}正在打开浏览器进行 BUC 授权...${RESET$3}`);
|
|
4629
|
+
console.log(`${DIM$3}如果浏览器没有自动打开,请手动访问:${RESET$3}`);
|
|
4630
|
+
console.log(`${DIM$3}${loginUrl}${RESET$3}`);
|
|
4631
|
+
openBrowser$2(loginUrl);
|
|
4632
|
+
console.log(`${TEXT$3}等待授权完成...${RESET$3}`);
|
|
4633
|
+
try {
|
|
4634
|
+
const { empId, account, name, privateToken, ssoRefreshToken, expiresAt } = await waitForCallback();
|
|
4635
|
+
saveAuth({
|
|
4636
|
+
empId,
|
|
4637
|
+
account,
|
|
4638
|
+
name,
|
|
4639
|
+
privateToken,
|
|
4640
|
+
ssoRefreshToken,
|
|
4641
|
+
expiresAt: expiresAt * 1e3,
|
|
4642
|
+
gatewayUrl
|
|
4643
|
+
});
|
|
4644
|
+
console.log();
|
|
4645
|
+
console.log(`${GREEN$2}✓ 登录成功!${RESET$3}`);
|
|
4646
|
+
console.log(`${TEXT$3}欢迎: ${name} (${account})${RESET$3}`);
|
|
4647
|
+
console.log(`${DIM$3}Token 有效期至: ${(/* @__PURE__ */ new Date(expiresAt * 1e3)).toLocaleDateString()}${RESET$3}`);
|
|
4648
|
+
if (!ssoRefreshToken) console.log(`${DIM$3}注意: SSO_REFRESH_TOKEN 未获取,需要时将从服务端获取${RESET$3}`);
|
|
4649
|
+
} catch (error) {
|
|
4650
|
+
close();
|
|
4651
|
+
throw error;
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
async function logout() {
|
|
4655
|
+
if (!readAuth()) {
|
|
4656
|
+
console.log(`${DIM$3}您尚未登录${RESET$3}`);
|
|
4657
|
+
return;
|
|
4658
|
+
}
|
|
4659
|
+
removeAuth();
|
|
4660
|
+
console.log(`${GREEN$2}✓ 已登出${RESET$3}`);
|
|
4661
|
+
}
|
|
4662
|
+
async function status() {
|
|
4663
|
+
const auth = readAuth();
|
|
4664
|
+
if (!auth) {
|
|
4665
|
+
console.log(`${DIM$3}您尚未登录${RESET$3}`);
|
|
4666
|
+
console.log(`${TEXT$3}请执行: npx ali-skills login${RESET$3}`);
|
|
4667
|
+
return;
|
|
4668
|
+
}
|
|
4669
|
+
const expired = isAuthExpired(auth);
|
|
4670
|
+
const remaining = auth.expiresAt - Date.now();
|
|
4671
|
+
const remainingDays = Math.max(0, Math.floor(remaining / (1440 * 60 * 1e3)));
|
|
4672
|
+
console.log(`${TEXT$3}登录状态:${RESET$3}`);
|
|
4673
|
+
console.log(` 用户: ${auth.name} (${auth.account})`);
|
|
4674
|
+
console.log(` 工号: ${auth.empId}`);
|
|
4675
|
+
console.log(` 网关: ${auth.gatewayUrl}`);
|
|
4676
|
+
if (expired) {
|
|
4677
|
+
console.log(` Token: ${YELLOW$2}即将过期${RESET$3}`);
|
|
4678
|
+
console.log(`${TEXT$3}建议重新登录: npx ali-skills login${RESET$3}`);
|
|
4679
|
+
} else console.log(` Token: ${GREEN$2}有效${RESET$3} (还剩 ${remainingDays} 天)`);
|
|
4680
|
+
}
|
|
4681
|
+
async function ensureLogin() {
|
|
4682
|
+
const auth = readAuth();
|
|
4683
|
+
if (!auth) throw new Error("未登录,请先执行: npx ali-skills login");
|
|
4684
|
+
if (isAuthExpired(auth)) throw new Error("登录已过期,请重新执行: npx ali-skills login");
|
|
4685
|
+
return {
|
|
4686
|
+
privateToken: auth.privateToken,
|
|
4687
|
+
ssoRefreshToken: auth.ssoRefreshToken
|
|
4688
|
+
};
|
|
4689
|
+
}
|
|
4690
|
+
const PLATFORMS = {
|
|
4691
|
+
code: {
|
|
4692
|
+
name: "Code (GitLab)",
|
|
4693
|
+
host: "code.alibaba-inc.com",
|
|
4694
|
+
aliases: ["gitlab", "git"],
|
|
4695
|
+
authType: "private_token",
|
|
4696
|
+
tokenHeaderName: "PRIVATE-TOKEN",
|
|
4697
|
+
tokenDocUrl: "https://code.alibaba-inc.com/profile/account"
|
|
4698
|
+
},
|
|
4699
|
+
yuque: {
|
|
4700
|
+
name: "语雀",
|
|
4701
|
+
host: "aliyuque.antfin.com",
|
|
4702
|
+
aliases: ["yuque"],
|
|
4703
|
+
authType: "private_token",
|
|
4704
|
+
tokenHeaderName: "X-Auth-Token",
|
|
4705
|
+
tokenDocUrl: "https://aliyuque.antfin.com/settings/tokens"
|
|
4706
|
+
},
|
|
4707
|
+
o2: {
|
|
4708
|
+
name: "O2 平台",
|
|
4709
|
+
host: "o2.alibaba-inc.com",
|
|
4710
|
+
authType: "app_code+buc",
|
|
4711
|
+
appCode: process.env.O2_APP_CODE || "SYKcE5kklxmOS7Z2XatNn"
|
|
4712
|
+
},
|
|
4713
|
+
o2open: {
|
|
4714
|
+
name: "O2 开放接口",
|
|
4715
|
+
host: "open.o2.alibaba-inc.com",
|
|
4716
|
+
authType: "app_code",
|
|
4717
|
+
appCode: process.env.O2_APP_CODE || "cli-skills"
|
|
4718
|
+
},
|
|
4719
|
+
aone: {
|
|
4720
|
+
name: "Aone",
|
|
4721
|
+
host: "aone.alibaba-inc.com",
|
|
4722
|
+
authType: "buc_sso_refresh",
|
|
4723
|
+
ssoType: "refresh_token"
|
|
4724
|
+
},
|
|
4725
|
+
aonedevops: {
|
|
4726
|
+
name: "Aone DevOps",
|
|
4727
|
+
host: "devops.alibaba-inc.com",
|
|
4728
|
+
authType: "buc_sso_refresh",
|
|
4729
|
+
ssoType: "refresh_token"
|
|
4730
|
+
}
|
|
4731
|
+
};
|
|
4732
|
+
function detectPlatformWithKey(hostname) {
|
|
4733
|
+
for (const [key, config] of Object.entries(PLATFORMS)) {
|
|
4734
|
+
if (config.host === hostname) return {
|
|
4735
|
+
key,
|
|
4736
|
+
config
|
|
4737
|
+
};
|
|
4738
|
+
if (config.aliases?.some((alias) => hostname.includes(alias))) return {
|
|
4739
|
+
key,
|
|
4740
|
+
config
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
for (const [key, config] of Object.entries(PLATFORMS)) if (hostname.endsWith(config.host) || hostname === config.host) return {
|
|
4744
|
+
key,
|
|
4745
|
+
config
|
|
4746
|
+
};
|
|
4747
|
+
return null;
|
|
4748
|
+
}
|
|
4749
|
+
function getPlatform(key) {
|
|
4750
|
+
if (PLATFORMS[key]) return PLATFORMS[key];
|
|
4751
|
+
for (const [, config] of Object.entries(PLATFORMS)) if (config.aliases?.includes(key)) return config;
|
|
4752
|
+
return null;
|
|
4753
|
+
}
|
|
4754
|
+
function listPlatforms() {
|
|
4755
|
+
return Object.entries(PLATFORMS).map(([key, config]) => ({
|
|
4756
|
+
key,
|
|
4757
|
+
config
|
|
4758
|
+
}));
|
|
4759
|
+
}
|
|
4760
|
+
const DIM$2 = "\x1B[38;5;102m";
|
|
4761
|
+
const TEXT$2 = "\x1B[38;5;145m";
|
|
4762
|
+
const RESET$2 = "\x1B[0m";
|
|
4763
|
+
const GREEN$1 = "\x1B[32m";
|
|
4764
|
+
const RED$1 = "\x1B[31m";
|
|
4765
|
+
const YELLOW$1 = "\x1B[33m";
|
|
4766
|
+
function openBrowser$1(url) {
|
|
4767
|
+
const platform = process.platform;
|
|
4768
|
+
spawn(platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open", [url], {
|
|
4769
|
+
detached: true,
|
|
4770
|
+
stdio: "ignore"
|
|
4771
|
+
});
|
|
4772
|
+
}
|
|
4773
|
+
function detectAuthMode(platformKey, platform, hostname) {
|
|
4774
|
+
if (platform.authType === "private_token") return "private_token";
|
|
4775
|
+
if (platformKey === "o2" || platform.authType === "app_code+buc") return "app_code+buc";
|
|
4776
|
+
if (platform.authType === "app_code") return "app_code";
|
|
4777
|
+
if (hostname.endsWith(".alibaba-inc.com") || hostname === "alibaba-inc.com") return "sso_cookie";
|
|
4778
|
+
return "sso_ticket";
|
|
4779
|
+
}
|
|
4780
|
+
function getAuthModeDescription(mode) {
|
|
4781
|
+
switch (mode) {
|
|
4782
|
+
case "private_token": return "Private Token";
|
|
4783
|
+
case "app_code": return "App Code";
|
|
4784
|
+
case "app_code+buc": return "App Code + BUC SSO";
|
|
4785
|
+
case "sso_cookie": return "SSO Cookie (REFRESH_TOKEN)";
|
|
4786
|
+
case "sso_ticket": return "SSO Ticket";
|
|
4787
|
+
case "gateway": return "Gateway Proxy";
|
|
4788
|
+
default: return "Auto";
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
function getPlatformPrivateToken(platformKey, explicitToken) {
|
|
4792
|
+
if (explicitToken) return explicitToken;
|
|
4793
|
+
const envVarName = `ALI_SKILLS_${platformKey.toUpperCase()}_TOKEN`;
|
|
4794
|
+
const envToken = process.env[envVarName] || process.env[`${platformKey.toUpperCase()}_TOKEN`];
|
|
4795
|
+
if (envToken) return envToken;
|
|
4796
|
+
return readConfig().platforms?.[platformKey]?.privateToken || null;
|
|
4797
|
+
}
|
|
4798
|
+
async function promptConfigureToken(platformKey, platform) {
|
|
4799
|
+
console.log();
|
|
4800
|
+
console.log(`${YELLOW$1}⚠️ ${platform.name} 需要配置 Private Token${RESET$2}`);
|
|
4801
|
+
if (platform.tokenDocUrl) console.log(`${DIM$2}Token 获取地址: ${platform.tokenDocUrl}${RESET$2}`);
|
|
4802
|
+
if (!await ye({
|
|
4803
|
+
message: "是否现在配置?",
|
|
4804
|
+
initialValue: true
|
|
4805
|
+
})) {
|
|
4806
|
+
console.log(`${DIM$2}已取消配置${RESET$2}`);
|
|
4807
|
+
return null;
|
|
4808
|
+
}
|
|
4809
|
+
if (platform.tokenDocUrl) {
|
|
4810
|
+
console.log(`${TEXT$2}正在打开浏览器...${RESET$2}`);
|
|
4811
|
+
openBrowser$1(platform.tokenDocUrl);
|
|
4812
|
+
}
|
|
4813
|
+
const token = await ge({
|
|
4814
|
+
message: `请输入 ${platform.name} 的 Private Token:`,
|
|
4815
|
+
mask: "*"
|
|
4816
|
+
});
|
|
4817
|
+
if (pD(token) || !token) {
|
|
4818
|
+
console.log(`${DIM$2}已取消配置${RESET$2}`);
|
|
4819
|
+
return null;
|
|
4820
|
+
}
|
|
4821
|
+
setPlatformToken(platformKey, { privateToken: token });
|
|
4822
|
+
console.log(`${GREEN$1}✓ ${platform.name} Token 已保存${RESET$2}`);
|
|
4823
|
+
return token;
|
|
4824
|
+
}
|
|
4825
|
+
async function sendDirectRequest(targetUrl, platform, token, options) {
|
|
4826
|
+
const requestHeaders = {
|
|
4827
|
+
"User-Agent": "ali-skills-cli/1.0",
|
|
4828
|
+
[platform.tokenHeaderName]: token
|
|
4829
|
+
};
|
|
4830
|
+
if (options.headers) Object.entries(options.headers).forEach(([key, value]) => {
|
|
4831
|
+
if (key.toLowerCase() !== platform.tokenHeaderName?.toLowerCase()) requestHeaders[key] = value;
|
|
4832
|
+
});
|
|
4833
|
+
let body;
|
|
4834
|
+
if (options.body) {
|
|
4835
|
+
body = options.body;
|
|
4836
|
+
if (!requestHeaders["Content-Type"]) requestHeaders["Content-Type"] = "application/json";
|
|
4837
|
+
}
|
|
4838
|
+
if (!options.quiet) {
|
|
4839
|
+
console.log(`${DIM$2}${options.method.toUpperCase()} ${targetUrl}${RESET$2}`);
|
|
4840
|
+
console.log(`${DIM$2}→ 直接请求 (Header: ${platform.tokenHeaderName})${RESET$2}`);
|
|
4841
|
+
}
|
|
4842
|
+
const controller = new AbortController();
|
|
4843
|
+
const timeout = options.timeout || 3e4;
|
|
4844
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
4845
|
+
const response = await fetch(targetUrl, {
|
|
4846
|
+
method: options.method.toUpperCase(),
|
|
4847
|
+
headers: requestHeaders,
|
|
4848
|
+
body: body || void 0,
|
|
4849
|
+
signal: controller.signal
|
|
4850
|
+
});
|
|
4851
|
+
clearTimeout(timeoutId);
|
|
4852
|
+
const responseBody = await response.text();
|
|
4853
|
+
if (!options.quiet) {
|
|
4854
|
+
const statusColor = response.ok ? GREEN$1 : RED$1;
|
|
4855
|
+
console.log(`${statusColor}${response.status} ${response.statusText}${RESET$2}`);
|
|
4856
|
+
if (options.includeHeaders) {
|
|
4857
|
+
console.log();
|
|
4858
|
+
console.log(`${TEXT$2}Response Headers:${RESET$2}`);
|
|
4859
|
+
response.headers.forEach((value, key) => {
|
|
4860
|
+
console.log(` ${key}: ${value}`);
|
|
4861
|
+
});
|
|
4862
|
+
console.log();
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
let outputBody = responseBody;
|
|
4866
|
+
if (!options.raw && options.pretty !== false) try {
|
|
4867
|
+
const json = JSON.parse(responseBody);
|
|
4868
|
+
outputBody = JSON.stringify(json, null, 2);
|
|
4869
|
+
} catch {}
|
|
4870
|
+
if (options.output) {
|
|
4871
|
+
const { writeFileSync } = __require("fs");
|
|
4872
|
+
writeFileSync(options.output, outputBody);
|
|
4873
|
+
console.log(`${GREEN$1}✓ 已保存到 ${options.output}${RESET$2}`);
|
|
4874
|
+
} else console.log(outputBody);
|
|
4875
|
+
if (!response.ok) process.exit(1);
|
|
4876
|
+
}
|
|
4877
|
+
async function sendGatewayRequest(targetUrl, platformKey, options, authMode = "gateway") {
|
|
4878
|
+
let credentials;
|
|
4879
|
+
try {
|
|
4880
|
+
credentials = await ensureLogin();
|
|
4881
|
+
} catch (error) {
|
|
4882
|
+
console.error(`${RED$1}Error: ${error.message}${RESET$2}`);
|
|
4883
|
+
process.exit(1);
|
|
4884
|
+
}
|
|
4885
|
+
const proxyUrl = `${getGatewayUrl()}/api/gateway/proxy`;
|
|
4886
|
+
const requestHeaders = {
|
|
4887
|
+
Authorization: `Bearer ${credentials.privateToken}`,
|
|
4888
|
+
"X-Private-Token": credentials.privateToken,
|
|
4889
|
+
"X-Target-Url": targetUrl,
|
|
4890
|
+
"X-Target-Method": options.method.toUpperCase(),
|
|
4891
|
+
"X-Platform": platformKey,
|
|
4892
|
+
"X-Auth-Mode": authMode
|
|
4893
|
+
};
|
|
4894
|
+
if (options.headers) Object.entries(options.headers).forEach(([key, value]) => {
|
|
4895
|
+
requestHeaders[key] = value;
|
|
4896
|
+
});
|
|
4897
|
+
let body;
|
|
4898
|
+
if (options.body) {
|
|
4899
|
+
body = options.body;
|
|
4900
|
+
if (!requestHeaders["Content-Type"]) requestHeaders["Content-Type"] = "application/json";
|
|
4901
|
+
}
|
|
4902
|
+
if (!options.quiet) {
|
|
4903
|
+
console.log(`${DIM$2}${options.method.toUpperCase()} ${targetUrl}${RESET$2}`);
|
|
4904
|
+
console.log(`${DIM$2}→ Gateway: ${proxyUrl}${RESET$2}`);
|
|
4905
|
+
}
|
|
4906
|
+
const controller = new AbortController();
|
|
4907
|
+
const timeout = options.timeout || 3e4;
|
|
4908
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
4909
|
+
const response = await fetch(proxyUrl, {
|
|
4910
|
+
method: "POST",
|
|
4911
|
+
headers: requestHeaders,
|
|
4912
|
+
body: body || void 0,
|
|
4913
|
+
signal: controller.signal
|
|
4914
|
+
});
|
|
4915
|
+
clearTimeout(timeoutId);
|
|
4916
|
+
if (response.status === 401) {
|
|
4917
|
+
if ((await response.json().catch(() => ({
|
|
4918
|
+
error: "Unauthorized",
|
|
4919
|
+
code: "UNKNOWN"
|
|
4920
|
+
}))).code === "INVALID_TOKEN") {
|
|
4921
|
+
console.error(`${RED$1}Error: Token 已失效,请重新登录${RESET$2}`);
|
|
4922
|
+
console.log(`${TEXT$2}执行: npx ali-skills login${RESET$2}`);
|
|
4923
|
+
process.exit(1);
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
const responseBody = await response.text();
|
|
4927
|
+
const requestId = response.headers.get("X-Gateway-Request-Id");
|
|
4928
|
+
if (!options.quiet) {
|
|
4929
|
+
const statusColor = response.ok ? GREEN$1 : RED$1;
|
|
4930
|
+
console.log(`${statusColor}${response.status} ${response.statusText}${RESET$2}`);
|
|
4931
|
+
if (requestId) console.log(`${DIM$2}Request ID: ${requestId}${RESET$2}`);
|
|
4932
|
+
if (options.includeHeaders) {
|
|
4933
|
+
console.log();
|
|
4934
|
+
console.log(`${TEXT$2}Response Headers:${RESET$2}`);
|
|
4935
|
+
response.headers.forEach((value, key) => {
|
|
4936
|
+
console.log(` ${key}: ${value}`);
|
|
4937
|
+
});
|
|
4938
|
+
console.log();
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
let outputBody = responseBody;
|
|
4942
|
+
if (!options.raw && options.pretty !== false) try {
|
|
4943
|
+
const json = JSON.parse(responseBody);
|
|
4944
|
+
outputBody = JSON.stringify(json, null, 2);
|
|
4945
|
+
} catch {}
|
|
4946
|
+
if (options.output) {
|
|
4947
|
+
const { writeFileSync } = __require("fs");
|
|
4948
|
+
writeFileSync(options.output, outputBody);
|
|
4949
|
+
console.log(`${GREEN$1}✓ 已保存到 ${options.output}${RESET$2}`);
|
|
4950
|
+
} else console.log(outputBody);
|
|
4951
|
+
if (!response.ok) process.exit(1);
|
|
4952
|
+
}
|
|
4953
|
+
async function proxyRequest(targetUrl, options) {
|
|
4954
|
+
let url;
|
|
4955
|
+
try {
|
|
4956
|
+
url = new URL(targetUrl);
|
|
4957
|
+
} catch {
|
|
4958
|
+
console.error(`${RED$1}Error: 无效的 URL "${targetUrl}"${RESET$2}`);
|
|
4959
|
+
process.exit(1);
|
|
4960
|
+
}
|
|
4961
|
+
const platformResult = detectPlatformWithKey(url.hostname);
|
|
4962
|
+
if (!platformResult) {
|
|
4963
|
+
console.error(`${RED$1}Error: 无法识别平台 "${url.hostname}"${RESET$2}`);
|
|
4964
|
+
console.log(`${DIM$2}支持的平台:${RESET$2}`);
|
|
4965
|
+
console.log(`${DIM$2} - code.alibaba-inc.com (GitLab)${RESET$2}`);
|
|
4966
|
+
console.log(`${DIM$2} - aliyuque.antfin.com (语雀)${RESET$2}`);
|
|
4967
|
+
console.log(`${DIM$2} - o2.alibaba-inc.com${RESET$2}`);
|
|
4968
|
+
console.log(`${DIM$2} - aone.alibaba-inc.com${RESET$2}`);
|
|
4969
|
+
process.exit(1);
|
|
4970
|
+
}
|
|
4971
|
+
const { key: platformKey, config: platform } = platformResult;
|
|
4972
|
+
if (!options.quiet) console.log(`${DIM$2}平台: ${platform.name}${RESET$2}`);
|
|
4973
|
+
const authMode = options.authMode === "auto" || !options.authMode ? detectAuthMode(platformKey, platform, url.hostname) : options.authMode;
|
|
4974
|
+
if (!options.quiet && authMode !== "auto") console.log(`${DIM$2}认证方式: ${getAuthModeDescription(authMode)}${RESET$2}`);
|
|
4975
|
+
try {
|
|
4976
|
+
switch (authMode) {
|
|
4977
|
+
case "private_token": {
|
|
4978
|
+
let token = getPlatformPrivateToken(platformKey, options.token);
|
|
4979
|
+
if (!token) {
|
|
4980
|
+
token = await promptConfigureToken(platformKey, platform);
|
|
4981
|
+
if (!token) {
|
|
4982
|
+
console.error(`${RED$1}Error: ${platform.name} 需要配置 Private Token 才能访问${RESET$2}`);
|
|
4983
|
+
console.log(`${DIM$2}您可以通过以下方式配置:${RESET$2}`);
|
|
4984
|
+
console.log(` 1. 参数: --token <your_token>`);
|
|
4985
|
+
console.log(` 2. 环境变量: ALI_SKILLS_${platformKey.toUpperCase()}_TOKEN=<your_token>`);
|
|
4986
|
+
console.log(` 3. 配置文件: npx ali-skills gateway config --platform ${platformKey}`);
|
|
4987
|
+
process.exit(1);
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
await sendDirectRequest(targetUrl, platform, token, options);
|
|
4991
|
+
break;
|
|
4992
|
+
}
|
|
4993
|
+
case "app_code":
|
|
4994
|
+
case "app_code+buc":
|
|
4995
|
+
case "sso_cookie":
|
|
4996
|
+
case "sso_ticket":
|
|
4997
|
+
case "gateway":
|
|
4998
|
+
await sendGatewayRequest(targetUrl, platformKey, options, authMode);
|
|
4999
|
+
break;
|
|
5000
|
+
default: if (platform.authType === "private_token") {
|
|
5001
|
+
const token = getPlatformPrivateToken(platformKey, options.token);
|
|
5002
|
+
if (token) await sendDirectRequest(targetUrl, platform, token, options);
|
|
5003
|
+
else {
|
|
5004
|
+
console.error(`${RED$1}Error: ${platform.name} 需要配置 Private Token${RESET$2}`);
|
|
5005
|
+
console.log(`${DIM$2}您可以通过以下方式配置:${RESET$2}`);
|
|
5006
|
+
console.log(` 1. 参数: --token <your_token>`);
|
|
5007
|
+
console.log(` 2. 环境变量: ALI_SKILLS_${platformKey.toUpperCase()}_TOKEN=<your_token>`);
|
|
5008
|
+
console.log(` 3. 配置文件: npx ali-skills gateway config --platform ${platformKey}`);
|
|
5009
|
+
process.exit(1);
|
|
5010
|
+
}
|
|
5011
|
+
} else await sendGatewayRequest(targetUrl, platformKey, options, "gateway");
|
|
5012
|
+
}
|
|
5013
|
+
} catch (error) {
|
|
5014
|
+
if (error instanceof Error) if (error.name === "AbortError") console.error(`${RED$1}Error: 请求超时${RESET$2}`);
|
|
5015
|
+
else console.error(`${RED$1}Error: ${error.message}${RESET$2}`);
|
|
5016
|
+
process.exit(1);
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
function readBodyFromFile(filePath) {
|
|
5020
|
+
const { readFileSync, existsSync } = __require("fs");
|
|
5021
|
+
const { resolve } = __require("path");
|
|
5022
|
+
const fullPath = resolve(filePath);
|
|
5023
|
+
if (!existsSync(fullPath)) throw new Error(`File not found: ${filePath}`);
|
|
5024
|
+
return readFileSync(fullPath, "utf-8");
|
|
5025
|
+
}
|
|
5026
|
+
const DIM$1 = "\x1B[38;5;102m";
|
|
5027
|
+
const TEXT$1 = "\x1B[38;5;145m";
|
|
5028
|
+
const RESET$1 = "\x1B[0m";
|
|
5029
|
+
const GREEN = "\x1B[32m";
|
|
5030
|
+
const YELLOW = "\x1B[33m";
|
|
5031
|
+
const RED = "\x1B[31m";
|
|
5032
|
+
function showConfigHelp() {
|
|
5033
|
+
console.log(`
|
|
5034
|
+
${TEXT$1}npx ali-skills config - 配置各平台访问凭证${RESET$1}
|
|
5035
|
+
|
|
5036
|
+
${DIM$1}Usage:${RESET$1}
|
|
5037
|
+
npx ali-skills config <platform> 配置指定平台的 token
|
|
5038
|
+
npx ali-skills config --list 列出已配置的平台
|
|
5039
|
+
npx ali-skills config --remove <p> 移除指定平台的配置
|
|
5040
|
+
|
|
5041
|
+
${DIM$1}Supported Platforms:${RESET$1}`);
|
|
5042
|
+
const platforms = listPlatforms();
|
|
5043
|
+
const displayWidth = (str) => {
|
|
5044
|
+
let width = 0;
|
|
5045
|
+
for (const char of str) width += char.charCodeAt(0) > 127 ? 2 : 1;
|
|
5046
|
+
return width;
|
|
5047
|
+
};
|
|
5048
|
+
const maxNameWidth = Math.max(...platforms.map(({ config }) => displayWidth(config.name)));
|
|
5049
|
+
for (const { key, config } of platforms) {
|
|
5050
|
+
const authType = config.authType === "private_token" ? "需配置 Token" : config.authType === "app_code" ? "内置凭证" : config.authType === "buc_sso_refresh" ? "需 BUC 登录" : "需 BUC 登录";
|
|
5051
|
+
const namePadding = " ".repeat(15 - displayWidth(config.name) + maxNameWidth);
|
|
5052
|
+
console.log(` ${key.padEnd(12)} ${config.name}${namePadding} ${DIM$1}${authType}${RESET$1}`);
|
|
5053
|
+
}
|
|
5054
|
+
console.log(`
|
|
5055
|
+
${DIM$1}Examples:${RESET$1}
|
|
5056
|
+
npx ali-skills config code
|
|
5057
|
+
npx ali-skills config yuque
|
|
5058
|
+
npx ali-skills config --list
|
|
5059
|
+
npx ali-skills config --remove code
|
|
5060
|
+
`);
|
|
5061
|
+
}
|
|
5062
|
+
async function prompt(question, defaultValue) {
|
|
5063
|
+
const rl = (await import("readline")).createInterface({
|
|
5064
|
+
input: process.stdin,
|
|
5065
|
+
output: process.stdout
|
|
5066
|
+
});
|
|
5067
|
+
return new Promise((resolve) => {
|
|
5068
|
+
const fullQuestion = defaultValue ? `${question} (默认: ${defaultValue}): ` : `${question}: `;
|
|
5069
|
+
rl.question(fullQuestion, (answer) => {
|
|
5070
|
+
rl.close();
|
|
5071
|
+
resolve(answer.trim() || defaultValue || "");
|
|
5072
|
+
});
|
|
5073
|
+
});
|
|
5074
|
+
}
|
|
5075
|
+
async function openBrowser(url) {
|
|
5076
|
+
const { spawn } = await import("node:child_process");
|
|
5077
|
+
const platform = process.platform;
|
|
5078
|
+
spawn(platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open", [url], {
|
|
5079
|
+
detached: true,
|
|
5080
|
+
stdio: "ignore"
|
|
5081
|
+
});
|
|
5082
|
+
}
|
|
5083
|
+
async function configurePlatform(platformKey) {
|
|
5084
|
+
const platform = getPlatform(platformKey);
|
|
5085
|
+
if (!platform) {
|
|
5086
|
+
console.error(`${RED}Error: 不支持的平台 "${platformKey}"${RESET$1}`);
|
|
5087
|
+
console.log(`${DIM$1}运行 'npx ali-skills config --list' 查看支持的平台${RESET$1}`);
|
|
5088
|
+
process.exit(1);
|
|
5089
|
+
}
|
|
5090
|
+
console.log(`${TEXT$1}配置 ${platform.name}${RESET$1}`);
|
|
5091
|
+
console.log(`${DIM$1}认证方式: ${platform.authType}${RESET$1}`);
|
|
5092
|
+
switch (platform.authType) {
|
|
5093
|
+
case "private_token":
|
|
5094
|
+
await configurePrivateToken(platformKey, platform);
|
|
5095
|
+
break;
|
|
5096
|
+
case "app_code":
|
|
5097
|
+
console.log(`${GREEN}✓ ${platform.name} 使用内置凭证,无需配置${RESET$1}`);
|
|
5098
|
+
break;
|
|
5099
|
+
case "app_code+buc":
|
|
5100
|
+
console.log(`${YELLOW}! ${platform.name} 需要 BUC 登录${RESET$1}`);
|
|
5101
|
+
console.log(`${DIM$1}请运行: npx ali-skills login${RESET$1}`);
|
|
5102
|
+
break;
|
|
5103
|
+
case "buc_sso_refresh":
|
|
5104
|
+
case "buc_sso_ticket":
|
|
5105
|
+
console.log(`${YELLOW}! ${platform.name} 需要 BUC 登录${RESET$1}`);
|
|
5106
|
+
console.log(`${DIM$1}请运行: npx ali-skills login${RESET$1}`);
|
|
5107
|
+
break;
|
|
5108
|
+
default:
|
|
5109
|
+
console.error(`${RED}Error: 未知的认证类型${RESET$1}`);
|
|
5110
|
+
process.exit(1);
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
async function configurePrivateToken(platformKey, platform) {
|
|
5114
|
+
if (!platform.tokenDocUrl) {
|
|
5115
|
+
console.error(`${RED}Error: 未找到 ${platform.name} 的 token 文档地址${RESET$1}`);
|
|
5116
|
+
process.exit(1);
|
|
5117
|
+
}
|
|
5118
|
+
console.log();
|
|
5119
|
+
console.log(`${DIM$1}1. 正在打开 ${platform.name} Token 页面...${RESET$1}`);
|
|
5120
|
+
console.log(`${DIM$1} ${platform.tokenDocUrl}${RESET$1}`);
|
|
5121
|
+
await openBrowser(platform.tokenDocUrl);
|
|
5122
|
+
console.log();
|
|
5123
|
+
console.log(`${DIM$1}2. 请在浏览器中创建 Access Token,然后复制到这里${RESET$1}`);
|
|
5124
|
+
const token = await prompt("Access Token");
|
|
5125
|
+
if (!token) {
|
|
5126
|
+
console.error(`${RED}Error: Token 不能为空${RESET$1}`);
|
|
5127
|
+
process.exit(1);
|
|
5128
|
+
}
|
|
5129
|
+
setPlatformToken(platformKey, { privateToken: token });
|
|
5130
|
+
console.log();
|
|
5131
|
+
console.log(`${GREEN}✓ ${platform.name} Token 已保存${RESET$1}`);
|
|
5132
|
+
console.log(`${DIM$1}配置文件: ${getConfigFilePath()}${RESET$1}`);
|
|
5133
|
+
}
|
|
5134
|
+
function listConfigured() {
|
|
5135
|
+
const config = readConfig();
|
|
5136
|
+
const platforms = Object.entries(config.platforms);
|
|
5137
|
+
if (platforms.length === 0) {
|
|
5138
|
+
console.log(`${DIM$1}尚未配置任何平台${RESET$1}`);
|
|
5139
|
+
console.log(`${TEXT$1}运行 'npx ali-skills config <platform>' 开始配置${RESET$1}`);
|
|
5140
|
+
return;
|
|
5141
|
+
}
|
|
5142
|
+
console.log(`${TEXT$1}已配置的平台:${RESET$1}`);
|
|
5143
|
+
for (const [key, token] of platforms) {
|
|
5144
|
+
const name = getPlatform(key)?.name || key;
|
|
5145
|
+
const hasToken = token.privateToken ? "✓" : "✗";
|
|
5146
|
+
console.log(` ${hasToken} ${name} (${key})`);
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
function removePlatform(platformKey) {
|
|
5150
|
+
const name = getPlatform(platformKey)?.name || platformKey;
|
|
5151
|
+
removePlatformToken(platformKey);
|
|
5152
|
+
console.log(`${GREEN}✓ 已移除 ${name} 的配置${RESET$1}`);
|
|
5153
|
+
}
|
|
5154
|
+
async function runConfig(args) {
|
|
5155
|
+
if (args.length === 0) {
|
|
5156
|
+
showConfigHelp();
|
|
5157
|
+
return;
|
|
5158
|
+
}
|
|
5159
|
+
const command = args[0];
|
|
5160
|
+
switch (command) {
|
|
5161
|
+
case "--help":
|
|
5162
|
+
case "-h":
|
|
5163
|
+
showConfigHelp();
|
|
5164
|
+
break;
|
|
5165
|
+
case "--list":
|
|
5166
|
+
case "-l":
|
|
5167
|
+
listConfigured();
|
|
5168
|
+
break;
|
|
5169
|
+
case "--remove":
|
|
5170
|
+
case "-r": {
|
|
5171
|
+
const platform = args[1];
|
|
5172
|
+
if (!platform) {
|
|
5173
|
+
console.error(`${RED}Error: 请指定要移除的平台${RESET$1}`);
|
|
5174
|
+
process.exit(1);
|
|
5175
|
+
}
|
|
5176
|
+
removePlatform(platform);
|
|
5177
|
+
break;
|
|
5178
|
+
}
|
|
5179
|
+
default: if (!command.startsWith("-")) await configurePlatform(command);
|
|
5180
|
+
else {
|
|
5181
|
+
console.error(`${RED}Error: 未知选项 "${command}"${RESET$1}`);
|
|
5182
|
+
showConfigHelp();
|
|
5183
|
+
process.exit(1);
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
}
|
|
4400
5187
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
4401
5188
|
function getVersion() {
|
|
4402
5189
|
try {
|
|
@@ -4439,32 +5226,32 @@ function showBanner() {
|
|
|
4439
5226
|
console.log();
|
|
4440
5227
|
console.log(`${DIM}The open agent skills ecosystem${RESET}`);
|
|
4441
5228
|
console.log();
|
|
4442
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx
|
|
4443
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx
|
|
4444
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx
|
|
4445
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx
|
|
5229
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx ali-skills add ${DIM}<package>${RESET} ${DIM}Add a new skill${RESET}`);
|
|
5230
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx ali-skills remove${RESET} ${DIM}Remove installed skills${RESET}`);
|
|
5231
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx ali-skills list${RESET} ${DIM}List installed skills${RESET}`);
|
|
5232
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx ali-skills find ${DIM}[query]${RESET} ${DIM}Search for skills${RESET}`);
|
|
4446
5233
|
console.log();
|
|
4447
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx
|
|
5234
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx ali-skills publish${RESET} ${DIM}Publish skills to OSS${RESET}`);
|
|
4448
5235
|
console.log();
|
|
4449
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx
|
|
4450
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx
|
|
5236
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx ali-skills check${RESET} ${DIM}Check for updates${RESET}`);
|
|
5237
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx ali-skills update${RESET} ${DIM}Update all skills${RESET}`);
|
|
4451
5238
|
console.log();
|
|
4452
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx
|
|
4453
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx
|
|
4454
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx
|
|
5239
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx ali-skills experimental_install${RESET} ${DIM}Restore from skills-lock.json${RESET}`);
|
|
5240
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx ali-skills init ${DIM}[name]${RESET} ${DIM}Create a new skill${RESET}`);
|
|
5241
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx ali-skills experimental_sync${RESET} ${DIM}Sync skills from node_modules${RESET}`);
|
|
4455
5242
|
console.log();
|
|
4456
|
-
console.log(`${DIM}try:${RESET} npx
|
|
5243
|
+
console.log(`${DIM}try:${RESET} npx ali-skills add group/project`);
|
|
4457
5244
|
console.log();
|
|
4458
|
-
console.log(`Discover more skills at ${TEXT}https://skills.
|
|
5245
|
+
console.log(`Discover more skills at ${TEXT}https://ali-skills.alibaba-inc.com/${RESET}`);
|
|
4459
5246
|
console.log();
|
|
4460
5247
|
}
|
|
4461
5248
|
function showHelp() {
|
|
4462
5249
|
console.log(`
|
|
4463
|
-
${BOLD}Usage:${RESET}
|
|
5250
|
+
${BOLD}Usage:${RESET} ali-skills <command> [options]
|
|
4464
5251
|
|
|
4465
5252
|
${BOLD}Manage Skills:${RESET}
|
|
4466
5253
|
add <package> Add a skill package (alias: a)
|
|
4467
|
-
e.g.
|
|
5254
|
+
e.g. group/project
|
|
4468
5255
|
https://github.com/vercel-labs/agent-skills
|
|
4469
5256
|
remove [skills] Remove installed skills
|
|
4470
5257
|
list, ls List installed skills
|
|
@@ -4474,8 +5261,12 @@ ${BOLD}Publish:${RESET}
|
|
|
4474
5261
|
publish [options] Publish skills from current repo to OSS (alias: pub)
|
|
4475
5262
|
|
|
4476
5263
|
${BOLD}Updates:${RESET}
|
|
4477
|
-
check Check for available skill updates
|
|
4478
|
-
|
|
5264
|
+
check Check for available skill updates (project-level by default)
|
|
5265
|
+
check -g Check global skill updates
|
|
5266
|
+
update Update skills interactively (project-level by default)
|
|
5267
|
+
update -g Update global skills
|
|
5268
|
+
update -y Update all skills without prompting
|
|
5269
|
+
update <skill> Update specific skill(s)
|
|
4479
5270
|
|
|
4480
5271
|
${BOLD}Project:${RESET}
|
|
4481
5272
|
experimental_install Restore skills from skills-lock.json
|
|
@@ -4514,32 +5305,32 @@ ${BOLD}Options:${RESET}
|
|
|
4514
5305
|
--version, -v Show version number
|
|
4515
5306
|
|
|
4516
5307
|
${BOLD}Examples:${RESET}
|
|
4517
|
-
${DIM}$${RESET}
|
|
4518
|
-
${DIM}$${RESET}
|
|
4519
|
-
${DIM}$${RESET}
|
|
4520
|
-
${DIM}$${RESET}
|
|
4521
|
-
${DIM}$${RESET}
|
|
4522
|
-
${DIM}$${RESET}
|
|
4523
|
-
${DIM}$${RESET}
|
|
4524
|
-
${DIM}$${RESET}
|
|
4525
|
-
${DIM}$${RESET}
|
|
4526
|
-
${DIM}$${RESET}
|
|
4527
|
-
${DIM}$${RESET}
|
|
4528
|
-
${DIM}$${RESET}
|
|
4529
|
-
${DIM}$${RESET}
|
|
4530
|
-
${DIM}$${RESET}
|
|
4531
|
-
${DIM}$${RESET}
|
|
4532
|
-
${DIM}$${RESET}
|
|
4533
|
-
${DIM}$${RESET}
|
|
4534
|
-
${DIM}$${RESET}
|
|
4535
|
-
${DIM}$${RESET}
|
|
5308
|
+
${DIM}$${RESET} ali-skills add group/project
|
|
5309
|
+
${DIM}$${RESET} ali-skills add group/project -g
|
|
5310
|
+
${DIM}$${RESET} ali-skills add group/project --agent claude-code cursor
|
|
5311
|
+
${DIM}$${RESET} ali-skills add group/project --skill pr-review commit
|
|
5312
|
+
${DIM}$${RESET} ali-skills remove ${DIM}# interactive remove${RESET}
|
|
5313
|
+
${DIM}$${RESET} ali-skills remove web-design ${DIM}# remove by name${RESET}
|
|
5314
|
+
${DIM}$${RESET} ali-skills rm --global frontend-design
|
|
5315
|
+
${DIM}$${RESET} ali-skills list ${DIM}# list project skills${RESET}
|
|
5316
|
+
${DIM}$${RESET} ali-skills ls -g ${DIM}# list global skills${RESET}
|
|
5317
|
+
${DIM}$${RESET} ali-skills ls -a claude-code ${DIM}# filter by agent${RESET}
|
|
5318
|
+
${DIM}$${RESET} ali-skills ls --json ${DIM}# JSON output${RESET}
|
|
5319
|
+
${DIM}$${RESET} ali-skills find ${DIM}# interactive search${RESET}
|
|
5320
|
+
${DIM}$${RESET} ali-skills find typescript ${DIM}# search by keyword${RESET}
|
|
5321
|
+
${DIM}$${RESET} ali-skills check
|
|
5322
|
+
${DIM}$${RESET} ali-skills update
|
|
5323
|
+
${DIM}$${RESET} ali-skills experimental_install ${DIM}# restore from skills-lock.json${RESET}
|
|
5324
|
+
${DIM}$${RESET} ali-skills init my-skill
|
|
5325
|
+
${DIM}$${RESET} ali-skills experimental_sync ${DIM}# sync from node_modules${RESET}
|
|
5326
|
+
${DIM}$${RESET} ali-skills experimental_sync -y ${DIM}# sync without prompts${RESET}
|
|
4536
5327
|
|
|
4537
|
-
Discover more skills at ${TEXT}https://skills.
|
|
5328
|
+
Discover more skills at ${TEXT}https://ali-skills.alibaba-inc.com/${RESET}
|
|
4538
5329
|
`);
|
|
4539
5330
|
}
|
|
4540
5331
|
function showRemoveHelp() {
|
|
4541
5332
|
console.log(`
|
|
4542
|
-
${BOLD}Usage:${RESET}
|
|
5333
|
+
${BOLD}Usage:${RESET} ali-skills remove [skills...] [options]
|
|
4543
5334
|
|
|
4544
5335
|
${BOLD}Description:${RESET}
|
|
4545
5336
|
Remove installed skills from agents. If no skill names are provided,
|
|
@@ -4556,17 +5347,159 @@ ${BOLD}Options:${RESET}
|
|
|
4556
5347
|
--all Shorthand for --skill '*' --agent '*' -y
|
|
4557
5348
|
|
|
4558
5349
|
${BOLD}Examples:${RESET}
|
|
4559
|
-
${DIM}$${RESET}
|
|
4560
|
-
${DIM}$${RESET}
|
|
4561
|
-
${DIM}$${RESET}
|
|
4562
|
-
${DIM}$${RESET}
|
|
4563
|
-
${DIM}$${RESET}
|
|
4564
|
-
${DIM}$${RESET}
|
|
4565
|
-
${DIM}$${RESET}
|
|
5350
|
+
${DIM}$${RESET} ali-skills remove ${DIM}# interactive selection${RESET}
|
|
5351
|
+
${DIM}$${RESET} ali-skills remove my-skill ${DIM}# remove specific skill${RESET}
|
|
5352
|
+
${DIM}$${RESET} ali-skills remove skill1 skill2 -y ${DIM}# remove multiple skills${RESET}
|
|
5353
|
+
${DIM}$${RESET} ali-skills remove --global my-skill ${DIM}# remove from global scope${RESET}
|
|
5354
|
+
${DIM}$${RESET} ali-skills rm --agent claude-code my-skill ${DIM}# remove from specific agent${RESET}
|
|
5355
|
+
${DIM}$${RESET} ali-skills remove --all ${DIM}# remove all skills${RESET}
|
|
5356
|
+
${DIM}$${RESET} ali-skills remove --skill '*' -a cursor ${DIM}# remove all skills from cursor${RESET}
|
|
4566
5357
|
|
|
4567
|
-
Discover more skills at ${TEXT}https://skills.
|
|
5358
|
+
Discover more skills at ${TEXT}https://ali-skills.alibaba-inc.com/${RESET}
|
|
4568
5359
|
`);
|
|
4569
5360
|
}
|
|
5361
|
+
function showGatewayHelp() {
|
|
5362
|
+
console.log(`
|
|
5363
|
+
${BOLD}Gateway Commands:${RESET}
|
|
5364
|
+
login Login to Gateway (BUC OAuth)
|
|
5365
|
+
logout Logout from Gateway
|
|
5366
|
+
status Show login status
|
|
5367
|
+
request <url> Send HTTP request via Gateway proxy
|
|
5368
|
+
req <url> Alias for request
|
|
5369
|
+
|
|
5370
|
+
${BOLD}Config Commands:${RESET}
|
|
5371
|
+
config <platform> Configure platform token
|
|
5372
|
+
config --list List configured platforms
|
|
5373
|
+
config --remove <p> Remove platform config
|
|
5374
|
+
|
|
5375
|
+
${BOLD}Request Options:${RESET}
|
|
5376
|
+
-X, --method <method> HTTP method (default: GET)
|
|
5377
|
+
-b, --body <body> Request body (JSON string or @file)
|
|
5378
|
+
-h, --header <header> Custom header (can be used multiple times)
|
|
5379
|
+
-o, --output <file> Save response to file
|
|
5380
|
+
-i, --include-headers Include response headers in output
|
|
5381
|
+
-t, --timeout <ms> Request timeout (default: 30000)
|
|
5382
|
+
--pretty Pretty print JSON output (default: true)
|
|
5383
|
+
--raw Raw output without formatting
|
|
5384
|
+
-q, --quiet Quiet mode, only output response body
|
|
5385
|
+
|
|
5386
|
+
${BOLD}Examples:${RESET}
|
|
5387
|
+
${DIM}$${RESET} ali-skills login
|
|
5388
|
+
${DIM}$${RESET} ali-skills config code
|
|
5389
|
+
${DIM}$${RESET} ali-skills config yuque
|
|
5390
|
+
${DIM}$${RESET} ali-skills req https://o2.alibaba-inc.com/api/v2/apps
|
|
5391
|
+
${DIM}$${RESET} ali-skills req https://code.alibaba-inc.com/api/v4/projects -X GET
|
|
5392
|
+
${DIM}$${RESET} ali-skills req https://aliyuque.antfin.com/api/v2/repos -o repos.json
|
|
5393
|
+
${DIM}$${RESET} ali-skills logout
|
|
5394
|
+
`);
|
|
5395
|
+
}
|
|
5396
|
+
function parseRequestOptions(args) {
|
|
5397
|
+
let url = "";
|
|
5398
|
+
const options = { method: "GET" };
|
|
5399
|
+
const headers = {};
|
|
5400
|
+
for (let i = 0; i < args.length; i++) {
|
|
5401
|
+
const arg = args[i];
|
|
5402
|
+
switch (arg) {
|
|
5403
|
+
case "-m":
|
|
5404
|
+
case "--method":
|
|
5405
|
+
options.method = args[++i] || "GET";
|
|
5406
|
+
break;
|
|
5407
|
+
case "-b":
|
|
5408
|
+
case "--body":
|
|
5409
|
+
options.body = args[++i];
|
|
5410
|
+
break;
|
|
5411
|
+
case "-h":
|
|
5412
|
+
case "--header": {
|
|
5413
|
+
const header = args[++i];
|
|
5414
|
+
if (header) {
|
|
5415
|
+
const [key, ...valueParts] = header.split(":");
|
|
5416
|
+
if (key && valueParts.length > 0) headers[key.trim()] = valueParts.join(":").trim();
|
|
5417
|
+
}
|
|
5418
|
+
break;
|
|
5419
|
+
}
|
|
5420
|
+
case "-o":
|
|
5421
|
+
case "--output":
|
|
5422
|
+
options.output = args[++i];
|
|
5423
|
+
break;
|
|
5424
|
+
case "-i":
|
|
5425
|
+
case "--include-headers":
|
|
5426
|
+
options.includeHeaders = true;
|
|
5427
|
+
break;
|
|
5428
|
+
case "-t":
|
|
5429
|
+
case "--timeout":
|
|
5430
|
+
options.timeout = parseInt(args[++i], 10);
|
|
5431
|
+
break;
|
|
5432
|
+
case "--pretty":
|
|
5433
|
+
options.pretty = true;
|
|
5434
|
+
break;
|
|
5435
|
+
case "--raw":
|
|
5436
|
+
options.raw = true;
|
|
5437
|
+
break;
|
|
5438
|
+
case "-q":
|
|
5439
|
+
case "--quiet":
|
|
5440
|
+
options.quiet = true;
|
|
5441
|
+
break;
|
|
5442
|
+
default:
|
|
5443
|
+
if (!arg.startsWith("-") && !url) url = arg;
|
|
5444
|
+
break;
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
if (Object.keys(headers).length > 0) options.headers = headers;
|
|
5448
|
+
if (options.body?.startsWith("@")) options.body = readBodyFromFile(options.body.slice(1));
|
|
5449
|
+
return {
|
|
5450
|
+
url,
|
|
5451
|
+
options
|
|
5452
|
+
};
|
|
5453
|
+
}
|
|
5454
|
+
async function runGatewayCommand(args) {
|
|
5455
|
+
if (args.length === 0) {
|
|
5456
|
+
showGatewayHelp();
|
|
5457
|
+
return;
|
|
5458
|
+
}
|
|
5459
|
+
const command = args[0];
|
|
5460
|
+
const rest = args.slice(1);
|
|
5461
|
+
switch (command) {
|
|
5462
|
+
case "login":
|
|
5463
|
+
showLogo();
|
|
5464
|
+
console.log();
|
|
5465
|
+
await login();
|
|
5466
|
+
break;
|
|
5467
|
+
case "logout":
|
|
5468
|
+
await logout();
|
|
5469
|
+
break;
|
|
5470
|
+
case "status":
|
|
5471
|
+
await status();
|
|
5472
|
+
break;
|
|
5473
|
+
case "token": {
|
|
5474
|
+
const token = getPrivateToken();
|
|
5475
|
+
if (token) console.log(token);
|
|
5476
|
+
else {
|
|
5477
|
+
console.error("Not logged in");
|
|
5478
|
+
process.exit(1);
|
|
5479
|
+
}
|
|
5480
|
+
break;
|
|
5481
|
+
}
|
|
5482
|
+
case "request":
|
|
5483
|
+
case "req": {
|
|
5484
|
+
const { url, options } = parseRequestOptions(rest);
|
|
5485
|
+
if (!url) {
|
|
5486
|
+
console.error("Error: URL is required");
|
|
5487
|
+
showGatewayHelp();
|
|
5488
|
+
process.exit(1);
|
|
5489
|
+
}
|
|
5490
|
+
await proxyRequest(url, options);
|
|
5491
|
+
break;
|
|
5492
|
+
}
|
|
5493
|
+
case "--help":
|
|
5494
|
+
case "-h":
|
|
5495
|
+
showGatewayHelp();
|
|
5496
|
+
break;
|
|
5497
|
+
default:
|
|
5498
|
+
console.error(`Unknown gateway command: ${command}`);
|
|
5499
|
+
showGatewayHelp();
|
|
5500
|
+
process.exit(1);
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
4570
5503
|
function runInit(args) {
|
|
4571
5504
|
const cwd = process.cwd();
|
|
4572
5505
|
const skillName = args[0] || basename(cwd);
|
|
@@ -4608,10 +5541,10 @@ Describe when this skill should be used.
|
|
|
4608
5541
|
console.log(` 2. Update the ${TEXT}name${RESET} and ${TEXT}description${RESET} in the frontmatter`);
|
|
4609
5542
|
console.log();
|
|
4610
5543
|
console.log(`${DIM}Publishing:${RESET}`);
|
|
4611
|
-
console.log(` ${DIM}GitHub:${RESET} Push to a repo, then ${TEXT}npx
|
|
4612
|
-
console.log(` ${DIM}URL:${RESET} Host the file, then ${TEXT}npx
|
|
5544
|
+
console.log(` ${DIM}GitHub:${RESET} Push to a repo, then ${TEXT}npx ali-skills add <owner>/<repo>${RESET}`);
|
|
5545
|
+
console.log(` ${DIM}URL:${RESET} Host the file, then ${TEXT}npx ali-skills add https://example.com/${displayPath}${RESET}`);
|
|
4613
5546
|
console.log();
|
|
4614
|
-
console.log(`Browse existing skills for inspiration at ${TEXT}https://skills.
|
|
5547
|
+
console.log(`Browse existing skills for inspiration at ${TEXT}https://ali-skills.alibaba-inc.com/${RESET}`);
|
|
4615
5548
|
console.log();
|
|
4616
5549
|
}
|
|
4617
5550
|
const AGENTS_DIR = ".agents";
|
|
@@ -4650,31 +5583,11 @@ function getSkipReason(entry) {
|
|
|
4650
5583
|
if (!entry.skillPath) return "No skill path recorded";
|
|
4651
5584
|
return "No version tracking";
|
|
4652
5585
|
}
|
|
4653
|
-
function
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
console.log(`${DIM}${skipped.length} skill(s) cannot be checked automatically:${RESET}`);
|
|
4657
|
-
for (const skill of skipped) {
|
|
4658
|
-
console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
|
|
4659
|
-
console.log(` ${DIM}To update: ${TEXT}npx @ali/cli-skills add ${skill.sourceUrl} -g -y${RESET}`);
|
|
4660
|
-
}
|
|
4661
|
-
}
|
|
4662
|
-
async function runCheck(args = []) {
|
|
4663
|
-
console.log(`${TEXT}Checking for skill updates...${RESET}`);
|
|
4664
|
-
console.log();
|
|
4665
|
-
const lock = readSkillLock();
|
|
4666
|
-
const skillNames = Object.keys(lock.skills);
|
|
4667
|
-
if (skillNames.length === 0) {
|
|
4668
|
-
console.log(`${DIM}No skills tracked in lock file.${RESET}`);
|
|
4669
|
-
console.log(`${DIM}Install skills with${RESET} ${TEXT}npx @ali/cli-skills add <package>${RESET}`);
|
|
4670
|
-
return;
|
|
4671
|
-
}
|
|
4672
|
-
const token = getGitHubToken();
|
|
4673
|
-
const skillsBySource = /* @__PURE__ */ new Map();
|
|
5586
|
+
async function checkSkillsForUpdates(skills, token) {
|
|
5587
|
+
const updates = [];
|
|
5588
|
+
const errors = [];
|
|
4674
5589
|
const skipped = [];
|
|
4675
|
-
for (const skillName of
|
|
4676
|
-
const entry = lock.skills[skillName];
|
|
4677
|
-
if (!entry) continue;
|
|
5590
|
+
for (const [skillName, entry] of Object.entries(skills)) {
|
|
4678
5591
|
if (!entry.skillFolderHash || !entry.skillPath) {
|
|
4679
5592
|
skipped.push({
|
|
4680
5593
|
name: skillName,
|
|
@@ -4683,84 +5596,260 @@ async function runCheck(args = []) {
|
|
|
4683
5596
|
});
|
|
4684
5597
|
continue;
|
|
4685
5598
|
}
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
for (const [source, skills] of skillsBySource) for (const { name, entry } of skills) try {
|
|
4703
|
-
const latestHash = await fetchSkillFolderHash(source, entry.skillPath, token);
|
|
4704
|
-
if (!latestHash) {
|
|
5599
|
+
try {
|
|
5600
|
+
const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token);
|
|
5601
|
+
if (!latestHash) {
|
|
5602
|
+
errors.push({
|
|
5603
|
+
name: skillName,
|
|
5604
|
+
source: entry.source,
|
|
5605
|
+
error: "Could not fetch from GitHub"
|
|
5606
|
+
});
|
|
5607
|
+
continue;
|
|
5608
|
+
}
|
|
5609
|
+
if (latestHash !== entry.skillFolderHash) updates.push({
|
|
5610
|
+
name: skillName,
|
|
5611
|
+
source: entry.source,
|
|
5612
|
+
entry
|
|
5613
|
+
});
|
|
5614
|
+
} catch (err) {
|
|
4705
5615
|
errors.push({
|
|
4706
|
-
name,
|
|
4707
|
-
source,
|
|
4708
|
-
error:
|
|
5616
|
+
name: skillName,
|
|
5617
|
+
source: entry.source,
|
|
5618
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
4709
5619
|
});
|
|
4710
|
-
continue;
|
|
4711
5620
|
}
|
|
4712
|
-
if (latestHash !== entry.skillFolderHash) updates.push({
|
|
4713
|
-
name,
|
|
4714
|
-
source
|
|
4715
|
-
});
|
|
4716
|
-
} catch (err) {
|
|
4717
|
-
errors.push({
|
|
4718
|
-
name,
|
|
4719
|
-
source,
|
|
4720
|
-
error: err instanceof Error ? err.message : "Unknown error"
|
|
4721
|
-
});
|
|
4722
5621
|
}
|
|
5622
|
+
return {
|
|
5623
|
+
updates,
|
|
5624
|
+
errors,
|
|
5625
|
+
skipped
|
|
5626
|
+
};
|
|
5627
|
+
}
|
|
5628
|
+
async function runCheck(args = []) {
|
|
5629
|
+
const isGlobal = args.includes("-g") || args.includes("--global");
|
|
5630
|
+
console.log(`${TEXT}Checking for skill updates...${RESET}`);
|
|
4723
5631
|
console.log();
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
5632
|
+
const token = getGitHubToken();
|
|
5633
|
+
let updates = [];
|
|
5634
|
+
let skipped = [];
|
|
5635
|
+
let skillCount = 0;
|
|
5636
|
+
if (isGlobal) {
|
|
5637
|
+
const globalLock = readSkillLock();
|
|
5638
|
+
const globalSkillNames = Object.keys(globalLock.skills);
|
|
5639
|
+
skillCount = globalSkillNames.length;
|
|
5640
|
+
if (globalSkillNames.length === 0) {
|
|
5641
|
+
console.log(`${DIM}No global skills found.${RESET}`);
|
|
5642
|
+
console.log();
|
|
5643
|
+
return;
|
|
5644
|
+
}
|
|
5645
|
+
console.log(`${BOLD}Global skills${RESET}`);
|
|
5646
|
+
console.log(`${DIM}Checking ${globalSkillNames.length} skill(s)...${RESET}`);
|
|
5647
|
+
const result = await checkSkillsForUpdates(globalLock.skills, token);
|
|
5648
|
+
updates = result.updates;
|
|
5649
|
+
result.errors;
|
|
5650
|
+
skipped = result.skipped;
|
|
5651
|
+
if (updates.length === 0) console.log(`${TEXT} ✓ All global skills are up to date${RESET}`);
|
|
5652
|
+
else {
|
|
5653
|
+
console.log(`${TEXT} ${updates.length} update(s) available:${RESET}`);
|
|
5654
|
+
for (const update of updates) console.log(` ${TEXT}↑${RESET} ${update.name} ${DIM}(${update.source})${RESET}`);
|
|
5655
|
+
}
|
|
5656
|
+
if (skipped.length > 0) {
|
|
5657
|
+
console.log();
|
|
5658
|
+
console.log(`${DIM} ${skipped.length} skill(s) cannot be checked automatically:${RESET}`);
|
|
5659
|
+
for (const skill of skipped) {
|
|
5660
|
+
console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
|
|
5661
|
+
console.log(` ${DIM}To update: ${TEXT}npx ali-skills add ${skill.sourceUrl} -g -y${RESET}`);
|
|
5662
|
+
}
|
|
4731
5663
|
}
|
|
4732
5664
|
console.log();
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
5665
|
+
if (updates.length > 0) {
|
|
5666
|
+
console.log(`${TEXT}${updates.length} update(s) available${RESET}`);
|
|
5667
|
+
console.log();
|
|
5668
|
+
console.log(`${DIM}Run${RESET} ${TEXT}npx ali-skills update -g${RESET} ${DIM}to update all global skills${RESET}`);
|
|
5669
|
+
}
|
|
5670
|
+
} else {
|
|
5671
|
+
const projectLock = await readLocalLock();
|
|
5672
|
+
const projectSkillNames = Object.keys(projectLock.skills);
|
|
5673
|
+
skillCount = projectSkillNames.length;
|
|
5674
|
+
if (projectSkillNames.length === 0) {
|
|
5675
|
+
console.log(`${DIM}No project-level skills found.${RESET}`);
|
|
5676
|
+
console.log();
|
|
5677
|
+
console.log(`${DIM}To check global skills, use:${RESET} ${TEXT}npx ali-skills check -g${RESET}`);
|
|
5678
|
+
return;
|
|
5679
|
+
}
|
|
5680
|
+
console.log(`${BOLD}Project-level skills${RESET}`);
|
|
5681
|
+
console.log(`${DIM}Checking ${projectSkillNames.length} skill(s)...${RESET}`);
|
|
5682
|
+
for (const [skillName, entry] of Object.entries(projectLock.skills)) {
|
|
5683
|
+
if (entry.sourceType === "internal") {
|
|
5684
|
+
if (!entry.commitHash) {
|
|
5685
|
+
skipped.push({
|
|
5686
|
+
name: skillName,
|
|
5687
|
+
reason: "Reinstall required to enable update checking (missing commit hash)",
|
|
5688
|
+
sourceUrl: `https://code.alibaba-inc.com/${entry.source}`
|
|
5689
|
+
});
|
|
5690
|
+
continue;
|
|
5691
|
+
}
|
|
5692
|
+
const skillPath = (entry.skillPath || "").replace(/\/?SKILL\.md$/i, "");
|
|
5693
|
+
const latestCommit = await fetchAliInternalCommitHash(entry.source, skillPath, null);
|
|
5694
|
+
if (latestCommit === null) skipped.push({
|
|
5695
|
+
name: skillName,
|
|
5696
|
+
reason: "Ali internal repository (API authentication failed)",
|
|
5697
|
+
sourceUrl: `https://code.alibaba-inc.com/${entry.source}`
|
|
5698
|
+
});
|
|
5699
|
+
else if (latestCommit !== entry.commitHash) updates.push({
|
|
5700
|
+
name: skillName,
|
|
5701
|
+
source: entry.source,
|
|
5702
|
+
entry: {
|
|
5703
|
+
source: entry.source,
|
|
5704
|
+
sourceType: entry.sourceType,
|
|
5705
|
+
sourceUrl: `https://code.alibaba-inc.com/${entry.source}`,
|
|
5706
|
+
skillPath,
|
|
5707
|
+
skillFolderHash: entry.computedHash,
|
|
5708
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5709
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5710
|
+
}
|
|
5711
|
+
});
|
|
5712
|
+
continue;
|
|
5713
|
+
}
|
|
5714
|
+
if (entry.sourceType === "github") {
|
|
5715
|
+
if (!entry.skillFolderHash) {
|
|
5716
|
+
skipped.push({
|
|
5717
|
+
name: skillName,
|
|
5718
|
+
reason: "Reinstall required to enable update checking (missing folder hash)",
|
|
5719
|
+
sourceUrl: `https://github.com/${entry.source}`
|
|
5720
|
+
});
|
|
5721
|
+
continue;
|
|
5722
|
+
}
|
|
5723
|
+
const possiblePaths = entry.skillPath ? [entry.skillPath] : [`skills/${skillName}`, `skills/${skillName}/SKILL.md`];
|
|
5724
|
+
let foundMatch = false;
|
|
5725
|
+
for (const skillPath of possiblePaths) try {
|
|
5726
|
+
const latestHash = await fetchSkillFolderHash(entry.source, skillPath, token);
|
|
5727
|
+
if (latestHash === null) continue;
|
|
5728
|
+
foundMatch = true;
|
|
5729
|
+
if (latestHash !== entry.skillFolderHash) updates.push({
|
|
5730
|
+
name: skillName,
|
|
5731
|
+
source: entry.source,
|
|
5732
|
+
entry: {
|
|
5733
|
+
source: entry.source,
|
|
5734
|
+
sourceType: entry.sourceType,
|
|
5735
|
+
sourceUrl: `https://github.com/${entry.source}`,
|
|
5736
|
+
skillPath,
|
|
5737
|
+
skillFolderHash: entry.skillFolderHash,
|
|
5738
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5739
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5740
|
+
}
|
|
5741
|
+
});
|
|
5742
|
+
break;
|
|
5743
|
+
} catch {
|
|
5744
|
+
continue;
|
|
5745
|
+
}
|
|
5746
|
+
if (!foundMatch) skipped.push({
|
|
5747
|
+
name: skillName,
|
|
5748
|
+
reason: "Unable to verify (API failed for all possible paths)",
|
|
5749
|
+
sourceUrl: `https://github.com/${entry.source}`
|
|
5750
|
+
});
|
|
5751
|
+
} else skipped.push({
|
|
5752
|
+
name: skillName,
|
|
5753
|
+
reason: `${entry.sourceType} source type not supported for update checking`,
|
|
5754
|
+
sourceUrl: entry.source
|
|
5755
|
+
});
|
|
5756
|
+
}
|
|
5757
|
+
if (updates.length === 0) console.log(`${TEXT} ✓ All project skills are up to date${RESET}`);
|
|
5758
|
+
else {
|
|
5759
|
+
console.log(`${TEXT} ${updates.length} update(s) available:${RESET}`);
|
|
5760
|
+
for (const update of updates) console.log(` ${TEXT}↑${RESET} ${update.name} ${DIM}(${update.source})${RESET}`);
|
|
5761
|
+
}
|
|
5762
|
+
if (skipped.length > 0) {
|
|
5763
|
+
console.log();
|
|
5764
|
+
console.log(`${DIM} ${skipped.length} skill(s) cannot be checked automatically:${RESET}`);
|
|
5765
|
+
for (const skill of skipped) console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
|
|
5766
|
+
}
|
|
4736
5767
|
console.log();
|
|
4737
|
-
|
|
5768
|
+
if (updates.length > 0) {
|
|
5769
|
+
console.log(`${TEXT}${updates.length} update(s) available${RESET}`);
|
|
5770
|
+
console.log();
|
|
5771
|
+
console.log(`${DIM}Run${RESET} ${TEXT}npx ali-skills update${RESET} ${DIM}to update all project skills${RESET}`);
|
|
5772
|
+
console.log(`${DIM}Run${RESET} ${TEXT}npx ali-skills update <skill-name>${RESET} ${DIM}to update specific skill${RESET}`);
|
|
5773
|
+
}
|
|
4738
5774
|
}
|
|
4739
|
-
printSkippedSkills(skipped);
|
|
4740
5775
|
track({
|
|
4741
5776
|
event: "check",
|
|
4742
|
-
skillCount: String(
|
|
5777
|
+
skillCount: String(skillCount),
|
|
4743
5778
|
updatesAvailable: String(updates.length)
|
|
4744
5779
|
});
|
|
4745
5780
|
console.log();
|
|
4746
5781
|
}
|
|
4747
|
-
async function runUpdate() {
|
|
5782
|
+
async function runUpdate(args = []) {
|
|
5783
|
+
const isGlobal = args.includes("-g") || args.includes("--global");
|
|
5784
|
+
const yesFlag = args.includes("-y") || args.includes("--yes");
|
|
5785
|
+
const skillArgs = args.filter((arg) => !arg.startsWith("-"));
|
|
4748
5786
|
console.log(`${TEXT}Checking for skill updates...${RESET}`);
|
|
4749
5787
|
console.log();
|
|
4750
|
-
const
|
|
4751
|
-
|
|
5788
|
+
const token = getGitHubToken();
|
|
5789
|
+
let skillsToCheck = {};
|
|
5790
|
+
let scopeLabel = "project";
|
|
5791
|
+
if (isGlobal) {
|
|
5792
|
+
skillsToCheck = readSkillLock().skills;
|
|
5793
|
+
scopeLabel = "global";
|
|
5794
|
+
} else {
|
|
5795
|
+
const projectLock = await readLocalLock();
|
|
5796
|
+
for (const [name, entry] of Object.entries(projectLock.skills)) {
|
|
5797
|
+
let sourceUrl;
|
|
5798
|
+
if (entry.sourceType === "github") sourceUrl = `https://github.com/${entry.source}`;
|
|
5799
|
+
else if (entry.sourceType === "internal") sourceUrl = `https://code.alibaba-inc.com/${entry.source}`;
|
|
5800
|
+
else sourceUrl = entry.source;
|
|
5801
|
+
skillsToCheck[name] = {
|
|
5802
|
+
source: entry.source,
|
|
5803
|
+
sourceType: entry.sourceType,
|
|
5804
|
+
sourceUrl,
|
|
5805
|
+
skillPath: entry.skillPath || "",
|
|
5806
|
+
skillFolderHash: entry.skillFolderHash || entry.computedHash,
|
|
5807
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5808
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5809
|
+
};
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
const skillNames = Object.keys(skillsToCheck);
|
|
4752
5813
|
if (skillNames.length === 0) {
|
|
4753
|
-
console.log(`${DIM}No skills
|
|
4754
|
-
console.log(
|
|
5814
|
+
console.log(`${DIM}No ${scopeLabel}-level skills found.${RESET}`);
|
|
5815
|
+
console.log();
|
|
5816
|
+
if (!isGlobal) console.log(`${DIM}To update global skills, use:${RESET} ${TEXT}npx ali-skills update -g${RESET}`);
|
|
4755
5817
|
return;
|
|
4756
5818
|
}
|
|
4757
|
-
const
|
|
4758
|
-
const updates = [];
|
|
5819
|
+
const availableUpdates = [];
|
|
4759
5820
|
const skipped = [];
|
|
5821
|
+
const projectLock = !isGlobal ? await readLocalLock() : null;
|
|
4760
5822
|
for (const skillName of skillNames) {
|
|
4761
|
-
const entry =
|
|
5823
|
+
const entry = skillsToCheck[skillName];
|
|
4762
5824
|
if (!entry) continue;
|
|
4763
|
-
if (
|
|
5825
|
+
if (entry.sourceType === "internal" && projectLock) {
|
|
5826
|
+
const localEntry = projectLock.skills[skillName];
|
|
5827
|
+
if (!localEntry?.commitHash) {
|
|
5828
|
+
skipped.push({
|
|
5829
|
+
name: skillName,
|
|
5830
|
+
reason: "Reinstall required to enable update checking (missing commit hash)",
|
|
5831
|
+
sourceUrl: entry.sourceUrl
|
|
5832
|
+
});
|
|
5833
|
+
continue;
|
|
5834
|
+
}
|
|
5835
|
+
try {
|
|
5836
|
+
const skillPath = (localEntry.skillPath || "").replace(/\/?SKILL\.md$/i, "");
|
|
5837
|
+
const latestCommit = await fetchAliInternalCommitHash(entry.source, skillPath, null);
|
|
5838
|
+
if (latestCommit && latestCommit !== localEntry.commitHash) availableUpdates.push({
|
|
5839
|
+
name: skillName,
|
|
5840
|
+
source: entry.source,
|
|
5841
|
+
entry
|
|
5842
|
+
});
|
|
5843
|
+
} catch {
|
|
5844
|
+
skipped.push({
|
|
5845
|
+
name: skillName,
|
|
5846
|
+
reason: "Ali internal repository (API check failed)",
|
|
5847
|
+
sourceUrl: entry.sourceUrl
|
|
5848
|
+
});
|
|
5849
|
+
}
|
|
5850
|
+
continue;
|
|
5851
|
+
}
|
|
5852
|
+
if (!entry.skillFolderHash) {
|
|
4764
5853
|
skipped.push({
|
|
4765
5854
|
name: skillName,
|
|
4766
5855
|
reason: getSkipReason(entry),
|
|
@@ -4768,58 +5857,97 @@ async function runUpdate() {
|
|
|
4768
5857
|
});
|
|
4769
5858
|
continue;
|
|
4770
5859
|
}
|
|
5860
|
+
if (entry.sourceType === "github") {
|
|
5861
|
+
const possiblePaths = entry.skillPath ? [entry.skillPath] : [`skills/${skillName}`, `skills/${skillName}/SKILL.md`];
|
|
5862
|
+
for (const skillPath of possiblePaths) try {
|
|
5863
|
+
const latestHash = await fetchSkillFolderHash(entry.source, skillPath, token);
|
|
5864
|
+
if (latestHash === null) continue;
|
|
5865
|
+
if (latestHash !== entry.skillFolderHash) availableUpdates.push({
|
|
5866
|
+
name: skillName,
|
|
5867
|
+
source: entry.source,
|
|
5868
|
+
entry
|
|
5869
|
+
});
|
|
5870
|
+
break;
|
|
5871
|
+
} catch {
|
|
5872
|
+
continue;
|
|
5873
|
+
}
|
|
5874
|
+
continue;
|
|
5875
|
+
}
|
|
4771
5876
|
try {
|
|
4772
|
-
const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token);
|
|
4773
|
-
if (latestHash && latestHash !== entry.skillFolderHash)
|
|
5877
|
+
const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath || "", token);
|
|
5878
|
+
if (latestHash && latestHash !== entry.skillFolderHash) availableUpdates.push({
|
|
4774
5879
|
name: skillName,
|
|
4775
5880
|
source: entry.source,
|
|
4776
5881
|
entry
|
|
4777
5882
|
});
|
|
4778
5883
|
} catch {}
|
|
4779
5884
|
}
|
|
4780
|
-
if (
|
|
4781
|
-
console.log(`${
|
|
4782
|
-
|
|
5885
|
+
if (availableUpdates.length === 0) {
|
|
5886
|
+
console.log(`${TEXT}✓ All ${scopeLabel} skills are up to date${RESET}`);
|
|
5887
|
+
console.log();
|
|
4783
5888
|
return;
|
|
4784
5889
|
}
|
|
4785
|
-
|
|
4786
|
-
|
|
5890
|
+
let selectedUpdates = [];
|
|
5891
|
+
if (skillArgs.length > 0) {
|
|
5892
|
+
const requestedSkills = skillArgs.map((s) => s.toLowerCase());
|
|
5893
|
+
selectedUpdates = availableUpdates.filter((u) => requestedSkills.includes(u.name.toLowerCase()));
|
|
5894
|
+
for (const reqSkill of skillArgs) {
|
|
5895
|
+
const exists = skillNames.some((n) => n.toLowerCase() === reqSkill.toLowerCase());
|
|
5896
|
+
const hasUpdate = availableUpdates.some((u) => u.name.toLowerCase() === reqSkill.toLowerCase());
|
|
5897
|
+
if (!exists) console.log(`${DIM}Skill not found: ${reqSkill}${RESET}`);
|
|
5898
|
+
else if (!hasUpdate) console.log(`${TEXT}✓ ${reqSkill} is already up to date${RESET}`);
|
|
5899
|
+
}
|
|
5900
|
+
} else if (yesFlag) selectedUpdates = availableUpdates;
|
|
5901
|
+
else {
|
|
5902
|
+
console.log(`${TEXT}Found ${availableUpdates.length} ${scopeLabel} skill update(s) available:${RESET}`);
|
|
4787
5903
|
console.log();
|
|
5904
|
+
const selected = await fe({
|
|
5905
|
+
message: "Select skills to update (space to select, enter to confirm):",
|
|
5906
|
+
options: [{
|
|
5907
|
+
value: "__ALL__",
|
|
5908
|
+
label: `${import_picocolors.default.green("Update all")} (${availableUpdates.length} skill(s))`
|
|
5909
|
+
}, ...availableUpdates.map((u) => ({
|
|
5910
|
+
value: u.name,
|
|
5911
|
+
label: ` ${u.name} ${DIM}(${u.source})${RESET}`
|
|
5912
|
+
}))],
|
|
5913
|
+
required: true
|
|
5914
|
+
});
|
|
5915
|
+
if (pD(selected)) {
|
|
5916
|
+
Se(import_picocolors.default.yellow("Update cancelled."));
|
|
5917
|
+
return;
|
|
5918
|
+
}
|
|
5919
|
+
const selectedArray = selected;
|
|
5920
|
+
if (selectedArray.includes("__ALL__")) selectedUpdates = availableUpdates;
|
|
5921
|
+
else selectedUpdates = availableUpdates.filter((u) => selectedArray.includes(u.name));
|
|
5922
|
+
}
|
|
5923
|
+
if (selectedUpdates.length === 0) {
|
|
5924
|
+
console.log(`${DIM}No skills selected for update.${RESET}`);
|
|
4788
5925
|
return;
|
|
4789
5926
|
}
|
|
4790
|
-
console.log(
|
|
5927
|
+
console.log();
|
|
5928
|
+
console.log(`${TEXT}Updating ${selectedUpdates.length} ${scopeLabel} skill(s)...${RESET}`);
|
|
4791
5929
|
console.log();
|
|
4792
5930
|
let successCount = 0;
|
|
4793
5931
|
let failCount = 0;
|
|
4794
|
-
for (const update of
|
|
5932
|
+
for (const update of selectedUpdates) {
|
|
4795
5933
|
console.log(`${TEXT}Updating ${update.name}...${RESET}`);
|
|
4796
|
-
let
|
|
4797
|
-
if (
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
5934
|
+
let skillFolder = update.entry.skillPath || "";
|
|
5935
|
+
if (skillFolder.endsWith("/SKILL.md")) skillFolder = skillFolder.slice(0, -9);
|
|
5936
|
+
else if (skillFolder.endsWith("SKILL.md")) skillFolder = skillFolder.slice(0, -8);
|
|
5937
|
+
if (skillFolder.endsWith("/")) skillFolder = skillFolder.slice(0, -1);
|
|
5938
|
+
let installUrl;
|
|
5939
|
+
if (skillFolder) {
|
|
4802
5940
|
installUrl = update.entry.sourceUrl.replace(/\.git$/, "").replace(/\/$/, "");
|
|
4803
5941
|
installUrl = `${installUrl}/tree/main/${skillFolder}`;
|
|
4804
|
-
}
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
"-g",
|
|
4811
|
-
"-y"
|
|
4812
|
-
], {
|
|
4813
|
-
stdio: [
|
|
4814
|
-
"inherit",
|
|
4815
|
-
"pipe",
|
|
4816
|
-
"pipe"
|
|
4817
|
-
],
|
|
4818
|
-
shell: process.platform === "win32"
|
|
4819
|
-
}).status === 0) {
|
|
5942
|
+
} else installUrl = update.entry.sourceUrl;
|
|
5943
|
+
try {
|
|
5944
|
+
await runAdd([installUrl], {
|
|
5945
|
+
yes: true,
|
|
5946
|
+
global: isGlobal
|
|
5947
|
+
});
|
|
4820
5948
|
successCount++;
|
|
4821
5949
|
console.log(` ${TEXT}✓${RESET} Updated ${update.name}`);
|
|
4822
|
-
}
|
|
5950
|
+
} catch {
|
|
4823
5951
|
failCount++;
|
|
4824
5952
|
console.log(` ${DIM}✗ Failed to update ${update.name}${RESET}`);
|
|
4825
5953
|
}
|
|
@@ -4829,7 +5957,7 @@ async function runUpdate() {
|
|
|
4829
5957
|
if (failCount > 0) console.log(`${DIM}Failed to update ${failCount} skill(s)${RESET}`);
|
|
4830
5958
|
track({
|
|
4831
5959
|
event: "update",
|
|
4832
|
-
skillCount: String(
|
|
5960
|
+
skillCount: String(selectedUpdates.length),
|
|
4833
5961
|
successCount: String(successCount),
|
|
4834
5962
|
failCount: String(failCount)
|
|
4835
5963
|
});
|
|
@@ -4857,6 +5985,11 @@ async function main() {
|
|
|
4857
5985
|
console.log();
|
|
4858
5986
|
runInit(restArgs);
|
|
4859
5987
|
break;
|
|
5988
|
+
case "config":
|
|
5989
|
+
showLogo();
|
|
5990
|
+
console.log();
|
|
5991
|
+
await runConfig(restArgs);
|
|
5992
|
+
break;
|
|
4860
5993
|
case "experimental_install":
|
|
4861
5994
|
showLogo();
|
|
4862
5995
|
await runInstallFromLock(restArgs);
|
|
@@ -4900,9 +6033,33 @@ async function main() {
|
|
|
4900
6033
|
case "check":
|
|
4901
6034
|
await runCheck(restArgs);
|
|
4902
6035
|
break;
|
|
6036
|
+
case "login":
|
|
6037
|
+
await login();
|
|
6038
|
+
break;
|
|
6039
|
+
case "logout":
|
|
6040
|
+
await logout();
|
|
6041
|
+
break;
|
|
6042
|
+
case "status":
|
|
6043
|
+
await status();
|
|
6044
|
+
break;
|
|
6045
|
+
case "req":
|
|
6046
|
+
case "request": {
|
|
6047
|
+
const { url, options } = parseRequestOptions(restArgs);
|
|
6048
|
+
if (!url) {
|
|
6049
|
+
console.error("Error: URL is required");
|
|
6050
|
+
showGatewayHelp();
|
|
6051
|
+
process.exit(1);
|
|
6052
|
+
}
|
|
6053
|
+
await proxyRequest(url, options);
|
|
6054
|
+
break;
|
|
6055
|
+
}
|
|
6056
|
+
case "gateway":
|
|
6057
|
+
case "gw":
|
|
6058
|
+
await runGatewayCommand(restArgs);
|
|
6059
|
+
break;
|
|
4903
6060
|
case "update":
|
|
4904
6061
|
case "upgrade":
|
|
4905
|
-
await runUpdate();
|
|
6062
|
+
await runUpdate(restArgs);
|
|
4906
6063
|
break;
|
|
4907
6064
|
case "--help":
|
|
4908
6065
|
case "-h":
|
|
@@ -4914,7 +6071,7 @@ async function main() {
|
|
|
4914
6071
|
break;
|
|
4915
6072
|
default:
|
|
4916
6073
|
console.log(`Unknown command: ${command}`);
|
|
4917
|
-
console.log(`Run ${BOLD}
|
|
6074
|
+
console.log(`Run ${BOLD}ali-skills --help${RESET} for usage.`);
|
|
4918
6075
|
}
|
|
4919
6076
|
}
|
|
4920
6077
|
main().then(async () => {
|