ali-skills 0.1.4 → 0.1.5

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.
@@ -16,7 +16,7 @@ import { spawn } from "child_process";
16
16
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
17
17
  import { basename, dirname, join, normalize, relative, resolve, sep } from "path";
18
18
  import { homedir, platform as platform$1 } from "os";
19
- import { createHash } from "crypto";
19
+ import { createHash, randomBytes, randomInt } from "crypto";
20
20
  import { URL as URL$1, fileURLToPath } from "url";
21
21
  import * as readline from "readline";
22
22
  import { Writable } from "stream";
@@ -1386,7 +1386,7 @@ var WellKnownProvider = class {
1386
1386
  }
1387
1387
  };
1388
1388
  const wellKnownProvider = new WellKnownProvider();
1389
- var version$1 = "2.1.5";
1389
+ var version$1 = "2.1.6";
1390
1390
  async function checkGithubVersions$1(requests, debugLog = () => {}) {
1391
1391
  const results = [];
1392
1392
  for (let offset = 0; offset < requests.length; offset += 100) results.push(...await checkGithubVersionBatch(requests.slice(offset, offset + 100), debugLog));
@@ -3834,6 +3834,56 @@ function listPlatforms() {
3834
3834
  config
3835
3835
  }));
3836
3836
  }
3837
+ function isTruthy(value) {
3838
+ return value !== void 0 && [
3839
+ "1",
3840
+ "true",
3841
+ "yes"
3842
+ ].includes(value.trim().toLowerCase());
3843
+ }
3844
+ function detectLoginEnvironment({ platform, env }) {
3845
+ if (isTruthy(env.CI)) return {
3846
+ mode: "manual",
3847
+ reason: "检测到 CI 环境"
3848
+ };
3849
+ if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY) return {
3850
+ mode: "manual",
3851
+ reason: "未检测到 Linux 图形桌面"
3852
+ };
3853
+ return { mode: "browser" };
3854
+ }
3855
+ function parseLoginCallbackUrl(rawValue, expectation) {
3856
+ let url;
3857
+ try {
3858
+ url = new URL(rawValue.trim());
3859
+ } catch {
3860
+ throw new Error("回调地址格式无效,请粘贴浏览器地址栏中的完整 URL");
3861
+ }
3862
+ if (url.protocol !== "http:") throw new Error("回调地址协议无效");
3863
+ if (url.hostname !== "localhost" && url.hostname !== "127.0.0.1") throw new Error("回调地址主机无效");
3864
+ if (url.port !== String(expectation.port) || url.pathname !== "/callback") throw new Error("回调地址不属于本次登录");
3865
+ if (url.searchParams.get("state") !== expectation.state) throw new Error("回调地址 state 校验失败,请使用本次登录生成的地址");
3866
+ const empId = url.searchParams.get("emp_id") || "";
3867
+ const account = url.searchParams.get("account") || "";
3868
+ const name = url.searchParams.get("name") || "";
3869
+ const email = url.searchParams.get("email") || "";
3870
+ const privateToken = url.searchParams.get("private_token") || "";
3871
+ const ssoRefreshToken = url.searchParams.get("sso_refresh_token") || "";
3872
+ const expiresAtRaw = url.searchParams.get("expires_at") || "";
3873
+ if (!empId || !privateToken) throw new Error("回调地址缺少必要的登录凭证");
3874
+ const expiresAt = Number(expiresAtRaw);
3875
+ const nowSeconds = expectation.nowSeconds ?? Math.floor(Date.now() / 1e3);
3876
+ if (!Number.isSafeInteger(expiresAt) || expiresAt <= nowSeconds) throw new Error("回调地址中的登录凭证已过期或无效");
3877
+ return {
3878
+ empId,
3879
+ account,
3880
+ name,
3881
+ email,
3882
+ privateToken,
3883
+ ssoRefreshToken,
3884
+ expiresAt
3885
+ };
3886
+ }
3837
3887
  const DIM$4 = "\x1B[38;5;102m";
3838
3888
  const TEXT$3 = "\x1B[38;5;145m";
3839
3889
  const RESET$4 = "\x1B[0m";
@@ -3863,34 +3913,34 @@ function displayWidth$1(str) {
3863
3913
  function printJson(payload, pretty = false) {
3864
3914
  console.log(JSON.stringify(payload, null, pretty ? 2 : 0));
3865
3915
  }
3866
- function startLocalServer() {
3916
+ function startLocalServer(state) {
3867
3917
  return new Promise((resolve) => {
3868
3918
  let resolveCallback;
3869
3919
  const callbackPromise = new Promise((resolve) => {
3870
3920
  resolveCallback = resolve;
3871
3921
  });
3872
3922
  const server = http.createServer((req, res) => {
3873
- const url = new URL$1(req.url || "/", `http://localhost`);
3874
- if (url.pathname === "/callback") {
3875
- const empId = url.searchParams.get("emp_id") || "";
3876
- const account = url.searchParams.get("account") || "";
3877
- const name = url.searchParams.get("name") || "";
3878
- const email = url.searchParams.get("email") || "";
3879
- const privateToken = url.searchParams.get("private_token") || "";
3880
- const ssoRefreshToken = url.searchParams.get("sso_refresh_token") || "";
3881
- const expiresAt = url.searchParams.get("expires_at") || "0";
3882
- if (!empId || !privateToken) {
3923
+ if (new URL$1(req.url || "/", `http://localhost`).pathname === "/callback") {
3924
+ let callbackData;
3925
+ try {
3926
+ const address = server.address();
3927
+ callbackData = parseLoginCallbackUrl(`http://localhost:${address.port}${req.url || ""}`, {
3928
+ port: address.port,
3929
+ state
3930
+ });
3931
+ } catch (error) {
3883
3932
  res.writeHead(400, { "Content-Type": "text/html" });
3884
3933
  res.end(`
3885
3934
  <html>
3886
3935
  <body style="font-family: sans-serif; text-align: center; padding: 50px;">
3887
3936
  <h1 style="color: #ff4d4f;">登录失败</h1>
3888
- <p>缺少必要的参数</p>
3937
+ <p>${error instanceof Error ? error.message : "回调地址无效"}</p>
3889
3938
  </body>
3890
3939
  </html>
3891
3940
  `);
3892
3941
  return;
3893
3942
  }
3943
+ const { empId, account, name } = callbackData;
3894
3944
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
3895
3945
  res.end(`
3896
3946
  <html>
@@ -4028,22 +4078,14 @@ function startLocalServer() {
4028
4078
  </body>
4029
4079
  </html>
4030
4080
  `);
4031
- resolveCallback({
4032
- empId,
4033
- account,
4034
- name,
4035
- email,
4036
- privateToken,
4037
- ssoRefreshToken,
4038
- expiresAt: parseInt(expiresAt, 10)
4039
- });
4081
+ resolveCallback(callbackData);
4040
4082
  setTimeout(() => server.close(), 1e3);
4041
4083
  } else {
4042
4084
  res.writeHead(404);
4043
4085
  res.end("Not Found");
4044
4086
  }
4045
4087
  });
4046
- server.listen(0, "localhost", () => {
4088
+ server.listen(0, "127.0.0.1", () => {
4047
4089
  const port = server.address().port;
4048
4090
  resolve({
4049
4091
  port,
@@ -4053,13 +4095,106 @@ function startLocalServer() {
4053
4095
  });
4054
4096
  });
4055
4097
  }
4056
- function openBrowser$2(url) {
4057
- const platform = process.platform;
4058
- spawn(platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open", [url], {
4059
- detached: true,
4060
- stdio: "ignore"
4098
+ function requestBrowserOpen(url) {
4099
+ const command = process.platform === "darwin" ? {
4100
+ cmd: "open",
4101
+ args: [url]
4102
+ } : process.platform === "win32" ? {
4103
+ cmd: "cmd",
4104
+ args: [
4105
+ "/c",
4106
+ "start",
4107
+ "",
4108
+ url
4109
+ ]
4110
+ } : {
4111
+ cmd: "xdg-open",
4112
+ args: [url]
4113
+ };
4114
+ return new Promise((resolve, reject) => {
4115
+ const child = spawn(command.cmd, command.args, { stdio: "ignore" });
4116
+ let settled = false;
4117
+ const finish = (error) => {
4118
+ if (settled) return;
4119
+ settled = true;
4120
+ if (error) reject(error);
4121
+ else resolve();
4122
+ };
4123
+ child.once("error", (error) => finish(error));
4124
+ child.once("exit", (code) => {
4125
+ if (code === 0) finish();
4126
+ else finish(/* @__PURE__ */ new Error(`浏览器启动命令退出,code=${String(code)}`));
4127
+ });
4128
+ child.unref();
4129
+ setTimeout(() => finish(), 1500).unref();
4061
4130
  });
4062
4131
  }
4132
+ function startManualCallbackReader(expectation) {
4133
+ let settled = false;
4134
+ let resolveCallback;
4135
+ const callbackPromise = new Promise((resolve) => {
4136
+ resolveCallback = resolve;
4137
+ });
4138
+ const rl = readline.createInterface({
4139
+ input: process.stdin,
4140
+ output: process.stdout,
4141
+ terminal: Boolean(process.stdin.isTTY)
4142
+ });
4143
+ const writable = rl;
4144
+ let maskedLength = 0;
4145
+ let maskTruncated = false;
4146
+ if (process.stdin.isTTY) writable._writeToOutput = (chunk) => {
4147
+ const visible = chunk.replace(/\u001b\[[0-9;]*[A-Za-z]/g, "").replace(/[\u0000-\u001f\u007f]/g, "");
4148
+ if (!visible) return;
4149
+ const remaining = Math.max(0, 12 - maskedLength);
4150
+ const nextCount = Math.min(remaining, visible.length);
4151
+ if (nextCount > 0) {
4152
+ process.stdout.write("*".repeat(nextCount));
4153
+ maskedLength += nextCount;
4154
+ }
4155
+ if (visible.length > nextCount && !maskTruncated) {
4156
+ process.stdout.write("…");
4157
+ maskTruncated = true;
4158
+ }
4159
+ };
4160
+ const resetMask = () => {
4161
+ maskedLength = 0;
4162
+ maskTruncated = false;
4163
+ };
4164
+ const showPrompt = () => {
4165
+ process.stdout.write(`${TEXT$3}请粘贴浏览器地址栏中的完整 localhost 回调地址(仅显示掩码):${RESET$4}\n> `);
4166
+ };
4167
+ rl.on("line", (line) => {
4168
+ if (settled || !line.trim()) {
4169
+ if (!settled) {
4170
+ process.stdout.write("\n");
4171
+ resetMask();
4172
+ showPrompt();
4173
+ }
4174
+ return;
4175
+ }
4176
+ try {
4177
+ const data = parseLoginCallbackUrl(line, expectation);
4178
+ settled = true;
4179
+ process.stdout.write("\n");
4180
+ resolveCallback(data);
4181
+ } catch (error) {
4182
+ process.stdout.write("\n");
4183
+ const message = error instanceof Error ? error.message : "回调地址无效";
4184
+ console.error(`${YELLOW$3}⚠ ${message}${RESET$4}`);
4185
+ resetMask();
4186
+ showPrompt();
4187
+ }
4188
+ });
4189
+ showPrompt();
4190
+ return {
4191
+ waitForCallback: () => callbackPromise,
4192
+ close: () => {
4193
+ settled = true;
4194
+ rl.close();
4195
+ }
4196
+ };
4197
+ }
4063
4198
  async function revokeGatewaySessionIfPossible(auth) {
4064
4199
  try {
4065
4200
  const revokeUrl = `${auth.gatewayUrl || getGatewayUrl()}/api/gateway/revoke`;
@@ -4090,18 +4225,50 @@ async function login(options) {
4090
4225
  return;
4091
4226
  }
4092
4227
  }
4093
- console.log(`${TEXT$3}正在启动本地服务...${RESET$4}`);
4094
- const { port, waitForCallback, close } = await startLocalServer();
4095
- const callbackUrl = `http://localhost:${port}/callback`;
4228
+ console.log(`${TEXT$3}正在准备登录回调...${RESET$4}`);
4229
+ const state = randomBytes(24).toString("hex");
4230
+ const environment = detectLoginEnvironment({
4231
+ platform: process.platform,
4232
+ env: process.env
4233
+ });
4234
+ const localServer = environment.mode === "browser" ? await startLocalServer(state) : void 0;
4235
+ const callbackPort = localServer?.port ?? randomInt(49152, 65536);
4236
+ const callbackUrl = `http://127.0.0.1:${callbackPort}/callback?state=${state}`;
4096
4237
  const gatewayUrl = getGatewayUrl();
4097
4238
  const loginUrl = `${gatewayUrl}/api/gateway/login?callback_url=${encodeURIComponent(callbackUrl)}&type=cli`;
4098
- console.log(`${TEXT$3}正在打开浏览器进行 BUC 授权...${RESET$4}`);
4099
- console.log(`${DIM$4}如果浏览器没有自动打开,请手动访问:${RESET$4}`);
4100
- console.log(`${DIM$4}${loginUrl}${RESET$4}`);
4101
- openBrowser$2(loginUrl);
4102
- console.log(`${TEXT$3}等待授权完成...${RESET$4}`);
4239
+ const expectation = {
4240
+ port: callbackPort,
4241
+ state
4242
+ };
4243
+ if (environment.mode === "manual") {
4244
+ console.log(`${YELLOW$3}${environment.reason},将使用手动浏览器授权。${RESET$4}`);
4245
+ console.log(`${TEXT$3}请在本机浏览器访问以下地址:${RESET$4}`);
4246
+ console.log();
4247
+ console.log(`${DIM$4} ${loginUrl}${RESET$4}`);
4248
+ console.log();
4249
+ console.log(`${DIM$4}授权后页面可能提示无法访问 localhost;请复制地址栏中的完整地址并粘贴到终端。${RESET$4}`);
4250
+ } else {
4251
+ console.log(`${TEXT$3}正在打开浏览器进行 BUC 授权...${RESET$4}`);
4252
+ console.log(`${DIM$4}如果浏览器没有自动打开,请手动访问:${RESET$4}`);
4253
+ console.log();
4254
+ console.log(`${DIM$4} ${loginUrl}${RESET$4}`);
4255
+ console.log();
4256
+ try {
4257
+ await requestBrowserOpen(loginUrl);
4258
+ } catch {
4259
+ console.log(`${YELLOW$3}未能打开浏览器,请使用上方地址在任意本机浏览器完成授权。${RESET$4}`);
4260
+ }
4261
+ }
4262
+ console.log(`${TEXT$3}${localServer ? "等待自动回调或手动粘贴" : "等待手动粘贴回调地址"}...${RESET$4}`);
4263
+ const manualReader = startManualCallbackReader(expectation);
4103
4264
  try {
4104
- const { empId, account, name, email, privateToken, ssoRefreshToken, expiresAt } = await waitForCallback();
4265
+ const { empId, account, name, email, privateToken, ssoRefreshToken, expiresAt } = await Promise.race([
4266
+ localServer?.waitForCallback(),
4267
+ manualReader.waitForCallback(),
4268
+ new Promise((_, reject) => {
4269
+ setTimeout(() => reject(/* @__PURE__ */ new Error("登录授权等待超时,请重新执行 login")), 900 * 1e3).unref();
4270
+ })
4271
+ ].filter((promise) => promise !== void 0));
4105
4272
  saveAuth({
4106
4273
  empId,
4107
4274
  account,
@@ -4123,8 +4290,10 @@ async function login(options) {
4123
4290
  console.log(`${DIM$4}可尝试:在网页端(https://ali-skills.alibaba-inc.com)退出平台登录后重新执行 login 命令。同时请将此问题反馈给本工具开发者处理,感谢!${RESET$4}`);
4124
4291
  }
4125
4292
  } catch (error) {
4126
- close();
4127
4293
  throw error;
4294
+ } finally {
4295
+ manualReader.close();
4296
+ localServer?.close();
4128
4297
  }
4129
4298
  }
4130
4299
  async function logout(platformInput, yuqueGroup, clearAll) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ali/cli-skills",
3
- "version": "2.1.5",
3
+ "version": "2.1.6",
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.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,7 @@
32
32
  "author": "",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@ali/cli-skills": "^2.1.5"
35
+ "@ali/cli-skills": "^2.1.6"
36
36
  },
37
37
  "bundleDependencies": [
38
38
  "@ali/cli-skills"