@xbongbong/xbbcli 2.0.0 → 2.1.1

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