@meet-im/lxcli 0.1.3 → 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.
package/dist/commands/auth.js
CHANGED
|
@@ -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>', `
|
|
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
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
const
|
|
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 =
|
|
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 ?
|
|
218
|
-
const paramsUsage = hasParams ?
|
|
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(
|
|
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 帮助请查看
|
|
256
|
+
usageLines.push(`完整参数与最新 CLI 帮助请查看 \`${methodCommand} --help\`。`, '');
|
|
235
257
|
}
|
|
236
258
|
else if (hasParams) {
|
|
237
|
-
usageLines.push(`完整参数与最新 CLI 帮助请查看
|
|
259
|
+
usageLines.push(`完整参数与最新 CLI 帮助请查看 \`${methodCommand} --help\`。`, '');
|
|
238
260
|
}
|
|
239
261
|
// 组装 SKILL.md
|
|
240
262
|
const frontmatter = `---
|
|
241
263
|
name: ${skillName}
|
|
242
|
-
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: "
|
|
272
|
+
cliHelp: "${methodCommand} --help"
|
|
251
273
|
---`;
|
|
252
274
|
const lines = [
|
|
253
275
|
frontmatter,
|
|
254
276
|
'',
|
|
255
|
-
`#
|
|
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 ||
|
|
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
|
-
|
|
279
|
-
if (
|
|
280
|
-
logger.info(`Service "${serviceName}" is not supported. Supported services:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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));
|
|
@@ -49,7 +49,7 @@ export const PARAM_DEFS = {
|
|
|
49
49
|
{ name: 'owner_id', type: 'integer', required: false, description: '处理人 Kanboard 用户 ID。处理人必须可分配到该项目。' },
|
|
50
50
|
{ name: 'creator_id', type: 'integer', required: false, description: '创建人 Kanboard 用户 ID。登录态存在时创建人会使用当前用户。' },
|
|
51
51
|
{ name: 'category_id', type: 'integer', required: false, description: '分类 ID。服务端会根据分类和 bug 类型选择泳道。' },
|
|
52
|
-
{ name: 'iteration_id', type: 'integer', required: false, description: '迭代 ID;未传或为 0
|
|
52
|
+
{ name: 'iteration_id', type: 'integer', required: false, description: '迭代 ID;未传或为 0 时由服务端选择需求池或首个迭代。可用 getAllActiveIterations 查询启用迭代列表,根据 date_start/date_end(Unix 时间戳)确定目标迭代。' },
|
|
53
53
|
{ name: 'date_due', type: 'string', required: false, description: '截止日期,建议使用 YYYY-MM-DD' },
|
|
54
54
|
{ name: 'date_started', type: 'string', required: false, description: '开始日期,建议使用 YYYY-MM-DD' },
|
|
55
55
|
{ name: 'priority', type: 'integer', required: false, description: '优先级:0 高、1 中、2 低,其他值为无关紧要' },
|
|
@@ -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: [
|
|
@@ -74,7 +75,7 @@ export const PARAM_DEFS = {
|
|
|
74
75
|
{ name: 'description', type: 'string', required: false, description: '描述内容(Markdown)' },
|
|
75
76
|
{ name: 'owner_id', type: 'integer', required: false, description: '处理人用户 ID。目标用户必须可分配到该项目。' },
|
|
76
77
|
{ name: 'category_id', type: 'integer', required: false, description: '分类 ID' },
|
|
77
|
-
{ name: 'iteration_id', type: 'integer', required: false, description: '迭代 ID。传入后修改需求/bug
|
|
78
|
+
{ name: 'iteration_id', type: 'integer', required: false, description: '迭代 ID。传入后修改需求/bug 所属迭代;未传时保持原迭代不变。可用 getAllActiveIterations 查询启用迭代列表,根据 date_start/date_end(Unix 时间戳)确定目标迭代。' },
|
|
78
79
|
{ name: 'date_due', type: 'string', required: false, description: '截止日期,建议使用 YYYY-MM-DD' },
|
|
79
80
|
{ name: 'date_started', type: 'string', required: false, description: '开始日期,建议使用 YYYY-MM-DD' },
|
|
80
81
|
{ name: 'priority', type: 'integer', required: false, description: '优先级:0 高、1 中、2 低,其他值为无关紧要' },
|
|
@@ -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: '预估时间' },
|
|
@@ -26,6 +26,42 @@ export const METHOD_SKILL_METADATA = {
|
|
|
26
26
|
'仅在确实需要时才传 `with_comments`、`with_files`、`with_links`、`with_subtasks`,避免把额外第三方内容带入代理上下文。',
|
|
27
27
|
],
|
|
28
28
|
},
|
|
29
|
+
getAllActiveIterations: {
|
|
30
|
+
paramsExample: {
|
|
31
|
+
project_id: 4,
|
|
32
|
+
},
|
|
33
|
+
sections: [
|
|
34
|
+
{
|
|
35
|
+
title: '返回值说明',
|
|
36
|
+
kind: 'bullets',
|
|
37
|
+
items: [
|
|
38
|
+
'返回数组中每个对象包含 `id`、`name`、`date_start`、`date_end`、`position`、`nb_open_tasks`、`nb_closed_tasks` 等字段。',
|
|
39
|
+
'`date_start` 和 `date_end` 是 **Unix 时间戳**(秒级整数),`0` 表示未设置。显示或比较前需转换为可读日期。',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
title: '确定下个迭代',
|
|
44
|
+
kind: 'bullets',
|
|
45
|
+
items: [
|
|
46
|
+
'将当前时间转为 Unix 时间戳(秒),筛选 `date_start > 当前时间戳` 的迭代。',
|
|
47
|
+
'在筛选结果中取 `date_start` 最小的那个,即为"下个迭代"。',
|
|
48
|
+
'若所有迭代的 `date_start` 都 ≤ 当前时间,说明没有未来的迭代;可取 `date_end` 最大且仍 ≥ 当前时间的迭代作为"当前迭代"。',
|
|
49
|
+
'获得迭代后,将其 `id` 传入 `createTask --iteration_id` 或 `updateTask --iteration_id`。',
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
createTask: {
|
|
55
|
+
notes: [
|
|
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`。',
|
|
63
|
+
],
|
|
64
|
+
},
|
|
29
65
|
searchTasks: {
|
|
30
66
|
paramsExample: {
|
|
31
67
|
project_id: 4,
|