@dreamor/atlas-cli 0.7.22 → 0.7.24

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 (43) hide show
  1. package/README.github.md +230 -0
  2. package/README.md +1 -55
  3. package/atlas.cjs +226 -0
  4. package/package.json +7 -9
  5. package/atlas.js +0 -5
  6. package/dist/adapters/atlas/auth/browser.js +0 -37
  7. package/dist/adapters/atlas/auth/index.js +0 -3
  8. package/dist/adapters/atlas/auth/login.js +0 -147
  9. package/dist/adapters/atlas/auth/refresh.js +0 -138
  10. package/dist/adapters/atlas/auth/session.js +0 -115
  11. package/dist/adapters/atlas/cli.js +0 -512
  12. package/dist/adapters/atlas/commands/_output_schema.js +0 -379
  13. package/dist/adapters/atlas/commands/actual/_logic.js +0 -116
  14. package/dist/adapters/atlas/commands/actual/index.js +0 -138
  15. package/dist/adapters/atlas/commands/auth.js +0 -1
  16. package/dist/adapters/atlas/commands/baseline/index.js +0 -137
  17. package/dist/adapters/atlas/commands/compare/_logic.js +0 -39
  18. package/dist/adapters/atlas/commands/compare/index.js +0 -89
  19. package/dist/adapters/atlas/commands/exec.js +0 -81
  20. package/dist/adapters/atlas/commands/project/index.js +0 -218
  21. package/dist/adapters/atlas/commands/schema.js +0 -25
  22. package/dist/adapters/atlas/commands/suggest.js +0 -83
  23. package/dist/adapters/atlas/commands/update.js +0 -104
  24. package/dist/adapters/atlas/daemon/index.js +0 -64
  25. package/dist/adapters/atlas/dict/index.js +0 -41
  26. package/dist/adapters/atlas/http/client.js +0 -200
  27. package/dist/adapters/atlas/http/index.js +0 -1
  28. package/dist/adapters/atlas/schema/actual.js +0 -16
  29. package/dist/adapters/atlas/schema/baseline.js +0 -34
  30. package/dist/adapters/atlas/schema/department.js +0 -11
  31. package/dist/adapters/atlas/schema/index.js +0 -4
  32. package/dist/adapters/atlas/schema/project.js +0 -13
  33. package/dist/adapters/atlas/util/constants.js +0 -4
  34. package/dist/adapters/atlas/util/env.js +0 -56
  35. package/dist/adapters/atlas/util/errors.js +0 -49
  36. package/dist/adapters/atlas/util/helpers.js +0 -17
  37. package/dist/adapters/atlas/util/months.js +0 -65
  38. package/dist/adapters/atlas/util/output-limit.js +0 -21
  39. package/dist/adapters/atlas/util/output.js +0 -70
  40. package/dist/adapters/atlas/util/paths.js +0 -40
  41. package/dist/adapters/atlas/util/secure-fs.js +0 -41
  42. package/dist/adapters/atlas/util/time.js +0 -17
  43. package/dist/adapters/atlas/util/version.js +0 -1
@@ -1,379 +0,0 @@
1
- const schemas = {
2
- 'atlas auth login': {
3
- jsonSchema: {
4
- type: 'object',
5
- properties: {
6
- ok: { type: 'boolean', const: true },
7
- data: {
8
- type: 'object',
9
- properties: {
10
- status: { type: 'string', enum: ['logged_in'] },
11
- via: { type: 'string', enum: ['daemon'] },
12
- },
13
- },
14
- },
15
- },
16
- },
17
- 'atlas auth status': {
18
- jsonSchema: {
19
- type: 'object',
20
- properties: {
21
- ok: { type: 'boolean' },
22
- data: {
23
- type: 'object',
24
- properties: {
25
- loggedIn: { type: 'boolean' },
26
- expiresAt: { type: 'number' },
27
- },
28
- },
29
- },
30
- },
31
- },
32
- 'atlas auth refresh': {
33
- jsonSchema: {
34
- type: 'object',
35
- properties: {
36
- ok: { type: 'boolean', const: true },
37
- data: {
38
- type: 'object',
39
- properties: {
40
- status: { type: 'string', enum: ['refreshed', 'still_valid'] },
41
- account: { type: 'string' },
42
- },
43
- },
44
- },
45
- },
46
- },
47
- 'atlas projects': {
48
- jsonSchema: {
49
- type: 'object',
50
- properties: {
51
- ok: { type: 'boolean' },
52
- data: { type: 'array', items: { type: 'object' } },
53
- },
54
- },
55
- },
56
- 'atlas find': {
57
- jsonSchema: {
58
- type: 'object',
59
- properties: {
60
- ok: { type: 'boolean' },
61
- data: {
62
- type: 'array',
63
- items: { type: 'object' },
64
- },
65
- meta: {
66
- type: 'object',
67
- properties: {
68
- count: { type: 'number' },
69
- kind: { type: 'string' },
70
- query: { type: 'string' },
71
- },
72
- },
73
- },
74
- },
75
- },
76
- 'atlas link': {
77
- jsonSchema: {
78
- type: 'object',
79
- properties: {
80
- ok: { type: 'boolean' },
81
- data: {
82
- type: 'object',
83
- properties: {
84
- projectId: { type: 'string' },
85
- projectName: { type: 'string' },
86
- linkedAt: { type: 'string' },
87
- linked: { type: 'object', description: '已绑定的项目信息(link 无参时)' },
88
- envProjectId: { type: 'string', description: 'BANMA_PROJECT_ID 环境变量值' },
89
- dryRun: { type: 'boolean' },
90
- },
91
- },
92
- },
93
- },
94
- },
95
- 'atlas unlink': {
96
- jsonSchema: {
97
- type: 'object',
98
- properties: {
99
- ok: { type: 'boolean' },
100
- data: {
101
- type: 'object',
102
- properties: {
103
- unlinked: { type: 'boolean' },
104
- projectName: { type: 'string' },
105
- },
106
- },
107
- },
108
- },
109
- },
110
- 'atlas baseline month': {
111
- jsonSchema: {
112
- type: 'object',
113
- properties: {
114
- ok: { type: 'boolean' },
115
- data: {
116
- type: 'object',
117
- properties: {
118
- projectId: { type: 'string' },
119
- months: { type: 'array', items: { type: 'string' }, description: 'YYYY-MM 格式月份列表' },
120
- entries: {
121
- type: 'array',
122
- items: {
123
- type: 'object',
124
- description: '单个月份的基线条目',
125
- },
126
- },
127
- totalManpower: { type: 'number', description: '单位:人月' },
128
- },
129
- },
130
- },
131
- },
132
- },
133
- 'atlas baseline summary': {
134
- jsonSchema: {
135
- type: 'object',
136
- properties: {
137
- ok: { type: 'boolean' },
138
- data: {
139
- type: 'array',
140
- items: {
141
- type: 'object',
142
- properties: {
143
- month: { type: 'string', description: 'YYYY-MM' },
144
- manpower: { type: 'number', description: '单位:人月' },
145
- },
146
- },
147
- },
148
- },
149
- },
150
- },
151
- 'atlas baseline export': {
152
- jsonSchema: {
153
- type: 'object',
154
- properties: {
155
- ok: { type: 'boolean' },
156
- data: {
157
- type: 'object',
158
- properties: {
159
- exported: { type: 'number', description: '导出行数' },
160
- format: { type: 'string', enum: ['csv', 'json'] },
161
- out: { type: 'string', description: '导出文件绝对路径(经过白名单校验)' },
162
- },
163
- },
164
- },
165
- },
166
- },
167
- 'atlas actual show': {
168
- jsonSchema: {
169
- type: 'object',
170
- properties: {
171
- ok: { type: 'boolean' },
172
- data: {
173
- type: 'object',
174
- properties: {
175
- staffId: { type: 'string' },
176
- month: { type: 'string', description: 'YYYY-MM' },
177
- personnel: { type: 'array', items: { type: 'object' } },
178
- },
179
- },
180
- },
181
- },
182
- },
183
- 'atlas actual month': {
184
- jsonSchema: {
185
- type: 'object',
186
- properties: {
187
- ok: { type: 'boolean' },
188
- data: {
189
- type: 'object',
190
- properties: {
191
- projectId: { type: 'string' },
192
- entries: { type: 'array', items: { type: 'object' } },
193
- },
194
- },
195
- meta: {
196
- type: 'object',
197
- properties: {
198
- failedMonths: { type: 'array', items: { type: 'string' } },
199
- },
200
- },
201
- },
202
- },
203
- },
204
- 'atlas actual summary': {
205
- jsonSchema: {
206
- type: 'object',
207
- properties: {
208
- ok: { type: 'boolean' },
209
- data: {
210
- type: 'object',
211
- properties: {
212
- rows: { type: 'array', items: { type: 'object' } },
213
- },
214
- },
215
- meta: {
216
- type: 'object',
217
- properties: {
218
- failedMonths: { type: 'array', items: { type: 'string' } },
219
- },
220
- },
221
- },
222
- },
223
- },
224
- 'atlas actual export': {
225
- jsonSchema: {
226
- type: 'object',
227
- properties: {
228
- ok: { type: 'boolean' },
229
- data: {
230
- type: 'object',
231
- properties: {
232
- exported: { type: 'number', description: '导出行数' },
233
- format: { type: 'string', enum: ['csv', 'json'] },
234
- out: { type: 'string', description: '导出文件绝对路径' },
235
- },
236
- },
237
- meta: {
238
- type: 'object',
239
- properties: {
240
- failedMonths: { type: 'array', items: { type: 'string' } },
241
- },
242
- },
243
- },
244
- },
245
- },
246
- 'atlas compare': {
247
- jsonSchema: {
248
- type: 'object',
249
- properties: {
250
- ok: { type: 'boolean' },
251
- data: {
252
- type: 'object',
253
- properties: {
254
- rows: {
255
- type: 'array',
256
- items: {
257
- type: 'object',
258
- properties: {
259
- month: { type: 'string', description: 'YYYY-MM' },
260
- plan: { type: 'number', description: '基线人月' },
261
- actual: { type: 'number', description: '实际人月' },
262
- diff: { type: 'number', description: '实际 - 基线(人月)' },
263
- },
264
- },
265
- },
266
- },
267
- },
268
- meta: {
269
- type: 'object',
270
- properties: {
271
- failedMonths: { type: 'array', items: { type: 'string' } },
272
- },
273
- },
274
- },
275
- },
276
- },
277
- 'atlas schema commands': {
278
- jsonSchema: {
279
- type: 'object',
280
- properties: {
281
- ok: { type: 'boolean' },
282
- data: {
283
- type: 'object',
284
- properties: {
285
- commands: { type: 'array', items: { type: 'string' } },
286
- },
287
- },
288
- },
289
- },
290
- },
291
- 'atlas exec': {
292
- jsonSchema: {
293
- type: 'object',
294
- properties: {
295
- ok: { type: 'boolean' },
296
- data: {
297
- type: 'object',
298
- properties: {
299
- results: {
300
- type: 'array',
301
- items: {
302
- type: 'object',
303
- properties: {
304
- step: { type: 'number' },
305
- command: { type: 'string' },
306
- status: { type: 'string', description: 'success | failed: <msg>' },
307
- },
308
- },
309
- },
310
- },
311
- },
312
- },
313
- },
314
- },
315
- 'atlas suggest': {
316
- jsonSchema: {
317
- type: 'object',
318
- properties: {
319
- ok: { type: 'boolean' },
320
- data: {
321
- type: 'object',
322
- properties: {
323
- query: { type: 'string' },
324
- suggestions: {
325
- type: 'array',
326
- maxItems: 5,
327
- items: {
328
- type: 'object',
329
- properties: {
330
- command: { type: 'string', description: 'atlas 子命令片段' },
331
- description: { type: 'string' },
332
- score: { type: 'number' },
333
- },
334
- },
335
- },
336
- },
337
- },
338
- },
339
- },
340
- },
341
- 'atlas update': {
342
- jsonSchema: {
343
- type: 'object',
344
- properties: {
345
- ok: { type: 'boolean' },
346
- data: {
347
- type: 'object',
348
- properties: {
349
- mode: { type: 'string', enum: ['npm', 'other'] },
350
- disabled: { type: 'boolean' },
351
- current: { type: 'string', description: '当前版本号' },
352
- updateCommand: { type: 'string', description: 'npm 更新命令' },
353
- migrationCommand: { type: 'string', description: '迁移到 npm 安装的命令' },
354
- },
355
- },
356
- },
357
- },
358
- },
359
- 'atlas daemon': {
360
- jsonSchema: {
361
- type: 'object',
362
- properties: {
363
- ok: { type: 'boolean' },
364
- data: { type: 'object' },
365
- },
366
- },
367
- },
368
- };
369
- export function getOutputSchema(commandPath) {
370
- return schemas[commandPath] ?? {
371
- jsonSchema: {
372
- type: 'object',
373
- properties: {
374
- ok: { type: 'boolean' },
375
- data: {},
376
- },
377
- },
378
- };
379
- }
@@ -1,116 +0,0 @@
1
- /**
2
- * actual 命令的纯逻辑层。
3
- *
4
- * 与命令解析层(index.ts)分离:这里不做 IO、不调 API、不输出,
5
- * 只做数据变换。便于单元测试(参见 tests/actual_logic.test.ts)。
6
- *
7
- * 单位约定(关键):
8
- * - API detail.manpower 字段单位为**人天**,不是人月。
9
- * - 网页"人力汇总"页显示的人月 = Σ(叶子节点 detail.manpower) ÷ WORK_DAYS_PER_MONTH。
10
- * - 经 2026-07-01 实测验证:BMW(2548) 2026-03 叶子节点合计 321.9 人天 ÷ 22 = 14.63 人月,
11
- * 与网页"各项目显示值:已确认"完全一致。
12
- */
13
- /** 每月工作日,用于把 API 返回的人天换算为人月(与网页口径一致) */
14
- export const WORK_DAYS_PER_MONTH = 22;
15
- /**
16
- * expandMonths 已移至 util/months.ts,此处不再导出。
17
- */
18
- /** 给每条记录标注 month 字段,返回新数组(不改原数据) */
19
- export function annotateWithMonth(entries, month) {
20
- return entries.map((p) => ({ ...p, month }));
21
- }
22
- /** 将 CLI 字符串选项规范化为 StatusFilter,默认 approved(与网站默认视图对齐) */
23
- export function normalizeStatusFilter(raw) {
24
- if (raw === 'all' || raw === 'pending' || raw === 'approved')
25
- return raw;
26
- return 'approved';
27
- }
28
- /** 判断单条 detail 记录是否通过状态筛选 */
29
- function passesStatusFilter(statusRaw, filter) {
30
- const status = Number(statusRaw);
31
- if (filter === 'approved')
32
- return status === 2;
33
- if (filter === 'pending')
34
- return status !== 2;
35
- return true;
36
- }
37
- /**
38
- * 按轴聚合人月。
39
- * - month:按月份分组(需先用 annotateWithMonth 打标)
40
- * - department:按 departmentName 分组,缺失归 "其他"
41
- * - role:按 role 分组,缺失归 "其他"
42
- * 返回 [{ [axis]: key, manpower }],manpower 四舍五入到 2 位。
43
- */
44
- export function aggregateByAxis(rows, axis) {
45
- const groups = new Map();
46
- for (const p of rows) {
47
- const k = axis === 'month' ? p.month
48
- : axis === 'department' ? (p.departmentName ?? '其他')
49
- : (p.role ?? '其他');
50
- groups.set(k, (groups.get(k) ?? 0) + (p.manpower ?? 0));
51
- }
52
- return [...groups.entries()].map(([k, v]) => ({
53
- [axis]: k,
54
- manpower: Math.round(v * 100) / 100,
55
- }));
56
- }
57
- /**
58
- * 过滤出指定人员的记录:staffId 精确匹配,或 staffName 包含匹配。
59
- */
60
- export function filterByStaff(entries, staffId) {
61
- return entries.filter((p) => p.staffId === staffId || p.staffName?.includes(staffId));
62
- }
63
- /**
64
- * 走查 summaryByProject 返回的树结构,提取指定项目的实际工时条目(人月)。
65
- *
66
- * 关键口径(与网页"人力汇总"页一致,2026-07-01 实测验证):
67
- * 1. **只在叶子节点累加**:manager 节点的 detail 是下属汇总的副本,
68
- * 若一并计入会把同一笔工时重复累加(实测会把 321.9 人天虚增到 440.6)。
69
- * 叶子 = children 为 null 或空数组的节点。
70
- * 2. **人天 ÷ 工作日 → 人月**:API detail.manpower 单位是人天,
71
- * 需除以 WORK_DAYS_PER_MONTH(22) 才是网页显示的人月。
72
- * 3. **状态筛选**:默认 approved 只统计 status===2(已确认),与网页默认视图对齐。
73
- *
74
- * 返回按员工去重的条目,manpower 为人月(四舍五入 2 位)。
75
- */
76
- export function flattenTree(nodes, targetPid, statusFilter = 'approved') {
77
- const byStaffId = new Map();
78
- function isLeaf(node) {
79
- const kids = node.children;
80
- return !Array.isArray(kids) || kids.length === 0;
81
- }
82
- function walk(items) {
83
- for (const raw of items ?? []) {
84
- const node = raw;
85
- // 只在叶子节点累加 detail,避免 manager 层重复计入
86
- if (isLeaf(node)) {
87
- const details = (node.detail ?? []);
88
- let personDays = 0;
89
- for (const d of details) {
90
- if (String(d.projectId) === targetPid && passesStatusFilter(d.status, statusFilter)) {
91
- personDays += Number(d.manpower ?? 0);
92
- }
93
- }
94
- if (personDays > 0) {
95
- const sid = String(node.staffId ?? '');
96
- const manMonths = personDays / WORK_DAYS_PER_MONTH;
97
- // 同一叶子节点不可能重复出现;若员工跨多节点,按 manpower 累加
98
- const existing = byStaffId.get(sid);
99
- const total = (existing?.manpower ?? 0) + manMonths;
100
- byStaffId.set(sid, {
101
- staffId: sid,
102
- staffName: String(node.realname ?? ''),
103
- manpower: Math.round(total * 100) / 100,
104
- departmentName: String(node.department ?? ''),
105
- role: String(node.role ?? ''),
106
- });
107
- }
108
- }
109
- else {
110
- walk(node.children);
111
- }
112
- }
113
- }
114
- walk(nodes);
115
- return [...byStaffId.values()];
116
- }
@@ -1,138 +0,0 @@
1
- import { createClient } from '../../http/client.js';
2
- import { isJsonMode, jsonOk, log } from '../../util/output.js';
3
- import { enforceOutputLimit } from '../../util/output-limit.js';
4
- import { expandMonths, expandMonthsDefault } from '../../util/months.js';
5
- import { resolveSecureExportPath, secureWriteFile } from '../../util/secure-fs.js';
6
- import { annotateWithMonth, aggregateByAxis, filterByStaff, flattenTree, normalizeStatusFilter, } from './_logic.js';
7
- import { resolveProjectId } from '../../util/env.js';
8
- import { readBanmaIdentity } from '../../auth/session.js';
9
- function getProjectId(opts) {
10
- return resolveProjectId(opts.projectId);
11
- }
12
- async function fetchActual(pid, month, statusFilter) {
13
- const client = createClient();
14
- const identity = await readBanmaIdentity();
15
- const loginStaffId = identity?.staffId ?? '';
16
- // 项目视图:使用 summaryByProject 获取全项目维度的实际工时。
17
- // API 返回全部状态的数据,statusFilter 在客户端按 status 字段过滤(与网站一致)。
18
- const raw = await client.post('/yuntu-service/manpower/weekly/summaryByProject.json', { month, staffIds: [], projectIds: [pid], isConfirm: false, loginStaffId });
19
- const tree = (Array.isArray(raw) ? raw : []);
20
- // 根节点为当前用户,children 为团队成员
21
- if (tree.length > 0 && tree[0].children) {
22
- return flattenTree(tree[0].children, pid, statusFilter);
23
- }
24
- return [];
25
- }
26
- export async function showCmd(staffId, opts) {
27
- const pid = getProjectId(opts);
28
- const m = opts.month ?? expandMonthsDefault()[0];
29
- const data = await fetchActual(pid, m, normalizeStatusFilter(opts.status));
30
- const filtered = filterByStaff(data, staffId);
31
- if (opts.json || isJsonMode()) {
32
- jsonOk({ staffId, month: m, personnel: filtered });
33
- return;
34
- }
35
- if (filtered.length === 0) {
36
- log(`${staffId} 在 ${m} 无实际工时数据`);
37
- return;
38
- }
39
- for (const p of filtered) {
40
- log(` ${p.staffName} (${p.staffId}): ${p.manpower.toFixed(2)} 人月`);
41
- }
42
- }
43
- export async function monthCmd(opts) {
44
- const pid = getProjectId(opts);
45
- // 不传月份时默认最近 12 个月,与 baseline month 行为一致
46
- const months = opts.month ? [opts.month] : (opts.from || opts.to ? expandMonths(opts.from, opts.to) : expandMonthsDefault());
47
- const statusFilter = normalizeStatusFilter(opts.status);
48
- const all = [];
49
- const failedMonths = [];
50
- for (const m of months) {
51
- try {
52
- const data = await fetchActual(pid, m, statusFilter);
53
- all.push(...annotateWithMonth(data, m));
54
- }
55
- catch (e) {
56
- // 实际工时 API 对无数据月份也返回 501("当前token已失效"),
57
- // 不是真正的 token 过期。不 rethrow,只记录到 failedMonths。
58
- failedMonths.push({ month: m, error: e instanceof Error ? e.message : String(e) });
59
- log(`月 ${m} 无实际工时数据`);
60
- }
61
- }
62
- // Apply --department / --role filter
63
- const filtered = all.filter(p => (!opts.department || (p.departmentName && p.departmentName.includes(opts.department)))
64
- && (!opts.role || (p.role && p.role.includes(opts.role))));
65
- if (opts.json || isJsonMode()) {
66
- jsonOk({ projectId: pid, entries: filtered }, { failedMonths });
67
- return;
68
- }
69
- if (filtered.length === 0) {
70
- log('无实际工时数据');
71
- return;
72
- }
73
- for (const p of filtered) {
74
- log(` ${p.staffName}: ${p.manpower.toFixed(2)} (${p.role ?? ''})`);
75
- }
76
- }
77
- export async function summaryCmd(opts) {
78
- const pid = getProjectId(opts);
79
- const months = opts.month ? [opts.month] : (opts.from || opts.to ? expandMonths(opts.from, opts.to) : expandMonthsDefault());
80
- const statusFilter = normalizeStatusFilter(opts.status);
81
- const all = [];
82
- const failedMonths = [];
83
- for (const m of months) {
84
- try {
85
- all.push(...annotateWithMonth(await fetchActual(pid, m, statusFilter), m));
86
- }
87
- catch (e) {
88
- // 实际工时 API 对无数据月份也返回 501("当前token已失效"),
89
- // 不是真正的 token 过期。不 rethrow,只记录到 failedMonths。
90
- failedMonths.push({ month: m, error: e instanceof Error ? e.message : String(e) });
91
- log(`月 ${m} 无实际工时数据`);
92
- }
93
- }
94
- // Apply --department / --role filter
95
- const filtered = all.filter(p => (!opts.department || (p.departmentName && p.departmentName.includes(opts.department)))
96
- && (!opts.role || (p.role && p.role.includes(opts.role))));
97
- const axis = (opts.by ?? 'month');
98
- const rows = aggregateByAxis(filtered, axis);
99
- if (opts.json || isJsonMode()) {
100
- jsonOk({ rows }, { failedMonths });
101
- return;
102
- }
103
- for (const r of rows) {
104
- log(` ${r[axis]}: ${r.manpower.toFixed(2)} 人月`);
105
- }
106
- }
107
- export async function exportCmd(opts) {
108
- const pid = getProjectId(opts);
109
- const months = expandMonths(opts.from, opts.to);
110
- const statusFilter = normalizeStatusFilter(opts.status);
111
- const rows = [];
112
- const failedMonths = [];
113
- for (const m of months) {
114
- try {
115
- const data = await fetchActual(pid, m, statusFilter);
116
- rows.push(...annotateWithMonth(data, m));
117
- }
118
- catch (e) {
119
- // 实际工时 API 对无数据月份也返回 501("当前token已失效"),
120
- // 不是真正的 token 过期。不 rethrow,只记录到 failedMonths。
121
- failedMonths.push({ month: m, error: e instanceof Error ? e.message : String(e) });
122
- log(`月 ${m} 无实际工时数据`);
123
- }
124
- }
125
- // Apply --department / --role filter
126
- const filteredRows = rows.filter(p => (!opts.department || (p.departmentName && p.departmentName.includes(opts.department)))
127
- && (!opts.role || (p.role && p.role.includes(opts.role))));
128
- const content = opts.format === 'csv'
129
- ? (await import('papaparse')).default.unparse(filteredRows)
130
- : JSON.stringify(filteredRows, null, 2);
131
- enforceOutputLimit(content);
132
- const safePath = resolveSecureExportPath(opts.out);
133
- await secureWriteFile(safePath, content);
134
- log(`已导出 ${filteredRows.length} 条记录`);
135
- if (opts.json || isJsonMode()) {
136
- jsonOk({ exported: filteredRows.length, format: opts.format, out: safePath }, { failedMonths });
137
- }
138
- }
@@ -1 +0,0 @@
1
- export { loginCmd as authLoginCmd, statusCmd as authStatusCmd, refreshCmd as authRefreshCmd, } from '../auth/index.js';