@meet-im/lxcli 0.0.5 → 0.0.6

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.
@@ -4,7 +4,7 @@ import * as path from 'path';
4
4
  import { outputCliError } from '../core/error.js';
5
5
  import { logger } from '../core/logger.js';
6
6
  import { PARAM_DEFS, METHOD_DESCRIPTIONS } from './services/kb/descriptions.js';
7
- import { METHOD_SKILL_METADATA } from './services/kb/skill-metadata.js';
7
+ import { METHOD_SHORTCUT_PARAMS, METHOD_SKILL_METADATA } from './services/kb/skill-metadata.js';
8
8
  const API_ROOT = path.resolve('api');
9
9
  const DEFAULT_SKILLS_DIR = path.resolve('api/skills');
10
10
  const DEFAULT_OUTPUT = 'api/docs/skills.md';
@@ -155,41 +155,51 @@ async function readPackageVersion() {
155
155
  const packageJson = JSON.parse(await fs.readFile(PACKAGE_JSON_PATH, 'utf-8'));
156
156
  return packageJson.version || '1.0.0';
157
157
  }
158
+ function buildParamsExample(params) {
159
+ const paramsExample = {};
160
+ for (const p of params) {
161
+ if (p.type === 'array<integer>') {
162
+ paramsExample[p.name] = [1, 2];
163
+ }
164
+ else if (p.type === 'array<string>') {
165
+ paramsExample[p.name] = ['id1', 'id2'];
166
+ }
167
+ else if (p.type === 'integer') {
168
+ paramsExample[p.name] = 123;
169
+ }
170
+ else if (p.type === 'object') {
171
+ paramsExample[p.name] = {};
172
+ }
173
+ else {
174
+ paramsExample[p.name] = 'value';
175
+ }
176
+ }
177
+ return paramsExample;
178
+ }
158
179
  function renderSkillMd(method, version) {
159
180
  const description = METHOD_DESCRIPTIONS[method];
160
181
  const params = PARAM_DEFS[method] || [];
161
182
  const metadata = METHOD_SKILL_METADATA[method];
162
183
  const skillName = `lxcli-kb-${method}`;
163
184
  const hasParams = params.length > 0;
164
- // 快捷参数列表
165
- const shortcuts = params.filter(p => p.required).map(p => `--${p.name} <${p.type === 'integer' ? 'ID' : 'VALUE'}>`);
185
+ // 快捷参数列表(显式配置,不从 required 自动推断)
186
+ const shortcutParams = METHOD_SHORTCUT_PARAMS[method] || [];
187
+ const shortcuts = shortcutParams.map((name) => {
188
+ const param = params.find((p) => p.name === name);
189
+ if (!param) {
190
+ return `--${name} <VALUE>`;
191
+ }
192
+ return `--${param.name} <${param.type === 'integer' ? 'ID' : 'VALUE'}>`;
193
+ });
166
194
  const shortcutUsage = shortcuts.length > 0 ? `lxcli kb ${method} ${shortcuts.join(' ')}` : '';
195
+ const paramsUsage = hasParams ? `lxcli kb ${method} --params '${JSON.stringify(buildParamsExample(params))}'` : '';
167
196
  // Usage 部分
168
197
  const usageLines = ['## Usage', '', '```bash'];
169
198
  if (hasParams) {
170
199
  if (shortcutUsage) {
171
200
  usageLines.push(shortcutUsage);
172
201
  }
173
- // 生成 --params 示例
174
- const paramsExample = {};
175
- for (const p of params) {
176
- if (p.type === 'array<integer>') {
177
- paramsExample[p.name] = [1, 2];
178
- }
179
- else if (p.type === 'array<string>') {
180
- paramsExample[p.name] = ['id1', 'id2'];
181
- }
182
- else if (p.type === 'integer') {
183
- paramsExample[p.name] = 123;
184
- }
185
- else if (p.type === 'object') {
186
- paramsExample[p.name] = {};
187
- }
188
- else {
189
- paramsExample[p.name] = 'value';
190
- }
191
- }
192
- usageLines.push(`lxcli kb ${method} --params '${JSON.stringify(paramsExample)}'`);
202
+ usageLines.push(paramsUsage);
193
203
  }
194
204
  else {
195
205
  usageLines.push(`lxcli kb ${method}`);
@@ -224,7 +234,7 @@ metadata:
224
234
  if (hasParams) {
225
235
  lines.push(renderParamsTable(params));
226
236
  }
227
- lines.push('## Examples', '', '```bash', shortcutUsage || `lxcli kb ${method}`, '```', '');
237
+ lines.push('## Examples', '', '```bash', shortcutUsage || paramsUsage || `lxcli kb ${method}`, '```', '');
228
238
  if (metadata?.notes?.length) {
229
239
  lines.push('## Notes', '', ...metadata.notes.map((note) => `- ${note}`), '');
230
240
  }
@@ -17,6 +17,7 @@ export const METHOD_DESCRIPTIONS = {
17
17
  createSubtask: '创建子任务',
18
18
  updateTask: '修改需求',
19
19
  updateSubtask: '更新子任务',
20
+ moveTaskPosition: '移动需求状态列',
20
21
  getAllComments: '获取评论列表',
21
22
  createComment: '对需求增加评论',
22
23
  createProjectFile: '上传附件到项目',
@@ -90,4 +90,11 @@ export const PARAM_DEFS = {
90
90
  downloadTaskFile: [
91
91
  { name: 'file_id', type: 'integer', required: true, description: '需求附件 ID' },
92
92
  ],
93
+ moveTaskPosition: [
94
+ { name: 'project_id', type: 'integer', required: true, description: '项目 ID。必须是任务所属项目。' },
95
+ { name: 'task_id', type: 'integer', required: true, description: '需求/bug ID' },
96
+ { name: 'column_id', type: 'integer', required: false, description: '目标状态列 ID。未传时保留当前状态列' },
97
+ { name: 'position', type: 'integer', required: false, description: '目标列内排序位置,必须 >= 1。未传时保留当前排序值' },
98
+ { name: 'swimlane_id', type: 'integer', required: false, description: '目标泳道 ID。未传或传 0 保留当前泳道' },
99
+ ],
93
100
  };
@@ -20,7 +20,7 @@ export async function jsonRpcCall(gateway, method, params, pat) {
20
20
  throwOnError: false,
21
21
  });
22
22
  // HTTP 层错误
23
- if (result.error) {
23
+ if (!result.ok) {
24
24
  outputCliError('NETWORK_ERROR', result.error.message, {
25
25
  code: result.error.code,
26
26
  details: result.error.details,
@@ -2,4 +2,5 @@ import type { Method } from './descriptions.js';
2
2
  export interface MethodSkillMetadata {
3
3
  notes?: string[];
4
4
  }
5
+ export declare const METHOD_SHORTCUT_PARAMS: Partial<Record<Method, string[]>>;
5
6
  export declare const METHOD_SKILL_METADATA: Partial<Record<Method, MethodSkillMetadata>>;
@@ -1,3 +1,16 @@
1
+ export const METHOD_SHORTCUT_PARAMS = {
2
+ getTask: ['task_id'],
3
+ createTask: ['title', 'project_id', 'owner_id', 'category_id'],
4
+ searchTasks: ['project_id', 'query'],
5
+ getAllActiveIterations: ['project_id'],
6
+ getAllCategories: ['project_id'],
7
+ getAllComments: ['task_id'],
8
+ createComment: ['task_id', 'user_id', 'content'],
9
+ createProjectFile: ['file', 'project_id'],
10
+ downloadProjectFile: ['project_id', 'file_id'],
11
+ createTaskFile: ['file', 'task_id', 'project_id'],
12
+ downloadTaskFile: ['file_id'],
13
+ };
1
14
  export const METHOD_SKILL_METADATA = {
2
15
  downloadProjectFile: {
3
16
  notes: [
@@ -1,16 +1,19 @@
1
- export interface RequestResult<T> {
2
- /** HTTP 状态码 */
1
+ export interface RequestSuccessResult<T> {
2
+ ok: true;
3
3
  status: number;
4
- /** 响应数据(成功时) */
5
- data?: T;
6
- /** 错误信息(失败时) */
7
- error?: {
4
+ data: T;
5
+ }
6
+ export interface RequestErrorResult {
7
+ ok: false;
8
+ status: number;
9
+ error: {
8
10
  code: number;
9
11
  message: string;
10
12
  details?: unknown;
11
13
  };
12
14
  }
13
- export interface RequestOptionsWithThrow {
15
+ export type RequestResult<T> = RequestSuccessResult<T> | RequestErrorResult;
16
+ interface RequestOptionsBase {
14
17
  /** gateway 地址(必填) */
15
18
  gateway: string;
16
19
  /** 请求路径(不含域名) */
@@ -27,29 +30,15 @@ export interface RequestOptionsWithThrow {
27
30
  userAgent?: string;
28
31
  /** 最大重试次数,默认 3 */
29
32
  maxRetries?: number;
30
- /** 错误时抛出异常而非直接退出,默认 true */
31
- throwOnError?: true;
32
33
  }
33
- export interface RequestOptionsWithoutThrow {
34
- /** gateway 地址(必填) */
35
- gateway: string;
36
- /** 请求路径(不含域名) */
37
- path: string;
38
- /** HTTP 方法,默认 GET */
39
- method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
40
- /** URL query 参数 */
41
- query?: Record<string, string | number | boolean>;
42
- /** 请求体 */
43
- body?: Record<string, unknown>;
44
- /** 自定义 headers(会覆盖默认 headers) */
45
- headers?: Record<string, string>;
46
- /** User-Agent,默认 lxcli/版本号 */
47
- userAgent?: string;
48
- /** 最大重试次数,默认 3 */
49
- maxRetries?: number;
34
+ export type RequestOptionsWithThrow = RequestOptionsBase & {
35
+ /** 错误时调用 outputApiError 退出进程,默认 true */
36
+ throwOnError?: true;
37
+ };
38
+ export type RequestOptionsWithoutThrow = RequestOptionsBase & {
50
39
  /** 错误时返回 RequestResult 而非退出 */
51
40
  throwOnError: false;
52
- }
41
+ };
53
42
  export type RequestOptions = RequestOptionsWithThrow | RequestOptionsWithoutThrow;
54
43
  /**
55
44
  * 通用请求函数
@@ -64,3 +53,4 @@ export type RequestOptions = RequestOptionsWithThrow | RequestOptionsWithoutThro
64
53
  */
65
54
  export declare function request<T>(options: RequestOptionsWithThrow): Promise<T>;
66
55
  export declare function request<T>(options: RequestOptionsWithoutThrow): Promise<RequestResult<T>>;
56
+ export {};
package/dist/core/http.js CHANGED
@@ -56,12 +56,16 @@ export async function request(options) {
56
56
  };
57
57
  const requestBody = body ? JSON.stringify(body) : undefined;
58
58
  let lastError = null;
59
- // 辅助函数:处理错误响应
60
- const makeErrorResult = (status, code, message, details) => {
59
+ // 辅助函数:构造错误结果
60
+ const errorResult = (status, code, message, details) => {
61
+ return { ok: false, status, error: { code, message, details } };
62
+ };
63
+ // 辅助函数:处理错误(throwOnError 时退出,否则返回错误结果)
64
+ const handleError = (result) => {
61
65
  if (throwOnError) {
62
- outputApiError(code, message, details, status);
66
+ outputApiError(result.error.code, result.error.message, result.error.details, result.status);
63
67
  }
64
- return { status, error: { code, message, details } };
68
+ return result;
65
69
  };
66
70
  // 发送请求(带重试)
67
71
  for (let attempt = 0; attempt < maxRetries; attempt++) {
@@ -92,32 +96,22 @@ export async function request(options) {
92
96
  await sleep(delay);
93
97
  continue;
94
98
  }
95
- const result = makeErrorResult(response.status, errorResponse?.error?.code || response.status, errorResponse?.error?.message || 'Request failed', errorResponse?.error?.details);
96
- if (!throwOnError)
97
- return result;
98
- // throwOnError 为 true 时 makeErrorResult 已经调用 outputApiError 退出了
99
+ return handleError(errorResult(response.status, errorResponse?.error?.code || response.status, errorResponse?.error?.message || 'Request failed', errorResponse?.error?.details));
99
100
  }
100
101
  let responseData;
101
102
  try {
102
103
  responseData = parseJsonResponse(responseText);
103
104
  }
104
105
  catch {
105
- const result = makeErrorResult(response.status, response.status, `Invalid JSON response: ${responseText}`);
106
- if (!throwOnError)
107
- return result;
108
- // throwOnError 为 true 时 makeErrorResult 已经调用 outputApiError 退出了
106
+ return handleError(errorResult(response.status, response.status, `Invalid JSON response: ${responseText}`));
109
107
  }
110
108
  logger.debug('Response', { status: response.status, data: responseData, attempt });
111
109
  if (!response.ok) {
112
- const errorResponse = responseData;
113
- const result = makeErrorResult(response.status, errorResponse?.error?.code || response.status, errorResponse?.error?.message || 'Request failed', errorResponse?.error?.details);
114
- if (!throwOnError)
115
- return result;
116
- // throwOnError 为 true 时 makeErrorResult 已经调用 outputApiError 退出了
110
+ const errResp = responseData;
111
+ return handleError(errorResult(response.status, errResp?.error?.code || response.status, errResp?.error?.message || 'Request failed', errResp?.error?.details));
117
112
  }
118
- // throwOnError 为 false 时返回完整结果
119
113
  if (!throwOnError) {
120
- return { status: response.status, data: responseData };
114
+ return { ok: true, status: response.status, data: responseData };
121
115
  }
122
116
  return responseData;
123
117
  }
@@ -136,9 +130,7 @@ export async function request(options) {
136
130
  }
137
131
  if (isNetworkError) {
138
132
  logger.error('Request failed', error);
139
- const result = makeErrorResult(500, 500, error.message);
140
- if (!throwOnError)
141
- return result;
133
+ return handleError(errorResult(500, 500, error.message));
142
134
  }
143
135
  throw error;
144
136
  }
@@ -146,9 +138,7 @@ export async function request(options) {
146
138
  // 所有重试都失败了
147
139
  if (lastError) {
148
140
  logger.error('All retries failed', lastError);
149
- const result = makeErrorResult(500, 500, lastError.message);
150
- if (!throwOnError)
151
- return result;
141
+ return handleError(errorResult(500, 500, lastError.message));
152
142
  }
153
143
  // 不应该到达这里,但 TypeScript 需要返回值
154
144
  throw new Error('Request failed after all retries');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meet-im/lxcli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "连续科技命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",