@meet-im/lxcli 0.1.4 → 0.1.5

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.
@@ -15,14 +15,22 @@ function parseEnv(value) {
15
15
  return value;
16
16
  }
17
17
  export function registerAuthCommand(program) {
18
+ const tenantNames = getTenantNames();
18
19
  program
19
20
  .command('auth <name> [token]')
20
21
  .description('Add user authentication')
21
- .requiredOption('--tenant <tenant>', `Tenant name (${getTenantNames().join(', ')})`)
22
+ .requiredOption('--tenant <tenant>', `Account owning tenant (${tenantNames.join(', ')})`)
22
23
  .option('--token-env <envName>', 'Read token from environment variable')
23
24
  .option('--token-stdin', 'Read token from stdin')
24
25
  .option('-e, --env <env>', `Environment (${ENVIRONMENTS.join(', ')})`, parseEnv, 'prod')
25
26
  .option('--company <id>', 'Company ID (default: 1)', Number, 1)
27
+ .addHelpText('after', `
28
+ Tenant selection:
29
+ --tenant identifies the account's owning tenant.
30
+ If the user has not explicitly provided it, ask the user which tenant the account belongs to: ${tenantNames.slice(0, -1).join(', ')}, or ${tenantNames.at(-1)}.
31
+ Wait for confirmation before constructing the auth command.
32
+ Do not infer it from the username, default user, or existing configuration.
33
+ `)
26
34
  .action((name, token, options) => {
27
35
  const { tenant, env, company, tokenEnv, tokenStdin } = options;
28
36
  if (!tenantExists(tenant)) {
@@ -3,12 +3,32 @@ import { promises as fs } from 'fs';
3
3
  import * as path from 'path';
4
4
  import { outputCliError } from '../core/error.js';
5
5
  import { logger } from '../core/logger.js';
6
- import { PARAM_DEFS, METHOD_DESCRIPTIONS } from './services/kb/descriptions.js';
7
- import { METHOD_SHORTCUT_PARAMS, METHOD_SKILL_METADATA } from './services/kb/skill-metadata.js';
6
+ import { METHODS as KB_METHODS, PARAM_DEFS as KB_PARAM_DEFS, METHOD_DESCRIPTIONS as KB_METHOD_DESCRIPTIONS, } from './services/kb/descriptions.js';
7
+ import { METHOD_SHORTCUT_PARAMS as KB_METHOD_SHORTCUT_PARAMS, METHOD_SKILL_METADATA as KB_METHOD_SKILL_METADATA, } from './services/kb/skill-metadata.js';
8
+ import { METHODS as MEET_METHODS, PARAM_DEFS as MEET_PARAM_DEFS, METHOD_DESCRIPTIONS as MEET_METHOD_DESCRIPTIONS, } from './services/meet/descriptions.js';
9
+ import { METHOD_SHORTCUT_PARAMS as MEET_METHOD_SHORTCUT_PARAMS, METHOD_SKILL_METADATA as MEET_METHOD_SKILL_METADATA, } from './services/meet/skill-metadata.js';
8
10
  const API_ROOT = path.resolve('api');
9
11
  const DEFAULT_SKILLS_DIR = path.resolve('api/skills');
10
12
  const DEFAULT_OUTPUT = 'api/docs/skills.md';
11
13
  const PACKAGE_JSON_PATH = path.resolve('package.json');
14
+ const SERVICE_SKILL_DEFINITIONS = {
15
+ kb: {
16
+ displayName: 'Kanboard',
17
+ methods: KB_METHODS,
18
+ descriptions: KB_METHOD_DESCRIPTIONS,
19
+ paramDefs: KB_PARAM_DEFS,
20
+ shortcutParams: KB_METHOD_SHORTCUT_PARAMS,
21
+ metadata: KB_METHOD_SKILL_METADATA,
22
+ },
23
+ meet: {
24
+ displayName: 'MeetIM',
25
+ methods: MEET_METHODS,
26
+ descriptions: MEET_METHOD_DESCRIPTIONS,
27
+ paramDefs: MEET_PARAM_DEFS,
28
+ shortcutParams: MEET_METHOD_SHORTCUT_PARAMS,
29
+ metadata: MEET_METHOD_SKILL_METADATA,
30
+ },
31
+ };
12
32
  // 分类配置:前缀 -> 分类名/描述
13
33
  const CATEGORY_CONFIG = {
14
34
  'lxcli-builtin': { title: 'Builtin', description: 'lxcli 内置命令。' },
@@ -199,14 +219,16 @@ function buildParamsExample(params) {
199
219
  }
200
220
  return paramsExample;
201
221
  }
202
- function renderSkillMd(method, version) {
203
- const description = METHOD_DESCRIPTIONS[method];
204
- const metadata = METHOD_SKILL_METADATA[method];
205
- const params = metadata?.skillParams || PARAM_DEFS[method] || [];
206
- const skillName = `lxcli-kb-${camelToKebab(method)}`;
222
+ function renderSkillMd(method, version, serviceName, service) {
223
+ const serviceCommand = `lxcli ${serviceName}`;
224
+ const methodCommand = `${serviceCommand} ${method}`;
225
+ const description = service.descriptions[method];
226
+ const metadata = service.metadata[method];
227
+ const params = metadata?.skillParams || service.paramDefs[method] || [];
228
+ const skillName = `lxcli-${serviceName}-${camelToKebab(method)}`;
207
229
  const hasParams = params.length > 0;
208
230
  // 快捷参数列表(显式配置,不从 required 自动推断)
209
- const shortcutParams = METHOD_SHORTCUT_PARAMS[method] || [];
231
+ const shortcutParams = service.shortcutParams[method] || [];
210
232
  const shortcuts = shortcutParams.map((name) => {
211
233
  const param = params.find((p) => p.name === name);
212
234
  if (!param) {
@@ -214,8 +236,8 @@ function renderSkillMd(method, version) {
214
236
  }
215
237
  return `--${param.name} <${param.type === 'integer' ? 'ID' : 'VALUE'}>`;
216
238
  });
217
- const shortcutUsage = metadata?.shortcutUsageExample || (shortcuts.length > 0 ? `lxcli kb ${method} ${shortcuts.join(' ')}` : '');
218
- const paramsUsage = hasParams ? `lxcli kb ${method} --params '${JSON.stringify(metadata?.paramsExample || buildParamsExample(params))}'` : '';
239
+ const shortcutUsage = metadata?.shortcutUsageExample || (shortcuts.length > 0 ? `${methodCommand} ${shortcuts.join(' ')}` : '');
240
+ const paramsUsage = hasParams ? `${methodCommand} --params '${JSON.stringify(metadata?.paramsExample || buildParamsExample(params))}'` : '';
219
241
  // Usage 部分
220
242
  const usageLines = ['## Usage', '', '```bash'];
221
243
  if (hasParams) {
@@ -225,21 +247,21 @@ function renderSkillMd(method, version) {
225
247
  usageLines.push(paramsUsage);
226
248
  }
227
249
  else {
228
- usageLines.push(`lxcli kb ${method}`);
250
+ usageLines.push(methodCommand);
229
251
  }
230
252
  usageLines.push('```', '');
231
253
  // 快捷参数说明
232
254
  if (hasParams && shortcutUsage) {
233
255
  usageLines.push(`快捷参数使用见上方;\`--params\` 用于完整参数场景。若同时传入,CLI 优先使用 \`--params\`。`, '');
234
- usageLines.push(`完整参数与最新 CLI 帮助请查看 \`lxcli kb ${method} --help\`。`, '');
256
+ usageLines.push(`完整参数与最新 CLI 帮助请查看 \`${methodCommand} --help\`。`, '');
235
257
  }
236
258
  else if (hasParams) {
237
- usageLines.push(`完整参数与最新 CLI 帮助请查看 \`lxcli kb ${method} --help\`。`, '');
259
+ usageLines.push(`完整参数与最新 CLI 帮助请查看 \`${methodCommand} --help\`。`, '');
238
260
  }
239
261
  // 组装 SKILL.md
240
262
  const frontmatter = `---
241
263
  name: ${skillName}
242
- description: "Kanboard: ${description}"
264
+ description: "${service.displayName}: ${description}"
243
265
  metadata:
244
266
  version: ${version}
245
267
  openclaw:
@@ -247,21 +269,32 @@ metadata:
247
269
  requires:
248
270
  bins:
249
271
  - lxcli
250
- cliHelp: "lxcli kb ${method} --help"
272
+ cliHelp: "${methodCommand} --help"
251
273
  ---`;
252
274
  const lines = [
253
275
  frontmatter,
254
276
  '',
255
- `# kb ${method}`,
277
+ `# ${serviceName} ${method}`,
256
278
  '',
257
279
  description + '。',
258
280
  '',
281
+ '## Network approval',
282
+ '',
283
+ 'This service command requires outbound network access.',
284
+ '',
285
+ `- Treat every \`${serviceCommand}\` invocation as requiring network access.`,
286
+ '- If the runtime reports outbound network access is enabled, execute directly.',
287
+ '- If access is disabled, or the runtime does not expose network status, request sandbox escalation for the first actual service command.',
288
+ '- Use the justification: "需要访问远程服务 API 执行请求。"',
289
+ `- Request the reusable command prefix \`${serviceCommand}\` when supported.`,
290
+ '- Do not run a separate network probe before the service command.',
291
+ '',
259
292
  ...usageLines,
260
293
  ];
261
294
  if (hasParams) {
262
295
  lines.push(renderParamsTable(params));
263
296
  }
264
- lines.push('## Examples', '', '```bash', shortcutUsage || paramsUsage || `lxcli kb ${method}`, '```', '');
297
+ lines.push('## Examples', '', '```bash', shortcutUsage || paramsUsage || methodCommand, '```', '');
265
298
  const extraSections = renderExtraSections(metadata);
266
299
  if (extraSections) {
267
300
  lines.push(extraSections);
@@ -275,12 +308,12 @@ function camelToKebab(str) {
275
308
  return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
276
309
  }
277
310
  async function generateServiceSkills(skillsDir, serviceName, force) {
278
- // 目前只支持 kb 服务
279
- if (serviceName !== 'kb') {
280
- logger.info(`Service "${serviceName}" is not supported. Supported services: kb`);
311
+ const service = SERVICE_SKILL_DEFINITIONS[serviceName];
312
+ if (!service) {
313
+ logger.info(`Service "${serviceName}" is not supported. Supported services: ${Object.keys(SERVICE_SKILL_DEFINITIONS).join(', ')}`);
281
314
  return;
282
315
  }
283
- const methods = Object.keys(PARAM_DEFS);
316
+ const methods = service.methods;
284
317
  const version = await readPackageVersion();
285
318
  for (const method of methods) {
286
319
  const skillName = `lxcli-${serviceName}-${camelToKebab(method)}`;
@@ -298,7 +331,7 @@ async function generateServiceSkills(skillsDir, serviceName, force) {
298
331
  }
299
332
  }
300
333
  await fs.mkdir(skillDir, { recursive: true });
301
- const content = renderSkillMd(method, version);
334
+ const content = renderSkillMd(method, version, serviceName, service);
302
335
  await fs.writeFile(skillPath, content, 'utf-8');
303
336
  logger.info(`Generated ${skillName}/SKILL.md`);
304
337
  }
@@ -47,13 +47,15 @@ function renderMethodExtraHelp(method) {
47
47
  }
48
48
  return lines.join('\n');
49
49
  }
50
- function addParamsOption(command, method) {
50
+ function addParamsOption(command, method, required = false) {
51
51
  const paramsHelp = renderParamsHelp(method);
52
52
  const extraHelp = renderMethodExtraHelp(method);
53
53
  const description = paramsHelp
54
54
  ? `JSON/JSON5 params; CLI fills jsonrpc/method/id\n${paramsHelp}${extraHelp ? `\n${extraHelp}` : ''}`
55
55
  : 'JSON/JSON5 params; CLI fills jsonrpc/method/id';
56
- return command.option('--params <json>', description);
56
+ return required
57
+ ? command.requiredOption('--params <json>', description)
58
+ : command.option('--params <json>', description);
57
59
  }
58
60
  const kbService = {
59
61
  name: 'kb',
@@ -199,10 +201,11 @@ const kbService = {
199
201
  item !== 'createTaskFile' &&
200
202
  item !== 'downloadTaskFile' &&
201
203
  item !== 'createProjectFile')) {
202
- cmd
204
+ const methodCommand = cmd
203
205
  .command(method)
204
- .description(METHOD_DESCRIPTIONS[method])
205
- .requiredOption('--params <json>', 'JSON/JSON5 params only; CLI fills jsonrpc/method/id')
206
+ .description(METHOD_DESCRIPTIONS[method]);
207
+ addParamsOption(methodCommand, method, true);
208
+ methodCommand
206
209
  .option('--user <user>', 'Specify user')
207
210
  .option('--gateway <url>', 'Override kanboard gateway')
208
211
  .action(createKbAction(method));
@@ -58,6 +58,7 @@ export const PARAM_DEFS = {
58
58
  { name: 'cc_id', type: 'array<string>', required: false, description: '抄送人列表。数组元素格式为 用户ID-用户名称,例如 ["22-Alice", "33-Bob"];未传时不设置抄送人。' },
59
59
  { name: 'is_bug', type: 'integer', required: false, description: '是否为 bug,1 表示 bug,0 表示需求' },
60
60
  { name: 'platform', type: 'string', required: false, description: 'bug 平台,例如 Server,未传时保存为空字符串' },
61
+ { name: 'bug_test', type: 'integer', required: false, description: 'bug 测试人员的 Kanboard 用户 ID,即 queryUsers 返回项中的 id(不是 oauth2_uid)。传入前先调用 queryUsers 查询确认;正整数必须是项目可分配用户,0 表示不指定;未传时使用服务端默认。' },
61
62
  { name: 'related_task_id', type: 'integer', required: false, description: '关联需求 ID。创建 bug 时可传入对应的需求 ID,建立 bug 与需求的关联;未传时不关联。' },
62
63
  ],
63
64
  createSubtask: [
@@ -83,6 +84,7 @@ export const PARAM_DEFS = {
83
84
  { name: 'reference', type: 'string', required: false, description: '外部引用编号' },
84
85
  { name: 'tags', type: 'array<string>', required: false, description: '标签列表,例如 ["Meet", "Sky", "Co"]' },
85
86
  { name: 'cc_id', type: 'array<string>', required: false, description: '抄送人列表。数组元素格式为 用户ID-用户名称,例如 ["22-Alice", "33-Bob"];传空数组 [] 表示清空抄送人,未传则保持不变。' },
87
+ { name: 'bug_test', type: 'integer', required: false, description: 'bug 测试人员的 Kanboard 用户 ID,即 queryUsers 返回项中的 id(不是 oauth2_uid)。传入前先调用 queryUsers 查询确认;正整数必须是项目可分配用户,0 表示清空;未传时保持不变。' },
86
88
  { name: 'related_task_id', type: 'integer', required: false, description: '修改 bug 时可传入要关联的需求 ID。仅当前任务是 bug 时生效;目标必须是已存在的需求(is_bug = 0)。' },
87
89
  { name: 'time_spent', type: 'number', required: false, description: '已花费时间' },
88
90
  { name: 'time_estimated', type: 'number', required: false, description: '预估时间' },
@@ -54,6 +54,12 @@ export const METHOD_SKILL_METADATA = {
54
54
  createTask: {
55
55
  notes: [
56
56
  '如需指定迭代,先调用 `getAllActiveIterations` 查询项目的启用迭代列表,根据 `date_start`/`date_end`(Unix 时间戳)确定目标迭代 ID,再传入 `iteration_id`。',
57
+ '如需指定 bug 测试人员,先调用 `queryUsers` 查询目标用户,再将返回项中的 Kanboard 用户 `id` 传入 `bug_test`;不要使用 `oauth2_uid`。',
58
+ ],
59
+ },
60
+ updateTask: {
61
+ notes: [
62
+ '如需修改 bug 测试人员,先调用 `queryUsers` 查询目标用户,再将返回项中的 Kanboard 用户 `id` 传入 `bug_test`;不要使用 `oauth2_uid`。',
57
63
  ],
58
64
  },
59
65
  searchTasks: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meet-im/lxcli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "连续科技命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",