@xbongbong/xbbcli 2.0.0 → 2.2.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 (258) hide show
  1. package/README.md +101 -2
  2. package/ai-call-add-task.js +3 -6
  3. package/ai-word-list.js +3 -6
  4. package/bin/xbbcli.js +20 -2
  5. package/buyer-inquiry-detail.js +2 -5
  6. package/buyer-inquiry-list.js +2 -5
  7. package/clue-add-couser.js +2 -5
  8. package/clue-add.js +2 -5
  9. package/clue-back.js +2 -5
  10. package/clue-del.js +2 -5
  11. package/clue-delete-couser.js +2 -5
  12. package/clue-delete-mainuser.js +2 -5
  13. package/clue-detail.js +2 -5
  14. package/clue-distribution.js +2 -5
  15. package/clue-edit.js +2 -5
  16. package/clue-handover.js +3 -6
  17. package/clue-list.js +2 -5
  18. package/clue-thorough-delete.js +2 -5
  19. package/communicate-add.js +2 -5
  20. package/communicate-del.js +2 -5
  21. package/communicate-detail.js +2 -5
  22. package/communicate-edit.js +2 -5
  23. package/communicate-list.js +2 -5
  24. package/communicate-plan-add.js +2 -5
  25. package/communicate-plan-cancel.js +2 -5
  26. package/communicate-plan-del.js +2 -5
  27. package/communicate-plan-detail.js +2 -5
  28. package/communicate-plan-list.js +2 -5
  29. package/communicate-plan-single-operate.js +2 -5
  30. package/contact-add-couser.js +2 -5
  31. package/contact-add-mainuser.js +2 -5
  32. package/contact-add.js +2 -5
  33. package/contact-del.js +2 -1
  34. package/contact-delete-couser.js +2 -5
  35. package/contact-delete-mainuser.js +2 -5
  36. package/contact-detail.js +2 -1
  37. package/contact-edit.js +2 -5
  38. package/contact-handover.js +2 -5
  39. package/contact-list.js +2 -5
  40. package/contract-add-couser.js +2 -5
  41. package/contract-add-mainuser.js +2 -5
  42. package/contract-add.js +2 -5
  43. package/contract-del.js +2 -5
  44. package/contract-delete-couser.js +2 -5
  45. package/contract-delete-mainuser.js +2 -5
  46. package/contract-detail.js +2 -5
  47. package/contract-edit.js +2 -5
  48. package/contract-handover.js +2 -5
  49. package/contract-list.js +2 -5
  50. package/contract-outstock-add.js +2 -5
  51. package/contract-outstock-del.js +2 -5
  52. package/contract-outstock-detail.js +2 -5
  53. package/contract-outstock-edit.js +2 -5
  54. package/contract-outstock-list.js +2 -5
  55. package/customer-add-couser.js +2 -5
  56. package/customer-add.js +2 -5
  57. package/customer-back.js +2 -5
  58. package/customer-del.js +2 -5
  59. package/customer-delete-couser.js +2 -5
  60. package/customer-delete-mainuser.js +2 -5
  61. package/customer-detail.js +2 -5
  62. package/customer-distribution.js +2 -5
  63. package/customer-edit.js +2 -5
  64. package/customer-handover.js +2 -5
  65. package/customer-invoice-address-add.js +2 -5
  66. package/customer-invoice-address-edit.js +2 -5
  67. package/customer-invoice-address.js +2 -5
  68. package/customer-invoice-info-add.js +2 -5
  69. package/customer-invoice-info-edit.js +2 -5
  70. package/customer-invoice-info.js +2 -5
  71. package/customer-list.js +2 -5
  72. package/department-add.js +2 -5
  73. package/department-del.js +2 -5
  74. package/department-edit.js +2 -5
  75. package/department-list.js +2 -5
  76. package/form-data-add-couser.js +2 -5
  77. package/form-data-add-mainuser.js +2 -5
  78. package/form-data-add.js +2 -5
  79. package/form-data-del.js +2 -5
  80. package/form-data-delete-couser.js +2 -5
  81. package/form-data-delete-mainuser.js +2 -5
  82. package/form-data-detail.js +2 -5
  83. package/form-data-edit.js +2 -5
  84. package/form-data-handover.js +2 -5
  85. package/form-data-list.js +2 -5
  86. package/form-get.js +2 -5
  87. package/form-list.js +2 -5
  88. package/invoice-add.js +3 -6
  89. package/invoice-cancel.js +3 -6
  90. package/invoice-del.js +3 -6
  91. package/invoice-detail.js +3 -6
  92. package/invoice-edit.js +3 -6
  93. package/invoice-get-amount-detail.js +3 -6
  94. package/invoice-list.js +3 -6
  95. package/invoice-red.js +3 -6
  96. package/label-add.js +3 -6
  97. package/label-all-list.js +3 -6
  98. package/label-batch-add.js +3 -6
  99. package/label-batch-remove.js +3 -6
  100. package/label-del-completely.js +3 -6
  101. package/label-del.js +3 -6
  102. package/label-edit.js +3 -6
  103. package/label-group-add.js +3 -6
  104. package/label-group-del.js +3 -6
  105. package/label-group-list.js +3 -6
  106. package/label-group-update.js +3 -6
  107. package/label-list.js +3 -6
  108. package/label-reduction.js +3 -6
  109. package/label-repeat.js +3 -6
  110. package/market-activity-add.js +2 -5
  111. package/market-activity-del.js +2 -5
  112. package/market-activity-detail.js +2 -5
  113. package/market-activity-edit.js +2 -5
  114. package/market-activity-handover.js +2 -5
  115. package/market-activity-list.js +2 -5
  116. package/opportunity-add-couser.js +2 -5
  117. package/opportunity-add-mainuser.js +2 -5
  118. package/opportunity-add.js +2 -5
  119. package/opportunity-del.js +2 -5
  120. package/opportunity-delete-couser.js +2 -5
  121. package/opportunity-delete-mainuser.js +2 -5
  122. package/opportunity-detail.js +2 -5
  123. package/opportunity-edit.js +2 -5
  124. package/opportunity-handover.js +2 -5
  125. package/opportunity-list.js +2 -5
  126. package/package.json +1 -1
  127. package/pay-plan-add-couser.js +2 -5
  128. package/pay-plan-add-mainuser.js +2 -5
  129. package/pay-plan-add.js +2 -5
  130. package/pay-plan-delete-couser.js +2 -5
  131. package/pay-plan-delete-mainuser.js +2 -5
  132. package/pay-plan-handover.js +2 -5
  133. package/pay-sheet-add-bad.js +2 -5
  134. package/pay-sheet-add-couser.js +2 -5
  135. package/pay-sheet-add-mainuser.js +2 -5
  136. package/pay-sheet-add-red.js +2 -5
  137. package/pay-sheet-add-write-off.js +2 -5
  138. package/pay-sheet-del.js +2 -5
  139. package/pay-sheet-delete-couser.js +2 -5
  140. package/pay-sheet-delete-mainuser.js +2 -5
  141. package/pay-sheet-detail.js +2 -5
  142. package/pay-sheet-edit-bad.js +2 -5
  143. package/pay-sheet-edit-red.js +2 -5
  144. package/pay-sheet-edit-write-off.js +2 -5
  145. package/pay-sheet-get-amount-detail.js +2 -5
  146. package/pay-sheet-list.js +2 -5
  147. package/payment-add-couser.js +2 -5
  148. package/payment-add-mainuser.js +2 -5
  149. package/payment-add.js +2 -5
  150. package/payment-del.js +2 -5
  151. package/payment-delete-couser.js +2 -5
  152. package/payment-delete-mainuser.js +2 -5
  153. package/payment-detail.js +2 -5
  154. package/payment-edit.js +2 -5
  155. package/payment-handover.js +2 -5
  156. package/payment-list.js +2 -5
  157. package/payment-sheet-add-bad-debt.js +2 -5
  158. package/payment-sheet-add-pre.js +2 -5
  159. package/payment-sheet-add-red.js +2 -5
  160. package/payment-sheet-add-write-off.js +2 -5
  161. package/payment-sheet-del.js +2 -5
  162. package/payment-sheet-detail.js +2 -5
  163. package/payment-sheet-edit-bad-debt.js +2 -5
  164. package/payment-sheet-edit-red.js +2 -5
  165. package/payment-sheet-edit-write-off.js +2 -5
  166. package/payment-sheet-get-amount-detail.js +2 -5
  167. package/payment-sheet-handover.js +2 -5
  168. package/payment-sheet-list.js +2 -5
  169. package/product-add.js +2 -5
  170. package/product-category-add.js +2 -5
  171. package/product-category-del.js +2 -5
  172. package/product-category-list.js +2 -5
  173. package/product-category-update.js +2 -5
  174. package/product-del.js +2 -5
  175. package/product-detail.js +2 -5
  176. package/product-edit.js +2 -5
  177. package/product-list.js +2 -5
  178. package/product-online.js +2 -5
  179. package/quotation-add-couser.js +3 -6
  180. package/quotation-add-mainuser.js +3 -6
  181. package/quotation-add.js +3 -6
  182. package/quotation-del.js +3 -6
  183. package/quotation-delete-couser.js +3 -6
  184. package/quotation-delete-mainuser.js +3 -6
  185. package/quotation-detail.js +3 -6
  186. package/quotation-edit.js +3 -6
  187. package/quotation-handover.js +2 -5
  188. package/quotation-list.js +3 -6
  189. package/refund-add.js +4 -1
  190. package/refund-del.js +4 -1
  191. package/refund-detail.js +4 -1
  192. package/refund-edit.js +4 -1
  193. package/refund-list.js +2 -5
  194. package/role-list.js +2 -5
  195. package/sign-in-list.js +2 -5
  196. package/stage-get.js +2 -5
  197. package/stage-jump.js +2 -5
  198. package/supplier-add.js +2 -5
  199. package/token-del.js +106 -0
  200. package/token-generate.js +2 -5
  201. package/token-list.js +119 -0
  202. package/token-set.js +191 -10
  203. package/token-use.js +102 -0
  204. package/user-add.js +2 -5
  205. package/user-del.js +2 -5
  206. package/user-edit.js +2 -5
  207. package/user-handover.js +2 -5
  208. package/user-list.js +5 -8
  209. package/work-order-add.js +2 -5
  210. package/work-order-asset-add-couser.js +3 -6
  211. package/work-order-asset-add-mainuser.js +3 -6
  212. package/work-order-asset-add.js +3 -6
  213. package/work-order-asset-del-couser.js +3 -6
  214. package/work-order-asset-del-mainuser.js +3 -6
  215. package/work-order-asset-del.js +3 -6
  216. package/work-order-asset-detail.js +3 -6
  217. package/work-order-asset-edit.js +3 -6
  218. package/work-order-asset-handover.js +3 -6
  219. package/work-order-asset-list.js +3 -6
  220. package/work-order-del.js +2 -5
  221. package/work-order-detail.js +2 -5
  222. package/work-order-dynamic-list.js +2 -5
  223. package/work-order-edit.js +2 -5
  224. package/work-order-list.js +2 -5
  225. package/work-order-operate.js +2 -5
  226. package/work-order-template-detail.js +2 -5
  227. package/work-order-template-list.js +2 -5
  228. package/work-report-daily-add.js +2 -5
  229. package/work-report-daily-del.js +2 -5
  230. package/work-report-daily-detail.js +2 -5
  231. package/work-report-daily-edit.js +2 -5
  232. package/work-report-daily-get-work-plan.js +2 -5
  233. package/work-report-daily-list.js +2 -5
  234. package/work-report-monthly-add.js +2 -5
  235. package/work-report-monthly-del.js +2 -5
  236. package/work-report-monthly-detail.js +2 -5
  237. package/work-report-monthly-edit.js +2 -5
  238. package/work-report-monthly-list.js +2 -5
  239. package/work-report-weekly-add.js +2 -5
  240. package/work-report-weekly-del.js +2 -5
  241. package/work-report-weekly-detail.js +2 -5
  242. package/work-report-weekly-edit.js +2 -5
  243. package/work-report-weekly-list.js +2 -5
  244. package/work-task-add-couser.js +2 -5
  245. package/work-task-add.js +2 -5
  246. package/work-task-delete-couser.js +2 -5
  247. package/work-task-detail.js +2 -5
  248. package/work-task-edit.js +2 -5
  249. package/work-task-handover.js +2 -5
  250. package/workorder-add-couser.js +2 -5
  251. package/workorder-delete-couser.js +2 -5
  252. package/workorder-handover.js +2 -5
  253. package/workorderlist.js +2 -5
  254. package/workorderproductlist.js +2 -5
  255. package/worktimerecorddetail.js +2 -5
  256. package/worktimerecordlist.js +2 -5
  257. package/xbb-config.js +113 -0
  258. package/version.js +0 -18
package/token-generate.js CHANGED
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { cli, Strategy } from './xbb-registry.js';
6
+ import { readActiveConfig } from './xbb-config.js';
6
7
 
7
8
  const CONFIG_FILE = path.join(os.homedir(), '.xbbcli', 'config.env');
8
9
  const API_URL = 'https://proapi.xbongbong.com/pro/v2/api/user/generateToken';
@@ -10,11 +11,7 @@ const DEFAULT_BASE_URL = 'https://proapi.xbongbong.com';
10
11
  const MISSING_TOKEN_MESSAGE = '缺少 token;请先执行 xbbcli token-set --corpid <CORPID> --token <TOKEN> --userId <USERID>';
11
12
 
12
13
  function readConfig() {
13
- try {
14
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
15
- } catch {
16
- return {};
17
- }
14
+ return readActiveConfig();
18
15
  }
19
16
 
20
17
  function resolveBaseUrl(corpid) {
package/token-list.js ADDED
@@ -0,0 +1,119 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { cli, Strategy } from './xbb-registry.js';
5
+ import { readEnvConfig, hasEnvConfig, isEnvOnly } from './xbb-config.js';
6
+
7
+ const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
8
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
9
+
10
+ function readCompanies() {
11
+ try {
12
+ const parsed = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
13
+ return normalizeCompanies(parsed);
14
+ } catch {
15
+ return [];
16
+ }
17
+ }
18
+
19
+ function normalizeCompanies(parsed) {
20
+ if (Array.isArray(parsed)) {
21
+ return parsed.filter((item) => item && typeof item === 'object');
22
+ }
23
+ if (parsed && Array.isArray(parsed.companies)) {
24
+ return parsed.companies.filter((item) => item && typeof item === 'object');
25
+ }
26
+ if (parsed && parsed.corpid) {
27
+ const { corpid, token, baseurl, userId } = parsed;
28
+ return [{ corpid, token, baseurl, userId, enable: true }];
29
+ }
30
+ return [];
31
+ }
32
+
33
+ function writeCompanies(companies) {
34
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
35
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(companies, null, 2) + '\n', 'utf8');
36
+ }
37
+
38
+ function enableOnly(companies, index) {
39
+ return companies.map((item, itemIndex) => ({ ...item, enable: itemIndex === index }));
40
+ }
41
+
42
+ function enableSingle(companies) {
43
+ const enabledIndex = companies.findIndex((item) => item.enable === true);
44
+ return enableOnly(companies, enabledIndex === -1 ? 0 : enabledIndex);
45
+ }
46
+
47
+ function maskToken(token) {
48
+ const value = String(token || '').trim();
49
+ if (!value) return '';
50
+ if (value.length <= 12) return `${value.slice(0, 4)}****`;
51
+ return `${value.slice(0, 8)}****${value.slice(-4)}`;
52
+ }
53
+ function makeErrorRow(code, msg) {
54
+ return [{ enable: '', corpid: '', corpName: '', userName: '', userId: '', baseurl: '', token: '', code, msg, source: '' }];
55
+ }
56
+
57
+ cli({
58
+ site: 'xbb',
59
+ name: 'token-list',
60
+ description: '列出本地保存的多家公司配置,并标记唯一启用的公司(enable=true)',
61
+ strategy: Strategy.PUBLIC,
62
+ access: 'read',
63
+ browser: false,
64
+ domain: 'proapi.xbongbong.com',
65
+ args: [
66
+ { name: 'showToken', type: 'bool', default: false, help: '显示完整 token;默认只显示脱敏预览' },
67
+ ],
68
+ columns: ['enable', 'corpid', 'userId', 'baseurl', 'token', 'corpName', 'userName', 'code', 'msg', 'source'],
69
+ func: async (kwargs) => {
70
+ const companies = readCompanies();
71
+ const envConfig = readEnvConfig();
72
+ if (isEnvOnly() || (!companies.some((item) => item && item.enable === true) && hasEnvConfig(envConfig))) {
73
+ return [{
74
+ enable: 'true',
75
+ corpid: String(envConfig.corpid || ''),
76
+ userId: String(envConfig.userId || ''),
77
+ baseurl: String(envConfig.baseurl || ''),
78
+ token: Boolean(kwargs.showToken) ? String(envConfig.token || '') : maskToken(envConfig.token),
79
+ corpName: String(envConfig.corpName || ''),
80
+ userName: String(envConfig.userName || ''),
81
+ code: '',
82
+ msg: '来源为环境变量 XBB_*(环境变量模式不支持多公司切换)',
83
+ source: 'env',
84
+ }];
85
+ }
86
+ if (!companies.length) {
87
+ return makeErrorRow('NO_CONFIG', '缺少本地配置;请先执行 xbbcli token-set --corpid <CORPID> --token <TOKEN> --userId <USERID>');
88
+ }
89
+ const showToken = Boolean(kwargs.showToken);
90
+ const enabledCount = companies.filter((item) => item.enable === true).length;
91
+ const rows = companies.map((item) => ({
92
+ enable: item.enable === true ? 'true' : 'false',
93
+ corpid: String(item.corpid || ''),
94
+ userId: String(item.userId || ''),
95
+ baseurl: String(item.baseurl || ''),
96
+ token: showToken ? String(item.token || '') : maskToken(item.token),
97
+ corpName: String(item.corpName || ''),
98
+ userName: String(item.userName || ''),
99
+ code: '',
100
+ msg: '',
101
+ source: 'config',
102
+ }));
103
+ if (enabledCount !== 1) {
104
+ rows.push({
105
+ enable: '',
106
+ corpid: '',
107
+ userId: '',
108
+ baseurl: '',
109
+ token: '',
110
+ corpName: '',
111
+ userName: '',
112
+ source: 'config',
113
+ code: 'INVALID_ENABLE',
114
+ msg: `配置中启用(enable=true)的公司数量为 ${enabledCount},请用 xbbcli token-use --corpid <CORPID> 修正`,
115
+ });
116
+ }
117
+ return rows;
118
+ },
119
+ });
package/token-set.js CHANGED
@@ -2,10 +2,15 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { cli, Strategy, getRegistry } from './xbb-registry.js';
5
+ import { persistEnvVars } from './xbb-config.js';
5
6
 
6
7
  const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
7
8
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
8
9
  const FORMLIST_FILE_SUFFIX = '.formlist.json';
10
+ const COMMAND_MAP_FILE_SUFFIX = '.command-map.md';
11
+ const DEPARTMENT_USER_FILE_SUFFIX = '.department-user.json';
12
+ const READ_ALL_PAGE_SIZE = 200;
13
+ const READ_ALL_MAX_PAGES = 1000;
9
14
 
10
15
  function resolveBaseUrl(corpid) {
11
16
  if (corpid.startsWith('ding') || corpid.includes('$$ding')) {
@@ -22,6 +27,10 @@ function getFormlistFile(corpid) {
22
27
  return path.join(CONFIG_DIR, `${corpid}${FORMLIST_FILE_SUFFIX}`);
23
28
  }
24
29
 
30
+ function getCommandMapFile(corpid) {
31
+ return path.join(CONFIG_DIR, `${corpid}${COMMAND_MAP_FILE_SUFFIX}`);
32
+ }
33
+
25
34
  async function runCommandJson(name, kwargs) {
26
35
  const command = getRegistry().get(`xbb/${name}`);
27
36
  if (!command) throw new Error(`找不到命令:${name}`);
@@ -45,10 +54,10 @@ async function getFormlistRows(corpid, saasMark) {
45
54
  return rows;
46
55
  }
47
56
 
48
- async function getPersonalToken(corpid, token, userId) {
57
+ async function requestPersonalToken(corpid, token, userId, resetToken) {
49
58
  const rows = await runCommandJson('token-generate', {
50
59
  checkUserId: userId,
51
- resetToken: '1',
60
+ resetToken: String(resetToken),
52
61
  token,
53
62
  corpid,
54
63
  });
@@ -62,7 +71,20 @@ async function getPersonalToken(corpid, token, userId) {
62
71
  throw new Error(`token-generate 获取个人 token 失败:${errorRow.code} ${errorRow.msg || ''}`.trim());
63
72
  }
64
73
 
65
- const personalToken = String(rows[0]?.token || '').trim();
74
+ return String(rows[0]?.token || '').trim();
75
+ }
76
+
77
+ async function getPersonalToken(corpid, token, userId) {
78
+ const existingToken = await requestPersonalToken(corpid, token, userId, 0);
79
+
80
+ if (existingToken) {
81
+ if (!existingToken.startsWith('user_')) {
82
+ throw new Error('token-generate 未返回有效的个人 token');
83
+ }
84
+ return existingToken;
85
+ }
86
+
87
+ const personalToken = await requestPersonalToken(corpid, token, userId, 1);
66
88
  if (!personalToken.startsWith('user_')) {
67
89
  throw new Error('token-generate 未返回有效的个人 token');
68
90
  }
@@ -80,6 +102,106 @@ async function writeFormlistFile(corpid) {
80
102
  return formlistFile;
81
103
  }
82
104
 
105
+ function getDepartmentUserFile(corpid) {
106
+ return path.join(CONFIG_DIR, `${corpid}${DEPARTMENT_USER_FILE_SUFFIX}`);
107
+ }
108
+
109
+ async function fetchAllRows(name, pageSize) {
110
+ const rows = [];
111
+ for (let page = 1; page <= READ_ALL_MAX_PAGES; page += 1) {
112
+ const pageRows = await runCommandJson(name, { page: String(page), pageSize: String(pageSize) });
113
+ const errorRow = pageRows.find((item) => item && String(item.code ?? '') !== '');
114
+ if (errorRow) {
115
+ if (String(errorRow.code) === 'NO_DATA') {
116
+ return rows;
117
+ }
118
+ throw new Error(`${name} 获取失败(page=${page}):${errorRow.code} ${errorRow.msg || ''}`.trim());
119
+ }
120
+ rows.push(...pageRows);
121
+ }
122
+ throw new Error(`${name} 分页超过 ${READ_ALL_MAX_PAGES} 页,未获取到完整数据`);
123
+ }
124
+
125
+ function parseDepartmentListField(value) {
126
+ if (Array.isArray(value)) {
127
+ return value;
128
+ }
129
+ const text = String(value ?? '').trim();
130
+ if (!text) {
131
+ return [];
132
+ }
133
+ try {
134
+ const parsed = JSON.parse(text);
135
+ return Array.isArray(parsed) ? parsed : text;
136
+ } catch {
137
+ return text;
138
+ }
139
+ }
140
+
141
+ function toDepartmentEntry(row) {
142
+ return {
143
+ id: row.id ?? '',
144
+ name: row.name ?? '',
145
+ parentId: row.parentId ?? '',
146
+ depIdRouter: row.depIdRouter ?? '',
147
+ };
148
+ }
149
+
150
+ function toUserEntry(row) {
151
+ return {
152
+ userId: row.userId ?? '',
153
+ name: row.name ?? '',
154
+ position: row.position ?? '',
155
+ jobnumber: row.jobnumber ?? '',
156
+ departmentList: parseDepartmentListField(row.departmentList),
157
+ };
158
+ }
159
+
160
+ async function writeDepartmentUserFile(corpid, userId) {
161
+ const departmentRows = await fetchAllRows('department-list', READ_ALL_PAGE_SIZE);
162
+ const userRows = await fetchAllRows('user-list', READ_ALL_PAGE_SIZE);
163
+ const payload = {
164
+ corpid,
165
+ updatedAt: new Date().toISOString(),
166
+ pageSize: READ_ALL_PAGE_SIZE,
167
+ departmentCount: departmentRows.length,
168
+ userCount: userRows.length,
169
+ departments: departmentRows.map(toDepartmentEntry),
170
+ users: userRows.map(toUserEntry),
171
+ };
172
+ const file = getDepartmentUserFile(corpid);
173
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
174
+ fs.writeFileSync(file, JSON.stringify(payload, null, 2) + '\n', 'utf8');
175
+ return { file, corpName: getCorpName(departmentRows), userName: getUserName(userRows, userId) };
176
+ }
177
+
178
+ function getCorpName(departmentRows) {
179
+ const root = departmentRows.find((row) => Number(row.id) === 1);
180
+ return root ? String(root.name || '').trim() : '';
181
+ }
182
+
183
+ function getUserName(userRows, userId) {
184
+ const target = String(userId || '').trim();
185
+ if (!target) {
186
+ return '';
187
+ }
188
+ const exact = userRows.find((row) => String(row.userId || '').trim() === target);
189
+ const matched = exact || userRows.find((row) => String(row.userId || '').trim().toLowerCase() === target.toLowerCase());
190
+ return matched ? String(matched.name || '').trim() : '';
191
+ }
192
+
193
+ function writeCompanyProfile(corpid, profile) {
194
+ const fields = Object.entries(profile).filter(([, value]) => String(value || '').trim() !== '');
195
+ if (!fields.length) {
196
+ return;
197
+ }
198
+ const patch = Object.fromEntries(fields);
199
+ const companies = readCompanies().map((item) => (
200
+ String(item.corpid || '').trim() === corpid ? { ...item, ...patch } : item
201
+ ));
202
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(companies, null, 2) + '\n', 'utf8');
203
+ }
204
+
83
205
  function normalizeBusinessType(value) {
84
206
  return String(value).trim().replace(/[))\]】,,;;::。!?!?]+$/g, '');
85
207
  }
@@ -137,13 +259,13 @@ function writeCommandMap(corpid) {
137
259
  );
138
260
  }
139
261
 
140
- const commandMapFile = path.join(CONFIG_DIR, 'command-map.md');
262
+ const commandMapFile = getCommandMapFile(corpid);
141
263
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
142
264
  fs.writeFileSync(commandMapFile, `${lines.join('\n')}\n`, 'utf8');
143
265
  return commandMapFile;
144
266
  }
145
267
 
146
- function createResult(status, message, corpid, baseurl, userId, formlistFile = '', commandMapFile = '') {
268
+ function createResult(status, message, corpid, baseurl, userId, formlistFile = '', commandMapFile = '', enable = '', companyCount = '', departmentUserFile = '', corpName = '', userName = '', envStored = '', envFile = '') {
147
269
  return [{
148
270
  status,
149
271
  message,
@@ -151,11 +273,52 @@ function createResult(status, message, corpid, baseurl, userId, formlistFile = '
151
273
  corpid,
152
274
  baseurl,
153
275
  userId,
276
+ enable,
277
+ companyCount,
154
278
  formlistFile,
155
279
  commandMapFile,
280
+ departmentUserFile,
281
+ corpName,
282
+ userName,
283
+ envStored,
284
+ envFile,
156
285
  }];
157
286
  }
158
287
 
288
+ function readCompanies() {
289
+ try {
290
+ const parsed = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
291
+ return normalizeCompanies(parsed);
292
+ } catch {
293
+ return [];
294
+ }
295
+ }
296
+
297
+ function normalizeCompanies(parsed) {
298
+ if (Array.isArray(parsed)) {
299
+ return parsed.filter((item) => item && typeof item === 'object');
300
+ }
301
+ if (parsed && Array.isArray(parsed.companies)) {
302
+ return parsed.companies.filter((item) => item && typeof item === 'object');
303
+ }
304
+ if (parsed && parsed.corpid) {
305
+ const { corpid, token, baseurl, userId } = parsed;
306
+ return [{ corpid, token, baseurl, userId, enable: true }];
307
+ }
308
+ return [];
309
+ }
310
+
311
+ function upsertCompany(companies, entry) {
312
+ const index = companies.findIndex((item) => String(item.corpid || '').trim() === entry.corpid);
313
+ const next = companies.map((item) => ({ ...item, enable: false }));
314
+ if (index === -1) {
315
+ next.push({ ...entry, enable: true });
316
+ } else {
317
+ next[index] = { ...next[index], ...entry, enable: true };
318
+ }
319
+ return next;
320
+ }
321
+
159
322
  async function setToken(kwargs) {
160
323
  const corpid = normalizeArg(kwargs.corpid);
161
324
  const token = normalizeArg(kwargs.token);
@@ -183,15 +346,29 @@ async function setToken(kwargs) {
183
346
  }
184
347
 
185
348
  const baseurl = resolveBaseUrl(corpid);
186
- const config = { corpid, token: personalToken, baseurl, userId };
349
+ const companies = upsertCompany(readCompanies(), { corpid, token: personalToken, baseurl, userId });
187
350
 
188
351
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
189
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n', 'utf8');
352
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(companies, null, 2) + '\n', 'utf8');
190
353
 
191
354
  try {
192
355
  const formlistFile = await writeFormlistFile(corpid);
193
356
  const commandMapFile = writeCommandMap(corpid);
194
- return createResult('ok', '已保存 corpid、token、baseurl、userId,并同步表单模板缓存与命令映射文件', corpid, baseurl, userId, formlistFile, commandMapFile);
357
+ const { file: departmentUserFile, corpName, userName } = await writeDepartmentUserFile(corpid, userId);
358
+ writeCompanyProfile(corpid, { corpName, userName });
359
+ let envStored = 'skipped';
360
+ let envFile = '';
361
+ if (!kwargs.noEnv) {
362
+ try {
363
+ const envResult = persistEnvVars({ corpid, token: personalToken, baseurl, userId, corpName, userName });
364
+ envStored = envResult.stored;
365
+ envFile = envResult.file;
366
+ } catch (error) {
367
+ envStored = 'failed';
368
+ envFile = String(error.message || error);
369
+ }
370
+ }
371
+ return createResult('ok', '已保存 corpid、token、baseurl、userId,并同步表单模板缓存、命令映射文件与部门/员工缓存', corpid, baseurl, userId, formlistFile, commandMapFile, true, companies.length, departmentUserFile, corpName, userName, envStored, envFile);
195
372
  } catch (error) {
196
373
  return createResult(
197
374
  'partial',
@@ -201,6 +378,9 @@ async function setToken(kwargs) {
201
378
  userId,
202
379
  getFormlistFile(corpid),
203
380
  '',
381
+ true,
382
+ companies.length,
383
+ '',
204
384
  );
205
385
  }
206
386
  }
@@ -208,7 +388,7 @@ async function setToken(kwargs) {
208
388
  cli({
209
389
  site: 'xbb',
210
390
  name: 'token-set',
211
- description: '保存 xbb API token,corpid,formId清单 到本地配置文件,其他命令需要意图识别或找formId时,优先查命令映射文件(command-map.md)',
391
+ description: '保存 xbb API token,corpid,formId清单 到本地配置文件,并缓存部门与员工清单(<corpid>.department-user.json);其他命令需要意图识别或找formId时,优先查命令映射文件(<corpid>.command-map.md)',
212
392
  strategy: Strategy.PUBLIC,
213
393
  access: 'write',
214
394
  browser: false,
@@ -216,7 +396,8 @@ cli({
216
396
  { name: 'corpid', type: 'str', help: '公司id(必填)' },
217
397
  { name: 'token', type: 'str', help: '要保存的 API token' },
218
398
  { name: 'userId', type: 'str', help: '操作人id(必填)' },
399
+ { name: 'noEnv', type: 'bool', default: false, help: '不写入环境变量,仅保存到 config.env' },
219
400
  ],
220
- columns: ['status', 'message', 'configFile', 'corpid', 'baseurl', 'userId', 'formlistFile', 'commandMapFile'],
401
+ columns: ['status', 'message', 'configFile', 'corpid', 'baseurl', 'userId', 'enable', 'companyCount', 'formlistFile', 'commandMapFile', 'departmentUserFile', 'corpName', 'userName', 'envStored', 'envFile'],
221
402
  func: setToken,
222
403
  });
package/token-use.js ADDED
@@ -0,0 +1,102 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { cli, Strategy } from './xbb-registry.js';
5
+ import { isEnvActive, isEnvOnly, getEnvVarNames } from './xbb-config.js';
6
+
7
+ const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
8
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
9
+
10
+ function readCompanies() {
11
+ try {
12
+ const parsed = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
13
+ return normalizeCompanies(parsed);
14
+ } catch {
15
+ return [];
16
+ }
17
+ }
18
+
19
+ function normalizeCompanies(parsed) {
20
+ if (Array.isArray(parsed)) {
21
+ return parsed.filter((item) => item && typeof item === 'object');
22
+ }
23
+ if (parsed && Array.isArray(parsed.companies)) {
24
+ return parsed.companies.filter((item) => item && typeof item === 'object');
25
+ }
26
+ if (parsed && parsed.corpid) {
27
+ const { corpid, token, baseurl, userId } = parsed;
28
+ return [{ corpid, token, baseurl, userId, enable: true }];
29
+ }
30
+ return [];
31
+ }
32
+
33
+ function writeCompanies(companies) {
34
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
35
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(companies, null, 2) + '\n', 'utf8');
36
+ }
37
+
38
+ function enableOnly(companies, index) {
39
+ return companies.map((item, itemIndex) => ({ ...item, enable: itemIndex === index }));
40
+ }
41
+
42
+ function enableSingle(companies) {
43
+ const enabledIndex = companies.findIndex((item) => item.enable === true);
44
+ return enableOnly(companies, enabledIndex === -1 ? 0 : enabledIndex);
45
+ }
46
+
47
+ function maskToken(token) {
48
+ const value = String(token || '').trim();
49
+ if (!value) return '';
50
+ if (value.length <= 12) return `${value.slice(0, 4)}****`;
51
+ return `${value.slice(0, 8)}****${value.slice(-4)}`;
52
+ }
53
+ function createResult(status, message, corpid, enable, companyCount) {
54
+ return [{
55
+ status,
56
+ message,
57
+ configFile: CONFIG_FILE,
58
+ corpid,
59
+ enable,
60
+ companyCount,
61
+ }];
62
+ }
63
+
64
+ async function useCompany(kwargs) {
65
+ const corpid = String(kwargs.corpid || '').trim();
66
+ if (!corpid) {
67
+ return createResult('error', '缺少 --corpid', '', '', '');
68
+ }
69
+
70
+ if (isEnvOnly() || isEnvActive()) {
71
+ return createResult('error', `当前为环境变量模式(${getEnvVarNames().join(', ')}),不支持切换公司;如需多公司请改用 config.env`, corpid, '', '');
72
+ }
73
+
74
+ const companies = readCompanies();
75
+ if (!companies.length) {
76
+ return createResult('error', '缺少本地配置;请先执行 xbbcli token-set --corpid <CORPID> --token <TOKEN> --userId <USERID>', corpid, '', 0);
77
+ }
78
+
79
+ const index = companies.findIndex((item) => String(item.corpid || '').trim() === corpid);
80
+ if (index === -1) {
81
+ return createResult('error', `未找到 corpid=${corpid} 的本地配置`, corpid, '', companies.length);
82
+ }
83
+
84
+ const next = enableOnly(companies, index);
85
+ writeCompanies(next);
86
+ return createResult('ok', `已启用 corpid=${corpid},其余公司已标记为 enable=false`, corpid, 'true', next.length);
87
+ }
88
+
89
+ cli({
90
+ site: 'xbb',
91
+ name: 'token-use',
92
+ description: '切换当前启用的公司(enable),保证仅且只有一个公司被启用',
93
+ strategy: Strategy.PUBLIC,
94
+ access: 'write',
95
+ browser: false,
96
+ domain: 'proapi.xbongbong.com',
97
+ args: [
98
+ { name: 'corpid', type: 'str', help: '要启用的公司 id,必须是已保存的 corpid(必填)' },
99
+ ],
100
+ columns: ['status', 'message', 'configFile', 'corpid', 'enable', 'companyCount'],
101
+ func: useCompany,
102
+ });
package/user-add.js CHANGED
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { cli, Strategy } from './xbb-registry.js';
6
+ import { readActiveConfig } from './xbb-config.js';
6
7
 
7
8
  const CONFIG_FILE = path.join(os.homedir(), '.xbbcli', 'config.env');
8
9
  const API_URL = 'https://proapi.xbongbong.com/pro/v2/api/user/add';
@@ -10,11 +11,7 @@ const DEFAULT_BASE_URL = 'https://proapi.xbongbong.com';
10
11
  const MISSING_TOKEN_MESSAGE = '缺少 token;请先执行 xbbcli token-set --corpid <CORPID> --token <TOKEN> --userId <USERID>';
11
12
 
12
13
  function readConfig() {
13
- try {
14
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
15
- } catch {
16
- return {};
17
- }
14
+ return readActiveConfig();
18
15
  }
19
16
 
20
17
  function getRuntimeConfig() {
package/user-del.js CHANGED
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { cli, Strategy } from './xbb-registry.js';
6
+ import { readActiveConfig } from './xbb-config.js';
6
7
 
7
8
  const CONFIG_FILE = path.join(os.homedir(), '.xbbcli', 'config.env');
8
9
  const API_URL = 'https://proapi.xbongbong.com/pro/v2/api/user/del';
@@ -10,11 +11,7 @@ const DEFAULT_BASE_URL = 'https://proapi.xbongbong.com';
10
11
  const MISSING_TOKEN_MESSAGE = '缺少 token;请先执行 xbbcli token-set --corpid <CORPID> --token <TOKEN> --userId <USERID>';
11
12
 
12
13
  function readConfig() {
13
- try {
14
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
15
- } catch {
16
- return {};
17
- }
14
+ return readActiveConfig();
18
15
  }
19
16
 
20
17
  function getRuntimeConfig() {
package/user-edit.js CHANGED
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { cli, Strategy } from './xbb-registry.js';
6
+ import { readActiveConfig } from './xbb-config.js';
6
7
 
7
8
  const CONFIG_FILE = path.join(os.homedir(), '.xbbcli', 'config.env');
8
9
  const API_URL = 'https://proapi.xbongbong.com/pro/v2/api/user/edit';
@@ -10,11 +11,7 @@ const DEFAULT_BASE_URL = 'https://proapi.xbongbong.com';
10
11
  const MISSING_TOKEN_MESSAGE = '缺少 token;请先执行 xbbcli token-set --corpid <CORPID> --token <TOKEN> --userId <USERID>';
11
12
 
12
13
  function readConfig() {
13
- try {
14
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
15
- } catch {
16
- return {};
17
- }
14
+ return readActiveConfig();
18
15
  }
19
16
 
20
17
  function getRuntimeConfig() {
package/user-handover.js CHANGED
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { cli, Strategy } from './xbb-registry.js';
6
+ import { readActiveConfig } from './xbb-config.js';
6
7
 
7
8
  const CONFIG_FILE = path.join(os.homedir(), '.xbbcli', 'config.env');
8
9
  const API_URL = 'https://proapi.xbongbong.com/pro/v2/api/user/handover';
@@ -10,11 +11,7 @@ const DEFAULT_BASE_URL = 'https://proapi.xbongbong.com';
10
11
  const MISSING_TOKEN_MESSAGE = '缺少 token;请先执行 xbbcli token-set --corpid <CORPID> --token <TOKEN> --userId <USERID>';
11
12
 
12
13
  function readConfig() {
13
- try {
14
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
15
- } catch {
16
- return {};
17
- }
14
+ return readActiveConfig();
18
15
  }
19
16
 
20
17
  function getRuntimeConfig() {
package/user-list.js CHANGED
@@ -3,6 +3,7 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import crypto from 'node:crypto';
5
5
  import { cli, Strategy } from './xbb-registry.js';
6
+ import { readActiveConfig } from './xbb-config.js';
6
7
 
7
8
  const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
8
9
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
@@ -11,11 +12,7 @@ const USER_LIST_API_PATH = '/pro/v2/api/user/list';
11
12
  let lastPagination = null;
12
13
 
13
14
  function readConfig() {
14
- try {
15
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
16
- } catch {
17
- return {};
18
- }
15
+ return readActiveConfig();
19
16
  }
20
17
 
21
18
  function getRuntimeConfig() {
@@ -53,7 +50,7 @@ function makeErrorRow(code, msg) {
53
50
  name: '',
54
51
  position: '',
55
52
  jobnumber: '',
56
- avatar: '',
53
+ departmentList: '',
57
54
  code,
58
55
  msg,
59
56
  }];
@@ -78,7 +75,7 @@ cli({
78
75
  { name: 'debug', type: 'bool', default: false, help: '输出请求体和返回体调试信息' },
79
76
  { name: 'raw', type: 'bool', default: false, help: '输出接口返回的原文' },
80
77
  ],
81
- columns: ['rank', 'userId', 'name', 'position', 'jobnumber', 'avatar', 'code', 'msg'],
78
+ columns: ['rank', 'userId', 'name', 'position', 'jobnumber', 'departmentList', 'code', 'msg'],
82
79
  footerExtra: () => (lastPagination ? `totalCount ${lastPagination.totalCount} / totalPage ${lastPagination.totalPage}` : undefined),
83
80
  func: async (kwargs) => {
84
81
  const debug = Boolean(kwargs.debug);
@@ -139,7 +136,7 @@ cli({
139
136
  name: item.name || '',
140
137
  position: item.position || '',
141
138
  jobnumber: item.jobnumber || '',
142
- avatar: item.avatar || '',
139
+ departmentList: Array.isArray(item.departmentList) ? JSON.stringify(item.departmentList) : (item.departmentList || ''),
143
140
  code: '',
144
141
  msg: '',
145
142
  }));