@meet-im/lxcli 0.0.12 → 0.0.14

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.
@@ -180,7 +180,7 @@ function renderSkillMd(method, version) {
180
180
  const description = METHOD_DESCRIPTIONS[method];
181
181
  const params = PARAM_DEFS[method] || [];
182
182
  const metadata = METHOD_SKILL_METADATA[method];
183
- const skillName = `lxcli-kb-${method}`;
183
+ const skillName = `lxcli-kb-${camelToKebab(method)}`;
184
184
  const hasParams = params.length > 0;
185
185
  // 快捷参数列表(显式配置,不从 required 自动推断)
186
186
  const shortcutParams = METHOD_SHORTCUT_PARAMS[method] || [];
@@ -240,6 +240,9 @@ metadata:
240
240
  }
241
241
  return lines.join('\n');
242
242
  }
243
+ function camelToKebab(str) {
244
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
245
+ }
243
246
  async function generateServiceSkills(skillsDir, serviceName, force) {
244
247
  // 目前只支持 kb 服务
245
248
  if (serviceName !== 'kb') {
@@ -249,7 +252,7 @@ async function generateServiceSkills(skillsDir, serviceName, force) {
249
252
  const methods = Object.keys(PARAM_DEFS);
250
253
  const version = await readPackageVersion();
251
254
  for (const method of methods) {
252
- const skillName = `lxcli-${serviceName}-${method}`;
255
+ const skillName = `lxcli-${serviceName}-${camelToKebab(method)}`;
253
256
  const skillDir = path.join(skillsDir, skillName);
254
257
  const skillPath = path.join(skillDir, 'SKILL.md');
255
258
  // 检查是否已有 SKILL.md(非 --force 时跳过已有文件)
@@ -14,6 +14,7 @@ export const METHOD_DESCRIPTIONS = {
14
14
  searchSubtasks: '搜索子任务列表',
15
15
  getAllActiveIterations: '获取启用迭代列表',
16
16
  getAllCategories: '获取项目分类列表',
17
+ getColumns: '获取项目状态列',
17
18
  createTask: '创建需求/bug',
18
19
  createSubtask: '创建子任务',
19
20
  updateTask: '修改需求',
@@ -180,6 +180,14 @@ export function createKbAction(method) {
180
180
  }
181
181
  params = { project_id: projectId };
182
182
  }
183
+ else if (method === 'getColumns' && options.project_id) {
184
+ const projectId = parseTaskId(options.project_id);
185
+ if (projectId === null) {
186
+ outputCliError('PARAM_INVALID', '--project_id must be a positive integer');
187
+ return;
188
+ }
189
+ params = { project_id: projectId };
190
+ }
183
191
  else if (method === 'getAllComments' && options.task_id) {
184
192
  const taskId = parseTaskId(options.task_id);
185
193
  if (taskId === null) {
@@ -261,6 +269,7 @@ export function createKbAction(method) {
261
269
  searchSubtasks: '--project_id, --iteration_id, --user_id, --page',
262
270
  getAllActiveIterations: '--project_id',
263
271
  getAllCategories: '--project_id',
272
+ getColumns: '--project_id',
264
273
  getAllComments: '--task_id',
265
274
  createComment: '--task_id, --user_id and --content',
266
275
  downloadProjectFile: '--project_id and --file_id',
@@ -103,6 +103,15 @@ const kbService = {
103
103
  .option('--user <user>', 'Specify user')
104
104
  .option('--gateway <url>', 'Override kanboard gateway')
105
105
  .action(createKbAction('getAllComments'));
106
+ // getColumns 支持快捷参数 --project_id
107
+ cmd
108
+ .command('getColumns')
109
+ .description(METHOD_DESCRIPTIONS.getColumns)
110
+ .option('--params <json>', 'JSON/JSON5 params; CLI fills jsonrpc/method/id')
111
+ .option('--project_id <id>', 'Project ID (shortcut)')
112
+ .option('--user <user>', 'Specify user')
113
+ .option('--gateway <url>', 'Override kanboard gateway')
114
+ .action(createKbAction('getColumns'));
106
115
  // createComment 支持快捷参数 --task_id / --user_id / --content
107
116
  cmd
108
117
  .command('createComment')
@@ -122,6 +131,7 @@ const kbService = {
122
131
  item !== 'searchSubtasks' &&
123
132
  item !== 'getAllActiveIterations' &&
124
133
  item !== 'getAllCategories' &&
134
+ item !== 'getColumns' &&
125
135
  item !== 'getAllComments' &&
126
136
  item !== 'createComment' &&
127
137
  item !== 'downloadProjectFile' &&
@@ -35,6 +35,9 @@ export const PARAM_DEFS = {
35
35
  getAllCategories: [
36
36
  { name: 'project_id', type: 'integer', required: true, description: '项目 ID' },
37
37
  ],
38
+ getColumns: [
39
+ { name: 'project_id', type: 'integer', required: true, description: '项目 ID' },
40
+ ],
38
41
  getAllComments: [
39
42
  { name: 'task_id', type: 'integer', required: true, description: '需求/bug ID' },
40
43
  ],
@@ -5,6 +5,7 @@ export const METHOD_SHORTCUT_PARAMS = {
5
5
  searchSubtasks: ['project_id', 'iteration_id', 'user_id', 'page'],
6
6
  getAllActiveIterations: ['project_id'],
7
7
  getAllCategories: ['project_id'],
8
+ getColumns: ['project_id'],
8
9
  getAllComments: ['task_id'],
9
10
  createComment: ['task_id', 'user_id', 'content'],
10
11
  createProjectFile: ['file', 'project_id'],
@@ -138,6 +138,7 @@ export function registerUsersCommand(program) {
138
138
  .description('Update user configuration (id: name or name@tenant)')
139
139
  .option('--token <token>', 'Update authentication token')
140
140
  .option('-e, --env <env>', `Update environment (${ENVIRONMENTS.join(', ')})`)
141
+ .option('--name <name>', 'Update user name (changes id to name@tenant)')
141
142
  .action((id, options) => {
142
143
  if (!id.includes('@')) {
143
144
  const matches = findUsersByName(id);
@@ -164,13 +165,23 @@ export function registerUsersCommand(program) {
164
165
  validEnvs: [...ENVIRONMENTS],
165
166
  });
166
167
  }
168
+ if (options.name !== undefined) {
169
+ if (!options.name.trim()) {
170
+ outputCliError('PARAM_INVALID', 'Name cannot be empty', { value: options.name });
171
+ }
172
+ if (options.name.includes('@')) {
173
+ outputCliError('PARAM_INVALID', 'Name cannot contain "@"', { value: options.name });
174
+ }
175
+ }
167
176
  const updates = {};
168
177
  if (options.token !== undefined)
169
178
  updates.token = options.token;
170
179
  if (options.env !== undefined)
171
180
  updates.env = options.env;
181
+ if (options.name !== undefined)
182
+ updates.name = options.name;
172
183
  if (Object.keys(updates).length === 0) {
173
- outputCliError('PARAM_MISSING', 'No update options provided. Use --token or --env', {});
184
+ outputCliError('PARAM_MISSING', 'No update options provided. Use --token, --env, or --name', {});
174
185
  }
175
186
  const updated = updateUser(user.id, updates);
176
187
  outputJson({
@@ -65,6 +65,7 @@ export declare function enableUser(id: string): boolean;
65
65
  export declare function updateUser(id: string, updates: {
66
66
  token?: string;
67
67
  env?: string;
68
+ name?: string;
68
69
  }): UserConfig | null;
69
70
  /**
70
71
  * 列出所有用户
@@ -228,6 +228,19 @@ export function updateUser(id, updates) {
228
228
  const user = config.users.find(u => u.id === id);
229
229
  if (!user)
230
230
  return null;
231
+ if (updates.name !== undefined) {
232
+ const newId = getUserId(updates.name, user.tenant);
233
+ const existing = config.users.find(u => u.id === newId);
234
+ if (existing) {
235
+ outputCliError('USER_ALREADY_EXISTS', `User "${newId}" already exists`, { id: newId });
236
+ }
237
+ const oldId = user.id;
238
+ user.name = updates.name;
239
+ user.id = newId;
240
+ if (config.currentUser === oldId) {
241
+ config.currentUser = newId;
242
+ }
243
+ }
231
244
  if (updates.token !== undefined) {
232
245
  user.token = updates.token;
233
246
  }
@@ -37,6 +37,11 @@ export declare const CLI_ERROR_CODES: {
37
37
  readonly reason: "userDisabled";
38
38
  readonly exitCode: 3;
39
39
  };
40
+ readonly USER_ALREADY_EXISTS: {
41
+ readonly code: 20003;
42
+ readonly reason: "userAlreadyExists";
43
+ readonly exitCode: 2;
44
+ };
40
45
  readonly TENANT_NOT_FOUND: {
41
46
  readonly code: 30001;
42
47
  readonly reason: "tenantNotFound";
@@ -94,6 +99,10 @@ export declare function getErrorMeta(key: CliErrorKey): {
94
99
  readonly code: 20002;
95
100
  readonly reason: "userDisabled";
96
101
  readonly exitCode: 3;
102
+ } | {
103
+ readonly code: 20003;
104
+ readonly reason: "userAlreadyExists";
105
+ readonly exitCode: 2;
97
106
  } | {
98
107
  readonly code: 30001;
99
108
  readonly reason: "tenantNotFound";
@@ -18,6 +18,7 @@ export const CLI_ERROR_CODES = {
18
18
  PARAM_INVALID: { code: 10002, reason: 'paramInvalid', exitCode: CLI_EXIT_CODES.PARAM },
19
19
  USER_NOT_FOUND: { code: 20001, reason: 'userNotFound', exitCode: CLI_EXIT_CODES.NOT_FOUND },
20
20
  USER_DISABLED: { code: 20002, reason: 'userDisabled', exitCode: CLI_EXIT_CODES.AUTH },
21
+ USER_ALREADY_EXISTS: { code: 20003, reason: 'userAlreadyExists', exitCode: CLI_EXIT_CODES.PARAM },
21
22
  TENANT_NOT_FOUND: { code: 30001, reason: 'tenantNotFound', exitCode: CLI_EXIT_CODES.NOT_FOUND },
22
23
  SERVICE_NOT_FOUND: { code: 30002, reason: 'serviceNotFound', exitCode: CLI_EXIT_CODES.NOT_FOUND },
23
24
  CONFIG_READ_ERROR: { code: 40001, reason: 'configReadError', exitCode: CLI_EXIT_CODES.INTERNAL },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meet-im/lxcli",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "连续科技命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",