ali-skills 0.0.25 → 0.0.27

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.
@@ -2340,7 +2340,7 @@ function logSkillParseFailures(failures) {
2340
2340
  if (f.hint) M.message(import_picocolors.default.yellow(` 提示: ${f.hint}`));
2341
2341
  }
2342
2342
  }
2343
- var version$1 = "2.0.25";
2343
+ var version$1 = "2.0.28";
2344
2344
  const isCancelled$1 = (value) => typeof value === "symbol";
2345
2345
  function redactSensitiveText(text) {
2346
2346
  return text.replace(/(https?:\/\/)([^/\s:@]+)(?::([^@/\s]*))?@/gi, "$1").replace(/([?&](?:private_token|access_token|token|api_key|access_key|secret|password)=)[^&\s]+/gi, "$1***");
@@ -2531,6 +2531,18 @@ async function selectAgentsInteractive(options) {
2531
2531
  return [];
2532
2532
  }
2533
2533
  setVersion(version$1);
2534
+ function isCiEnvironment() {
2535
+ if (process.env.CI) return true;
2536
+ if (process.env.GITHUB_ACTIONS) return true;
2537
+ if (process.env.GITLAB_CI) return true;
2538
+ if (process.env.BUILDKITE) return true;
2539
+ if (process.env.JENKINS_URL) return true;
2540
+ return false;
2541
+ }
2542
+ function resolveInstallMode(options) {
2543
+ if (options.installMode) return options.installMode;
2544
+ return options.copy ? "copy" : "symlink";
2545
+ }
2534
2546
  async function handleWellKnownSkills(source, url, options, spinner) {
2535
2547
  spinner.start("Discovering skills from well-known endpoint...");
2536
2548
  const skills = await wellKnownProvider.fetchAllSkills(url);
@@ -2661,8 +2673,8 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2661
2673
  }
2662
2674
  installGlobally = scope;
2663
2675
  }
2664
- let installMode = options.copy ? "copy" : "symlink";
2665
- if (!options.copy && !options.yes) {
2676
+ let installMode = resolveInstallMode(options);
2677
+ if (!options.installMode && !options.copy && !options.yes) {
2666
2678
  const modeChoice = await ve({
2667
2679
  message: "Installation method",
2668
2680
  options: getInstallMethodOptions()
@@ -2819,6 +2831,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2819
2831
  async function runAdd(args, options = {}) {
2820
2832
  const source = args[0];
2821
2833
  let installTipShown = false;
2834
+ const ciMode = options.ci ?? isCiEnvironment();
2822
2835
  const showInstallTip = () => {
2823
2836
  if (installTipShown) return;
2824
2837
  M.message(import_picocolors.default.dim("Tip: use the --yes (-y) and --global (-g) flags to install without prompts."));
@@ -2836,6 +2849,7 @@ async function runAdd(args, options = {}) {
2836
2849
  console.log();
2837
2850
  process.exit(1);
2838
2851
  }
2852
+ if (ciMode && !options.yes) options.yes = true;
2839
2853
  if (options.all) {
2840
2854
  options.skill = ["*"];
2841
2855
  options.agent = ["*"];
@@ -3161,8 +3175,8 @@ async function runAdd(args, options = {}) {
3161
3175
  }
3162
3176
  installGlobally = scope;
3163
3177
  }
3164
- let installMode = options.copy ? "copy" : "symlink";
3165
- if (!options.copy && !options.yes) {
3178
+ let installMode = resolveInstallMode(options);
3179
+ if (!options.installMode && !options.copy && !options.yes) {
3166
3180
  const modeChoice = await ve({
3167
3181
  message: "Installation method",
3168
3182
  options: getInstallMethodOptions()
@@ -3498,6 +3512,12 @@ async function promptForFindSkills(options, targetAgents) {
3498
3512
  function parseAddOptions(args) {
3499
3513
  const options = {};
3500
3514
  const source = [];
3515
+ const installModes = [
3516
+ "symlink",
3517
+ "copy",
3518
+ "symlink-isolated",
3519
+ "copy-isolated"
3520
+ ];
3501
3521
  for (let i = 0; i < args.length; i++) {
3502
3522
  const arg = args[i];
3503
3523
  if (arg === "-g" || arg === "--global") options.global = true;
@@ -3531,7 +3551,13 @@ function parseAddOptions(args) {
3531
3551
  options.branch = args[i];
3532
3552
  } else if (arg === "--full-depth") options.fullDepth = true;
3533
3553
  else if (arg === "--copy") options.copy = true;
3534
- else if (arg && !arg.startsWith("-")) source.push(arg);
3554
+ else if (arg === "--ci") options.ci = true;
3555
+ else if (arg === "--install-mode") {
3556
+ i++;
3557
+ const mode = args[i];
3558
+ if (!mode || !installModes.includes(mode)) throw new Error(`Invalid value for --install-mode: ${mode ?? "(empty)"} (expected: symlink, copy, symlink-isolated, copy-isolated)`);
3559
+ options.installMode = mode;
3560
+ } else if (arg && !arg.startsWith("-")) source.push(arg);
3535
3561
  }
3536
3562
  return {
3537
3563
  source,
@@ -4720,6 +4746,10 @@ function readConfig() {
4720
4746
  function saveConfig(config) {
4721
4747
  safeWriteFile(CONFIG_FILE, JSON.stringify(config, null, 2));
4722
4748
  }
4749
+ function getSsoRefreshCookieTtlMs() {
4750
+ const sec = Number(process.env.ALI_SKILLS_SSO_REFRESH_COOKIE_TTL_SECONDS?.trim() || process.env.BUC_SSO_REFRESH_COOKIE_TTL_SECONDS?.trim() || "7200");
4751
+ return (Number.isFinite(sec) && sec > 0 ? sec : 7200) * 1e3;
4752
+ }
4723
4753
  function readAuth() {
4724
4754
  try {
4725
4755
  if (!existsSync(AUTH_FILE)) return null;
@@ -4951,6 +4981,9 @@ function startLocalServer() {
4951
4981
  const name = url.searchParams.get("name") || "";
4952
4982
  const privateToken = url.searchParams.get("private_token") || "";
4953
4983
  const ssoRefreshToken = url.searchParams.get("sso_refresh_token") || "";
4984
+ const ssoRtExpRaw = url.searchParams.get("sso_refresh_token_expires_at") || "";
4985
+ const ssoRtExpSec = parseInt(ssoRtExpRaw, 10);
4986
+ const ssoRefreshTokenExpiresAtMs = ssoRefreshToken && ssoRtExpRaw !== "" && Number.isFinite(ssoRtExpSec) && ssoRtExpSec > 0 ? ssoRtExpSec * 1e3 : void 0;
4954
4987
  const expiresAt = url.searchParams.get("expires_at") || "0";
4955
4988
  if (!empId || !privateToken) {
4956
4989
  res.writeHead(400, { "Content-Type": "text/html" });
@@ -5107,6 +5140,7 @@ function startLocalServer() {
5107
5140
  name,
5108
5141
  privateToken,
5109
5142
  ssoRefreshToken,
5143
+ ssoRefreshTokenExpiresAtMs,
5110
5144
  expiresAt: parseInt(expiresAt, 10)
5111
5145
  });
5112
5146
  setTimeout(() => server.close(), 1e3);
@@ -5132,13 +5166,51 @@ function openBrowser$2(url) {
5132
5166
  stdio: "ignore"
5133
5167
  });
5134
5168
  }
5135
- async function login() {
5136
- const existingAuth = readAuth();
5137
- if (existingAuth && !isAuthExpired(existingAuth)) {
5138
- console.log(`${YELLOW$3}! 您已经登录为: ${existingAuth.name} (${existingAuth.account})${RESET$3}`);
5139
- console.log();
5140
- console.log(`${DIM$3}如需重新登录,请先执行: npx ali-skills logout${RESET$3}`);
5141
- return;
5169
+ async function revokeGatewaySessionIfPossible(auth) {
5170
+ try {
5171
+ const revokeUrl = `${auth.gatewayUrl || getGatewayUrl()}/api/gateway/revoke`;
5172
+ const revokeRes = await fetch(revokeUrl, {
5173
+ method: "POST",
5174
+ headers: { "X-Private-Token": auth.privateToken }
5175
+ });
5176
+ if (revokeRes.ok) console.log(`${GREEN$2}✓ 已撤销当前网关会话${RESET$3}`);
5177
+ else if (revokeRes.status === 404) console.log(`${DIM$3}服务端暂不支持撤销网关会话,继续本机登出${RESET$3}`);
5178
+ else console.log(`${YELLOW$3}⚠️ 撤销网关会话失败,继续本机登出${RESET$3}`);
5179
+ } catch {
5180
+ console.log(`${YELLOW$3}⚠️ 撤销网关会话失败,继续本机登出${RESET$3}`);
5181
+ }
5182
+ }
5183
+ function getEffectiveSsoRefreshCookieExpiresAtMs(auth) {
5184
+ if (!auth.ssoRefreshToken?.trim()) return null;
5185
+ if (typeof auth.ssoRefreshTokenExpiresAt === "number" && auth.ssoRefreshTokenExpiresAt > 0) return auth.ssoRefreshTokenExpiresAt;
5186
+ if (typeof auth.ssoRefreshTokenFetchedAt === "number" && auth.ssoRefreshTokenFetchedAt > 0) return auth.ssoRefreshTokenFetchedAt + getSsoRefreshCookieTtlMs();
5187
+ return null;
5188
+ }
5189
+ function exitIfSsoRefreshCookieExpired(auth, opts) {
5190
+ const exp = getEffectiveSsoRefreshCookieExpiresAtMs(auth);
5191
+ if (exp === null) return;
5192
+ if (Date.now() < exp) return;
5193
+ if (!opts?.quiet) {
5194
+ console.error(`${YELLOW$3}SSO_REFRESH_TOKEN 已超过预估有效期,根域 cookie 直连会鉴权失败${RESET$3}`);
5195
+ console.log(`${DIM$3}请执行: npx ali-skills login --reauth${RESET$3}`);
5196
+ }
5197
+ process.exit(1);
5198
+ }
5199
+ async function login(options) {
5200
+ if (options?.reauth) {
5201
+ const auth = readAuth();
5202
+ if (auth) {
5203
+ await revokeGatewaySessionIfPossible(auth);
5204
+ removeAuth();
5205
+ }
5206
+ } else {
5207
+ const existingAuth = readAuth();
5208
+ if (existingAuth && !isAuthExpired(existingAuth)) {
5209
+ console.log(`${YELLOW$3}! 您已经登录为: ${existingAuth.name} (${existingAuth.account})${RESET$3}`);
5210
+ console.log();
5211
+ console.log(`${DIM$3}如需重新登录,请执行: npx ali-skills login --reauth${RESET$3}`);
5212
+ return;
5213
+ }
5142
5214
  }
5143
5215
  console.log(`${TEXT$3}正在启动本地服务...${RESET$3}`);
5144
5216
  const { port, waitForCallback, close } = await startLocalServer();
@@ -5151,7 +5223,9 @@ async function login() {
5151
5223
  openBrowser$2(loginUrl);
5152
5224
  console.log(`${TEXT$3}等待授权完成...${RESET$3}`);
5153
5225
  try {
5154
- const { empId, account, name, privateToken, ssoRefreshToken, expiresAt } = await waitForCallback();
5226
+ const { empId, account, name, privateToken, ssoRefreshToken, ssoRefreshTokenExpiresAtMs, expiresAt } = await waitForCallback();
5227
+ let ssoRtExpStored;
5228
+ if (ssoRefreshToken) ssoRtExpStored = ssoRefreshTokenExpiresAtMs ?? Date.now() + getSsoRefreshCookieTtlMs();
5155
5229
  saveAuth({
5156
5230
  empId,
5157
5231
  account,
@@ -5161,13 +5235,19 @@ async function login() {
5161
5235
  ssoRefreshToken,
5162
5236
  ssoRefreshTokenFetchedAt: ssoRefreshToken ? Date.now() : void 0,
5163
5237
  ssoRefreshTokenLastCheckedAt: ssoRefreshToken ? Date.now() : void 0,
5238
+ ssoRefreshTokenExpiresAt: ssoRtExpStored,
5164
5239
  expiresAt: expiresAt * 1e3
5165
5240
  });
5166
5241
  console.log();
5167
5242
  console.log(`${GREEN$2}✓ 登录成功!${RESET$3}`);
5168
5243
  console.log(`${TEXT$3}欢迎: ${name} (${account})${RESET$3}`);
5169
5244
  console.log(`${DIM$3}Token 有效期至: ${(/* @__PURE__ */ new Date(expiresAt * 1e3)).toLocaleDateString()}${RESET$3}`);
5170
- if (!ssoRefreshToken) console.log(`${DIM$3}注意: SSO_REFRESH_TOKEN 未获取,需要时将从服务端获取${RESET$3}`);
5245
+ if (ssoRefreshToken && ssoRtExpStored) console.log(`${DIM$3}SSO_REFRESH 预估过期: ${new Date(ssoRtExpStored).toLocaleString()}${RESET$3}`);
5246
+ if (!ssoRefreshToken) {
5247
+ console.log();
5248
+ console.log(`${YELLOW$3}⚠ SSO_REFRESH_TOKEN 获取异常:本次授权回调未携带该凭证,网关代理等依赖 SSO Cookie 的能力可能不可用${RESET$3}`);
5249
+ console.log(`${DIM$3}可尝试:在网页端(https://ali-skills.alibaba-inc.com)退出平台登录后重新执行 login 命令。同时请将此问题反馈给本工具开发者处理,感谢!${RESET$3}`);
5250
+ }
5171
5251
  } catch (error) {
5172
5252
  close();
5173
5253
  throw error;
@@ -5262,18 +5342,7 @@ async function logout(platformInput, yuqueGroup, clearAll) {
5262
5342
  return;
5263
5343
  }
5264
5344
  try {
5265
- try {
5266
- const revokeUrl = `${getGatewayUrl()}/api/gateway/revoke`;
5267
- const revokeRes = await fetch(revokeUrl, {
5268
- method: "POST",
5269
- headers: { "X-Private-Token": auth.privateToken }
5270
- });
5271
- if (revokeRes.ok) console.log(`${GREEN$2}✓ 已撤销当前网关会话${RESET$3}`);
5272
- else if (revokeRes.status === 404) console.log(`${DIM$3}服务端暂不支持撤销网关会话,继续本机登出${RESET$3}`);
5273
- else console.log(`${YELLOW$3}⚠️ 撤销网关会话失败,继续本机登出${RESET$3}`);
5274
- } catch {
5275
- console.log(`${YELLOW$3}⚠️ 撤销网关会话失败,继续本机登出${RESET$3}`);
5276
- }
5345
+ await revokeGatewaySessionIfPossible(auth);
5277
5346
  removeAuth();
5278
5347
  console.log(`${GREEN$2}✓ 已登出${RESET$3}`);
5279
5348
  } catch (error) {
@@ -5433,6 +5502,11 @@ async function status(args = {}) {
5433
5502
  console.log(` 工号: ${auth.empId}`);
5434
5503
  console.log(` 网关: ${getGatewayUrl()}`);
5435
5504
  console.log(` SSO_REFRESH_TOKEN: ${auth.ssoRefreshToken ? `${GREEN$2}已缓存${RESET$3}` : `${YELLOW$3}未缓存${RESET$3}`}`);
5505
+ const ssoRtExp = getEffectiveSsoRefreshCookieExpiresAtMs(auth);
5506
+ if (auth.ssoRefreshToken && ssoRtExp) {
5507
+ const rtDead = Date.now() >= ssoRtExp;
5508
+ console.log(` SSO_REFRESH 预估过期: ${new Date(ssoRtExp).toLocaleString()}${rtDead ? ` ${YELLOW$3}(已过期)${RESET$3}` : ""}`);
5509
+ } else if (auth.ssoRefreshToken) console.log(` SSO_REFRESH 预估过期: ${YELLOW$3}未知${RESET$3} ${DIM$3}(可 login --reauth 重新写入)${RESET$3}`);
5436
5510
  if (expired) {
5437
5511
  console.log(` Token: ${YELLOW$3}即将过期${RESET$3}`);
5438
5512
  console.log(`${TEXT$3}建议重新登录: npx ali-skills login${RESET$3}`);
@@ -5461,13 +5535,14 @@ async function ensureSsoRefreshToken() {
5461
5535
  });
5462
5536
  if (response.status === 200) {
5463
5537
  const data = await response.json();
5538
+ const mergedRtExp = typeof data.ssoRefreshTokenExpiresAt === "number" && data.ssoRefreshTokenExpiresAt > 0 ? data.ssoRefreshTokenExpiresAt : Date.now() + getSsoRefreshCookieTtlMs();
5464
5539
  saveAuth({
5465
5540
  ...auth,
5466
5541
  gatewayUrl: auth.gatewayUrl || getGatewayUrl(),
5467
5542
  ssoRefreshToken: data.ssoRefreshToken,
5468
5543
  ssoRefreshTokenFetchedAt: Date.now(),
5469
5544
  ssoRefreshTokenLastCheckedAt: Date.now(),
5470
- ssoRefreshTokenExpiresAt: data.ssoRefreshTokenExpiresAt
5545
+ ssoRefreshTokenExpiresAt: mergedRtExp
5471
5546
  });
5472
5547
  console.log(`${DIM$3}已从服务端获取 SSO_REFRESH_TOKEN${RESET$3}`);
5473
5548
  return data.ssoRefreshToken;
@@ -5568,6 +5643,12 @@ function getAuthModeDescription(mode) {
5568
5643
  default: return "Auto";
5569
5644
  }
5570
5645
  }
5646
+ function maskCredential(value) {
5647
+ const trimmed = value.trim();
5648
+ if (!trimmed) return "(empty)";
5649
+ if (trimmed.length <= 8) return "******";
5650
+ return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`;
5651
+ }
5571
5652
  const O2_CREDENTIALS_BASE64 = "eyJhY2Nlc3NLZXlJZCI6IlNZS2NFNWtrbHhtT1M3WjJYYXRObiIsImFjY2Vzc0tleVNlY3JldCI6IjQ2NGNhYTc0MmNiYTk2NzFkZjM5NzJkYzdhNWI3MmQ1Nzk5ZTc2NTQyYzRiZGFjM2M3YTk5N2QzMDM3YTIyNzAifQ==";
5572
5653
  function getO2Credentials() {
5573
5654
  const accessKeyId = process.env.O2_ACCESS_KEY_ID?.trim();
@@ -5606,8 +5687,14 @@ async function sendO2SdkRequest(targetUrl, options, withSsoTicket) {
5606
5687
  const path = parsed.pathname;
5607
5688
  const query = searchParamsToObject(parsed.searchParams);
5608
5689
  const headers = { ...options.headers ?? {} };
5690
+ let ssoTicketSource = null;
5609
5691
  if (withSsoTicket) {
5610
- const ssoTicket = await ensureSsoTicket();
5692
+ let ssoTicket = options.ssoTicket?.trim() || null;
5693
+ if (ssoTicket) ssoTicketSource = "arg";
5694
+ else {
5695
+ ssoTicket = await ensureSsoTicket();
5696
+ if (ssoTicket) ssoTicketSource = "gateway";
5697
+ }
5611
5698
  if (!ssoTicket) throw new Error("无法获取 SSO_TICKET,请重新登录后重试");
5612
5699
  headers.SSO_TICKET = ssoTicket;
5613
5700
  }
@@ -5632,6 +5719,10 @@ async function sendO2SdkRequest(targetUrl, options, withSsoTicket) {
5632
5719
  if (!options.quiet) {
5633
5720
  console.log(`${DIM$2}${method} ${parsed.toString()}${RESET$2}`);
5634
5721
  console.log(`${DIM$2}→ O2 SDK 直连${withSsoTicket ? " (携带 SSO_TICKET)" : ""}${RESET$2}`);
5722
+ if (withSsoTicket && ssoTicketSource) {
5723
+ const sourceText = ssoTicketSource === "arg" ? "参数传入(--sso-ticket)" : "登录态获取";
5724
+ console.log(`${DIM$2}→ SSO_TICKET 来源: ${sourceText}${RESET$2}`);
5725
+ }
5635
5726
  if (o2Env) console.log(`${DIM$2}→ O2 SDK Env: ${o2Env}${RESET$2}`);
5636
5727
  }
5637
5728
  let responseData;
@@ -5768,8 +5859,17 @@ async function sendDirectRequest(targetUrl, platform, token, options) {
5768
5859
  if (!response.ok) process.exit(1);
5769
5860
  }
5770
5861
  async function sendSsoCookieDirectRequest(targetUrl, options) {
5771
- let ssoRefreshToken = null;
5772
- try {
5862
+ const printReauthAndRetryHint = (location, forceStderr = false) => {
5863
+ const log = forceStderr ? console.error : console.log;
5864
+ log(`${YELLOW$2}检测到被重定向到登录页,当前 SSO_REFRESH_TOKEN 很可能已失效${RESET$2}`);
5865
+ if (location) log(`${DIM$2}Location: ${location}${RESET$2}`);
5866
+ log(`${TEXT$2}请先执行: npx ali-skills login --reauth(刷新 SSO_REFRESH_TOKEN)${RESET$2}`);
5867
+ log(`${TEXT$2}刷新后请重新发起当前请求${RESET$2}`);
5868
+ };
5869
+ const explicitSsoCookie = options.ssoCookie?.trim() || "";
5870
+ let ssoRefreshToken = explicitSsoCookie || null;
5871
+ const ssoCookieSource = explicitSsoCookie ? "arg" : "gateway";
5872
+ if (!ssoRefreshToken) try {
5773
5873
  await ensureLogin();
5774
5874
  ssoRefreshToken = await ensureSsoRefreshToken();
5775
5875
  } catch (error) {
@@ -5785,6 +5885,10 @@ async function sendSsoCookieDirectRequest(targetUrl, options) {
5785
5885
  console.log(`${TEXT$2}建议执行: npx ali-skills login${RESET$2}`);
5786
5886
  process.exit(1);
5787
5887
  }
5888
+ if (!explicitSsoCookie) {
5889
+ const authForRt = readAuth();
5890
+ if (authForRt) exitIfSsoRefreshCookieExpired(authForRt, { quiet: options.quiet });
5891
+ }
5788
5892
  const requestHeaders = {
5789
5893
  "User-Agent": "ali-skills-cli/1.0",
5790
5894
  Cookie: `SSO_REFRESH_TOKEN=${ssoRefreshToken}`
@@ -5800,11 +5904,13 @@ async function sendSsoCookieDirectRequest(targetUrl, options) {
5800
5904
  if (!options.quiet) {
5801
5905
  console.log(`${DIM$2}${options.method.toUpperCase()} ${targetUrl}${RESET$2}`);
5802
5906
  console.log(`${DIM$2}→ 直接请求 (Cookie: SSO_REFRESH_TOKEN)${RESET$2}`);
5907
+ console.log(`${DIM$2}→ SSO_REFRESH_TOKEN 来源: ${ssoCookieSource === "arg" ? "参数传入(--sso-refresh-token)" : "登录态获取"}${RESET$2}`);
5908
+ if (ssoCookieSource === "arg") console.log(`${DIM$2}→ SSO_REFRESH_TOKEN: ${maskCredential(explicitSsoCookie)}${RESET$2}`);
5803
5909
  }
5804
5910
  const controller = new AbortController();
5805
5911
  const timeout = options.timeout || 3e4;
5806
5912
  const timeoutId = setTimeout(() => controller.abort(), timeout);
5807
- const { response, responseBody } = await fetchWithRedirects(targetUrl, options.method.toUpperCase(), requestHeaders, body, timeout, options.quiet);
5913
+ const { response, responseBody, loginRedirectUrl } = await fetchWithRedirects(targetUrl, options.method.toUpperCase(), requestHeaders, body, timeout, options.quiet, { stopOnAlibabaLoginRedirect: true });
5808
5914
  clearTimeout(timeoutId);
5809
5915
  if (!options.quiet) {
5810
5916
  const statusColor = response.ok ? GREEN$1 : RED$2;
@@ -5816,7 +5922,15 @@ async function sendSsoCookieDirectRequest(targetUrl, options) {
5816
5922
  console.log(`${DIM$2} 2. 目标服务未启用该认证链路${RESET$2}`);
5817
5923
  console.log(`${DIM$2} 3. 当前账号在目标系统无访问权限${RESET$2}`);
5818
5924
  console.log(`${TEXT$2}建议: 重新登录后重试,或联系目标系统确认登录态策略${RESET$2}`);
5819
- }
5925
+ } else if (loginRedirectUrl) printReauthAndRetryHint(loginRedirectUrl);
5926
+ }
5927
+ if (options.includeHeaders) {
5928
+ console.log();
5929
+ console.log(`${TEXT$2}Response Headers:${RESET$2}`);
5930
+ response.headers.forEach((value, key) => {
5931
+ console.log(` ${key}: ${value}`);
5932
+ });
5933
+ console.log();
5820
5934
  }
5821
5935
  let outputBody = responseBody;
5822
5936
  if (!options.raw && options.pretty !== false) try {
@@ -5827,12 +5941,21 @@ async function sendSsoCookieDirectRequest(targetUrl, options) {
5827
5941
  const { writeFileSync } = __require("fs");
5828
5942
  writeFileSync(options.output, outputBody);
5829
5943
  console.log(`${GREEN$1}✓ 已保存到 ${options.output}${RESET$2}`);
5830
- } else console.log(outputBody);
5944
+ } else {
5945
+ if (options.quiet && !response.ok && outputBody.trim().length === 0 && (loginRedirectUrl || response.headers.get("location"))) {
5946
+ const location = loginRedirectUrl || response.headers.get("location") || "(empty)";
5947
+ console.error(`HTTP ${response.status} ${response.statusText} (Location: ${location})`);
5948
+ printReauthAndRetryHint(location, true);
5949
+ }
5950
+ console.log(outputBody);
5951
+ }
5831
5952
  if (!response.ok) process.exit(1);
5832
5953
  }
5833
5954
  async function sendSsoTicketDirectRequest(targetUrl, options) {
5834
- let ssoTicket = null;
5835
- try {
5955
+ const explicitSsoTicket = options.ssoTicket?.trim() || "";
5956
+ let ssoTicket = explicitSsoTicket || null;
5957
+ const ssoTicketSource = explicitSsoTicket ? "arg" : "gateway";
5958
+ if (!ssoTicket) try {
5836
5959
  await ensureLogin();
5837
5960
  ssoTicket = await ensureSsoTicket();
5838
5961
  } catch (error) {
@@ -5859,6 +5982,8 @@ async function sendSsoTicketDirectRequest(targetUrl, options) {
5859
5982
  if (!options.quiet) {
5860
5983
  console.log(`${DIM$2}${options.method.toUpperCase()} ${targetUrl}${RESET$2}`);
5861
5984
  console.log(`${DIM$2}→ 直接请求 (Header: SSO_TICKET)${RESET$2}`);
5985
+ console.log(`${DIM$2}→ SSO_TICKET 来源: ${ssoTicketSource === "arg" ? "参数传入(--sso-ticket)" : "登录态获取"}${RESET$2}`);
5986
+ if (ssoTicketSource === "arg") console.log(`${DIM$2}→ SSO_TICKET: ${maskCredential(explicitSsoTicket)}${RESET$2}`);
5862
5987
  }
5863
5988
  const controller = new AbortController();
5864
5989
  const timeout = options.timeout || 3e4;
@@ -5932,14 +6057,14 @@ async function sendPlainDirectRequest(targetUrl, options, allowSsoCookieFallback
5932
6057
  } else console.log(outputBody);
5933
6058
  if (!response.ok) process.exit(1);
5934
6059
  }
5935
- async function fetchWithRedirects(initialUrl, method, headers, body, timeout, quiet) {
6060
+ async function fetchWithRedirects(initialUrl, method, headers, body, timeout, quiet, opts) {
5936
6061
  const controller = new AbortController();
5937
6062
  const timeoutId = setTimeout(() => controller.abort(), timeout);
5938
6063
  let currentUrl = initialUrl;
5939
6064
  let redirects = 0;
5940
6065
  let sawAlibabaLoginRedirect = false;
5941
6066
  try {
5942
- while (true) {
6067
+ while (redirects <= MAX_REDIRECTS) {
5943
6068
  const response = await fetch(currentUrl, {
5944
6069
  method,
5945
6070
  headers,
@@ -5960,11 +6085,20 @@ async function fetchWithRedirects(initialUrl, method, headers, body, timeout, qu
5960
6085
  };
5961
6086
  if (redirects >= MAX_REDIRECTS) throw new Error(`重定向次数超过上限(${MAX_REDIRECTS})`);
5962
6087
  const nextUrl = new URL(location, currentUrl).toString();
5963
- if (new URL(nextUrl).hostname === "login.alibaba-inc.com") sawAlibabaLoginRedirect = true;
6088
+ if (new URL(nextUrl).hostname === "login.alibaba-inc.com") {
6089
+ sawAlibabaLoginRedirect = true;
6090
+ if (opts?.stopOnAlibabaLoginRedirect) return {
6091
+ response,
6092
+ responseBody: await response.text(),
6093
+ sawAlibabaLoginRedirect,
6094
+ loginRedirectUrl: nextUrl
6095
+ };
6096
+ }
5964
6097
  redirects += 1;
5965
6098
  if (!quiet) console.log(`${DIM$2}↪ 重定向 ${redirects}: ${response.status} ${response.statusText} -> ${nextUrl}${RESET$2}`);
5966
6099
  currentUrl = nextUrl;
5967
6100
  }
6101
+ throw new Error(`重定向次数超过上限(${MAX_REDIRECTS})`);
5968
6102
  } finally {
5969
6103
  clearTimeout(timeoutId);
5970
6104
  }
@@ -5978,6 +6112,8 @@ async function sendGatewayRequest(targetUrl, platformKey, options, authMode = "g
5978
6112
  process.exit(1);
5979
6113
  }
5980
6114
  const proxyUrl = `${getGatewayUrl()}/api/gateway/proxy`;
6115
+ const explicitSsoCookie = options.ssoCookie?.trim() || "";
6116
+ const explicitSsoTicket = options.ssoTicket?.trim() || "";
5981
6117
  const requestHeaders = {
5982
6118
  Authorization: `Bearer ${credentials.privateToken}`,
5983
6119
  "X-Private-Token": credentials.privateToken,
@@ -5986,6 +6122,8 @@ async function sendGatewayRequest(targetUrl, platformKey, options, authMode = "g
5986
6122
  "X-Platform": platformKey,
5987
6123
  "X-Auth-Mode": authMode
5988
6124
  };
6125
+ if (explicitSsoCookie) requestHeaders["X-SSO-Refresh-Token"] = explicitSsoCookie;
6126
+ if (explicitSsoTicket) requestHeaders["X-SSO-Ticket"] = explicitSsoTicket;
5989
6127
  if (options.headers) Object.entries(options.headers).forEach(([key, value]) => {
5990
6128
  requestHeaders[key] = value;
5991
6129
  });
@@ -5997,6 +6135,14 @@ async function sendGatewayRequest(targetUrl, platformKey, options, authMode = "g
5997
6135
  if (!options.quiet) {
5998
6136
  console.log(`${DIM$2}${options.method.toUpperCase()} ${targetUrl}${RESET$2}`);
5999
6137
  console.log(`${DIM$2}→ Gateway: ${proxyUrl}${RESET$2}`);
6138
+ if (explicitSsoCookie) {
6139
+ console.log(`${DIM$2}→ SSO_REFRESH_TOKEN 来源: 参数传入(--sso-refresh-token)${RESET$2}`);
6140
+ console.log(`${DIM$2}→ SSO_REFRESH_TOKEN: ${maskCredential(explicitSsoCookie)}${RESET$2}`);
6141
+ }
6142
+ if (explicitSsoTicket) {
6143
+ console.log(`${DIM$2}→ SSO_TICKET 来源: 参数传入(--sso-ticket)${RESET$2}`);
6144
+ console.log(`${DIM$2}→ SSO_TICKET: ${maskCredential(explicitSsoTicket)}${RESET$2}`);
6145
+ }
6000
6146
  }
6001
6147
  const controller = new AbortController();
6002
6148
  const timeout = options.timeout || 3e4;
@@ -6519,9 +6665,6 @@ ${BOLD}Manage Skills:${RESET}
6519
6665
  list, ls List installed skills
6520
6666
  find [query] Search for skills interactively
6521
6667
 
6522
- ${BOLD}Publish:${RESET}
6523
- publish [options] Publish skills from current repo to OSS (alias: pub)
6524
-
6525
6668
  ${BOLD}Updates:${RESET}
6526
6669
  check Check for available skill updates (project-level by default)
6527
6670
  check -g Check global skill updates
@@ -6532,9 +6675,7 @@ ${BOLD}Updates:${RESET}
6532
6675
  update <skill> Update specific skill(s)
6533
6676
 
6534
6677
  ${BOLD}Project:${RESET}
6535
- experimental_install Restore skills from skills-lock.json
6536
6678
  init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
6537
- experimental_sync Sync skills from node_modules into agent directories
6538
6679
 
6539
6680
  ${BOLD}Gateway:${RESET}
6540
6681
  gateway 鉴权请求能力入口(login/logout/status/req)
@@ -6548,8 +6689,10 @@ ${BOLD}Add Options:${RESET}
6548
6689
  -s, --skill <skills> Specify skill names to install (use '*' for all skills)
6549
6690
  -l, --list List available skills in the repository without installing
6550
6691
  -y, --yes Skip confirmation prompts
6692
+ --ci Enable CI mode (auto non-interactive, implies --yes)
6551
6693
  --github Force fetching from GitHub when using owner/repo shorthand
6552
- --copy Copy files instead of symlinking to agent directories
6694
+ --install-mode <mode> Install mode: symlink|copy|symlink-isolated|copy-isolated (default: symlink)
6695
+ --copy Backward-compatible alias of --install-mode copy (recommended: use --install-mode)
6553
6696
  --all Shorthand for --skill '*' --agent '*' -y
6554
6697
  --full-depth Search all subdirectories even when a root SKILL.md exists
6555
6698
 
@@ -6560,10 +6703,6 @@ ${BOLD}Remove Options:${RESET}
6560
6703
  -y, --yes Skip confirmation prompts
6561
6704
  --all Shorthand for --skill '*' --agent '*' -y
6562
6705
 
6563
- ${BOLD}Experimental Sync Options:${RESET}
6564
- -a, --agent <agents> Specify agents to install to (use '*' for all agents)
6565
- -y, --yes Skip confirmation prompts
6566
-
6567
6706
  ${BOLD}List Options:${RESET}
6568
6707
  -g, --global List global skills (default: project)
6569
6708
  -a, --agent <agents> Filter by specific agents
@@ -6573,14 +6712,12 @@ ${BOLD}Options:${RESET}
6573
6712
  --help, -h Show this help message
6574
6713
  --version, -v Show version number
6575
6714
 
6576
- ${BOLD}Debug:${RESET}
6577
- ALI_SKILLS_DEBUG_UPDATE=1 Print detailed logs for check/update diagnosis
6578
-
6579
6715
  ${BOLD}Examples:${RESET}
6580
6716
  ${DIM}$${RESET} ali-skills add group/project
6581
6717
  ${DIM}$${RESET} ali-skills add group/project -g
6582
6718
  ${DIM}$${RESET} ali-skills add group/project --agent claude-code cursor
6583
6719
  ${DIM}$${RESET} ali-skills add group/project --skill pr-review commit
6720
+ ${DIM}$${RESET} ali-skills add group/project --install-mode symlink-isolated
6584
6721
  ${DIM}$${RESET} ali-skills remove ${DIM}# interactive remove${RESET}
6585
6722
  ${DIM}$${RESET} ali-skills remove web-design ${DIM}# remove by name${RESET}
6586
6723
  ${DIM}$${RESET} ali-skills rm --global frontend-design
@@ -6593,10 +6730,7 @@ ${BOLD}Examples:${RESET}
6593
6730
  ${DIM}$${RESET} ali-skills check
6594
6731
  ${DIM}$${RESET} ali-skills check web-design react-best-practices
6595
6732
  ${DIM}$${RESET} ali-skills update
6596
- ${DIM}$${RESET} ali-skills experimental_install ${DIM}# restore from skills-lock.json${RESET}
6597
6733
  ${DIM}$${RESET} ali-skills init my-skill
6598
- ${DIM}$${RESET} ali-skills experimental_sync ${DIM}# sync from node_modules${RESET}
6599
- ${DIM}$${RESET} ali-skills experimental_sync -y ${DIM}# sync without prompts${RESET}
6600
6734
 
6601
6735
  Discover more skills at ${TEXT}https://ali-skills.alibaba-inc.com/${RESET}
6602
6736
  `);
@@ -6636,7 +6770,7 @@ function showGatewayHelp() {
6636
6770
  const platformKeysForHelp = listPlatforms().filter((p) => p.key !== "aone").map((p) => p.key).join(", ");
6637
6771
  console.log(`
6638
6772
  ${BOLD}Gateway Commands:${RESET}
6639
- login [platform] Login to Gateway or configure platform token
6773
+ login [--reauth] [platform] Login to Gateway or configure platform token; --reauth 等价于先 logout 再登录
6640
6774
  logout [platform] Logout BUC or clear platform token
6641
6775
  status [platform] [--all] Show login status, single platform, or all platform statuses
6642
6776
  request [platform] <url|path> Send HTTP request (supports platform + path shorthand)
@@ -6657,6 +6791,8 @@ ${BOLD}Request Options:${RESET}
6657
6791
  --auth, --auth-mode <mode> Auth mode: auto/private_token/sso_cookie/sso_ticket
6658
6792
  --sso <cookie|ticket> Shortcut for --auth sso_cookie/sso_ticket
6659
6793
  --token <token> Explicit token for private_token mode
6794
+ --sso-refresh-token <token> Explicit SSO_REFRESH_TOKEN for sso_cookie mode
6795
+ --sso-ticket <ticket> Explicit SSO_TICKET for sso_ticket mode
6660
6796
  -o, --output <file> Save response to file
6661
6797
  -i, --include-headers Include response headers in output
6662
6798
  -t, --timeout <ms> Request timeout (default: 30000)
@@ -6666,6 +6802,7 @@ ${BOLD}Request Options:${RESET}
6666
6802
 
6667
6803
  ${BOLD}Examples:${RESET}
6668
6804
  ${DIM}$${RESET} ali-skills login
6805
+ ${DIM}$${RESET} ali-skills login --reauth
6669
6806
  ${DIM}$${RESET} ali-skills login code
6670
6807
  ${DIM}$${RESET} ali-skills login yuque
6671
6808
  ${DIM}$${RESET} ali-skills login yuque group
@@ -6686,8 +6823,10 @@ ${BOLD}Examples:${RESET}
6686
6823
  ${DIM}$${RESET} ali-skills logout yuque --all
6687
6824
  ${DIM}$${RESET} ali-skills req 'https://subdomain.alibaba-inc.com/path?q=v'
6688
6825
  ${DIM}$${RESET} ali-skills req 'https://subdomain.alibaba-inc.com/path?q=v' --auth sso_cookie
6826
+ ${DIM}$${RESET} ali-skills req 'https://subdomain.alibaba-inc.com/path?q=v' --auth sso_cookie --sso-refresh-token '<SSO_REFRESH_TOKEN>'
6689
6827
  ${DIM}$${RESET} ali-skills req 'https://subdomain.alibaba-inc.com/path?q=v' --auth sso_cookie -X POST -b '{"k": "v"}' -h 'X-Custom-Header: value'
6690
6828
  ${DIM}$${RESET} ali-skills req 'https://support-buc-sso-ticket-site/path?q=v' --auth sso_ticket
6829
+ ${DIM}$${RESET} ali-skills req 'https://support-buc-sso-ticket-site/path?q=v' --auth sso_ticket --sso-ticket '<SSO_TICKET>'
6691
6830
  ${DIM}$${RESET} ali-skills req code '/api/v3/project?path=clam-ng/clam'
6692
6831
  ${DIM}$${RESET} ali-skills req yuque '/api/v2/repos/fliggyfe-infra/ali-skills-dev/docs/mll9xim6y6lcnvyg'
6693
6832
  ${DIM}$${RESET} ali-skills req yuque '/api/v2/search?q=buc&type=doc&scope=fliggyfe-infra'
@@ -6758,6 +6897,16 @@ function parseRequestOptions(args) {
6758
6897
  options.authMode = mapped;
6759
6898
  continue;
6760
6899
  }
6900
+ const ssoCookieValue = parseLongOptionValue("sso-refresh-token") ?? parseLongOptionValue("sso-cookie");
6901
+ if (ssoCookieValue !== void 0) {
6902
+ options.ssoCookie = ssoCookieValue;
6903
+ continue;
6904
+ }
6905
+ const ssoTicketValue = parseLongOptionValue("sso-ticket");
6906
+ if (ssoTicketValue !== void 0) {
6907
+ options.ssoTicket = ssoTicketValue;
6908
+ continue;
6909
+ }
6761
6910
  switch (arg) {
6762
6911
  case "-m":
6763
6912
  case "-x":
@@ -6814,6 +6963,13 @@ function parseRequestOptions(args) {
6814
6963
  options.authMode = mode;
6815
6964
  break;
6816
6965
  }
6966
+ case "--sso-refresh-token":
6967
+ case "--sso-cookie":
6968
+ options.ssoCookie = args[++i];
6969
+ break;
6970
+ case "--sso-ticket":
6971
+ options.ssoTicket = args[++i];
6972
+ break;
6817
6973
  case "-q":
6818
6974
  case "--quiet":
6819
6975
  options.quiet = true;
@@ -6842,6 +6998,13 @@ function expandRequestShorthand(args) {
6842
6998
  const normalizedPath = pathArg.startsWith("/") ? pathArg : `/${pathArg}`;
6843
6999
  return [`https://${platform.key === "yuque" ? "yuque-api.antfin-inc.com" : platform.config.host}${normalizedPath}`, ...args.slice(2)];
6844
7000
  }
7001
+ function getHostFromUrl(rawUrl) {
7002
+ try {
7003
+ return new URL(rawUrl).hostname;
7004
+ } catch {
7005
+ return "";
7006
+ }
7007
+ }
6845
7008
  function parseStatusArgs(args) {
6846
7009
  const showPrivateTokenFlags = new Set(["--show", "-s"]);
6847
7010
  const positionals = [];
@@ -6929,6 +7092,16 @@ function parseLogoutArgs(args) {
6929
7092
  clearAll
6930
7093
  };
6931
7094
  }
7095
+ function stripReauthFlag(args) {
7096
+ let reauth = false;
7097
+ const rest = [];
7098
+ for (const a of args) if (a === "--reauth") reauth = true;
7099
+ else rest.push(a);
7100
+ return {
7101
+ reauth,
7102
+ rest
7103
+ };
7104
+ }
6932
7105
  async function runGatewayCommand(args) {
6933
7106
  if (args.length === 0) {
6934
7107
  showGatewayHelp();
@@ -6937,11 +7110,14 @@ async function runGatewayCommand(args) {
6937
7110
  const command = args[0];
6938
7111
  const rest = args.slice(1);
6939
7112
  switch (command) {
6940
- case "login":
7113
+ case "login": {
6941
7114
  showLogo();
6942
7115
  console.log();
6943
- await login();
7116
+ const { reauth, rest: loginRest } = stripReauthFlag(rest);
7117
+ if (loginRest.length > 0) await runPlatformLogin(loginRest);
7118
+ else await login({ reauth });
6944
7119
  break;
7120
+ }
6945
7121
  case "logout": {
6946
7122
  const lo = parseLogoutArgs(rest);
6947
7123
  await logout(lo.platform, lo.yuqueGroup, lo.clearAll);
@@ -6968,6 +7144,12 @@ async function runGatewayCommand(args) {
6968
7144
  process.exit(1);
6969
7145
  }
6970
7146
  await proxyRequest(url, options);
7147
+ track({
7148
+ event: "req",
7149
+ source: getHostFromUrl(url),
7150
+ sourceType: options.authMode ?? "auto",
7151
+ query: options.method.toUpperCase()
7152
+ });
6971
7153
  break;
6972
7154
  }
6973
7155
  case "--help":
@@ -7593,7 +7775,14 @@ async function main() {
7593
7775
  case "a":
7594
7776
  case "add": {
7595
7777
  showLogo();
7596
- const { source: addSource, options: addOpts } = parseAddOptions(restArgs);
7778
+ let addParsed;
7779
+ try {
7780
+ addParsed = parseAddOptions(restArgs);
7781
+ } catch (error) {
7782
+ console.error(error instanceof Error ? error.message : "Invalid add options");
7783
+ process.exit(1);
7784
+ }
7785
+ const { source: addSource, options: addOpts } = addParsed;
7597
7786
  await runAdd(addSource, addOpts);
7598
7787
  break;
7599
7788
  }
@@ -7628,10 +7817,12 @@ async function main() {
7628
7817
  case "check":
7629
7818
  await runCheck(restArgs);
7630
7819
  break;
7631
- case "login":
7632
- if (restArgs.length > 0) await runPlatformLogin(restArgs);
7633
- else await login();
7820
+ case "login": {
7821
+ const { reauth, rest: loginRest } = stripReauthFlag(restArgs);
7822
+ if (loginRest.length > 0) await runPlatformLogin(loginRest);
7823
+ else await login({ reauth });
7634
7824
  break;
7825
+ }
7635
7826
  case "logout": {
7636
7827
  const lo = parseLogoutArgs(restArgs);
7637
7828
  await logout(lo.platform, lo.yuqueGroup, lo.clearAll);
@@ -7649,6 +7840,12 @@ async function main() {
7649
7840
  process.exit(1);
7650
7841
  }
7651
7842
  await proxyRequest(url, options);
7843
+ track({
7844
+ event: "req",
7845
+ source: getHostFromUrl(url),
7846
+ sourceType: options.authMode ?? "auto",
7847
+ query: options.method.toUpperCase()
7848
+ });
7652
7849
  break;
7653
7850
  }
7654
7851
  case "gateway":
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ali/cli-skills",
3
- "version": "2.0.25",
3
+ "version": "2.0.28",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ali-skills",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  "author": "",
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
- "@ali/cli-skills": "^2.0.25"
34
+ "@ali/cli-skills": "^2.0.28"
35
35
  },
36
36
  "bundleDependencies": [
37
37
  "@ali/cli-skills"