@meet-im/lxcli 0.0.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/LICENSE +15 -0
- package/README.md +3 -0
- package/bin/lxcli.js +2 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +45 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +28 -0
- package/dist/commands/generate-skills.d.ts +2 -0
- package/dist/commands/generate-skills.js +283 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.js +43 -0
- package/dist/commands/schema.d.ts +2 -0
- package/dist/commands/schema.js +37 -0
- package/dist/commands/services/kb/config.d.ts +2 -0
- package/dist/commands/services/kb/config.js +32 -0
- package/dist/commands/services/kb/descriptions.d.ts +4 -0
- package/dist/commands/services/kb/descriptions.js +24 -0
- package/dist/commands/services/kb/handler.d.ts +12 -0
- package/dist/commands/services/kb/handler.js +179 -0
- package/dist/commands/services/kb/index.d.ts +3 -0
- package/dist/commands/services/kb/index.js +119 -0
- package/dist/commands/services/kb/params.d.ts +8 -0
- package/dist/commands/services/kb/params.js +73 -0
- package/dist/commands/services/kb/rpc.d.ts +1 -0
- package/dist/commands/services/kb/rpc.js +35 -0
- package/dist/commands/services/kb/types.d.ts +97 -0
- package/dist/commands/services/kb/types.js +2 -0
- package/dist/commands/services/kb/utils.d.ts +1 -0
- package/dist/commands/services/kb/utils.js +4 -0
- package/dist/commands/services/meet/api.d.ts +13 -0
- package/dist/commands/services/meet/api.js +38 -0
- package/dist/commands/services/meet/config.d.ts +4 -0
- package/dist/commands/services/meet/config.js +16 -0
- package/dist/commands/services/meet/descriptions.d.ts +3 -0
- package/dist/commands/services/meet/descriptions.js +7 -0
- package/dist/commands/services/meet/handler.d.ts +25 -0
- package/dist/commands/services/meet/handler.js +50 -0
- package/dist/commands/services/meet/index.d.ts +3 -0
- package/dist/commands/services/meet/index.js +43 -0
- package/dist/commands/services/meet/types.d.ts +65 -0
- package/dist/commands/services/meet/types.js +2 -0
- package/dist/commands/services/meetbot/api.d.ts +37 -0
- package/dist/commands/services/meetbot/api.js +130 -0
- package/dist/commands/services/meetbot/config.d.ts +4 -0
- package/dist/commands/services/meetbot/config.js +21 -0
- package/dist/commands/services/meetbot/descriptions.d.ts +3 -0
- package/dist/commands/services/meetbot/descriptions.js +10 -0
- package/dist/commands/services/meetbot/handler.d.ts +42 -0
- package/dist/commands/services/meetbot/handler.js +83 -0
- package/dist/commands/services/meetbot/index.d.ts +3 -0
- package/dist/commands/services/meetbot/index.js +74 -0
- package/dist/commands/services/meetbot/types.d.ts +101 -0
- package/dist/commands/services/meetbot/types.js +2 -0
- package/dist/commands/services/meetbot/utils.d.ts +28 -0
- package/dist/commands/services/meetbot/utils.js +78 -0
- package/dist/commands/services.d.ts +2 -0
- package/dist/commands/services.js +50 -0
- package/dist/commands/tenants.d.ts +2 -0
- package/dist/commands/tenants.js +46 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +38 -0
- package/dist/commands/users.d.ts +2 -0
- package/dist/commands/users.js +182 -0
- package/dist/core/auth.d.ts +26 -0
- package/dist/core/auth.js +75 -0
- package/dist/core/config.d.ts +76 -0
- package/dist/core/config.js +255 -0
- package/dist/core/error.d.ts +128 -0
- package/dist/core/error.js +85 -0
- package/dist/core/help.d.ts +6 -0
- package/dist/core/help.js +15 -0
- package/dist/core/http.d.ts +28 -0
- package/dist/core/http.js +140 -0
- package/dist/core/logger.d.ts +24 -0
- package/dist/core/logger.js +53 -0
- package/dist/core/reserved.d.ts +13 -0
- package/dist/core/reserved.js +28 -0
- package/dist/core/tenants.d.ts +36 -0
- package/dist/core/tenants.js +50 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +152 -0
- package/dist/types/index.d.ts +59 -0
- package/dist/types/index.js +5 -0
- package/dist/utils/index.d.ts +20 -0
- package/dist/utils/index.js +133 -0
- package/dist/utils/loose-params.d.ts +9 -0
- package/dist/utils/loose-params.js +274 -0
- package/dist/utils/table.d.ts +4 -0
- package/dist/utils/table.js +25 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
package/bin/lxcli.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/commands/auth.ts
|
|
2
|
+
import { addUser, getUserId } from '../core/config.js';
|
|
3
|
+
import { tenantExists, listTenants, getTenantNames } from '../core/tenants.js';
|
|
4
|
+
import { outputCliError } from '../core/error.js';
|
|
5
|
+
import { outputJson } from '../utils/index.js';
|
|
6
|
+
import { ENVIRONMENTS } from '../types/index.js';
|
|
7
|
+
function parseEnv(value) {
|
|
8
|
+
if (!ENVIRONMENTS.includes(value)) {
|
|
9
|
+
outputCliError('PARAM_INVALID', `Invalid env: ${value}. Must be one of: ${ENVIRONMENTS.join(', ')}`, {
|
|
10
|
+
value,
|
|
11
|
+
validEnvs: [...ENVIRONMENTS],
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
export function registerAuthCommand(program) {
|
|
17
|
+
program
|
|
18
|
+
.command('auth <name> <token>')
|
|
19
|
+
.description('Add user authentication')
|
|
20
|
+
.requiredOption('--tenant <tenant>', `Tenant name (${getTenantNames().join(', ')})`)
|
|
21
|
+
.option('-e, --env <env>', `Environment (${ENVIRONMENTS.join(', ')})`, parseEnv, 'prod')
|
|
22
|
+
.option('--company <id>', 'Company ID (default: 1)', Number, 1)
|
|
23
|
+
.action((name, token, options) => {
|
|
24
|
+
const { tenant, env, company } = options;
|
|
25
|
+
if (!tenantExists(tenant)) {
|
|
26
|
+
outputCliError('PARAM_INVALID', `Unknown tenant: ${tenant}`, {
|
|
27
|
+
availableTenants: listTenants().map(t => t.name),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
addUser({
|
|
31
|
+
id: '',
|
|
32
|
+
name,
|
|
33
|
+
token,
|
|
34
|
+
tenant,
|
|
35
|
+
env,
|
|
36
|
+
companyID: company,
|
|
37
|
+
createdAt: new Date().toISOString(),
|
|
38
|
+
});
|
|
39
|
+
const id = getUserId(name, tenant);
|
|
40
|
+
outputJson({
|
|
41
|
+
success: true,
|
|
42
|
+
message: `User "${id}" added successfully (${env})`,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/commands/config.ts
|
|
2
|
+
import { getConfigPath, loadConfig } from '../core/config.js';
|
|
3
|
+
import { outputJson } from '../utils/index.js';
|
|
4
|
+
export function registerConfigCommand(program) {
|
|
5
|
+
const configCmd = program
|
|
6
|
+
.command('config')
|
|
7
|
+
.description('Manage configuration')
|
|
8
|
+
.addHelpCommand()
|
|
9
|
+
.action(() => {
|
|
10
|
+
// 默认执行 path 子命令
|
|
11
|
+
outputJson({ path: getConfigPath() });
|
|
12
|
+
});
|
|
13
|
+
// 配置路径
|
|
14
|
+
configCmd
|
|
15
|
+
.command('path')
|
|
16
|
+
.description('Show config file path')
|
|
17
|
+
.action(() => {
|
|
18
|
+
outputJson({ path: getConfigPath() });
|
|
19
|
+
});
|
|
20
|
+
// 显示配置
|
|
21
|
+
configCmd
|
|
22
|
+
.command('show')
|
|
23
|
+
.description('Show current configuration')
|
|
24
|
+
.action(() => {
|
|
25
|
+
const config = loadConfig();
|
|
26
|
+
outputJson(config);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// src/commands/generate-skills.ts
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { outputCliError } from '../core/error.js';
|
|
5
|
+
import { logger } from '../core/logger.js';
|
|
6
|
+
import { PARAM_DEFS, METHOD_DESCRIPTIONS } from './services/kb/descriptions.js';
|
|
7
|
+
const API_ROOT = path.resolve('api');
|
|
8
|
+
const DEFAULT_SKILLS_DIR = path.resolve('api/skills');
|
|
9
|
+
const DEFAULT_OUTPUT = 'api/docs/skills.md';
|
|
10
|
+
// 分类配置:前缀 -> 分类名/描述
|
|
11
|
+
const CATEGORY_CONFIG = {
|
|
12
|
+
'lxcli-builtin': { title: 'Builtin', description: 'lxcli 内置命令。' },
|
|
13
|
+
'kb-': { title: 'Kb', description: 'Kanboard 服务命令。' },
|
|
14
|
+
'meet-': { title: 'Meet', description: 'Meet 服务命令。' },
|
|
15
|
+
'meetbot-': { title: 'Meetbot', description: 'MeetBot 服务命令。' },
|
|
16
|
+
};
|
|
17
|
+
function getCategory(skillName) {
|
|
18
|
+
// 精确匹配 lxcli-builtin
|
|
19
|
+
if (skillName === 'lxcli-builtin') {
|
|
20
|
+
return 'builtin';
|
|
21
|
+
}
|
|
22
|
+
// 前缀匹配
|
|
23
|
+
for (const [prefix, _] of Object.entries(CATEGORY_CONFIG)) {
|
|
24
|
+
if (skillName.startsWith(prefix)) {
|
|
25
|
+
return CATEGORY_CONFIG[prefix].title.toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return 'other';
|
|
29
|
+
}
|
|
30
|
+
function parseFrontmatter(content) {
|
|
31
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
32
|
+
if (!fmMatch) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const fm = fmMatch[1];
|
|
36
|
+
const nameMatch = fm.match(/^name:\s*["']?([^"'\n]+)["']?/m);
|
|
37
|
+
const descMatch = fm.match(/^description:\s*["'](.+)["']/m);
|
|
38
|
+
if (!nameMatch || !descMatch) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
name: nameMatch[1].trim(),
|
|
43
|
+
description: descMatch[1].trim(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function scanSkills(skillsDir) {
|
|
47
|
+
const entries = [];
|
|
48
|
+
try {
|
|
49
|
+
const dirs = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
50
|
+
for (const dir of dirs) {
|
|
51
|
+
if (!dir.isDirectory())
|
|
52
|
+
continue;
|
|
53
|
+
const skillPath = path.join(skillsDir, dir.name, 'SKILL.md');
|
|
54
|
+
try {
|
|
55
|
+
const content = await fs.readFile(skillPath, 'utf-8');
|
|
56
|
+
const meta = parseFrontmatter(content);
|
|
57
|
+
if (meta) {
|
|
58
|
+
entries.push({
|
|
59
|
+
name: meta.name,
|
|
60
|
+
description: meta.description,
|
|
61
|
+
category: getCategory(meta.name),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
logger.info(`Failed to parse frontmatter: ${skillPath}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// SKILL.md not found, skip
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
throw new Error(`Failed to read skills directory: ${skillsDir}`);
|
|
75
|
+
}
|
|
76
|
+
// 按分类排序,同类按名称排序
|
|
77
|
+
const categoryOrder = ['builtin', 'kb', 'meet', 'meetbot', 'other'];
|
|
78
|
+
entries.sort((a, b) => {
|
|
79
|
+
const catA = categoryOrder.indexOf(a.category);
|
|
80
|
+
const catB = categoryOrder.indexOf(b.category);
|
|
81
|
+
if (catA !== catB)
|
|
82
|
+
return catA - catB;
|
|
83
|
+
return a.name.localeCompare(b.name);
|
|
84
|
+
});
|
|
85
|
+
return entries;
|
|
86
|
+
}
|
|
87
|
+
function renderIndex(entries) {
|
|
88
|
+
const lines = [
|
|
89
|
+
'# Skills Index',
|
|
90
|
+
'',
|
|
91
|
+
'> Auto-generated by `lxcli generate-skills`. Do not edit manually.',
|
|
92
|
+
'',
|
|
93
|
+
];
|
|
94
|
+
// 按分类分组
|
|
95
|
+
const grouped = {};
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
if (!grouped[entry.category]) {
|
|
98
|
+
grouped[entry.category] = [];
|
|
99
|
+
}
|
|
100
|
+
grouped[entry.category].push(entry);
|
|
101
|
+
}
|
|
102
|
+
// 渲染各分类
|
|
103
|
+
const categoryMeta = {
|
|
104
|
+
builtin: CATEGORY_CONFIG['lxcli-builtin'],
|
|
105
|
+
kb: CATEGORY_CONFIG['kb-'],
|
|
106
|
+
meet: CATEGORY_CONFIG['meet-'],
|
|
107
|
+
meetbot: CATEGORY_CONFIG['meetbot-'],
|
|
108
|
+
other: { title: 'Other', description: '其他命令。' },
|
|
109
|
+
};
|
|
110
|
+
for (const [cat, items] of Object.entries(grouped)) {
|
|
111
|
+
const meta = categoryMeta[cat] || { title: cat, description: '' };
|
|
112
|
+
lines.push(`## ${meta.title}`, '', meta.description, '');
|
|
113
|
+
lines.push('| Skill | Description |');
|
|
114
|
+
lines.push('|-------|-------------|');
|
|
115
|
+
for (const item of items) {
|
|
116
|
+
lines.push(`| [${item.name}](../skills/${item.name}/SKILL.md) | ${item.description} |`);
|
|
117
|
+
}
|
|
118
|
+
lines.push('');
|
|
119
|
+
}
|
|
120
|
+
return lines.join('\n');
|
|
121
|
+
}
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// SKILL.md 生成
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
function renderParamType(type) {
|
|
126
|
+
const typeMap = {
|
|
127
|
+
'integer': 'integer',
|
|
128
|
+
'string': 'string',
|
|
129
|
+
'array<integer>': 'integer[]',
|
|
130
|
+
'array<string>': 'string[]',
|
|
131
|
+
'object': 'object',
|
|
132
|
+
'file': 'string (base64)',
|
|
133
|
+
};
|
|
134
|
+
return typeMap[type] || type;
|
|
135
|
+
}
|
|
136
|
+
function renderParamsTable(params) {
|
|
137
|
+
if (params.length === 0)
|
|
138
|
+
return '';
|
|
139
|
+
const lines = [
|
|
140
|
+
'## Parameters',
|
|
141
|
+
'',
|
|
142
|
+
'| 参数 | 类型 | 必填 | 说明 |',
|
|
143
|
+
'|------|------|------|------|',
|
|
144
|
+
];
|
|
145
|
+
for (const p of params) {
|
|
146
|
+
const required = p.required ? '✓' : '';
|
|
147
|
+
lines.push(`| ${p.name} | ${renderParamType(p.type)} | ${required} | ${p.description} |`);
|
|
148
|
+
}
|
|
149
|
+
lines.push('');
|
|
150
|
+
return lines.join('\n');
|
|
151
|
+
}
|
|
152
|
+
function renderSkillMd(method) {
|
|
153
|
+
const description = METHOD_DESCRIPTIONS[method];
|
|
154
|
+
const params = PARAM_DEFS[method] || [];
|
|
155
|
+
const skillName = `kb-${method}`;
|
|
156
|
+
const hasParams = params.length > 0;
|
|
157
|
+
// 快捷参数列表
|
|
158
|
+
const shortcuts = params.filter(p => p.required).map(p => `--${p.name} <${p.type === 'integer' ? 'ID' : 'VALUE'}>`);
|
|
159
|
+
const shortcutUsage = shortcuts.length > 0 ? `lxcli kb ${method} ${shortcuts.join(' ')}` : '';
|
|
160
|
+
// Usage 部分
|
|
161
|
+
const usageLines = ['## Usage', '', '```bash'];
|
|
162
|
+
if (hasParams) {
|
|
163
|
+
if (shortcutUsage) {
|
|
164
|
+
usageLines.push(shortcutUsage);
|
|
165
|
+
}
|
|
166
|
+
// 生成 --params 示例
|
|
167
|
+
const paramsExample = {};
|
|
168
|
+
for (const p of params) {
|
|
169
|
+
if (p.type === 'array<integer>') {
|
|
170
|
+
paramsExample[p.name] = [1, 2];
|
|
171
|
+
}
|
|
172
|
+
else if (p.type === 'array<string>') {
|
|
173
|
+
paramsExample[p.name] = ['id1', 'id2'];
|
|
174
|
+
}
|
|
175
|
+
else if (p.type === 'integer') {
|
|
176
|
+
paramsExample[p.name] = 123;
|
|
177
|
+
}
|
|
178
|
+
else if (p.type === 'object') {
|
|
179
|
+
paramsExample[p.name] = {};
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
paramsExample[p.name] = 'value';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
usageLines.push(`lxcli kb ${method} --params '${JSON.stringify(paramsExample)}'`);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
usageLines.push(`lxcli kb ${method}`);
|
|
189
|
+
}
|
|
190
|
+
usageLines.push('```', '');
|
|
191
|
+
// 快捷参数说明
|
|
192
|
+
if (hasParams && shortcutUsage) {
|
|
193
|
+
usageLines.push(`快捷参数使用见上方;\`--params\` 用于完整参数场景。若同时传入,CLI 优先使用 \`--params\`。`, '');
|
|
194
|
+
}
|
|
195
|
+
// 组装 SKILL.md
|
|
196
|
+
const frontmatter = `---
|
|
197
|
+
name: ${skillName}
|
|
198
|
+
description: "Kanboard: ${description}"
|
|
199
|
+
metadata:
|
|
200
|
+
version: 1.0.0
|
|
201
|
+
openclaw:
|
|
202
|
+
category: "productivity"
|
|
203
|
+
requires:
|
|
204
|
+
bins:
|
|
205
|
+
- lxcli
|
|
206
|
+
cliHelp: "lxcli kb ${method} --help"
|
|
207
|
+
---`;
|
|
208
|
+
const lines = [
|
|
209
|
+
frontmatter,
|
|
210
|
+
'',
|
|
211
|
+
`# kb ${method}`,
|
|
212
|
+
'',
|
|
213
|
+
description + '。',
|
|
214
|
+
'',
|
|
215
|
+
...usageLines,
|
|
216
|
+
];
|
|
217
|
+
if (hasParams) {
|
|
218
|
+
lines.push(renderParamsTable(params));
|
|
219
|
+
}
|
|
220
|
+
lines.push('## Examples', '', '```bash', shortcutUsage || `lxcli kb ${method}`, '```', '');
|
|
221
|
+
return lines.join('\n');
|
|
222
|
+
}
|
|
223
|
+
async function generateServiceSkills(skillsDir, serviceName) {
|
|
224
|
+
// 目前只支持 kb 服务
|
|
225
|
+
if (serviceName !== 'kb') {
|
|
226
|
+
logger.info(`Service "${serviceName}" is not supported. Supported services: kb`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const methods = Object.keys(PARAM_DEFS);
|
|
230
|
+
for (const method of methods) {
|
|
231
|
+
const skillName = `${serviceName}-${method}`;
|
|
232
|
+
const skillDir = path.join(skillsDir, skillName);
|
|
233
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
234
|
+
// 检查是否已有手写的 SKILL.md
|
|
235
|
+
try {
|
|
236
|
+
await fs.access(skillPath);
|
|
237
|
+
logger.info(`Skipped ${skillName}: SKILL.md already exists`);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// 不存在,生成新的
|
|
242
|
+
}
|
|
243
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
244
|
+
const content = renderSkillMd(method);
|
|
245
|
+
await fs.writeFile(skillPath, content, 'utf-8');
|
|
246
|
+
logger.info(`Generated ${skillName}/SKILL.md`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
export function registerGenerateSkillsCommand(program) {
|
|
250
|
+
program
|
|
251
|
+
.command('generate-skills')
|
|
252
|
+
.description('生成 skills 索引文件')
|
|
253
|
+
.option('-o, --output <path>', '输出文件路径', DEFAULT_OUTPUT)
|
|
254
|
+
.option('-s, --service <name>', '为指定服务生成缺失的 SKILL.md 文件(如 kb, meet)')
|
|
255
|
+
.action(async (options) => {
|
|
256
|
+
try {
|
|
257
|
+
const stat = await fs.stat(API_ROOT);
|
|
258
|
+
if (!stat.isDirectory()) {
|
|
259
|
+
outputCliError('PARAM_INVALID', 'api submodule is not initialized. Run git submodule update --init --recursive first.');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
outputCliError('PARAM_INVALID', 'api submodule is not initialized. Run git submodule update --init --recursive first.');
|
|
264
|
+
}
|
|
265
|
+
const skillsDir = DEFAULT_SKILLS_DIR;
|
|
266
|
+
const outputPath = path.resolve(options.output);
|
|
267
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
268
|
+
logger.info(`Scanning skills from ${skillsDir}`);
|
|
269
|
+
// 如果指定了 --service,先生成缺失的 SKILL.md
|
|
270
|
+
if (options.service) {
|
|
271
|
+
logger.info(`Generating missing ${options.service} SKILL.md files...`);
|
|
272
|
+
await generateServiceSkills(skillsDir, options.service);
|
|
273
|
+
}
|
|
274
|
+
const entries = await scanSkills(skillsDir);
|
|
275
|
+
logger.info(`Found ${entries.length} skills`);
|
|
276
|
+
const content = renderIndex(entries);
|
|
277
|
+
// 确保输出目录存在
|
|
278
|
+
const outputDir = path.dirname(outputPath);
|
|
279
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
280
|
+
await fs.writeFile(outputPath, content, 'utf-8');
|
|
281
|
+
console.log(JSON.stringify({ generated: outputPath, count: entries.length }, null, 2));
|
|
282
|
+
});
|
|
283
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { ServiceCommand } from '../types/index.js';
|
|
3
|
+
export interface BuiltInCommand {
|
|
4
|
+
name: string;
|
|
5
|
+
register(program: Command): void;
|
|
6
|
+
}
|
|
7
|
+
export declare const builtInCommands: BuiltInCommand[];
|
|
8
|
+
export declare const externalServices: ServiceCommand[];
|
|
9
|
+
export declare function registerAllCommands(program: Command): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/commands/index.ts
|
|
2
|
+
import { configureHelp } from '../core/help.js';
|
|
3
|
+
import { registerAuthCommand } from './auth.js';
|
|
4
|
+
import { registerUsersCommand } from './users.js';
|
|
5
|
+
import { registerTenantsCommand } from './tenants.js';
|
|
6
|
+
import { registerServicesCommand } from './services.js';
|
|
7
|
+
import { registerConfigCommand } from './config.js';
|
|
8
|
+
import { registerUpgradeCommand } from './upgrade.js';
|
|
9
|
+
import { registerGenerateSkillsCommand } from './generate-skills.js';
|
|
10
|
+
import { registerSchemaCommand } from './schema.js';
|
|
11
|
+
import kbService from './services/kb/index.js';
|
|
12
|
+
import meetbotService from './services/meetbot/index.js';
|
|
13
|
+
import meetService from './services/meet/index.js';
|
|
14
|
+
export const builtInCommands = [
|
|
15
|
+
{ name: 'auth', register: registerAuthCommand },
|
|
16
|
+
{ name: 'users', register: registerUsersCommand },
|
|
17
|
+
{ name: 'tenants', register: registerTenantsCommand },
|
|
18
|
+
{ name: 'services', register: registerServicesCommand },
|
|
19
|
+
{ name: 'config', register: registerConfigCommand },
|
|
20
|
+
{ name: 'upgrade', register: registerUpgradeCommand },
|
|
21
|
+
{ name: 'generate-skills', register: registerGenerateSkillsCommand },
|
|
22
|
+
{ name: 'schema', register: registerSchemaCommand },
|
|
23
|
+
];
|
|
24
|
+
export const externalServices = [
|
|
25
|
+
kbService,
|
|
26
|
+
meetbotService,
|
|
27
|
+
meetService,
|
|
28
|
+
];
|
|
29
|
+
export function registerAllCommands(program) {
|
|
30
|
+
// 注册内置命令
|
|
31
|
+
builtInCommands.forEach(({ register }) => {
|
|
32
|
+
register(program);
|
|
33
|
+
});
|
|
34
|
+
// 过滤启用的服务
|
|
35
|
+
const enabledServices = externalServices.filter(service => service.enabled !== false);
|
|
36
|
+
// 注册服务命令(hidden: 不在默认 Commands 列表中显示,但仍可使用)
|
|
37
|
+
enabledServices.forEach((service) => {
|
|
38
|
+
const cmd = program.command(service.name, { hidden: true });
|
|
39
|
+
service.register(cmd);
|
|
40
|
+
});
|
|
41
|
+
// 配置自定义帮助:在 Commands 后追加 Services 分组
|
|
42
|
+
configureHelp(program, enabledServices);
|
|
43
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { outputCliError } from '../core/error.js';
|
|
2
|
+
import { METHOD_DESCRIPTIONS, METHODS, PARAM_DEFS } from './services/kb/descriptions.js';
|
|
3
|
+
const SORTED_METHODS = [...METHODS].sort();
|
|
4
|
+
export function registerSchemaCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('schema')
|
|
7
|
+
.description('查看服务方法参数 schema')
|
|
8
|
+
.argument('<service>', 'Service name')
|
|
9
|
+
.argument('[method]', 'Method name')
|
|
10
|
+
.action((service, method) => {
|
|
11
|
+
if (service !== 'kb') {
|
|
12
|
+
outputCliError('SERVICE_NOT_FOUND', `Schema for service "${service}" is not supported`, { service });
|
|
13
|
+
}
|
|
14
|
+
if (!method) {
|
|
15
|
+
const methods = SORTED_METHODS.map((name) => ({
|
|
16
|
+
name,
|
|
17
|
+
description: METHOD_DESCRIPTIONS[name],
|
|
18
|
+
paramsCount: PARAM_DEFS[name]?.length ?? 0,
|
|
19
|
+
}));
|
|
20
|
+
console.log(JSON.stringify({ service: 'kb', methods }, null, 2));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!METHODS.includes(method)) {
|
|
24
|
+
outputCliError('PARAM_INVALID', `Method "${method}" not found for service "kb"`, {
|
|
25
|
+
service: 'kb',
|
|
26
|
+
method,
|
|
27
|
+
availableMethods: SORTED_METHODS,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
console.log(JSON.stringify({
|
|
31
|
+
service: 'kb',
|
|
32
|
+
method,
|
|
33
|
+
description: METHOD_DESCRIPTIONS[method],
|
|
34
|
+
params: PARAM_DEFS[method] ?? [],
|
|
35
|
+
}, null, 2));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/commands/services/kb/config.ts
|
|
2
|
+
// kanboard gateway 配置(按企业、按环境)
|
|
3
|
+
// 实际地址需要根据部署环境配置
|
|
4
|
+
const meetGateways = {
|
|
5
|
+
test: 'https://kanboard-test.miyachat.com',
|
|
6
|
+
pre: 'https://kanboard-pre.miyachat.com',
|
|
7
|
+
staging: 'https://kanboard-staging.miyachat.com',
|
|
8
|
+
beta: 'https://kanboard-beta.miyachat.com',
|
|
9
|
+
prod: 'https://kanboard.miyachat.com',
|
|
10
|
+
};
|
|
11
|
+
const coGateways = {
|
|
12
|
+
test: 'https://kanboard-test.co.example.com',
|
|
13
|
+
pre: 'https://kanboard-pre.co.example.com',
|
|
14
|
+
staging: 'https://kanboard-staging.co.example.com',
|
|
15
|
+
beta: 'https://kanboard-beta.co.example.com',
|
|
16
|
+
prod: 'https://kanboard.co.example.com',
|
|
17
|
+
};
|
|
18
|
+
const skyGateways = {
|
|
19
|
+
test: 'https://kanboard-test.sky.example.com',
|
|
20
|
+
pre: 'https://kanboard-pre.sky.example.com',
|
|
21
|
+
staging: 'https://kanboard-staging.sky.example.com',
|
|
22
|
+
beta: 'https://kanboard-beta.sky.example.com',
|
|
23
|
+
prod: 'https://kanboard.sky.example.com',
|
|
24
|
+
};
|
|
25
|
+
export const kbConfig = {
|
|
26
|
+
name: 'kb',
|
|
27
|
+
gateways: {
|
|
28
|
+
meet: meetGateways,
|
|
29
|
+
co: coGateways,
|
|
30
|
+
sky: skyGateways,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/commands/services/kb/descriptions.ts
|
|
2
|
+
//
|
|
3
|
+
// 方法列表和描述,参数定义见 params.ts
|
|
4
|
+
import { PARAM_DEFS } from './params.js';
|
|
5
|
+
// 方法列表(从 PARAM_DEFS 自动推导)
|
|
6
|
+
export const METHODS = Object.keys(PARAM_DEFS);
|
|
7
|
+
// 方法描述
|
|
8
|
+
export const METHOD_DESCRIPTIONS = {
|
|
9
|
+
getMe: '获取当前用户信息',
|
|
10
|
+
getMyProjects: '获取我的项目列表',
|
|
11
|
+
getTask: '获取需求/bug 明细',
|
|
12
|
+
queryUsers: '获取用户信息',
|
|
13
|
+
searchTasks: '搜索需求/bug 列表',
|
|
14
|
+
getAllActiveIterations: '获取启用迭代列表',
|
|
15
|
+
createTask: '创建需求/bug',
|
|
16
|
+
createSubtask: '创建子任务',
|
|
17
|
+
updateTask: '修改需求',
|
|
18
|
+
getAllComments: '获取评论列表',
|
|
19
|
+
createComment: '对需求增加评论',
|
|
20
|
+
createProjectFile: '上传附件到项目',
|
|
21
|
+
createTaskFile: '上传附件到需求',
|
|
22
|
+
};
|
|
23
|
+
// 重新导出 params.ts 的类型和定义,方便其他模块使用
|
|
24
|
+
export { PARAM_DEFS } from './params.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Method } from './descriptions.js';
|
|
2
|
+
export interface KbCommandOptions {
|
|
3
|
+
params?: string;
|
|
4
|
+
task_id?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
project_id?: string;
|
|
7
|
+
query?: string;
|
|
8
|
+
user?: string;
|
|
9
|
+
gateway?: string;
|
|
10
|
+
file?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function createKbAction(method: Method): (options: KbCommandOptions) => Promise<void>;
|