@puyinkai/xiaobao-cli 0.1.0

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.
Files changed (41) hide show
  1. package/README.md +81 -0
  2. package/dist/api-Ccj5dLMo.mjs +100 -0
  3. package/dist/api-client-DwySN6x-.mjs +75 -0
  4. package/dist/audio-VmDQMq59.mjs +14 -0
  5. package/dist/auth-ChHvqjCS.mjs +15 -0
  6. package/dist/cli.d.mts +1 -0
  7. package/dist/cli.mjs +30 -0
  8. package/dist/consultant-ABFTL_jx.mjs +11 -0
  9. package/dist/customer-DtSnQqy_.mjs +11 -0
  10. package/dist/device-flow-BgsZipYA.mjs +166 -0
  11. package/dist/focus-BDYuP2vZ.mjs +11 -0
  12. package/dist/format-BZvv8lYc.mjs +610 -0
  13. package/dist/headers-D79npewp.mjs +11 -0
  14. package/dist/list--o5Q_PI8.mjs +80 -0
  15. package/dist/list-Bkyi95YO.mjs +76 -0
  16. package/dist/list-CGACp0y-.mjs +76 -0
  17. package/dist/list-CVKSGNu4.mjs +91 -0
  18. package/dist/list-CzB9_Jho.mjs +91 -0
  19. package/dist/list-DW6KAAaC.mjs +92 -0
  20. package/dist/list-iwd9cH1I.mjs +41 -0
  21. package/dist/login-BwkrByMm.mjs +91 -0
  22. package/dist/logout-BJm9pjje.mjs +49 -0
  23. package/dist/project-Ba05YzJ5.mjs +14 -0
  24. package/dist/project-store-Bz5kf-EI.mjs +62 -0
  25. package/dist/qa-D-B9FV9W.mjs +60 -0
  26. package/dist/resistance-Cf7zsdB3.mjs +11 -0
  27. package/dist/text-BLH4R3xv.mjs +49 -0
  28. package/dist/token-store-CHZ_rJQk.mjs +85 -0
  29. package/dist/use-DV9Ii3Tn.mjs +61 -0
  30. package/dist/util-DgwkUfV9.mjs +46 -0
  31. package/dist/visit-Ct3Vea8I.mjs +11 -0
  32. package/dist/whoami-DwTHvI4B.mjs +51 -0
  33. package/package.json +36 -0
  34. package/skills/wangxiaobao-audio-query/SKILL.md +252 -0
  35. package/skills/wangxiaobao-audio-wiki/SKILL.md +332 -0
  36. package/skills/wangxiaobao-customer-focus-query/SKILL.md +213 -0
  37. package/skills/wangxiaobao-customer-query/SKILL.md +214 -0
  38. package/skills/wangxiaobao-customer-resistance-query/SKILL.md +211 -0
  39. package/skills/wangxiaobao-quick-qa/SKILL.md +238 -0
  40. package/skills/wangxiaobao-switch-project/SKILL.md +179 -0
  41. package/skills/wangxiaobao-visit-query/SKILL.md +249 -0
@@ -0,0 +1,76 @@
1
+ import { a as resolveConfig } from "./device-flow-BgsZipYA.mjs";
2
+ import { t as xbApiFetch } from "./api-client-DwySN6x-.mjs";
3
+ import { n as writeResult, t as writeError } from "./format-BZvv8lYc.mjs";
4
+ import { t as requireActiveProject } from "./project-store-Bz5kf-EI.mjs";
5
+ import { r as toLocalDateTime } from "./util-DgwkUfV9.mjs";
6
+ import { t as tenantProjectHeaders } from "./headers-D79npewp.mjs";
7
+ import { defineCommand } from "citty";
8
+ //#region src/commands/visit/list.ts
9
+ /**
10
+ * `xiaobao-cli visit list` — page customer visits via POST /ai-open/visits/page.
11
+ *
12
+ * Mirrors openclaw-xiaobao xiaobao_list_visits. Each visit already carries an
13
+ * `audios` array (audioId / fileId / startTime / duration / signed fileUrl),
14
+ * so no need to call `audio list` separately for a visit's recordings.
15
+ */
16
+ var list_default = defineCommand({
17
+ meta: {
18
+ name: "list",
19
+ description: "客户来访分页查询(每条已含 audios 录音列表)"
20
+ },
21
+ args: {
22
+ "customer-id": {
23
+ type: "string",
24
+ description: "客户 ID(wang_id)精确过滤,优于 customer-name"
25
+ },
26
+ "customer-name": {
27
+ type: "string",
28
+ description: "客户姓名 LIKE 模糊(触发后端 JOIN,慢于 customer-id)"
29
+ },
30
+ from: {
31
+ type: "string",
32
+ description: "来访时间下限(含)"
33
+ },
34
+ to: {
35
+ type: "string",
36
+ description: "来访时间上限(不含)"
37
+ },
38
+ page: {
39
+ type: "string",
40
+ default: "1"
41
+ },
42
+ size: {
43
+ type: "string",
44
+ default: "10"
45
+ },
46
+ "api-base": { type: "string" },
47
+ "auth-base": { type: "string" },
48
+ format: {
49
+ type: "string",
50
+ default: "json"
51
+ }
52
+ },
53
+ async run({ args }) {
54
+ try {
55
+ writeResult(await xbApiFetch(resolveConfig({
56
+ apiBase: args["api-base"],
57
+ authBase: args["auth-base"]
58
+ }), "POST", "/ai-open/visits/page", {
59
+ headers: tenantProjectHeaders(await requireActiveProject()),
60
+ body: {
61
+ customerId: args["customer-id"],
62
+ customerName: args["customer-name"],
63
+ fromDate: toLocalDateTime(args.from),
64
+ toDate: toLocalDateTime(args.to),
65
+ page: Number(args.page),
66
+ size: Number(args.size)
67
+ }
68
+ }), args.format);
69
+ } catch (err) {
70
+ writeError(err, args.format);
71
+ process.exit(1);
72
+ }
73
+ }
74
+ });
75
+ //#endregion
76
+ export { list_default as default };
@@ -0,0 +1,76 @@
1
+ import { a as resolveConfig } from "./device-flow-BgsZipYA.mjs";
2
+ import { t as xbApiFetch } from "./api-client-DwySN6x-.mjs";
3
+ import { n as writeResult, t as writeError } from "./format-BZvv8lYc.mjs";
4
+ import { defineCommand } from "citty";
5
+ //#region src/commands/project/list.ts
6
+ /**
7
+ * `xiaobao-cli project list` — fetch the user's tenant + project roster.
8
+ *
9
+ * Mirrors openclaw-xiaobao/src/tools/list-projects.ts:
10
+ * - Calls GET /saas/v2/estate/tenant-and-estate/by-user-id
11
+ * - Flattens tenant.estateList into a single array
12
+ * - Optional `--keyword` does case-insensitive contains on tenantName/projectName
13
+ * - Skips estates with `enable === false`
14
+ */
15
+ var list_default = defineCommand({
16
+ meta: {
17
+ name: "list",
18
+ description: "列出有权限的租户与项目(扁平表)"
19
+ },
20
+ args: {
21
+ keyword: {
22
+ type: "string",
23
+ description: "租户名/项目名包含模糊过滤(大小写不敏感),不传返回全部"
24
+ },
25
+ "api-base": { type: "string" },
26
+ "auth-base": { type: "string" },
27
+ format: {
28
+ type: "string",
29
+ default: "json"
30
+ }
31
+ },
32
+ async run({ args }) {
33
+ try {
34
+ const result = await xbApiFetch(resolveConfig({
35
+ apiBase: args["api-base"],
36
+ authBase: args["auth-base"]
37
+ }), "GET", "/saas/v2/estate/tenant-and-estate/by-user-id");
38
+ if (!result.ok) {
39
+ writeError({
40
+ code: "API_ERROR",
41
+ status: result.status,
42
+ body: result.data
43
+ }, args.format);
44
+ process.exit(1);
45
+ }
46
+ const envelope = result.data;
47
+ const tenants = Array.isArray(envelope.data) ? envelope.data : [];
48
+ const flat = [];
49
+ for (const t of tenants) {
50
+ const tid = String(t.tenantId ?? "");
51
+ const tname = t.tenantName ?? "";
52
+ const estates = t.estateList ?? [];
53
+ for (const e of estates) {
54
+ if (e.enable === false) continue;
55
+ flat.push({
56
+ tenantId: tid,
57
+ tenantName: tname,
58
+ projectId: String(e.estateId ?? ""),
59
+ projectName: e.estateName ?? ""
60
+ });
61
+ }
62
+ }
63
+ const kw = args.keyword?.trim().toLowerCase();
64
+ const projects = kw ? flat.filter((p) => p.projectName.toLowerCase().includes(kw) || p.tenantName.toLowerCase().includes(kw)) : flat;
65
+ writeResult({
66
+ projects,
67
+ count: projects.length
68
+ }, args.format);
69
+ } catch (err) {
70
+ writeError(err, args.format);
71
+ process.exit(1);
72
+ }
73
+ }
74
+ });
75
+ //#endregion
76
+ export { list_default as default };
@@ -0,0 +1,91 @@
1
+ import { a as resolveConfig } from "./device-flow-BgsZipYA.mjs";
2
+ import { t as xbApiFetch } from "./api-client-DwySN6x-.mjs";
3
+ import { n as writeResult, t as writeError } from "./format-BZvv8lYc.mjs";
4
+ import { t as requireActiveProject } from "./project-store-Bz5kf-EI.mjs";
5
+ import { r as toLocalDateTime, t as csvToArray } from "./util-DgwkUfV9.mjs";
6
+ import { t as tenantProjectHeaders } from "./headers-D79npewp.mjs";
7
+ import { defineCommand } from "citty";
8
+ //#region src/commands/focus/list.ts
9
+ /**
10
+ * `xiaobao-cli focus list` — page customer 关注点 (focus) tags via
11
+ * POST /ai-open/customer-focus/page. Mirrors openclaw-xiaobao tool.
12
+ *
13
+ * Focus = 客户主动表达的关心(学区/户型/价格优惠/配套等)。每条 tag 已带
14
+ * customer / userInfo / audio + value_str + text_fragment。
15
+ */
16
+ var list_default = defineCommand({
17
+ meta: {
18
+ name: "list",
19
+ description: "客户关注点标签分页查询(visit_time DESC)"
20
+ },
21
+ args: {
22
+ "visit-ids": {
23
+ type: "string",
24
+ description: "CSV: 来访 ID 列表 IN 过滤"
25
+ },
26
+ "customer-ids": {
27
+ type: "string",
28
+ description: "CSV: 客户 ID(wang_id)列表 IN 过滤"
29
+ },
30
+ "audio-ids": {
31
+ type: "string",
32
+ description: "CSV: 录音 ID 列表 IN 过滤"
33
+ },
34
+ category: {
35
+ type: "string",
36
+ description: "一级分类 LIKE 模糊(如\"户型\"/\"价格\"/\"环境\")"
37
+ },
38
+ classification: {
39
+ type: "string",
40
+ description: "二级分类 LIKE 模糊(如\"户型因素\"/\"位置因素\")"
41
+ },
42
+ from: {
43
+ type: "string",
44
+ description: "来访时间下限(含)"
45
+ },
46
+ to: {
47
+ type: "string",
48
+ description: "来访时间上限(不含)"
49
+ },
50
+ page: {
51
+ type: "string",
52
+ default: "1"
53
+ },
54
+ size: {
55
+ type: "string",
56
+ default: "10"
57
+ },
58
+ "api-base": { type: "string" },
59
+ "auth-base": { type: "string" },
60
+ format: {
61
+ type: "string",
62
+ default: "json"
63
+ }
64
+ },
65
+ async run({ args }) {
66
+ try {
67
+ writeResult(await xbApiFetch(resolveConfig({
68
+ apiBase: args["api-base"],
69
+ authBase: args["auth-base"]
70
+ }), "POST", "/ai-open/customer-focus/page", {
71
+ headers: tenantProjectHeaders(await requireActiveProject()),
72
+ body: {
73
+ visitIds: csvToArray(args["visit-ids"]),
74
+ customerIds: csvToArray(args["customer-ids"]),
75
+ audioIds: csvToArray(args["audio-ids"]),
76
+ category: args.category,
77
+ classification: args.classification,
78
+ fromDate: toLocalDateTime(args.from),
79
+ toDate: toLocalDateTime(args.to),
80
+ page: Number(args.page),
81
+ size: Number(args.size)
82
+ }
83
+ }), args.format);
84
+ } catch (err) {
85
+ writeError(err, args.format);
86
+ process.exit(1);
87
+ }
88
+ }
89
+ });
90
+ //#endregion
91
+ export { list_default as default };
@@ -0,0 +1,91 @@
1
+ import { a as resolveConfig } from "./device-flow-BgsZipYA.mjs";
2
+ import { t as xbApiFetch } from "./api-client-DwySN6x-.mjs";
3
+ import { n as writeResult, t as writeError } from "./format-BZvv8lYc.mjs";
4
+ import { t as requireActiveProject } from "./project-store-Bz5kf-EI.mjs";
5
+ import { r as toLocalDateTime, t as csvToArray } from "./util-DgwkUfV9.mjs";
6
+ import { t as tenantProjectHeaders } from "./headers-D79npewp.mjs";
7
+ import { defineCommand } from "citty";
8
+ //#region src/commands/resistance/list.ts
9
+ /**
10
+ * `xiaobao-cli resistance list` — page customer 抗性点 (resistance) tags via
11
+ * POST /ai-open/customer-resistance/page. Mirrors openclaw-xiaobao tool;
12
+ * schema identical to focus, differs only in the underlying MV table.
13
+ *
14
+ * Resistance = 客户疑虑 / 反对 / 不满(价格高 / 户型差 / 地段差 等)。
15
+ */
16
+ var list_default = defineCommand({
17
+ meta: {
18
+ name: "list",
19
+ description: "客户抗性点标签分页查询(visit_time DESC)"
20
+ },
21
+ args: {
22
+ "visit-ids": {
23
+ type: "string",
24
+ description: "CSV: 来访 ID 列表 IN 过滤"
25
+ },
26
+ "customer-ids": {
27
+ type: "string",
28
+ description: "CSV: 客户 ID(wang_id)列表 IN 过滤"
29
+ },
30
+ "audio-ids": {
31
+ type: "string",
32
+ description: "CSV: 录音 ID 列表 IN 过滤"
33
+ },
34
+ category: {
35
+ type: "string",
36
+ description: "一级分类 LIKE 模糊(如\"价格\"/\"户型\"/\"环境\")"
37
+ },
38
+ classification: {
39
+ type: "string",
40
+ description: "二级分类 LIKE 模糊(如\"价格因素\"/\"位置因素\")"
41
+ },
42
+ from: {
43
+ type: "string",
44
+ description: "来访时间下限(含)"
45
+ },
46
+ to: {
47
+ type: "string",
48
+ description: "来访时间上限(不含)"
49
+ },
50
+ page: {
51
+ type: "string",
52
+ default: "1"
53
+ },
54
+ size: {
55
+ type: "string",
56
+ default: "10"
57
+ },
58
+ "api-base": { type: "string" },
59
+ "auth-base": { type: "string" },
60
+ format: {
61
+ type: "string",
62
+ default: "json"
63
+ }
64
+ },
65
+ async run({ args }) {
66
+ try {
67
+ writeResult(await xbApiFetch(resolveConfig({
68
+ apiBase: args["api-base"],
69
+ authBase: args["auth-base"]
70
+ }), "POST", "/ai-open/customer-resistance/page", {
71
+ headers: tenantProjectHeaders(await requireActiveProject()),
72
+ body: {
73
+ visitIds: csvToArray(args["visit-ids"]),
74
+ customerIds: csvToArray(args["customer-ids"]),
75
+ audioIds: csvToArray(args["audio-ids"]),
76
+ category: args.category,
77
+ classification: args.classification,
78
+ fromDate: toLocalDateTime(args.from),
79
+ toDate: toLocalDateTime(args.to),
80
+ page: Number(args.page),
81
+ size: Number(args.size)
82
+ }
83
+ }), args.format);
84
+ } catch (err) {
85
+ writeError(err, args.format);
86
+ process.exit(1);
87
+ }
88
+ }
89
+ });
90
+ //#endregion
91
+ export { list_default as default };
@@ -0,0 +1,92 @@
1
+ import { a as resolveConfig } from "./device-flow-BgsZipYA.mjs";
2
+ import { t as xbApiFetch } from "./api-client-DwySN6x-.mjs";
3
+ import { n as writeResult, t as writeError } from "./format-BZvv8lYc.mjs";
4
+ import { t as requireActiveProject } from "./project-store-Bz5kf-EI.mjs";
5
+ import { r as toLocalDateTime } from "./util-DgwkUfV9.mjs";
6
+ import { t as tenantProjectHeaders } from "./headers-D79npewp.mjs";
7
+ import { defineCommand } from "citty";
8
+ //#region src/commands/customer/list.ts
9
+ /**
10
+ * `xiaobao-cli customer list` — page customer profiles via
11
+ * POST /ai-open/customers/page.
12
+ *
13
+ * Mirrors openclaw-xiaobao xiaobao_list_customers. Filters: consultant id/name,
14
+ * customer name/phone (LIKE), portrait keyword (LIKE on dynamic_tags JSON),
15
+ * last-visit time range. Sorted by last_visit_time DESC.
16
+ */
17
+ var list_default = defineCommand({
18
+ meta: {
19
+ name: "list",
20
+ description: "客户画像分页查询(按顾问 / 客户名 / 画像 / 时间)"
21
+ },
22
+ args: {
23
+ "user-id": {
24
+ type: "string",
25
+ description: "置业顾问 user-id 精确过滤"
26
+ },
27
+ "user-name": {
28
+ type: "string",
29
+ description: "置业顾问姓名 LIKE 模糊"
30
+ },
31
+ "customer-name": {
32
+ type: "string",
33
+ description: "客户姓名 LIKE 模糊"
34
+ },
35
+ "customer-phone": {
36
+ type: "string",
37
+ description: "客户手机号 LIKE 模糊(可能脱敏)"
38
+ },
39
+ portrait: {
40
+ type: "string",
41
+ description: "画像关键词,在 dynamic_tags JSON 文本上 LIKE 模糊(如\"高意向\"/\"价格\")"
42
+ },
43
+ from: {
44
+ type: "string",
45
+ description: "最后来访时间下限(含)"
46
+ },
47
+ to: {
48
+ type: "string",
49
+ description: "最后来访时间上限(不含)"
50
+ },
51
+ page: {
52
+ type: "string",
53
+ default: "1"
54
+ },
55
+ size: {
56
+ type: "string",
57
+ default: "10"
58
+ },
59
+ "api-base": { type: "string" },
60
+ "auth-base": { type: "string" },
61
+ format: {
62
+ type: "string",
63
+ default: "json"
64
+ }
65
+ },
66
+ async run({ args }) {
67
+ try {
68
+ writeResult(await xbApiFetch(resolveConfig({
69
+ apiBase: args["api-base"],
70
+ authBase: args["auth-base"]
71
+ }), "POST", "/ai-open/customers/page", {
72
+ headers: tenantProjectHeaders(await requireActiveProject()),
73
+ body: {
74
+ userId: args["user-id"],
75
+ userName: args["user-name"],
76
+ customerName: args["customer-name"],
77
+ customerPhone: args["customer-phone"],
78
+ portrait: args.portrait,
79
+ fromDate: toLocalDateTime(args.from),
80
+ toDate: toLocalDateTime(args.to),
81
+ page: Number(args.page),
82
+ size: Number(args.size)
83
+ }
84
+ }), args.format);
85
+ } catch (err) {
86
+ writeError(err, args.format);
87
+ process.exit(1);
88
+ }
89
+ }
90
+ });
91
+ //#endregion
92
+ export { list_default as default };
@@ -0,0 +1,41 @@
1
+ import { a as resolveConfig } from "./device-flow-BgsZipYA.mjs";
2
+ import { t as xbApiFetch } from "./api-client-DwySN6x-.mjs";
3
+ import { n as writeResult, t as writeError } from "./format-BZvv8lYc.mjs";
4
+ import { t as requireActiveProject } from "./project-store-Bz5kf-EI.mjs";
5
+ import { t as tenantProjectHeaders } from "./headers-D79npewp.mjs";
6
+ import { defineCommand } from "citty";
7
+ //#region src/commands/consultant/list.ts
8
+ /**
9
+ * `xiaobao-cli consultant list` — list consultants the current user is
10
+ * authorised to view in the active project.
11
+ *
12
+ * Mirrors openclaw-xiaobao xiaobao_list_consultants: GET /ai-open/consultants
13
+ * with tenant/project headers from active-project store.
14
+ */
15
+ var list_default = defineCommand({
16
+ meta: {
17
+ name: "list",
18
+ description: "查询有权限的置业顾问列表(按当前激活项目)"
19
+ },
20
+ args: {
21
+ "api-base": { type: "string" },
22
+ "auth-base": { type: "string" },
23
+ format: {
24
+ type: "string",
25
+ default: "json"
26
+ }
27
+ },
28
+ async run({ args }) {
29
+ try {
30
+ writeResult(await xbApiFetch(resolveConfig({
31
+ apiBase: args["api-base"],
32
+ authBase: args["auth-base"]
33
+ }), "GET", "/ai-open/consultants", { headers: tenantProjectHeaders(await requireActiveProject()) }), args.format);
34
+ } catch (err) {
35
+ writeError(err, args.format);
36
+ process.exit(1);
37
+ }
38
+ }
39
+ });
40
+ //#endregion
41
+ export { list_default as default };
@@ -0,0 +1,91 @@
1
+ import { a as resolveConfig, n as pollForToken, r as refreshToken, t as initiateDeviceAuthorization } from "./device-flow-BgsZipYA.mjs";
2
+ import { a as writeToken, i as readToken, r as isAccessTokenValid, t as clearToken } from "./token-store-CHZ_rJQk.mjs";
3
+ import { n as writeResult, t as writeError } from "./format-BZvv8lYc.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 };
@@ -0,0 +1,49 @@
1
+ import { a as resolveConfig, i as revokeRefreshToken } from "./device-flow-BgsZipYA.mjs";
2
+ import { i as readToken, t as clearToken } from "./token-store-CHZ_rJQk.mjs";
3
+ import { n as writeResult, t as writeError } from "./format-BZvv8lYc.mjs";
4
+ import { defineCommand } from "citty";
5
+ //#region src/commands/auth/logout.ts
6
+ /**
7
+ * `xiaobao-cli auth logout` — clear local token + best-effort revoke remote
8
+ * refresh_token. Only touches the primary path (~/.xiaobao/token.json) —
9
+ * legacy ~/.openclaw/state/wangxiaobao/token.json is left intact so this
10
+ * doesn't silently nuke the plugin's session.
11
+ */
12
+ var logout_default = defineCommand({
13
+ meta: {
14
+ name: "logout",
15
+ description: "清除本地 token + 尽力 revoke 服务端 refresh_token"
16
+ },
17
+ args: {
18
+ "auth-base": {
19
+ type: "string",
20
+ description: "覆盖 auth base URL"
21
+ },
22
+ format: {
23
+ type: "string",
24
+ default: "json"
25
+ }
26
+ },
27
+ async run({ args }) {
28
+ try {
29
+ const config = resolveConfig({ authBase: args["auth-base"] });
30
+ const cached = await readToken();
31
+ let revoked = false;
32
+ if (cached?.refresh_token) revoked = await revokeRefreshToken(config, cached.refresh_token);
33
+ await clearToken();
34
+ writeResult({
35
+ message: "Logged out",
36
+ remote_revoked: revoked,
37
+ note: "Only ~/.xiaobao/token.json was cleared. Legacy ~/.openclaw/state/wangxiaobao/token.json (if present) was left intact."
38
+ }, args.format);
39
+ } catch (err) {
40
+ try {
41
+ await clearToken();
42
+ } catch {}
43
+ writeError(err, args.format);
44
+ process.exit(1);
45
+ }
46
+ }
47
+ });
48
+ //#endregion
49
+ export { logout_default as default };
@@ -0,0 +1,14 @@
1
+ import { defineCommand } from "citty";
2
+ //#region src/commands/project/index.ts
3
+ var project_default = defineCommand({
4
+ meta: {
5
+ name: "project",
6
+ description: "查询 / 切换激活项目"
7
+ },
8
+ subCommands: {
9
+ list: () => import("./list-CGACp0y-.mjs").then((m) => m.default),
10
+ use: () => import("./use-DV9Ii3Tn.mjs").then((m) => m.default)
11
+ }
12
+ });
13
+ //#endregion
14
+ export { project_default as default };
@@ -0,0 +1,62 @@
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
5
+ //#region src/core/project-store.ts
6
+ /**
7
+ * Active project store, file-backed.
8
+ *
9
+ * CLI-specific layout (mirrors token-store):
10
+ *
11
+ * PRIMARY ~/.xiaobao/active-project.json (writes here, mode 0600)
12
+ * FALLBACK ~/.openclaw/state/wangxiaobao/active-project.json (read-only legacy)
13
+ *
14
+ * To switch projects, run `xiaobao-cli project use ...`. The active project
15
+ * is a single global selection per host (not per-workspace) — every command
16
+ * that needs tenant/project context reads from here.
17
+ */
18
+ const PRIMARY_DIR = join(homedir(), ".xiaobao");
19
+ const PRIMARY_FILE = join(PRIMARY_DIR, "active-project.json");
20
+ const LEGACY_FILE = join(homedir(), ".openclaw", "state", "wangxiaobao", "active-project.json");
21
+ async function loadActiveProject() {
22
+ for (const f of [PRIMARY_FILE, LEGACY_FILE]) {
23
+ if (!existsSync(f)) continue;
24
+ try {
25
+ const raw = await readFile(f, "utf-8");
26
+ return JSON.parse(raw);
27
+ } catch (e) {
28
+ if (e.code === "ENOENT") continue;
29
+ throw e;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+ async function saveActiveProject(p) {
35
+ const record = {
36
+ ...p,
37
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
38
+ };
39
+ await mkdir(PRIMARY_DIR, {
40
+ recursive: true,
41
+ mode: 448
42
+ });
43
+ await writeFile(PRIMARY_FILE, JSON.stringify(record, null, 2), { mode: 384 });
44
+ try {
45
+ await chmod(PRIMARY_FILE, 384);
46
+ } catch {}
47
+ return record;
48
+ }
49
+ var NoActiveProjectError = class extends Error {
50
+ code = "NO_ACTIVE_PROJECT";
51
+ constructor() {
52
+ super("当前未设置激活项目。请先运行 `xiaobao-cli project list` 查看可选项目,再用 `xiaobao-cli project use --tenant-id ... --project-id ...` 激活。");
53
+ this.name = "NoActiveProjectError";
54
+ }
55
+ };
56
+ async function requireActiveProject() {
57
+ const p = await loadActiveProject();
58
+ if (!p) throw new NoActiveProjectError();
59
+ return p;
60
+ }
61
+ //#endregion
62
+ export { saveActiveProject as n, requireActiveProject as t };