@xbongbong/xbbcli 2.2.0 → 2.4.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.
package/auth-status.js ADDED
@@ -0,0 +1,105 @@
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 { readActiveConfig, hasEnvConfig, 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 trim(value) {
11
+ return String(value ?? '').trim();
12
+ }
13
+
14
+ // token 只用于人眼确认是哪一枚,默认中间掩码;完整值需显式 --showToken
15
+ function maskToken(token) {
16
+ const value = trim(token);
17
+ if (!value) return '';
18
+ if (value.length <= 10) return `${value.slice(0, 2)}***`;
19
+ return `${value.slice(0, 6)}***${value.slice(-4)}`;
20
+ }
21
+
22
+ // 仅用于统计公司数量与检查重复启用,生效值一律以 readActiveConfig() 为准
23
+ function readCompanies() {
24
+ try {
25
+ const parsed = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
26
+ if (Array.isArray(parsed)) {
27
+ return parsed.filter((item) => item && typeof item === 'object');
28
+ }
29
+ if (parsed && Array.isArray(parsed.companies)) {
30
+ return parsed.companies.filter((item) => item && typeof item === 'object');
31
+ }
32
+ if (parsed && parsed.corpid) {
33
+ const { corpid, token, baseurl, userId } = parsed;
34
+ return [{ corpid, token, baseurl, userId, enable: true }];
35
+ }
36
+ return [];
37
+ } catch {
38
+ return [];
39
+ }
40
+ }
41
+
42
+ // 回显固定为 key/value 两列,只列配置值,不输出 status/source/message 之类的元信息
43
+ const FIELDS = ['corpid', 'corpName', 'userName', 'userId', 'baseurl', 'token'];
44
+
45
+ function toRows(values) {
46
+ return FIELDS.map((key) => ({ key, value: trim(values[key]) }));
47
+ }
48
+
49
+ function withNotes(rows, notes) {
50
+ return [...rows, ...notes.map((value) => ({ key: 'note', value }))];
51
+ }
52
+
53
+ async function authStatus(kwargs) {
54
+ const showToken = Boolean(kwargs.showToken);
55
+ const envActive = hasEnvConfig();
56
+ const envUsed = isEnvActive();
57
+ const active = readActiveConfig();
58
+ const companies = readCompanies();
59
+ const hasActive = Boolean(trim(active.corpid) || trim(active.token));
60
+ const source = hasActive ? (envUsed ? 'env' : 'config') : 'none';
61
+
62
+ if (kwargs.debug) {
63
+ const envNames = getEnvVarNames().filter((name) => trim(process.env[name])).join(', ');
64
+ process.stderr.write(`[debug] ConfigFile: ${CONFIG_FILE}\n[debug] Companies: ${companies.length}\n[debug] Source: ${source}\n[debug] XBB_ENV_ONLY: ${trim(process.env.XBB_ENV_ONLY) || 'unset'}\n[debug] EnvVarsSet: ${envNames || 'none'}\n`);
65
+ }
66
+
67
+ const token = trim(active.token);
68
+ const rows = toRows({ ...active, token: showToken ? token : maskToken(token) });
69
+
70
+ if (source === 'none') {
71
+ // 没有生效配置时不把残留的环境变量值当真值回显(XBB_BASEURL 等可能单独存在)
72
+ return withNotes(toRows({}), [
73
+ '没有处于激活状态的配置:config.env 中不存在 enable=true 的公司,环境变量 XBB_* 也为空;请先执行 xbbcli auth-login 或 xbbcli token-set',
74
+ ]);
75
+ }
76
+
77
+ if (source === 'env') {
78
+ const reason = isEnvOnly() ? 'XBB_ENV_ONLY=1 已强制使用环境变量' : 'config.env 中没有启用公司,回落到环境变量';
79
+ return withNotes(rows, [
80
+ `以上取值来自环境变量 XBB_*(${reason});环境变量模式不支持多公司切换,配置文件路径与公司数量见 --debug`,
81
+ ]);
82
+ }
83
+
84
+ const notes = [];
85
+ if (envActive) notes.push('环境变量 XBB_* 同时存在,但 config.env 启用项优先;如需彻底登出请执行 xbbcli auth-logout');
86
+ const enabledCount = companies.filter((item) => item.enable === true).length;
87
+ if (enabledCount > 1) notes.push(`config.env 中存在 ${enabledCount} 个 enable=true,仅第一条生效,请用 xbbcli token-use 修正`);
88
+ return withNotes(rows, notes);
89
+ }
90
+
91
+ cli({
92
+ site: 'xbb',
93
+ name: 'auth-status',
94
+ description: '回显当前激活的配置',
95
+ strategy: Strategy.PUBLIC,
96
+ access: 'read',
97
+ browser: false,
98
+ domain: 'proapi.xbongbong.com',
99
+ args: [
100
+ { name: 'showToken', type: 'bool', default: false, help: '显示完整 token;默认只输出中间掩码' },
101
+ { name: 'debug', type: 'bool', default: false, help: '在 stderr 输出配置文件路径、公司数量与生效的环境变量名(不含 token 明文)' },
102
+ ],
103
+ columns: ['key', 'value'],
104
+ func: authStatus,
105
+ });
@@ -48,7 +48,7 @@ function makeErrorRow(code, msg) {
48
48
 
49
49
  cli({
50
50
  site: 'xbb',
51
- name: 'ai-call-add-task',
51
+ name: 'hkh-call-add-task',
52
52
  description: '批量AI呼叫接口(businessType: -1;业务模块: hkh AI呼叫)',
53
53
  strategy: Strategy.PUBLIC,
54
54
  access: 'write',
@@ -35,7 +35,7 @@ function makeErrorRow(code, msg) {
35
35
 
36
36
  cli({
37
37
  site: 'xbb',
38
- name: 'ai-word-list',
38
+ name: 'hkh-word-list',
39
39
  description: '获取AI话术列表接口(businessType: -1;业务模块: hkh AI呼叫)',
40
40
  strategy: Strategy.PUBLIC,
41
41
  access: 'read',
@@ -0,0 +1,131 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { cli, Strategy } from './xbb-registry.js';
5
+ import { readActiveConfig } 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
+ const DEFAULT_BASE_URL = 'https://proapi.xbongbong.com';
10
+ const ORIGINAL_TEXT_LIST_API_PATH = '/pro/v2/api/aiAgent/getAiOriginalText';
11
+ let lastPagination = null;
12
+
13
+ function readConfig() {
14
+ return readActiveConfig();
15
+ }
16
+
17
+ function getRuntimeConfig() {
18
+ const config = readConfig();
19
+ return {
20
+ corpid: String(config.corpid || '').trim(),
21
+ token: String(config.token || '').trim(),
22
+ baseUrl: String(config.baseurl || DEFAULT_BASE_URL).trim(),
23
+ userId: String(config.userId || '').trim(),
24
+ };
25
+ }
26
+
27
+ function buildApiUrl(baseUrl) {
28
+ return `${baseUrl.replace(/\/+$/, '')}${ORIGINAL_TEXT_LIST_API_PATH}`;
29
+ }
30
+
31
+ function buildPayload(kwargs, corpid) {
32
+ const payload = {
33
+ corpid,
34
+ ...(String(kwargs.userId ?? '') !== '' ? { userId: String(kwargs.userId) } : {}),
35
+ ...(String(kwargs.page ?? '') !== '' ? { page: Number(kwargs.page) } : {}),
36
+ ...(String(kwargs.pageSize ?? '') !== '' ? { pageSize: Number(kwargs.pageSize) } : {}),
37
+ ...(String(kwargs.startTime ?? '') !== '' ? { startTime: Number(kwargs.startTime) } : {}),
38
+ ...(String(kwargs.endTime ?? '') !== '' ? { endTime: Number(kwargs.endTime) } : {}),
39
+ };
40
+ return payload;
41
+ }
42
+
43
+ function makeErrorRow(code, msg) {
44
+ return [{
45
+ rank: '',
46
+ originalUrl: '',
47
+ code,
48
+ msg,
49
+ }];
50
+ }
51
+
52
+ cli({
53
+ site: 'xbb',
54
+ name: 'orginal-text-list',
55
+ description: '员工AI转录原文数据列表',
56
+ strategy: Strategy.PUBLIC,
57
+ access: 'read',
58
+ browser: false,
59
+ domain: 'proapi.xbongbong.com',
60
+ args: [
61
+ { name: 'userId', type: 'str', default: '', help: '员工userId' },
62
+ { name: 'page', type: 'str', default: '', help: '页码(可选)' },
63
+ { name: 'pageSize', type: 'str', default: '', help: '每页数量(可选)' },
64
+ { name: 'startTime', type: 'str', default: '', help: '开始时间戳,单位秒(可选)' },
65
+ { name: 'endTime', type: 'str', default: '', help: '结束时间戳,单位秒(可选)' },
66
+ { name: 'debug', type: 'bool', default: false, help: '输出请求体和返回体调试信息' },
67
+ { name: 'raw', type: 'bool', default: false, help: '输出接口返回的原文' },
68
+ ],
69
+ columns: ['rank', 'originalUrl', 'code', 'msg'],
70
+ footerExtra: () => (lastPagination ? `totalCount ${lastPagination.totalCount}` : undefined),
71
+ func: async (kwargs) => {
72
+ const debug = Boolean(kwargs.debug);
73
+ const { corpid, token, baseUrl, userId } = getRuntimeConfig();
74
+ const payload = buildPayload(kwargs, corpid);
75
+ const body = JSON.stringify(payload);
76
+
77
+ if (!payload.corpid) {
78
+ return makeErrorRow('NO_CORPID', '缺少本地 corpid;请先执行 xbbcli token-set --corpid <CORPID> --token <TOKEN> --userId <USERID>');
79
+ }
80
+ if (!token) {
81
+ return makeErrorRow('NO_TOKEN', '缺少 token;请先执行 xbbcli token-set --corpid <CORPID> --token <TOKEN> --userId <USERID>');
82
+ }
83
+
84
+ const sign = crypto.createHash('sha256').update(body + token).digest('hex');
85
+ const headers = {
86
+ 'Content-Type': 'application/json;charset=UTF-8',
87
+ sign,
88
+ };
89
+ if (userId) headers.userId = userId;
90
+ const apiUrl = buildApiUrl(baseUrl);
91
+
92
+ if (debug) {
93
+ process.stderr.write(`[debug] URL: ${apiUrl}\n[debug] Headers: ${JSON.stringify(headers)}\n[debug] RequestBody: ${body}\n`);
94
+ }
95
+
96
+ const resp = await fetch(apiUrl, {
97
+ method: 'POST',
98
+ headers,
99
+ body,
100
+ });
101
+
102
+ if (!resp.ok) {
103
+ const responseText = await resp.text();
104
+ if (debug) process.stderr.write(`[debug] ResponseBody: ${responseText}\n`);
105
+ return makeErrorRow(resp.status, `HTTP ${resp.status} ${resp.statusText}`);
106
+ }
107
+
108
+ const data = await resp.json();
109
+ lastPagination = data?.code === 1 && data?.totalCount != null
110
+ ? { totalCount: data.totalCount }
111
+ : null;
112
+ const responseBody = JSON.stringify(data);
113
+ if (debug) process.stderr.write(`[debug] ResponseBody: ${responseBody}\n`);
114
+ if (kwargs.raw) return [{ raw: responseBody }];
115
+ if (data.code !== 1) {
116
+ return makeErrorRow(data.code ?? '', data.msg ?? '未知错误');
117
+ }
118
+
119
+ const list = Array.isArray(data.result?.list) ? data.result.list : [];
120
+ if (!list.length) {
121
+ return makeErrorRow('NO_DATA', '接口成功,但 list 为空');
122
+ }
123
+
124
+ return list.map((item, index) => ({
125
+ rank: index + 1,
126
+ originalUrl: item.originalUrl || '',
127
+ code: '',
128
+ msg: '',
129
+ }));
130
+ },
131
+ });
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@xbongbong/xbbcli",
3
- "version": "2.2.0",
3
+ "version": "2.4.1",
4
4
  "description": "销帮帮 CRM 的独立命令行工具",
5
5
  "type": "module",
6
6
  "bin": {
7
- "xbbcli": "./bin/xbbcli.js"
7
+ "xbbcli": "bin/xbbcli.js"
8
8
  },
9
9
  "files": [
10
10
  "bin",
package/token-set.js CHANGED
@@ -1,389 +1,5 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { cli, Strategy, getRegistry } from './xbb-registry.js';
5
- import { persistEnvVars } 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
- 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;
14
-
15
- function resolveBaseUrl(corpid) {
16
- if (corpid.startsWith('ding') || corpid.includes('$$ding')) {
17
- return 'https://proapi.xbongbong.com';
18
- }
19
- return 'https://appapi.xbongbong.com';
20
- }
21
-
22
- function normalizeArg(value) {
23
- return String(value || '').trim();
24
- }
25
-
26
- function getFormlistFile(corpid) {
27
- return path.join(CONFIG_DIR, `${corpid}${FORMLIST_FILE_SUFFIX}`);
28
- }
29
-
30
- function getCommandMapFile(corpid) {
31
- return path.join(CONFIG_DIR, `${corpid}${COMMAND_MAP_FILE_SUFFIX}`);
32
- }
33
-
34
- async function runCommandJson(name, kwargs) {
35
- const command = getRegistry().get(`xbb/${name}`);
36
- if (!command) throw new Error(`找不到命令:${name}`);
37
- const rows = await command.func(kwargs);
38
- if (!Array.isArray(rows)) throw new Error(`${name} 返回结果不是数组`);
39
- return rows;
40
- }
41
-
42
- async function getFormlistRows(corpid, saasMark) {
43
- const rows = await runCommandJson('form-list', { saasMark: String(saasMark) });
44
-
45
- if (!Array.isArray(rows)) {
46
- throw new Error(`form-list 返回结果不是数组:saasMark=${saasMark}`);
47
- }
48
-
49
- const errorRow = rows.find((item) => item && item.code);
50
- if (errorRow) {
51
- throw new Error(`form-list 获取失败(saasMark=${saasMark}):${errorRow.code} ${errorRow.msg || ''}`.trim());
52
- }
53
-
54
- return rows;
55
- }
56
-
57
- async function requestPersonalToken(corpid, token, userId, resetToken) {
58
- const rows = await runCommandJson('token-generate', {
59
- checkUserId: userId,
60
- resetToken: String(resetToken),
61
- token,
62
- corpid,
63
- });
64
-
65
- if (!Array.isArray(rows)) {
66
- throw new Error('token-generate 返回结果不是数组');
67
- }
68
-
69
- const errorRow = rows.find((item) => item && String(item.code ?? '') !== '' && Number(item.code) !== 1);
70
- if (errorRow) {
71
- throw new Error(`token-generate 获取个人 token 失败:${errorRow.code} ${errorRow.msg || ''}`.trim());
72
- }
73
-
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);
88
- if (!personalToken.startsWith('user_')) {
89
- throw new Error('token-generate 未返回有效的个人 token');
90
- }
91
-
92
- return personalToken;
93
- }
94
-
95
- async function writeFormlistFile(corpid) {
96
- const customForms = await getFormlistRows(corpid, 2);
97
- const systemForms = await getFormlistRows(corpid, 1);
98
- const formlistFile = getFormlistFile(corpid);
99
- const mergedRows = [...customForms, ...systemForms];
100
-
101
- fs.writeFileSync(formlistFile, JSON.stringify(mergedRows, null, 2) + '\n', 'utf8');
102
- return formlistFile;
103
- }
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
-
205
- function normalizeBusinessType(value) {
206
- return String(value).trim().replace(/[))\]】,,;;::。!?!?]+$/g, '');
207
- }
208
-
209
- function getCommandBusinessType(command) {
210
- if (command.businessType !== undefined && command.businessType !== null) {
211
- return normalizeBusinessType(command.businessType);
212
- }
213
- const description = String(command.description || '');
214
- const match = description.match(/businessType\s*:\s*(\d+)/i);
215
- return match ? normalizeBusinessType(match[1]) : '';
216
- }
217
-
218
- function escapeCell(value) {
219
- return String(value ?? '').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
220
- }
221
-
222
- function optionDescription(option) {
223
- return `--${option.name}: ${option.help || ''}`;
224
- }
225
-
226
- function writeCommandMap(corpid) {
227
- const formFile = getFormlistFile(corpid);
228
- if (!fs.existsSync(formFile)) {
229
- throw new Error(`Form cache not found: ${formFile}`);
230
- }
231
- const raw = fs.readFileSync(formFile, 'utf8').replace(/^\uFEFF/, '');
232
- const data = JSON.parse(raw);
233
- const forms = Array.isArray(data) ? data : data.data || [];
234
-
235
- const commands = [...getRegistry().values()]
236
- .filter((command) => command.site === 'xbb')
237
- .map((command) => ({
238
- ...command,
239
- command_options: command.args || [],
240
- }));
241
-
242
- const lines = [
243
- '# XBB 命令映射',
244
- '',
245
- `来源:xbb --help;表单缓存:${path.basename(formFile)}`,
246
- '',
247
- '| 命令 | 命令名称 | formId | businessType | 字段说明 |',
248
- '| --- | --- | --- | --- | --- |',
249
- ];
250
-
251
- for (const command of commands) {
252
- const commandBusinessType = getCommandBusinessType(command);
253
- const matchedForms = forms.filter((form) => String(form.businessType ?? '').trim() === commandBusinessType);
254
- const formIds = [...new Set(matchedForms.map((form) => form.formId).filter(Boolean))];
255
- const businessTypes = [...new Set(matchedForms.map((form) => form.businessType).filter(Boolean))];
256
- const options = (command.command_options || []).map(optionDescription).join('<br>');
257
- lines.push(
258
- `| ${escapeCell(command.name)} | ${escapeCell(command.description)} | ${escapeCell(formIds.join(', '))} | ${escapeCell(businessTypes.join(', ') || commandBusinessType)} | ${escapeCell(options)} |`,
259
- );
260
- }
261
-
262
- const commandMapFile = getCommandMapFile(corpid);
263
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
264
- fs.writeFileSync(commandMapFile, `${lines.join('\n')}\n`, 'utf8');
265
- return commandMapFile;
266
- }
267
-
268
- function createResult(status, message, corpid, baseurl, userId, formlistFile = '', commandMapFile = '', enable = '', companyCount = '', departmentUserFile = '', corpName = '', userName = '', envStored = '', envFile = '') {
269
- return [{
270
- status,
271
- message,
272
- configFile: CONFIG_FILE,
273
- corpid,
274
- baseurl,
275
- userId,
276
- enable,
277
- companyCount,
278
- formlistFile,
279
- commandMapFile,
280
- departmentUserFile,
281
- corpName,
282
- userName,
283
- envStored,
284
- envFile,
285
- }];
286
- }
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
-
322
- async function setToken(kwargs) {
323
- const corpid = normalizeArg(kwargs.corpid);
324
- const token = normalizeArg(kwargs.token);
325
- const userId = normalizeArg(kwargs.userId);
326
-
327
- if (!corpid) {
328
- return createResult('error', '缺少 --corpid', '', '', '');
329
- }
330
-
331
- if (!token) {
332
- return createResult('error', '缺少 --token', corpid, '', '');
333
- }
334
-
335
- if (!userId) {
336
- return createResult('error', '缺少 --userId', corpid, '', '');
337
- }
338
-
339
- let personalToken = token;
340
- if (!token.startsWith('user_')) {
341
- try {
342
- personalToken = await getPersonalToken(corpid, token, userId);
343
- } catch (error) {
344
- return createResult('error', `生成个人 token 失败:${error.message}`, corpid, '', userId);
345
- }
346
- }
347
-
348
- const baseurl = resolveBaseUrl(corpid);
349
- const companies = upsertCompany(readCompanies(), { corpid, token: personalToken, baseurl, userId });
350
-
351
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
352
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(companies, null, 2) + '\n', 'utf8');
353
-
354
- try {
355
- const formlistFile = await writeFormlistFile(corpid);
356
- const commandMapFile = writeCommandMap(corpid);
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);
372
- } catch (error) {
373
- return createResult(
374
- 'partial',
375
- `已保存 corpid、token、baseurl、userId,但同步缓存失败:${error.message}`,
376
- corpid,
377
- baseurl,
378
- userId,
379
- getFormlistFile(corpid),
380
- '',
381
- true,
382
- companies.length,
383
- '',
384
- );
385
- }
386
- }
1
+ import { cli, Strategy } from './xbb-registry.js';
2
+ import { saveCompanyCredentials } from './xbb-token-store.js';
387
3
 
388
4
  cli({
389
5
  site: 'xbb',
@@ -396,8 +12,8 @@ cli({
396
12
  { name: 'corpid', type: 'str', help: '公司id(必填)' },
397
13
  { name: 'token', type: 'str', help: '要保存的 API token' },
398
14
  { name: 'userId', type: 'str', help: '操作人id(必填)' },
399
- { name: 'noEnv', type: 'bool', default: false, help: '不写入环境变量,仅保存到 config.env' },
15
+ { name: 'env', type: 'str', default: '0', help: '存储方式:0 仅写入 config.env 文件(默认),1 仅写入环境变量(不写任何本地文件)' },
400
16
  ],
401
17
  columns: ['status', 'message', 'configFile', 'corpid', 'baseurl', 'userId', 'enable', 'companyCount', 'formlistFile', 'commandMapFile', 'departmentUserFile', 'corpName', 'userName', 'envStored', 'envFile'],
402
- func: setToken,
403
- });
18
+ func: saveCompanyCredentials,
19
+ });