@puyinkai/xiaobao-cli 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{api-JZnABA4y.mjs → api-CX99_4K7.mjs} +5 -4
- package/dist/{api-client-DWbwtQbl.mjs → api-client-zDBFgk0y.mjs} +1 -1
- package/dist/{audio-BHWH6uhw.mjs → audio-ShfvRomP.mjs} +2 -2
- package/dist/{auth-BpzWivxf.mjs → auth-DpK81Vp5.mjs} +3 -3
- package/dist/cli.mjs +10 -10
- package/dist/{consultant-kV5r8Wj8.mjs → consultant-CS9B42d3.mjs} +1 -1
- package/dist/{customer-B2OV8BTR.mjs → customer-B1cxRbJ9.mjs} +1 -1
- package/dist/{focus-BjWD5PRV.mjs → focus-DfNPPhm5.mjs} +1 -1
- package/dist/{list-CHububT5.mjs → list-CD08BRLk.mjs} +1 -1
- package/dist/{list-FM3vkQJa.mjs → list-CKlx8YuL.mjs} +1 -1
- package/dist/{list-h1nuSJiN.mjs → list-CnlFIFiY.mjs} +1 -1
- package/dist/{list-CVMSGK08.mjs → list-Cu6WzPpA.mjs} +1 -1
- package/dist/{list-Bw8dFf8a.mjs → list-D56gEqiB.mjs} +1 -1
- package/dist/{list-VpZkzTEE.mjs → list-DCcCS6LM.mjs} +1 -1
- package/dist/{list-C30zFN6h.mjs → list-DI1OD95x.mjs} +1 -1
- package/dist/login-CUUcjWPd.mjs +156 -0
- package/dist/{logout-DVJX8_x6.mjs → logout-DP7REhCd.mjs} +2 -1
- package/dist/{project-CJkTgGOq.mjs → project-BuQQewOs.mjs} +1 -1
- package/dist/{qa-CQDRFw-g.mjs → qa-CKsKiQIm.mjs} +1 -1
- package/dist/{resistance-Ss-eUZoN.mjs → resistance-DsE-Ha6Y.mjs} +1 -1
- package/dist/{text-5q8lIWi8.mjs → text-C3jhdPoe.mjs} +1 -1
- package/dist/{token-store-D2umpIk-.mjs → token-store-CXizLE2_.mjs} +37 -1
- package/dist/{visit-CUI2ov-H.mjs → visit-CX0Mc1n6.mjs} +1 -1
- package/dist/{whoami-Uv7GBtGB.mjs → whoami-CZVez-rm.mjs} +1 -1
- package/package.json +1 -1
- package/skills/wangxiaobao-shared/SKILL.md +35 -2
- package/dist/login-DtDxISE8.mjs +0 -91
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { t as xbApiFetch } from "./api-client-
|
|
2
|
+
import { t as xbApiFetch } from "./api-client-zDBFgk0y.mjs";
|
|
3
3
|
import { t as loadActiveProject } from "./project-store-CKVJa54T.mjs";
|
|
4
4
|
import { t as tenantProjectHeaders } from "./headers-DSnR4sjk.mjs";
|
|
5
5
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
@@ -60,9 +60,10 @@ var api_default = defineCommand({
|
|
|
60
60
|
type: "string",
|
|
61
61
|
description: "自定义 headers,`K1=V1&K2=V2` 或 `K=V,K=V`。与默认注入的 tenant/project headers 合并(同名 key 用户传入优先)"
|
|
62
62
|
},
|
|
63
|
-
"
|
|
63
|
+
"default-headers": {
|
|
64
64
|
type: "boolean",
|
|
65
|
-
|
|
65
|
+
default: true,
|
|
66
|
+
description: "默认按当前激活项目自动注入 X-Tenant-Id / X-Project-Id headers(调 /ai-open/* 开箱即用);`--no-default-headers` 关闭注入"
|
|
66
67
|
},
|
|
67
68
|
"api-base": { type: "string" },
|
|
68
69
|
"auth-base": { type: "string" },
|
|
@@ -93,7 +94,7 @@ var api_default = defineCommand({
|
|
|
93
94
|
}
|
|
94
95
|
let mergedHeaders;
|
|
95
96
|
const userHeaders = parseKV(args.headers);
|
|
96
|
-
if (
|
|
97
|
+
if (args["default-headers"]) {
|
|
97
98
|
const active = await loadActiveProject();
|
|
98
99
|
if (active) mergedHeaders = {
|
|
99
100
|
...tenantProjectHeaders(active),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as refreshToken } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as isAccessTokenValid, l as writeToken, s as readToken } from "./token-store-CXizLE2_.mjs";
|
|
3
3
|
//#region src/core/api-client.ts
|
|
4
4
|
/**
|
|
5
5
|
* HTTP client for wangxiaobao open API endpoints.
|
|
@@ -6,8 +6,8 @@ var audio_default = defineCommand({
|
|
|
6
6
|
description: "录音元数据分页 / 单条文本查询"
|
|
7
7
|
},
|
|
8
8
|
subCommands: {
|
|
9
|
-
list: () => import("./list-
|
|
10
|
-
text: () => import("./text-
|
|
9
|
+
list: () => import("./list-DCcCS6LM.mjs").then((m) => m.default),
|
|
10
|
+
text: () => import("./text-C3jhdPoe.mjs").then((m) => m.default)
|
|
11
11
|
}
|
|
12
12
|
});
|
|
13
13
|
//#endregion
|
|
@@ -6,9 +6,9 @@ var auth_default = defineCommand({
|
|
|
6
6
|
description: "OAuth 登录 / 身份查询 / 登出(device flow)"
|
|
7
7
|
},
|
|
8
8
|
subCommands: {
|
|
9
|
-
login: () => import("./login-
|
|
10
|
-
whoami: () => import("./whoami-
|
|
11
|
-
logout: () => import("./logout-
|
|
9
|
+
login: () => import("./login-CUUcjWPd.mjs").then((m) => m.default),
|
|
10
|
+
whoami: () => import("./whoami-CZVez-rm.mjs").then((m) => m.default),
|
|
11
|
+
logout: () => import("./logout-DP7REhCd.mjs").then((m) => m.default)
|
|
12
12
|
}
|
|
13
13
|
});
|
|
14
14
|
//#endregion
|
package/dist/cli.mjs
CHANGED
|
@@ -18,16 +18,16 @@ runMain(defineCommand({
|
|
|
18
18
|
description: "旺小宝 CLI — host-agnostic 14 tools (openclaw-xiaobao plugin 的 CLI 等价物)"
|
|
19
19
|
},
|
|
20
20
|
subCommands: {
|
|
21
|
-
auth: () => import("./auth-
|
|
22
|
-
project: () => import("./project-
|
|
23
|
-
consultant: () => import("./consultant-
|
|
24
|
-
audio: () => import("./audio-
|
|
25
|
-
customer: () => import("./customer-
|
|
26
|
-
visit: () => import("./visit-
|
|
27
|
-
focus: () => import("./focus-
|
|
28
|
-
resistance: () => import("./resistance-
|
|
29
|
-
qa: () => import("./qa-
|
|
30
|
-
api: () => import("./api-
|
|
21
|
+
auth: () => import("./auth-DpK81Vp5.mjs").then((m) => m.default),
|
|
22
|
+
project: () => import("./project-BuQQewOs.mjs").then((m) => m.default),
|
|
23
|
+
consultant: () => import("./consultant-CS9B42d3.mjs").then((m) => m.default),
|
|
24
|
+
audio: () => import("./audio-ShfvRomP.mjs").then((m) => m.default),
|
|
25
|
+
customer: () => import("./customer-B1cxRbJ9.mjs").then((m) => m.default),
|
|
26
|
+
visit: () => import("./visit-CX0Mc1n6.mjs").then((m) => m.default),
|
|
27
|
+
focus: () => import("./focus-DfNPPhm5.mjs").then((m) => m.default),
|
|
28
|
+
resistance: () => import("./resistance-DsE-Ha6Y.mjs").then((m) => m.default),
|
|
29
|
+
qa: () => import("./qa-CKsKiQIm.mjs").then((m) => m.default),
|
|
30
|
+
api: () => import("./api-CX99_4K7.mjs").then((m) => m.default)
|
|
31
31
|
}
|
|
32
32
|
}));
|
|
33
33
|
//#endregion
|
|
@@ -5,7 +5,7 @@ var consultant_default = defineCommand({
|
|
|
5
5
|
name: "consultant",
|
|
6
6
|
description: "查询有权限的置业顾问列表"
|
|
7
7
|
},
|
|
8
|
-
subCommands: { list: () => import("./list-
|
|
8
|
+
subCommands: { list: () => import("./list-Cu6WzPpA.mjs").then((m) => m.default) }
|
|
9
9
|
});
|
|
10
10
|
//#endregion
|
|
11
11
|
export { consultant_default as default };
|
|
@@ -5,7 +5,7 @@ var customer_default = defineCommand({
|
|
|
5
5
|
name: "customer",
|
|
6
6
|
description: "客户画像分页查询"
|
|
7
7
|
},
|
|
8
|
-
subCommands: { list: () => import("./list-
|
|
8
|
+
subCommands: { list: () => import("./list-CnlFIFiY.mjs").then((m) => m.default) }
|
|
9
9
|
});
|
|
10
10
|
//#endregion
|
|
11
11
|
export { customer_default as default };
|
|
@@ -5,7 +5,7 @@ var focus_default = defineCommand({
|
|
|
5
5
|
name: "focus",
|
|
6
6
|
description: "客户关注点标签分页查询"
|
|
7
7
|
},
|
|
8
|
-
subCommands: { list: () => import("./list-
|
|
8
|
+
subCommands: { list: () => import("./list-D56gEqiB.mjs").then((m) => m.default) }
|
|
9
9
|
});
|
|
10
10
|
//#endregion
|
|
11
11
|
export { focus_default as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { t as xbApiFetch } from "./api-client-
|
|
2
|
+
import { t as xbApiFetch } from "./api-client-zDBFgk0y.mjs";
|
|
3
3
|
import { n as requireActiveProject } from "./project-store-CKVJa54T.mjs";
|
|
4
4
|
import { t as tenantProjectHeaders } from "./headers-DSnR4sjk.mjs";
|
|
5
5
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { t as xbApiFetch } from "./api-client-
|
|
2
|
+
import { t as xbApiFetch } from "./api-client-zDBFgk0y.mjs";
|
|
3
3
|
import { n as requireActiveProject } from "./project-store-CKVJa54T.mjs";
|
|
4
4
|
import { t as tenantProjectHeaders } from "./headers-DSnR4sjk.mjs";
|
|
5
5
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { t as xbApiFetch } from "./api-client-
|
|
2
|
+
import { t as xbApiFetch } from "./api-client-zDBFgk0y.mjs";
|
|
3
3
|
import { n as requireActiveProject } from "./project-store-CKVJa54T.mjs";
|
|
4
4
|
import { t as tenantProjectHeaders } from "./headers-DSnR4sjk.mjs";
|
|
5
5
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { t as xbApiFetch } from "./api-client-
|
|
2
|
+
import { t as xbApiFetch } from "./api-client-zDBFgk0y.mjs";
|
|
3
3
|
import { n as requireActiveProject } from "./project-store-CKVJa54T.mjs";
|
|
4
4
|
import { t as tenantProjectHeaders } from "./headers-DSnR4sjk.mjs";
|
|
5
5
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { t as xbApiFetch } from "./api-client-
|
|
2
|
+
import { t as xbApiFetch } from "./api-client-zDBFgk0y.mjs";
|
|
3
3
|
import { n as requireActiveProject } from "./project-store-CKVJa54T.mjs";
|
|
4
4
|
import { t as tenantProjectHeaders } from "./headers-DSnR4sjk.mjs";
|
|
5
5
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { t as xbApiFetch } from "./api-client-
|
|
2
|
+
import { t as xbApiFetch } from "./api-client-zDBFgk0y.mjs";
|
|
3
3
|
import { n as requireActiveProject } from "./project-store-CKVJa54T.mjs";
|
|
4
4
|
import { t as tenantProjectHeaders } from "./headers-DSnR4sjk.mjs";
|
|
5
5
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { t as xbApiFetch } from "./api-client-
|
|
2
|
+
import { t as xbApiFetch } from "./api-client-zDBFgk0y.mjs";
|
|
3
3
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
//#region src/commands/project/list.ts
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { a as resolveConfig, n as pollForToken, r as refreshToken, t as initiateDeviceAuthorization } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
+
import { a as isAccessTokenValid, c as writePendingAuth, l as writeToken, n as clearPendingAuth, o as readPendingAuth, r as clearToken, s as readToken, t as NoPendingAuthError } from "./token-store-CXizLE2_.mjs";
|
|
3
|
+
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
//#region src/commands/auth/login.ts
|
|
6
|
+
/**
|
|
7
|
+
* `xiaobao-cli auth login` — OAuth 2.0 Device Authorization Grant.
|
|
8
|
+
*
|
|
9
|
+
* Three modes:
|
|
10
|
+
*
|
|
11
|
+
* 1. Default (blocking) — cache hit / refresh / else initiate the device
|
|
12
|
+
* flow and synchronously poll until success/denial/expiry. Best for a
|
|
13
|
+
* human running it directly in a terminal.
|
|
14
|
+
*
|
|
15
|
+
* 2. `--no-wait` (non-blocking, agent-friendly split-flow step 1) — cache
|
|
16
|
+
* hit / refresh as usual; otherwise initiate the device flow, persist
|
|
17
|
+
* the device_code locally (~/.xiaobao/pending-auth.json, mode 0600) and
|
|
18
|
+
* return immediately with the verification URL — WITHOUT polling and
|
|
19
|
+
* WITHOUT exposing the credential-grade device_code on stdout. The agent
|
|
20
|
+
* forwards the URL to the user, ends its turn, then runs step 2.
|
|
21
|
+
*
|
|
22
|
+
* 3. `--resume` (split-flow step 2) — read the locally-saved device_code
|
|
23
|
+
* and exchange it for a token; no argument needed. `--device-code <code>`
|
|
24
|
+
* remains as an explicit escape hatch and takes precedence when given.
|
|
25
|
+
*/
|
|
26
|
+
var login_default = defineCommand({
|
|
27
|
+
meta: {
|
|
28
|
+
name: "login",
|
|
29
|
+
description: "走 OAuth device flow 登录(--no-wait 非阻塞发起 / --resume 完成)"
|
|
30
|
+
},
|
|
31
|
+
args: {
|
|
32
|
+
force: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "强制清除缓存并重新发起设备授权流程"
|
|
35
|
+
},
|
|
36
|
+
wait: {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
default: true,
|
|
39
|
+
description: "默认阻塞轮询直到授权完成。`--no-wait` 则发起 device flow 后立即返回 verification_uri 不轮询(device_code 安全存本地,不进 stdout);用户授权后 再用 `auth login --resume` 完成换 token。"
|
|
40
|
+
},
|
|
41
|
+
resume: {
|
|
42
|
+
type: "boolean",
|
|
43
|
+
description: "split-flow 第二步:用上次 `--no-wait` 本地保存的 device_code 换 token,用户在浏览器授权后跑,无需手动传 device_code。"
|
|
44
|
+
},
|
|
45
|
+
"device-code": {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "(高级 escape hatch)显式传入 device_code 换 token;一般用 `--resume` 即可,CLI 会自动读取本地保存的 device_code。"
|
|
48
|
+
},
|
|
49
|
+
"api-base": {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "覆盖 API base URL"
|
|
52
|
+
},
|
|
53
|
+
"auth-base": {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "覆盖 auth base URL"
|
|
56
|
+
},
|
|
57
|
+
format: {
|
|
58
|
+
type: "string",
|
|
59
|
+
default: "json"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
async run({ args }) {
|
|
63
|
+
const config = resolveConfig({
|
|
64
|
+
apiBase: args["api-base"],
|
|
65
|
+
authBase: args["auth-base"]
|
|
66
|
+
});
|
|
67
|
+
const fmt = args.format;
|
|
68
|
+
try {
|
|
69
|
+
if (args.resume || args["device-code"]) {
|
|
70
|
+
let deviceCode;
|
|
71
|
+
let remainingSeconds = 300;
|
|
72
|
+
if (args["device-code"]) deviceCode = String(args["device-code"]);
|
|
73
|
+
else {
|
|
74
|
+
const pending = await readPendingAuth();
|
|
75
|
+
if (!pending) throw new NoPendingAuthError();
|
|
76
|
+
if (pending.expires_at <= Date.now()) {
|
|
77
|
+
await clearPendingAuth();
|
|
78
|
+
throw new NoPendingAuthError("设备授权已过期,device_code 失效");
|
|
79
|
+
}
|
|
80
|
+
deviceCode = pending.device_code;
|
|
81
|
+
remainingSeconds = Math.max(1, Math.ceil((pending.expires_at - Date.now()) / 1e3));
|
|
82
|
+
}
|
|
83
|
+
const saved = await writeToken(await pollForToken(config, deviceCode, 2, remainingSeconds));
|
|
84
|
+
await clearPendingAuth();
|
|
85
|
+
writeResult({
|
|
86
|
+
source: "device-flow",
|
|
87
|
+
expires_at: saved.expires_at,
|
|
88
|
+
scope: saved.scope,
|
|
89
|
+
message: "登录成功,可以正常使用别的命令了"
|
|
90
|
+
}, fmt);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!args.force) {
|
|
94
|
+
const cached = await readToken();
|
|
95
|
+
if (isAccessTokenValid(cached)) {
|
|
96
|
+
writeResult({
|
|
97
|
+
source: "cache",
|
|
98
|
+
expires_at: cached.expires_at,
|
|
99
|
+
scope: cached.scope
|
|
100
|
+
}, fmt);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (cached?.refresh_token) try {
|
|
104
|
+
const saved = await writeToken(await refreshToken(config, cached.refresh_token));
|
|
105
|
+
writeResult({
|
|
106
|
+
source: "refresh",
|
|
107
|
+
expires_at: saved.expires_at,
|
|
108
|
+
scope: saved.scope
|
|
109
|
+
}, fmt);
|
|
110
|
+
return;
|
|
111
|
+
} catch {
|
|
112
|
+
await clearToken();
|
|
113
|
+
}
|
|
114
|
+
} else await clearToken();
|
|
115
|
+
const init = await initiateDeviceAuthorization(config);
|
|
116
|
+
const verificationUri = init.verification_uri_complete || init.verification_uri;
|
|
117
|
+
const expiresMinutes = Math.max(1, Math.round(init.expires_in / 60));
|
|
118
|
+
const interval = init.interval ?? 5;
|
|
119
|
+
if (!args.wait) {
|
|
120
|
+
await writePendingAuth({
|
|
121
|
+
device_code: init.device_code,
|
|
122
|
+
user_code: init.user_code,
|
|
123
|
+
verification_uri: verificationUri,
|
|
124
|
+
interval,
|
|
125
|
+
expires_at: Date.now() + init.expires_in * 1e3
|
|
126
|
+
});
|
|
127
|
+
writeResult({
|
|
128
|
+
awaiting_authorization: true,
|
|
129
|
+
verification_uri: verificationUri,
|
|
130
|
+
user_code: init.user_code,
|
|
131
|
+
expires_in: init.expires_in,
|
|
132
|
+
interval,
|
|
133
|
+
message: "请在浏览器打开 verification_uri 完成授权,然后跑 `xiaobao-cli auth login --resume` 完成登录。device_code 已安全保存在本地,无需手动传。"
|
|
134
|
+
}, fmt);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
process.stderr.write(`
|
|
138
|
+
请在浏览器中打开以下链接完成登录并授权访问你的旺小宝账号:
|
|
139
|
+
\n 🔗 ${verificationUri}\n\n 设备验证码: ${init.user_code}\n 有效期约 ${expiresMinutes} 分钟\n\n等待你在浏览器中确认 ...
|
|
140
|
+
`);
|
|
141
|
+
const saved = await writeToken(await pollForToken(config, init.device_code, interval, init.expires_in));
|
|
142
|
+
await clearPendingAuth();
|
|
143
|
+
writeResult({
|
|
144
|
+
source: "device-flow",
|
|
145
|
+
expires_at: saved.expires_at,
|
|
146
|
+
scope: saved.scope,
|
|
147
|
+
message: "登录成功,token 已缓存到 ~/.xiaobao/token.json"
|
|
148
|
+
}, fmt);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
writeError(err, fmt);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
//#endregion
|
|
156
|
+
export { login_default as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig, i as revokeRefreshToken } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { n as clearPendingAuth, r as clearToken, s as readToken } from "./token-store-CXizLE2_.mjs";
|
|
3
3
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
//#region src/commands/auth/logout.ts
|
|
@@ -33,6 +33,7 @@ var logout_default = defineCommand({
|
|
|
33
33
|
let revoked = false;
|
|
34
34
|
if (cached?.refresh_token) revoked = await revokeRefreshToken(config, cached.refresh_token);
|
|
35
35
|
await clearToken();
|
|
36
|
+
await clearPendingAuth();
|
|
36
37
|
writeResult({
|
|
37
38
|
message: "Logged out",
|
|
38
39
|
remote_revoked: revoked,
|
|
@@ -6,7 +6,7 @@ var project_default = defineCommand({
|
|
|
6
6
|
description: "查询 / 切换激活项目"
|
|
7
7
|
},
|
|
8
8
|
subCommands: {
|
|
9
|
-
list: () => import("./list-
|
|
9
|
+
list: () => import("./list-DI1OD95x.mjs").then((m) => m.default),
|
|
10
10
|
use: () => import("./use-BZES4FZC.mjs").then((m) => m.default),
|
|
11
11
|
current: () => import("./current-CfDExInT.mjs").then((m) => m.default)
|
|
12
12
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { t as xbApiFetch } from "./api-client-
|
|
2
|
+
import { t as xbApiFetch } from "./api-client-zDBFgk0y.mjs";
|
|
3
3
|
import { n as requireActiveProject } from "./project-store-CKVJa54T.mjs";
|
|
4
4
|
import { t as tenantProjectHeaders } from "./headers-DSnR4sjk.mjs";
|
|
5
5
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
@@ -5,7 +5,7 @@ var resistance_default = defineCommand({
|
|
|
5
5
|
name: "resistance",
|
|
6
6
|
description: "客户抗性点标签分页查询"
|
|
7
7
|
},
|
|
8
|
-
subCommands: { list: () => import("./list-
|
|
8
|
+
subCommands: { list: () => import("./list-CKlx8YuL.mjs").then((m) => m.default) }
|
|
9
9
|
});
|
|
10
10
|
//#endregion
|
|
11
11
|
export { resistance_default as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as resolveConfig } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { t as xbApiFetch } from "./api-client-
|
|
2
|
+
import { t as xbApiFetch } from "./api-client-zDBFgk0y.mjs";
|
|
3
3
|
import { n as requireActiveProject } from "./project-store-CKVJa54T.mjs";
|
|
4
4
|
import { t as tenantProjectHeaders } from "./headers-DSnR4sjk.mjs";
|
|
5
5
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
@@ -19,6 +19,7 @@ import { chmod, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
|
19
19
|
const PRIMARY_DIR = join(homedir(), ".xiaobao");
|
|
20
20
|
const PRIMARY_TOKEN_FILE = join(PRIMARY_DIR, "token.json");
|
|
21
21
|
const LEGACY_TOKEN_FILE = join(homedir(), ".openclaw", "state", "wangxiaobao", "token.json");
|
|
22
|
+
const PENDING_AUTH_FILE = join(PRIMARY_DIR, "pending-auth.json");
|
|
22
23
|
/**
|
|
23
24
|
* Read order: primary → legacy fallback. Returns null if both missing.
|
|
24
25
|
*/
|
|
@@ -85,5 +86,40 @@ function decodeJwtPayload(jwt) {
|
|
|
85
86
|
return null;
|
|
86
87
|
}
|
|
87
88
|
}
|
|
89
|
+
async function writePendingAuth(p) {
|
|
90
|
+
await mkdir(PRIMARY_DIR, {
|
|
91
|
+
recursive: true,
|
|
92
|
+
mode: 448
|
|
93
|
+
});
|
|
94
|
+
await writeFile(PENDING_AUTH_FILE, JSON.stringify(p, null, 2), { mode: 384 });
|
|
95
|
+
try {
|
|
96
|
+
await chmod(PENDING_AUTH_FILE, 384);
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
async function readPendingAuth() {
|
|
100
|
+
if (!existsSync(PENDING_AUTH_FILE)) return null;
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(await readFile(PENDING_AUTH_FILE, "utf-8"));
|
|
103
|
+
} catch (e) {
|
|
104
|
+
if (e.code === "ENOENT") return null;
|
|
105
|
+
throw e;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function clearPendingAuth() {
|
|
109
|
+
try {
|
|
110
|
+
await unlink(PENDING_AUTH_FILE);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
if (e.code !== "ENOENT") throw e;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** Thrown by `auth login --resume` when there is no usable pending device_code. */
|
|
116
|
+
var NoPendingAuthError = class extends Error {
|
|
117
|
+
code = "NO_PENDING_AUTH";
|
|
118
|
+
hint = "先跑 `xiaobao-cli auth login --no-wait` 发起设备授权,在浏览器完成授权后再跑 `xiaobao-cli auth login --resume`。";
|
|
119
|
+
constructor(message = "没有待完成的设备授权(从未发起,或 device_code 已过期)") {
|
|
120
|
+
super(message);
|
|
121
|
+
this.name = "NoPendingAuthError";
|
|
122
|
+
}
|
|
123
|
+
};
|
|
88
124
|
//#endregion
|
|
89
|
-
export {
|
|
125
|
+
export { isAccessTokenValid as a, writePendingAuth as c, decodeJwtPayload as i, writeToken as l, clearPendingAuth as n, readPendingAuth as o, clearToken as r, readToken as s, NoPendingAuthError as t };
|
|
@@ -5,7 +5,7 @@ var visit_default = defineCommand({
|
|
|
5
5
|
name: "visit",
|
|
6
6
|
description: "客户来访分页查询"
|
|
7
7
|
},
|
|
8
|
-
subCommands: { list: () => import("./list-
|
|
8
|
+
subCommands: { list: () => import("./list-CD08BRLk.mjs").then((m) => m.default) }
|
|
9
9
|
});
|
|
10
10
|
//#endregion
|
|
11
11
|
export { visit_default as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as
|
|
1
|
+
import { i as decodeJwtPayload, s as readToken } from "./token-store-CXizLE2_.mjs";
|
|
2
2
|
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/commands/auth/whoami.ts
|
package/package.json
CHANGED
|
@@ -30,8 +30,8 @@ npx -y @puyinkai/xiaobao-cli <subcommand>...
|
|
|
30
30
|
任何 xiaobao-cli 命令的**前置 3 步**:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
# 1. 登录(OAuth device flow,
|
|
34
|
-
xiaobao-cli auth login
|
|
33
|
+
# 1. 登录(OAuth device flow 非阻塞 split-flow,token 自动缓存)—— 见下方
|
|
34
|
+
xiaobao-cli auth login --no-wait
|
|
35
35
|
|
|
36
36
|
# 2. 选激活项目(多租户 / 多项目场景必做;旺小宝是多租户业务)
|
|
37
37
|
xiaobao-cli project list # 列可选
|
|
@@ -44,6 +44,34 @@ xiaobao-cli customer list --portrait 高意向
|
|
|
44
44
|
# ...
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### 登录 —— 用 `--no-wait` split-flow(两步、不阻塞)
|
|
48
|
+
|
|
49
|
+
登录走 OAuth device flow,固定用下面的两步、全程不阻塞。**不要跑裸
|
|
50
|
+
`xiaobao-cli auth login`** —— 它会阻塞轮询、卡满一整轮对话。
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 步骤 A:发起,立即返回(约 0.5s),拿到 verification_uri
|
|
54
|
+
xiaobao-cli auth login --no-wait
|
|
55
|
+
# stdout: { "awaiting_authorization": true, "verification_uri": "...",
|
|
56
|
+
# "user_code": "...", "expires_in": 300, "interval": 5, ... }
|
|
57
|
+
# device_code 不在 stdout —— 已安全存到 ~/.xiaobao/pending-auth.json(0600)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
agent 拿到后:把 `verification_uri` **原样**发给用户(放进只含 URL 的代码块,
|
|
61
|
+
不要改写不要做 URL 编码),让用户浏览器打开授权,然后**结束本轮**,把控制权
|
|
62
|
+
交还用户。
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 步骤 B:用户回复"授权完成"后,直接换 token(秒级)——
|
|
66
|
+
# device_code 已由步骤 A 存在本地,--resume 自动读取,agent 无需持有 / 传递它
|
|
67
|
+
xiaobao-cli auth login --resume
|
|
68
|
+
# stdout: { "source": "device-flow", "expires_at": ..., "scope": "..." }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
device_code 有效期 `expires_in` 秒(约 5 分钟)。超时就重跑步骤 A。
|
|
72
|
+
若 token 仍有效 / refresh_token 可用,`--no-wait` 会直接走 cache/refresh
|
|
73
|
+
快速返回(`source: cache` / `refresh`),不发起新的 device flow,也无需步骤 B。
|
|
74
|
+
|
|
47
75
|
查身份 / 当前激活项目(任何时候都可以):
|
|
48
76
|
|
|
49
77
|
```bash
|
|
@@ -106,6 +134,11 @@ Agent 消费时**只 parse stdout JSON**,stderr 是辅助。
|
|
|
106
134
|
| OAuth tokens | access_token + refresh_token + id_token + expires_at | `~/.xiaobao/token.json` | 0600 |
|
|
107
135
|
| Active project | tenantId / tenantName / projectId / projectName / updatedAt | `~/.xiaobao/active-project.json` | 0600 |
|
|
108
136
|
| User config(可选) | 覆盖 authBase / apiBase / scopes | `~/.xiaobao/config.json` | 0600 |
|
|
137
|
+
| Pending auth(临时) | `--no-wait` split-flow 进行中的 device_code / user_code / 过期时间 | `~/.xiaobao/pending-auth.json` | 0600 |
|
|
138
|
+
|
|
139
|
+
`pending-auth.json` 是 split-flow 的临时凭据中转:`--no-wait` 写入、`--resume`
|
|
140
|
+
成功换到 token 后删除,`auth logout` 或 device_code 过期时也会清掉。device_code
|
|
141
|
+
属凭据级数据,**只落本地文件、绝不进 stdout / agent 上下文**。
|
|
109
142
|
|
|
110
143
|
### `~/.openclaw/state/wangxiaobao/` fallback(向后兼容 openclaw-xiaobao plugin 用户)
|
|
111
144
|
|
package/dist/login-DtDxISE8.mjs
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { a as resolveConfig, n as pollForToken, r as refreshToken, t as initiateDeviceAuthorization } from "./device-flow-D6JVcEap.mjs";
|
|
2
|
-
import { a as writeToken, i as readToken, r as isAccessTokenValid, t as clearToken } from "./token-store-D2umpIk-.mjs";
|
|
3
|
-
import { n as writeResult, t as writeError } from "./format-yjmxrbNm.mjs";
|
|
4
|
-
import { defineCommand } from "citty";
|
|
5
|
-
//#region src/commands/auth/login.ts
|
|
6
|
-
/**
|
|
7
|
-
* `xiaobao-cli auth login` — OAuth 2.0 Device Authorization Grant.
|
|
8
|
-
*
|
|
9
|
-
* Behaviour:
|
|
10
|
-
* 1. Cache hit (valid access_token, no --force) → return immediately.
|
|
11
|
-
* 2. Refresh path (cached refresh_token, no --force) → refresh & store.
|
|
12
|
-
* 3. Device flow → print verification URL + user_code to stderr (humans see
|
|
13
|
-
* it in the terminal), and synchronously poll until success/denial/expiry.
|
|
14
|
-
* Polls foreground so the CLI exits with a final structured result on
|
|
15
|
-
* stdout (agents waiting on stdout JSON get a definitive answer).
|
|
16
|
-
*/
|
|
17
|
-
var login_default = defineCommand({
|
|
18
|
-
meta: {
|
|
19
|
-
name: "login",
|
|
20
|
-
description: "走 OAuth device flow 登录"
|
|
21
|
-
},
|
|
22
|
-
args: {
|
|
23
|
-
force: {
|
|
24
|
-
type: "boolean",
|
|
25
|
-
description: "强制清除缓存并重新发起设备授权流程"
|
|
26
|
-
},
|
|
27
|
-
"api-base": {
|
|
28
|
-
type: "string",
|
|
29
|
-
description: "覆盖 API base URL"
|
|
30
|
-
},
|
|
31
|
-
"auth-base": {
|
|
32
|
-
type: "string",
|
|
33
|
-
description: "覆盖 auth base URL"
|
|
34
|
-
},
|
|
35
|
-
format: {
|
|
36
|
-
type: "string",
|
|
37
|
-
default: "json"
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
async run({ args }) {
|
|
41
|
-
const config = resolveConfig({
|
|
42
|
-
apiBase: args["api-base"],
|
|
43
|
-
authBase: args["auth-base"]
|
|
44
|
-
});
|
|
45
|
-
const fmt = args.format;
|
|
46
|
-
try {
|
|
47
|
-
if (!args.force) {
|
|
48
|
-
const cached = await readToken();
|
|
49
|
-
if (isAccessTokenValid(cached)) {
|
|
50
|
-
writeResult({
|
|
51
|
-
source: "cache",
|
|
52
|
-
expires_at: cached.expires_at,
|
|
53
|
-
scope: cached.scope
|
|
54
|
-
}, fmt);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (cached?.refresh_token) try {
|
|
58
|
-
const saved = await writeToken(await refreshToken(config, cached.refresh_token));
|
|
59
|
-
writeResult({
|
|
60
|
-
source: "refresh",
|
|
61
|
-
expires_at: saved.expires_at,
|
|
62
|
-
scope: saved.scope
|
|
63
|
-
}, fmt);
|
|
64
|
-
return;
|
|
65
|
-
} catch {
|
|
66
|
-
await clearToken();
|
|
67
|
-
}
|
|
68
|
-
} else await clearToken();
|
|
69
|
-
const init = await initiateDeviceAuthorization(config);
|
|
70
|
-
const verificationUri = init.verification_uri_complete || init.verification_uri;
|
|
71
|
-
const expiresMinutes = Math.max(1, Math.round(init.expires_in / 60));
|
|
72
|
-
const interval = init.interval ?? 5;
|
|
73
|
-
process.stderr.write(`
|
|
74
|
-
请在浏览器中打开以下链接完成登录并授权访问你的旺小宝账号:
|
|
75
|
-
\n 🔗 ${verificationUri}\n\n 设备验证码: ${init.user_code}\n 有效期约 ${expiresMinutes} 分钟\n\n等待你在浏览器中确认 ...
|
|
76
|
-
`);
|
|
77
|
-
const saved = await writeToken(await pollForToken(config, init.device_code, interval, init.expires_in));
|
|
78
|
-
writeResult({
|
|
79
|
-
source: "device-flow",
|
|
80
|
-
expires_at: saved.expires_at,
|
|
81
|
-
scope: saved.scope,
|
|
82
|
-
message: "登录成功,token 已缓存到 ~/.xiaobao/token.json"
|
|
83
|
-
}, fmt);
|
|
84
|
-
} catch (err) {
|
|
85
|
-
writeError(err, fmt);
|
|
86
|
-
process.exit(1);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
//#endregion
|
|
91
|
-
export { login_default as default };
|