@hangox/pm-cli 0.0.1 → 0.2.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/dist/index.js CHANGED
@@ -334,17 +334,174 @@ function validateCredentials(creds, requiredFields = ["token", "host", "project"
334
334
 
335
335
  // src/utils/output.ts
336
336
  import { writeFileSync as writeFileSync2 } from "fs";
337
+
338
+ // src/utils/key-translator.ts
339
+ var KEY_MAP = {
340
+ // 问题 (Issue) 相关字段
341
+ id: "\u7F16\u53F7",
342
+ subject: "\u6807\u9898",
343
+ description: "\u63CF\u8FF0",
344
+ status: "\u72B6\u6001",
345
+ tracker: "\u8DDF\u8E2A\u5668",
346
+ priority: "\u4F18\u5148\u7EA7",
347
+ author: "\u521B\u5EFA\u8005",
348
+ assigned_to: "\u8D1F\u8D23\u4EBA",
349
+ project: "\u9879\u76EE",
350
+ parent: "\u7236\u5355",
351
+ parent_id: "\u7236\u5355\u7F16\u53F7",
352
+ parent_issue_id: "\u7236\u5355\u7F16\u53F7",
353
+ root_id: "\u6839\u5355\u7F16\u53F7",
354
+ children: "\u5B50\u5355\u5217\u8868",
355
+ level: "\u5C42\u7EA7",
356
+ fixed_version: "\u76EE\u6807\u7248\u672C",
357
+ version: "\u7248\u672C",
358
+ start_date: "\u5F00\u59CB\u65E5\u671F",
359
+ due_date: "\u622A\u6B62\u65E5\u671F",
360
+ estimated_hours: "\u9884\u4F30\u5DE5\u65F6",
361
+ spent_hours: "\u5DF2\u7528\u5DE5\u65F6",
362
+ done_ratio: "\u5B8C\u6210\u5EA6",
363
+ created_on: "\u521B\u5EFA\u65F6\u95F4",
364
+ updated_on: "\u66F4\u65B0\u65F6\u95F4",
365
+ closed_on: "\u5173\u95ED\u65F6\u95F4",
366
+ relations: "\u5173\u8054\u95EE\u9898",
367
+ custom_fields: "\u81EA\u5B9A\u4E49\u5B57\u6BB5",
368
+ attachments: "\u9644\u4EF6",
369
+ journals: "\u5386\u53F2\u8BB0\u5F55",
370
+ watchers: "\u8DDF\u8E2A\u8005",
371
+ watcher_user_ids: "\u8DDF\u8E2A\u8005\u7F16\u53F7",
372
+ // 通用对象字段
373
+ name: "\u540D\u79F0",
374
+ value: "\u503C",
375
+ mail: "\u90AE\u7BB1",
376
+ login: "\u767B\u5F55\u540D",
377
+ firstname: "\u540D",
378
+ lastname: "\u59D3",
379
+ identifier: "\u6807\u8BC6\u7B26",
380
+ is_public: "\u662F\u5426\u516C\u5F00",
381
+ // 工时 (TimeEntry) 相关字段
382
+ hours: "\u5DE5\u65F6",
383
+ spent_on: "\u5DE5\u65F6\u65E5\u671F",
384
+ activity: "\u6D3B\u52A8\u7C7B\u578B",
385
+ activity_id: "\u6D3B\u52A8\u7C7B\u578B\u7F16\u53F7",
386
+ comments: "\u5907\u6CE8",
387
+ time_entries: "\u5DE5\u65F6\u6761\u76EE",
388
+ time_entry: "\u5DE5\u65F6\u6761\u76EE",
389
+ total_count: "\u603B\u6570",
390
+ total_hours: "\u603B\u5DE5\u65F6",
391
+ // 用户 (User) 相关字段
392
+ user: "\u7528\u6237",
393
+ user_id: "\u7528\u6237\u7F16\u53F7",
394
+ author_mail: "\u4F5C\u8005\u90AE\u7BB1",
395
+ assigned_to_mail: "\u8D1F\u8D23\u4EBA\u90AE\u7BB1",
396
+ assigned_to_id: "\u8D1F\u8D23\u4EBA\u7F16\u53F7",
397
+ follows: "\u8DDF\u8FDBQA",
398
+ last_login_on: "\u6700\u540E\u767B\u5F55\u65F6\u95F4",
399
+ // API 响应字段
400
+ success: "\u6210\u529F",
401
+ data: "\u6570\u636E",
402
+ error: "\u9519\u8BEF",
403
+ message: "\u6D88\u606F",
404
+ msg: "\u6D88\u606F",
405
+ api_error_msg: "\u63A5\u53E3\u9519\u8BEF",
406
+ post_error_msg: "\u8BF7\u6C42\u9519\u8BEF",
407
+ output: "\u8F93\u51FA\u8DEF\u5F84",
408
+ issues: "\u95EE\u9898\u5217\u8868",
409
+ list: "\u5217\u8868",
410
+ total: "\u603B\u6570",
411
+ // 查询/统计相关字段
412
+ offset: "\u504F\u79FB\u91CF",
413
+ limit: "\u9650\u5236\u6570",
414
+ page: "\u9875\u7801",
415
+ per_page: "\u6BCF\u9875\u6570\u91CF",
416
+ period: "\u65F6\u95F4\u6BB5",
417
+ targetHours: "\u76EE\u6807\u5DE5\u65F6",
418
+ remainingHours: "\u5269\u4F59\u5DE5\u65F6",
419
+ isFilled: "\u662F\u5426\u586B\u6EE1",
420
+ projectBreakdown: "\u9879\u76EE\u5206\u5E03",
421
+ project_summary: "\u9879\u76EE\u6C47\u603B",
422
+ // 版本 (Version) 相关字段
423
+ effective_date: "\u6709\u6548\u65E5\u671F",
424
+ sharing: "\u5171\u4EAB\u8303\u56F4",
425
+ wiki_page_title: "Wiki\u9875\u9762",
426
+ // 自定义字段相关
427
+ custom_field: "\u81EA\u5B9A\u4E49\u5B57\u6BB5",
428
+ identify: "\u6807\u8BC6",
429
+ // 选项/配置相关
430
+ options: "\u9009\u9879",
431
+ activities: "\u6D3B\u52A8\u7C7B\u578B\u5217\u8868",
432
+ trackers: "\u8DDF\u8E2A\u5668\u5217\u8868",
433
+ statuses: "\u72B6\u6001\u5217\u8868",
434
+ priorities: "\u4F18\u5148\u7EA7\u5217\u8868",
435
+ versions: "\u7248\u672C\u5217\u8868",
436
+ users: "\u7528\u6237\u5217\u8868",
437
+ projects: "\u9879\u76EE\u5217\u8868",
438
+ // 其他字段
439
+ notes: "\u8BF4\u660E",
440
+ is_pending_version: "\u662F\u5426\u5F85\u5B9A\u7248\u672C",
441
+ count: "\u6570\u91CF",
442
+ category: "\u5206\u7C7B",
443
+ category_id: "\u5206\u7C7B\u7F16\u53F7"
444
+ };
445
+ var keyMappingEnabled = true;
446
+ function setKeyMappingEnabled(enabled) {
447
+ keyMappingEnabled = enabled;
448
+ }
449
+ function isKeyMappingEnabled() {
450
+ return keyMappingEnabled;
451
+ }
452
+ function collectKeysRecursive(obj, keys) {
453
+ if (obj === null || obj === void 0) return;
454
+ if (typeof obj !== "object") return;
455
+ if (Array.isArray(obj)) {
456
+ for (const item of obj) {
457
+ collectKeysRecursive(item, keys);
458
+ }
459
+ return;
460
+ }
461
+ for (const [key, value] of Object.entries(obj)) {
462
+ keys.add(key);
463
+ collectKeysRecursive(value, keys);
464
+ }
465
+ }
466
+ function collectUsedKeys(obj) {
467
+ const keys = /* @__PURE__ */ new Set();
468
+ collectKeysRecursive(obj, keys);
469
+ return Array.from(keys);
470
+ }
471
+ function generateKeyMapping(obj) {
472
+ const usedKeys = collectUsedKeys(obj);
473
+ const mapping = {};
474
+ for (const key of usedKeys) {
475
+ if (KEY_MAP[key]) {
476
+ mapping[key] = KEY_MAP[key];
477
+ }
478
+ }
479
+ return mapping;
480
+ }
481
+
482
+ // src/utils/output.ts
337
483
  function generateOutputPath(command, identifier) {
338
484
  const now = /* @__PURE__ */ new Date();
339
485
  const timestamp = now.getFullYear().toString() + (now.getMonth() + 1).toString().padStart(2, "0") + now.getDate().toString().padStart(2, "0") + now.getHours().toString().padStart(2, "0") + now.getMinutes().toString().padStart(2, "0") + now.getSeconds().toString().padStart(2, "0");
340
486
  return `/tmp/pm-cli_${command}_${identifier}_${timestamp}.json`;
341
487
  }
488
+ function addKeyMappingToResult(result) {
489
+ if (!isKeyMappingEnabled()) {
490
+ return result;
491
+ }
492
+ const mapping = generateKeyMapping(result);
493
+ return {
494
+ ...result,
495
+ _key_mapping: mapping
496
+ };
497
+ }
342
498
  function outputToFile(data, filePath) {
343
499
  const result = {
344
500
  success: true,
345
501
  data
346
502
  };
347
- writeFileSync2(filePath, JSON.stringify(result, null, 2), "utf-8");
503
+ const finalResult = addKeyMappingToResult(result);
504
+ writeFileSync2(filePath, JSON.stringify(finalResult, null, 2), "utf-8");
348
505
  return filePath;
349
506
  }
350
507
  function smartOutput(data, options) {
@@ -362,14 +519,16 @@ function outputSuccess(data) {
362
519
  success: true,
363
520
  data
364
521
  };
365
- console.log(JSON.stringify(result, null, 2));
522
+ const finalResult = addKeyMappingToResult(result);
523
+ console.log(JSON.stringify(finalResult, null, 2));
366
524
  }
367
525
  function outputError(error) {
368
526
  const result = {
369
527
  success: false,
370
528
  error
371
529
  };
372
- console.log(JSON.stringify(result, null, 2));
530
+ const finalResult = addKeyMappingToResult(result);
531
+ console.log(JSON.stringify(finalResult, null, 2));
373
532
  }
374
533
 
375
534
  // src/commands/test.ts
@@ -560,9 +719,15 @@ var IssueService = class {
560
719
  return await apiClient.post("filter_query_v6", params);
561
720
  }
562
721
  /**
563
- * 递归获取问题及其子单
722
+ * 递归获取问题及其子单(包含完整详情)
564
723
  * @param depth 递归深度,默认 10
565
724
  * @param currentLevel 当前层级(内部使用)
725
+ *
726
+ * 实现逻辑:
727
+ * 1. 调用 getIssue(includeChildren=true) 获取当前问题详情
728
+ * 2. API 返回的 children 字段只包含简略信息(status 是字符串,缺少 assigned_to 等)
729
+ * 3. 从 children 中提取子单 ID,递归调用 getIssue 获取每个子单的完整详情
730
+ * 4. 用完整详情替换原始的简略 children
566
731
  */
567
732
  async getIssueWithChildren(token, host, project, issueId, depth = 10, currentLevel2 = 0) {
568
733
  logger_default.info("\u9012\u5F52\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5", { host, project, issueId, depth, currentLevel: currentLevel2 });
@@ -575,15 +740,16 @@ var IssueService = class {
575
740
  if (currentLevel2 >= depth) {
576
741
  return { success: true, data: issue };
577
742
  }
578
- const childrenResult = await this.getDirectChildren(token, host, project, issueId);
579
- if (!childrenResult.success || !childrenResult.data) {
743
+ const rawChildren = issue.children;
744
+ if (!rawChildren || rawChildren.length === 0) {
580
745
  return { success: true, data: issue };
581
746
  }
582
- const childrenIds = childrenResult.data;
583
- if (childrenIds.length === 0) {
747
+ const directChildrenIds = rawChildren.filter((child) => child.id).map((child) => child.id);
748
+ if (directChildrenIds.length === 0) {
584
749
  return { success: true, data: issue };
585
750
  }
586
- const childrenPromises = childrenIds.map(
751
+ logger_default.info("\u83B7\u53D6\u5B50\u5355\u5B8C\u6574\u8BE6\u60C5", { parentId: issueId, childCount: directChildrenIds.length, level: currentLevel2 });
752
+ const childrenPromises = directChildrenIds.map(
587
753
  (childId) => this.getIssueWithChildren(token, host, project, childId, depth, currentLevel2 + 1)
588
754
  );
589
755
  const childrenResults = await Promise.all(childrenPromises);
@@ -672,6 +838,50 @@ var IssueService = class {
672
838
  }
673
839
  return result;
674
840
  }
841
+ /**
842
+ * 批量获取多个问题详情
843
+ * @param issueIds 问题 ID 数组
844
+ * @param options 可选参数
845
+ * @returns 返回所有问题的详情数组(包含成功和失败的结果)
846
+ */
847
+ async getMultipleIssues(token, host, project, issueIds, options) {
848
+ const { includeChildren = false, includeRelations = false, depth = 0 } = options || {};
849
+ logger_default.info("\u6279\u91CF\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5", { host, project, count: issueIds.length, issueIds });
850
+ const results = await Promise.all(
851
+ issueIds.map(async (issueId) => {
852
+ try {
853
+ let result;
854
+ if (includeChildren && depth > 0) {
855
+ result = await this.getIssueWithChildren(token, host, project, issueId, depth);
856
+ } else {
857
+ result = await this.getIssue(token, host, project, issueId, includeChildren, includeRelations);
858
+ }
859
+ if (result.success && result.data) {
860
+ return { id: issueId, success: true, data: result.data };
861
+ } else {
862
+ return {
863
+ id: issueId,
864
+ success: false,
865
+ error: result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u5931\u8D25"
866
+ };
867
+ }
868
+ } catch (error) {
869
+ return {
870
+ id: issueId,
871
+ success: false,
872
+ error: error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"
873
+ };
874
+ }
875
+ })
876
+ );
877
+ const successCount = results.filter((r) => r.success).length;
878
+ const failCount = results.filter((r) => !r.success).length;
879
+ logger_default.info("\u6279\u91CF\u83B7\u53D6\u5B8C\u6210", { total: issueIds.length, success: successCount, fail: failCount });
880
+ return {
881
+ success: true,
882
+ data: results
883
+ };
884
+ }
675
885
  };
676
886
  var issueService = new IssueService();
677
887
 
@@ -776,7 +986,7 @@ function createIssueCommand() {
776
986
  }
777
987
  }
778
988
  });
779
- issueCmd.command("create").description("\u521B\u5EFA\u95EE\u9898").requiredOption("--subject <subject>", "\u95EE\u9898\u6807\u9898").option("--description <description>", "\u95EE\u9898\u63CF\u8FF0").option("--tracker-id <id>", "\u8DDF\u8E2A\u5668 ID").option("--priority-id <id>", "\u4F18\u5148\u7EA7 ID").option("--assigned-to-id <id>", "\u6307\u6D3E\u4EBA ID").option("--parent-id <id>", "\u7236\u95EE\u9898 ID").option("--start-date <date>", "\u5F00\u59CB\u65E5\u671F").option("--due-date <date>", "\u622A\u6B62\u65E5\u671F").option("--estimated-hours <hours>", "\u9884\u4F30\u5DE5\u65F6").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
989
+ issueCmd.command("create").description("\u521B\u5EFA\u95EE\u9898").requiredOption("--subject <subject>", "\u95EE\u9898\u6807\u9898").option("--description <description>", "\u95EE\u9898\u63CF\u8FF0").option("--tracker <tracker>", "\u8DDF\u8E2A\u5668\u540D\u79F0").option("--tracker-id <id>", "\u8DDF\u8E2A\u5668 ID").option("--priority-id <id>", "\u4F18\u5148\u7EA7 ID").option("--assigned-to-mail <email>", "\u6307\u6D3E\u4EBA\u90AE\u7BB1").option("--assigned-to-id <id>", "\u6307\u6D3E\u4EBA ID").option("--parent-id <id>", "\u7236\u95EE\u9898 ID\uFF08\u5B50\u5355\u4F1A\u7EE7\u627F\u7236\u5355\u7684 tracker\u3001version\u3001assigned_to \u7B49\u4FE1\u606F\uFF09").option("--version <version>", "\u76EE\u6807\u7248\u672C\u540D\u79F0").option("--start-date <date>", "\u5F00\u59CB\u65E5\u671F").option("--due-date <date>", "\u622A\u6B62\u65E5\u671F").option("--estimated-hours <hours>", "\u9884\u4F30\u5DE5\u65F6").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
780
990
  const creds = resolveCredentials(options);
781
991
  const validation = validateCredentials(creds);
782
992
  if (!validation.valid) {
@@ -789,11 +999,71 @@ function createIssueCommand() {
789
999
  project: creds.project,
790
1000
  subject: options.subject
791
1001
  };
1002
+ if (options.parentId) {
1003
+ const parentId = parseInt(options.parentId, 10);
1004
+ const parentResult = await issueService.getIssue(
1005
+ creds.token,
1006
+ creds.host,
1007
+ creds.project || "",
1008
+ parentId,
1009
+ false,
1010
+ false
1011
+ );
1012
+ if (parentResult.success && parentResult.data) {
1013
+ const parent = parentResult.data;
1014
+ if (!options.tracker && !options.trackerId && parent.tracker?.name) {
1015
+ params.tracker = parent.tracker.name;
1016
+ }
1017
+ if (!options.assignedToMail && !options.assignedToId && parent.assigned_to?.mail) {
1018
+ params.assigned_to_mail = parent.assigned_to.mail;
1019
+ }
1020
+ if (!options.version && parent.fixed_version?.name) {
1021
+ params.version = parent.fixed_version.name;
1022
+ }
1023
+ if (!options.priorityId && parent.priority?.id) {
1024
+ params.priority_id = parent.priority.id;
1025
+ }
1026
+ params.status = "\u65B0\u5EFA";
1027
+ if (parent.custom_fields && parent.custom_fields.length > 0) {
1028
+ const customFieldMap = {};
1029
+ const followsMails = [];
1030
+ for (const field of parent.custom_fields) {
1031
+ if (field.value !== null && field.value !== void 0 && field.value !== "") {
1032
+ const fieldWithIdentify = field;
1033
+ if (fieldWithIdentify.identify === "IssuesQCFollow") {
1034
+ const followsValue = field.value;
1035
+ if (Array.isArray(followsValue)) {
1036
+ for (const item of followsValue) {
1037
+ if (item.user?.mail) {
1038
+ followsMails.push(item.user.mail);
1039
+ }
1040
+ }
1041
+ }
1042
+ } else {
1043
+ customFieldMap[field.id] = field.value;
1044
+ }
1045
+ }
1046
+ }
1047
+ if (Object.keys(customFieldMap).length > 0) {
1048
+ params.custom_field = JSON.stringify(customFieldMap);
1049
+ }
1050
+ if (followsMails.length > 0) {
1051
+ params.follows = followsMails;
1052
+ }
1053
+ }
1054
+ } else {
1055
+ outputError(`\u65E0\u6CD5\u83B7\u53D6\u7236\u5355 #${parentId} \u7684\u4FE1\u606F: ${parentResult.message || "\u672A\u77E5\u9519\u8BEF"}`);
1056
+ process.exit(1);
1057
+ }
1058
+ params.parent_issue_id = parentId;
1059
+ }
792
1060
  if (options.description) params.description = options.description;
1061
+ if (options.tracker) params.tracker = options.tracker;
793
1062
  if (options.trackerId) params.tracker_id = parseInt(options.trackerId, 10);
794
1063
  if (options.priorityId) params.priority_id = parseInt(options.priorityId, 10);
1064
+ if (options.assignedToMail) params.assigned_to_mail = options.assignedToMail;
795
1065
  if (options.assignedToId) params.assigned_to_id = parseInt(options.assignedToId, 10);
796
- if (options.parentId) params.parent_issue_id = parseInt(options.parentId, 10);
1066
+ if (options.version) params.version = options.version;
797
1067
  if (options.startDate) params.start_date = options.startDate;
798
1068
  if (options.dueDate) params.due_date = options.dueDate;
799
1069
  if (options.estimatedHours) params.estimated_hours = parseFloat(options.estimatedHours);
@@ -1006,6 +1276,61 @@ function createIssueCommand() {
1006
1276
  process.exit(1);
1007
1277
  }
1008
1278
  });
1279
+ issueCmd.command("mget [ids...]").description("\u6279\u91CF\u83B7\u53D6\u591A\u4E2A\u95EE\u9898\u8BE6\u60C5").option("--ids <ids>", "\u9017\u53F7\u5206\u9694\u7684\u95EE\u9898 ID \u5217\u8868").option("--depth <depth>", "\u9012\u5F52\u83B7\u53D6\u5B50\u5355\u7684\u6DF1\u5EA6\uFF080 \u8868\u793A\u4E0D\u83B7\u53D6\u5B50\u5355\uFF09", "0").option("--include-relations", "\u5305\u542B\u5173\u8054\u95EE\u9898").option("-o, --output <path>", "\u8F93\u51FA JSON \u5230\u6307\u5B9A\u6587\u4EF6\uFF08\u9ED8\u8BA4\u8F93\u51FA\u5230 /tmp\uFF09").option("--stdout", "\u5F3A\u5236\u8F93\u51FA\u5230\u63A7\u5236\u53F0\u800C\u975E\u6587\u4EF6").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (ids, options) => {
1280
+ let issueIds = [];
1281
+ if (ids && ids.length > 0) {
1282
+ const parsedIds = ids.map((id) => parseInt(id.replace(/^#/, ""), 10)).filter((id) => !isNaN(id));
1283
+ issueIds.push(...parsedIds);
1284
+ }
1285
+ if (options.ids) {
1286
+ const idsFromOption = options.ids.split(",").map((id) => parseInt(id.trim().replace(/^#/, ""), 10)).filter((id) => !isNaN(id));
1287
+ issueIds.push(...idsFromOption);
1288
+ }
1289
+ issueIds = [...new Set(issueIds)];
1290
+ if (issueIds.length === 0) {
1291
+ outputError("\u8BF7\u63D0\u4F9B\u81F3\u5C11\u4E00\u4E2A\u95EE\u9898 ID");
1292
+ process.exit(1);
1293
+ }
1294
+ const creds = resolveCredentials(options);
1295
+ const validation = validateCredentials(creds, ["token", "host"]);
1296
+ if (!validation.valid) {
1297
+ outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
1298
+ process.exit(1);
1299
+ }
1300
+ const depth = parseInt(options.depth, 10);
1301
+ const includeChildren = depth > 0;
1302
+ const includeRelations = options.includeRelations;
1303
+ const result = await issueService.getMultipleIssues(
1304
+ creds.token,
1305
+ creds.host,
1306
+ creds.project || "",
1307
+ issueIds,
1308
+ { includeChildren, includeRelations, depth }
1309
+ );
1310
+ if (result.success && result.data) {
1311
+ const successItems = result.data.filter((item) => item.success);
1312
+ const failedItems = result.data.filter((item) => !item.success);
1313
+ const output = {
1314
+ success: true,
1315
+ summary: {
1316
+ total: issueIds.length,
1317
+ success: successItems.length,
1318
+ failed: failedItems.length
1319
+ },
1320
+ issues: successItems.map((item) => item.data),
1321
+ errors: failedItems.length > 0 ? failedItems.map((item) => ({ id: item.id, error: item.error })) : void 0
1322
+ };
1323
+ smartOutput(output, {
1324
+ stdout: options.stdout,
1325
+ output: options.output,
1326
+ command: "issue-mget",
1327
+ identifier: issueIds.join("_")
1328
+ });
1329
+ } else {
1330
+ outputError(result.message || result.msg || result.api_error_msg || "\u6279\u91CF\u83B7\u53D6\u95EE\u9898\u5931\u8D25");
1331
+ process.exit(1);
1332
+ }
1333
+ });
1009
1334
  return issueCmd;
1010
1335
  }
1011
1336
 
@@ -1036,18 +1361,22 @@ var TimeEntryService = class {
1036
1361
  return await apiClient.get("query_time_entries", requestParams);
1037
1362
  }
1038
1363
  /**
1039
- * 获取工时条目选项
1364
+ * 获取工时条目选项(活动类型列表等)
1365
+ * 使用 time_entry GET API
1040
1366
  */
1041
- async getTimeEntryOptions(token, host, project) {
1042
- logger_default.info("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879", { host, project });
1043
- return await apiClient.post("time_entry_options", { token, host, project });
1367
+ async getTimeEntryOptions(token, host, project, issueId) {
1368
+ logger_default.info("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879", { host, project, issueId });
1369
+ const params = { token, host, project };
1370
+ if (issueId) params.issue_id = issueId;
1371
+ return await apiClient.get("time_entry", params);
1044
1372
  }
1045
1373
  /**
1046
1374
  * 创建工时条目
1375
+ * 使用 time_entry API(非 save_time_entry)
1047
1376
  */
1048
1377
  async createTimeEntry(params) {
1049
1378
  logger_default.info("\u521B\u5EFA\u5DE5\u65F6\u6761\u76EE", { params });
1050
- return await apiClient.post("save_time_entry", params);
1379
+ return await apiClient.post("time_entry", params);
1051
1380
  }
1052
1381
  /**
1053
1382
  * 更新工时条目
@@ -1059,13 +1388,13 @@ var TimeEntryService = class {
1059
1388
  /**
1060
1389
  * 删除工时条目
1061
1390
  */
1062
- async deleteTimeEntry(token, host, project, timeEntryId) {
1063
- logger_default.info("\u5220\u9664\u5DE5\u65F6\u6761\u76EE", { host, project, timeEntryId });
1391
+ async deleteTimeEntry(token, host, timeEntryId) {
1392
+ logger_default.info("\u5220\u9664\u5DE5\u65F6\u6761\u76EE", { host, timeEntryId });
1064
1393
  return await apiClient.get("delete_time_entry", {
1065
1394
  token,
1066
1395
  host,
1067
- project,
1068
- time_entry_id: timeEntryId
1396
+ id: timeEntryId
1397
+ // 使用 id 参数,非 time_entry_id
1069
1398
  });
1070
1399
  }
1071
1400
  };
@@ -1179,13 +1508,22 @@ function createTimeCommand() {
1179
1508
  }
1180
1509
  }
1181
1510
  });
1182
- timeCmd.command("create").description("\u521B\u5EFA\u5DE5\u65F6\u6761\u76EE").requiredOption("--issue <id>", "\u95EE\u9898 ID").requiredOption("--days <days>", "\u5DE5\u65F6\uFF08\u5929\uFF09").option("--activity <id>", "\u6D3B\u52A8\u7C7B\u578B ID").option("--date <date>", "\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u4ECA\u5929").option("--comments <comments>", "\u5907\u6CE8").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
1511
+ timeCmd.command("create").description("\u521B\u5EFA\u5DE5\u65F6\u6761\u76EE").requiredOption("--issue <id>", "\u95EE\u9898 ID").requiredOption("--days <days>", "\u5DE5\u65F6\uFF08\u5929\uFF09").requiredOption("--activity <id>", "\u6D3B\u52A8\u7C7B\u578B ID\uFF08\u4F7F\u7528 pm-cli time options \u83B7\u53D6\u53EF\u7528\u503C\uFF09").option("--date <date>", "\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u4ECA\u5929").option("--comments <comments>", "\u5907\u6CE8").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
1183
1512
  const creds = resolveCredentials(options);
1184
1513
  const validation = validateCredentials(creds);
1185
1514
  if (!validation.valid) {
1186
1515
  outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
1187
1516
  process.exit(1);
1188
1517
  }
1518
+ let userId;
1519
+ const configUserId = getConfigValue("userId", options.config);
1520
+ if (configUserId) {
1521
+ userId = parseInt(configUserId, 10);
1522
+ }
1523
+ if (!userId) {
1524
+ outputError("\u7F3A\u5C11\u7528\u6237 ID\uFF0C\u8BF7\u5148\u8BBE\u7F6E: pm-cli config set user-id <\u60A8\u7684\u7528\u6237ID>");
1525
+ process.exit(1);
1526
+ }
1189
1527
  const issueId = parseInt(options.issue.replace(/^#/, ""), 10);
1190
1528
  if (isNaN(issueId)) {
1191
1529
  outputError("\u65E0\u6548\u7684\u95EE\u9898 ID");
@@ -1196,6 +1534,11 @@ function createTimeCommand() {
1196
1534
  outputError("\u65E0\u6548\u7684\u5DE5\u65F6\u6570");
1197
1535
  process.exit(1);
1198
1536
  }
1537
+ const activityId = parseInt(options.activity, 10);
1538
+ if (isNaN(activityId)) {
1539
+ outputError("\u65E0\u6548\u7684\u6D3B\u52A8\u7C7B\u578B ID\uFF0C\u4F7F\u7528 pm-cli time options \u83B7\u53D6\u53EF\u7528\u503C");
1540
+ process.exit(1);
1541
+ }
1199
1542
  const params = {
1200
1543
  token: creds.token,
1201
1544
  host: creds.host,
@@ -1203,9 +1546,12 @@ function createTimeCommand() {
1203
1546
  issue_id: issueId,
1204
1547
  hours: days,
1205
1548
  // API 参数名为 hours,但单位是天
1206
- spent_on: options.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
1549
+ spent_on: options.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1550
+ user_id: userId,
1551
+ // 用户 ID(必填)
1552
+ activity_id: activityId
1553
+ // 活动类型 ID(必填)
1207
1554
  };
1208
- if (options.activity) params.activity_id = parseInt(options.activity, 10);
1209
1555
  if (options.comments) params.comments = options.comments;
1210
1556
  const result = await timeEntryService.createTimeEntry(params);
1211
1557
  if (result.success && result.data) {
@@ -1215,27 +1561,51 @@ function createTimeCommand() {
1215
1561
  process.exit(1);
1216
1562
  }
1217
1563
  });
1218
- timeCmd.command("update <id>").description("\u66F4\u65B0\u5DE5\u65F6\u6761\u76EE").option("--days <days>", "\u5DE5\u65F6\uFF08\u5929\uFF09").option("--activity <id>", "\u6D3B\u52A8\u7C7B\u578B ID").option("--date <date>", "\u65E5\u671F (YYYY-MM-DD)").option("--comments <comments>", "\u5907\u6CE8").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (id, options) => {
1564
+ timeCmd.command("update <id>").description("\u66F4\u65B0\u5DE5\u65F6\u6761\u76EE").requiredOption("--issue <id>", "\u95EE\u9898 ID\uFF08\u5FC5\u586B\uFF09").requiredOption("--days <days>", "\u5DE5\u65F6\uFF08\u5929\uFF09\uFF08\u5FC5\u586B\uFF09").requiredOption("--activity <id>", "\u6D3B\u52A8\u7C7B\u578B ID\uFF08\u5FC5\u586B\uFF09").requiredOption("--date <date>", "\u65E5\u671F (YYYY-MM-DD)\uFF08\u5FC5\u586B\uFF09").option("--comments <comments>", "\u5907\u6CE8").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (id, options) => {
1219
1565
  const creds = resolveCredentials(options);
1220
1566
  const validation = validateCredentials(creds);
1221
1567
  if (!validation.valid) {
1222
1568
  outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
1223
1569
  process.exit(1);
1224
1570
  }
1571
+ let userId;
1572
+ const configUserId = getConfigValue("userId", options.config);
1573
+ if (configUserId) {
1574
+ userId = parseInt(configUserId, 10);
1575
+ }
1576
+ if (!userId) {
1577
+ outputError("\u7F3A\u5C11\u7528\u6237 ID\uFF0C\u8BF7\u5148\u8BBE\u7F6E: pm-cli config set user-id <\u60A8\u7684\u7528\u6237ID>");
1578
+ process.exit(1);
1579
+ }
1225
1580
  const timeEntryId = parseInt(id, 10);
1226
1581
  if (isNaN(timeEntryId)) {
1227
1582
  outputError("\u65E0\u6548\u7684\u5DE5\u65F6\u6761\u76EE ID");
1228
1583
  process.exit(1);
1229
1584
  }
1585
+ const issueId = parseInt(options.issue.replace(/^#/, ""), 10);
1586
+ if (isNaN(issueId)) {
1587
+ outputError("\u65E0\u6548\u7684\u95EE\u9898 ID");
1588
+ process.exit(1);
1589
+ }
1230
1590
  const params = {
1231
1591
  token: creds.token,
1232
1592
  host: creds.host,
1233
1593
  project: creds.project,
1234
- time_entry_id: timeEntryId
1594
+ id: timeEntryId,
1595
+ // 工时 ID
1596
+ issue_id: issueId,
1597
+ // 问题 ID(必填)
1598
+ hours: parseFloat(options.days),
1599
+ // 工时(必填)
1600
+ activity_id: parseInt(options.activity, 10),
1601
+ // 活动类型(必填)
1602
+ spent_on: options.date,
1603
+ // 日期(必填)
1604
+ user_id: userId,
1605
+ // 用户 ID(必填)
1606
+ updated_by: userId
1607
+ // 操作用户 ID(必填)
1235
1608
  };
1236
- if (options.days) params.hours = parseFloat(options.days);
1237
- if (options.activity) params.activity_id = parseInt(options.activity, 10);
1238
- if (options.date) params.spent_on = options.date;
1239
1609
  if (options.comments) params.comments = options.comments;
1240
1610
  const result = await timeEntryService.updateTimeEntry(params);
1241
1611
  if (result.success && result.data) {
@@ -1247,7 +1617,7 @@ function createTimeCommand() {
1247
1617
  });
1248
1618
  timeCmd.command("delete <id>").description("\u5220\u9664\u5DE5\u65F6\u6761\u76EE").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (id, options) => {
1249
1619
  const creds = resolveCredentials(options);
1250
- const validation = validateCredentials(creds);
1620
+ const validation = validateCredentials(creds, ["token", "host"]);
1251
1621
  if (!validation.valid) {
1252
1622
  outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
1253
1623
  process.exit(1);
@@ -1260,7 +1630,6 @@ function createTimeCommand() {
1260
1630
  const result = await timeEntryService.deleteTimeEntry(
1261
1631
  creds.token,
1262
1632
  creds.host,
1263
- creds.project,
1264
1633
  timeEntryId
1265
1634
  );
1266
1635
  if (result.success) {
@@ -1270,26 +1639,34 @@ function createTimeCommand() {
1270
1639
  process.exit(1);
1271
1640
  }
1272
1641
  });
1273
- timeCmd.command("options").description("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
1642
+ timeCmd.command("options").description("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879\uFF08\u6D3B\u52A8\u7C7B\u578B\u5217\u8868\u7B49\uFF09").option("--issue <id>", "\u95EE\u9898 ID\uFF08\u53EF\u9009\uFF0C\u7528\u4E8E\u83B7\u53D6\u7279\u5B9A\u95EE\u9898\u7684\u5DE5\u65F6\u9009\u9879\uFF09").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
1274
1643
  const creds = resolveCredentials(options);
1275
1644
  const validation = validateCredentials(creds);
1276
1645
  if (!validation.valid) {
1277
1646
  outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
1278
1647
  process.exit(1);
1279
1648
  }
1649
+ const issueId = options.issue ? parseInt(options.issue.replace(/^#/, ""), 10) : void 0;
1280
1650
  const result = await timeEntryService.getTimeEntryOptions(
1281
1651
  creds.token,
1282
1652
  creds.host,
1283
- creds.project
1653
+ creds.project,
1654
+ issueId
1284
1655
  );
1285
- if (result.success) {
1286
- outputSuccess(result.data);
1656
+ if (result.success && result.data) {
1657
+ const data = result.data;
1658
+ if (data.options?.activities) {
1659
+ const activities = data.options.activities.map(([name, id]) => ({ id, name }));
1660
+ outputSuccess({ activities });
1661
+ } else {
1662
+ outputSuccess(result.data);
1663
+ }
1287
1664
  } else {
1288
1665
  outputError(result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u9009\u9879\u5931\u8D25");
1289
1666
  process.exit(1);
1290
1667
  }
1291
1668
  });
1292
- timeCmd.command("summary").description("\u5DE5\u65F6\u7EDF\u8BA1\u6C47\u603B\uFF08\u67E5\u8BE2\u6307\u5B9A\u65F6\u95F4\u6BB5\u7684\u5DE5\u65F6\u5E76\u8BA1\u7B97\u7F3A\u53E3\uFF09").option("--week <week>", "\u5468\u9009\u62E9: current (\u672C\u5468), last (\u4E0A\u5468), \u6216\u6570\u5B57\u504F\u79FB", "current").option("--from <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u4E0E --week \u4E92\u65A5").option("--to <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u4E0E --week \u4E92\u65A5").option("--target <days>", "\u76EE\u6807\u5DE5\u65F6\uFF08\u5929\uFF09\uFF0C\u9ED8\u8BA4 5", "5").option("--user-id <id>", "\u7528\u6237 ID\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 user-id\uFF09").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
1669
+ timeCmd.command("summary").description("\u5DE5\u65F6\u7EDF\u8BA1\u6C47\u603B\uFF08\u67E5\u8BE2\u6307\u5B9A\u65F6\u95F4\u6BB5\u7684\u5DE5\u65F6\u5E76\u8BA1\u7B97\u7F3A\u53E3\uFF09").option("--week <week>", "\u5468\u9009\u62E9: current (\u672C\u5468), last (\u4E0A\u5468), \u6216\u6570\u5B57\u504F\u79FB", "current").option("--from <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u4E0E --week \u4E92\u65A5").option("--to <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u4E0E --week \u4E92\u65A5").option("--target <days>", "\u76EE\u6807\u5DE5\u65F6\uFF08\u5929\uFF09\uFF0C\u9ED8\u8BA4 5", "5").option("--detail", "\u663E\u793A\u5B8C\u6574\u5DE5\u65F6\u6761\u76EE\u8BE6\u60C5").option("--user-id <id>", "\u7528\u6237 ID\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 user-id\uFF09").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
1293
1670
  const creds = resolveCredentials(options);
1294
1671
  const validation = validateCredentials(creds, ["token", "host"]);
1295
1672
  if (!validation.valid) {
@@ -1331,6 +1708,7 @@ function createTimeCommand() {
1331
1708
  const projectNames = Object.values(projects).map((p) => p.name);
1332
1709
  logger_default.info(`\u67E5\u8BE2 ${projectNames.length} \u4E2A\u9879\u76EE...`);
1333
1710
  const projectSummary = [];
1711
+ const allTimeEntries = [];
1334
1712
  let totalHours = 0;
1335
1713
  for (const projectName of projectNames) {
1336
1714
  const result = await timeEntryService.queryTimeEntries({
@@ -1353,13 +1731,14 @@ function createTimeCommand() {
1353
1731
  });
1354
1732
  totalHours += projectHours;
1355
1733
  }
1734
+ allTimeEntries.push(...data.time_entries);
1356
1735
  }
1357
1736
  }
1358
1737
  }
1359
1738
  const targetHours = parseFloat(options.target) || 5;
1360
1739
  const remainingHours = Math.max(0, targetHours - totalHours);
1361
1740
  totalHours = Math.round(totalHours * 100) / 100;
1362
- outputSuccess({
1741
+ const output = {
1363
1742
  period: periodLabel,
1364
1743
  userId: userId || "\u672A\u6307\u5B9A",
1365
1744
  totalHours,
@@ -1367,7 +1746,15 @@ function createTimeCommand() {
1367
1746
  remainingHours: Math.round(remainingHours * 100) / 100,
1368
1747
  isFilled: remainingHours === 0,
1369
1748
  projectBreakdown: projectSummary.sort((a, b) => b.hours - a.hours)
1370
- });
1749
+ };
1750
+ if (options.detail) {
1751
+ output.timeEntries = allTimeEntries.sort((a, b) => {
1752
+ const dateA = a.spent_on || "";
1753
+ const dateB = b.spent_on || "";
1754
+ return dateA.localeCompare(dateB);
1755
+ });
1756
+ }
1757
+ outputSuccess(output);
1371
1758
  });
1372
1759
  return timeCmd;
1373
1760
  }
@@ -1430,7 +1817,7 @@ function createProjectCommand() {
1430
1817
 
1431
1818
  // src/index.ts
1432
1819
  var program = new Command6();
1433
- program.name("pm-cli").description("\u7F51\u6613\u6613\u534F\u4F5C (PM NetEase) \u547D\u4EE4\u884C\u5DE5\u5177").version("0.1.0").option("--verbose", "\u8BE6\u7EC6\u8F93\u51FA").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F").hook("preAction", (thisCommand) => {
1820
+ program.name("pm-cli").description("\u7F51\u6613\u6613\u534F\u4F5C (PM NetEase) \u547D\u4EE4\u884C\u5DE5\u5177").version("0.1.0").option("--verbose", "\u8BE6\u7EC6\u8F93\u51FA").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F").option("--no-mapping", "\u4E0D\u8F93\u51FA key \u6620\u5C04\u8868\uFF08\u9ED8\u8BA4\u8F93\u51FA\uFF09").hook("preAction", (thisCommand) => {
1434
1821
  const opts = thisCommand.opts();
1435
1822
  if (opts.debug) {
1436
1823
  logger_default.setLevel("debug");
@@ -1439,6 +1826,9 @@ program.name("pm-cli").description("\u7F51\u6613\u6613\u534F\u4F5C (PM NetEase)
1439
1826
  } else {
1440
1827
  logger_default.setLevel("silent");
1441
1828
  }
1829
+ if (opts.mapping === false) {
1830
+ setKeyMappingEnabled(false);
1831
+ }
1442
1832
  });
1443
1833
  program.addCommand(createTestCommand());
1444
1834
  program.addCommand(createConfigCommand());