bfi-cli 0.1.110 → 0.1.111

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.
@@ -7,8 +7,8 @@ import { randomUUID } from "node:crypto";
7
7
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
8
8
  import { lstat, mkdir, open, readFile, readdir, realpath, rename, writeFile } from "node:fs/promises";
9
9
  import { homedir } from "node:os";
10
- import { z } from "zod";
11
10
  import { execFile } from "node:child_process";
11
+ import { z } from "zod";
12
12
  const nativeManifestFormats = {
13
13
  "claude-code": ["CLAUDE"],
14
14
  codex: [
package/dist/cli.mjs CHANGED
@@ -30,6 +30,7 @@ import { promisify } from "util";
30
30
  import { execFile, spawn, spawnSync } from "child_process";
31
31
  import { access as access$1, chmod, cp as cp$1, lstat as lstat$1, mkdir as mkdir$1, mkdtemp, readFile as readFile$1, readdir as readdir$1, readlink, realpath as realpath$1, rename as rename$1, rm as rm$1, stat, symlink, writeFile as writeFile$1 } from "fs/promises";
32
32
  import { determineAgent } from "@vercel/detect-agent";
33
+ import { spawn as spawn$1 } from "node:child_process";
33
34
  import { applyEdits, getNodeValue, modify, parseTree } from "jsonc-parser";
34
35
  import { parse as parse$1, stringify } from "smol-toml";
35
36
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
@@ -5899,9 +5900,68 @@ function fail(message) {
5899
5900
  process.exit(1);
5900
5901
  }
5901
5902
  var UseCommandError = class extends Error {};
5903
+ function openBrowser(url) {
5904
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
5905
+ const args = process.platform === "win32" ? [
5906
+ "/c",
5907
+ "start",
5908
+ "",
5909
+ url
5910
+ ] : [url];
5911
+ try {
5912
+ const child = spawn$1(cmd, args, {
5913
+ detached: true,
5914
+ stdio: "ignore"
5915
+ });
5916
+ child.on("error", () => {});
5917
+ child.unref();
5918
+ } catch {}
5919
+ }
5920
+ var DeviceFlowUnavailableError = class extends Error {};
5921
+ var DeviceFlowDeniedError = class extends Error {};
5922
+ function dataOf(body) {
5923
+ if (body && typeof body === "object") {
5924
+ const data = body.data;
5925
+ if (data && typeof data === "object") return data;
5926
+ }
5927
+ return {};
5928
+ }
5929
+ async function requestDeviceCode(client) {
5930
+ const resp = await client.request("POST", "/auth/device/code");
5931
+ const data = dataOf(resp.body);
5932
+ const deviceCode = String(data.deviceCode || "");
5933
+ const userCode = String(data.userCode || "");
5934
+ const verificationUri = String(data.verificationUri || "");
5935
+ if (resp.status !== 200 || !deviceCode || !userCode || !verificationUri) throw new DeviceFlowUnavailableError(`设备授权接口不可用(HTTP ${resp.status})`);
5936
+ return {
5937
+ deviceCode,
5938
+ userCode,
5939
+ verificationUri,
5940
+ expiresIn: Number(data.expiresIn) > 0 ? Number(data.expiresIn) : 900,
5941
+ interval: Number(data.interval) > 0 ? Number(data.interval) : 5
5942
+ };
5943
+ }
5944
+ async function pollDeviceToken(client, deviceCode, opts) {
5945
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
5946
+ const now = opts.now ?? (() => Date.now());
5947
+ const deadline = now() + opts.expiresInSec * 1e3;
5948
+ while (now() < deadline) {
5949
+ await sleep(opts.intervalSec * 1e3);
5950
+ const resp = await client.request("POST", "/auth/device/token", { body: { deviceCode } });
5951
+ const data = dataOf(resp.body);
5952
+ if (resp.status === 200 && data.accessToken) return String(data.accessToken);
5953
+ if (resp.status >= 500) throw new DeviceFlowUnavailableError(`设备授权轮询失败(HTTP ${resp.status})`);
5954
+ const error = String(data.error || "");
5955
+ if (error === "authorization_pending") continue;
5956
+ if (error) throw new DeviceFlowDeniedError(`设备授权失败:${error}`);
5957
+ }
5958
+ throw new DeviceFlowDeniedError("设备授权超时,请重试");
5959
+ }
5902
5960
  async function runLogin(args, globalOpts) {
5903
- const token = getOptionValue$3(args, "--token") || await promptForToken();
5904
5961
  const { registry } = resolveConfig(globalOpts);
5962
+ const explicitToken = getOptionValue$3(args, "--token");
5963
+ const noDevice = args.includes("--no-device");
5964
+ const token = explicitToken || (noDevice ? await promptForToken() : await loginViaDevice(registry));
5905
5965
  const api = new SkillHubAPI(registry, token);
5906
5966
  try {
5907
5967
  const response = await api.whoami();
@@ -5922,6 +5982,42 @@ async function runLogin(args, globalOpts) {
5922
5982
  process.exit(1);
5923
5983
  }
5924
5984
  }
5985
+ async function loginViaDevice(registry) {
5986
+ const tokenPage = `${registry.replace(/\/$/, "")}/dashboard/tokens`;
5987
+ const client = new SkillHubAPI(registry, null);
5988
+ let deviceCode;
5989
+ try {
5990
+ deviceCode = await requestDeviceCode(client);
5991
+ } catch (error) {
5992
+ return fallbackToTokenInput(error, tokenPage);
5993
+ }
5994
+ console.error("\n请在浏览器打开以下地址完成授权:");
5995
+ console.error(` ${deviceCode.verificationUri}`);
5996
+ console.error(`并输入设备代码:${deviceCode.userCode}(${Math.ceil(deviceCode.expiresIn / 60)} 分钟内有效)`);
5997
+ openBrowser(deviceCode.verificationUri);
5998
+ console.error("等待授权中…(Ctrl+C 取消)");
5999
+ try {
6000
+ const token = await pollDeviceToken(client, deviceCode.deviceCode, {
6001
+ intervalSec: deviceCode.interval,
6002
+ expiresInSec: deviceCode.expiresIn
6003
+ });
6004
+ console.error("✓ 设备授权成功");
6005
+ return token;
6006
+ } catch (error) {
6007
+ if (error instanceof DeviceFlowUnavailableError) return fallbackToTokenInput(error, tokenPage);
6008
+ if (error instanceof DeviceFlowDeniedError) {
6009
+ console.error(`登录失败:${error.message}`);
6010
+ process.exit(1);
6011
+ }
6012
+ throw error;
6013
+ }
6014
+ }
6015
+ async function fallbackToTokenInput(error, tokenPage) {
6016
+ console.error(`⚠️ ${error?.message || "设备授权不可用"},改用网页 Token。`);
6017
+ console.error(`Token 页面:${tokenPage}`);
6018
+ openBrowser(tokenPage);
6019
+ return promptForToken();
6020
+ }
5925
6021
  async function runLogout() {
5926
6022
  const config = loadGlobalConfig();
5927
6023
  if (config?.token) {
@@ -5934,7 +6030,7 @@ async function runLogout() {
5934
6030
  async function runWhoami(globalOpts) {
5935
6031
  const { registry, token } = resolveConfig(globalOpts);
5936
6032
  if (!token) {
5937
- console.error("未认证。运行:bfi auth login(在隐藏提示中输入 Token)");
6033
+ console.error("未认证。运行:bfi auth login(浏览器设备授权;或 --token 直接指定)");
5938
6034
  process.exit(1);
5939
6035
  }
5940
6036
  const response = await new SkillHubAPI(registry, token).whoami();
@@ -10710,7 +10806,7 @@ ${BOLD}bfi — 用友大业财BFI统一命令行工具${RESET}
10710
10806
  ${BOLD}Usage:${RESET} bfi <command> [options]
10711
10807
 
10712
10808
  ${BOLD}━━━ 认证 ━━━${RESET}
10713
- auth login [--token <token>] 登录 SkillHub(不带参数时隐藏输入 Token)
10809
+ auth login [--token <token>] 登录 SkillHub(默认设备授权:浏览器打开并输入代码;--no-device 走隐藏输入)
10714
10810
  auth logout 登出
10715
10811
  auth whoami 查看当前用户
10716
10812
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bfi-cli",
3
- "version": "0.1.110",
3
+ "version": "0.1.111",
4
4
  "description": "用友大业财BFI统一命令行工具,支持技能,技能包,元数据,UI元数据等",
5
5
  "type": "module",
6
6
  "bin": {