@qinsilk/scm-agent-tools 0.1.0 → 0.2.0
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/README.md +1 -1
- package/cli/commands/data.js +112 -0
- package/cli/commands/docs.js +121 -0
- package/cli/commands/query.js +58 -2
- package/cli/datasets.js +541 -0
- package/cli/scm-agent.js +6 -1
- package/docs/api.md +2067 -0
- package/docs/cli.md +72 -0
- package/package.json +2 -2
- package/skills/qinsilk-scm/SKILL.md +32 -1
package/README.md
CHANGED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// data 命令族:给 AI 一条受控取数入口,对应报表内部接口(数据集定义见 cli/datasets.js)。
|
|
2
|
+
// 三条固定限制在这里实施:强制条件不满足不发请求;日期跨度上限在本地校验;
|
|
3
|
+
// 分页数据集一次只取一页,末尾提示还有多少条(不自动翻页,翻页由调用方显式 --page)。
|
|
4
|
+
// 官方报表不受影响:reports/*.js 仍然直接调这些接口拉全量。
|
|
5
|
+
const { call } = require('../api');
|
|
6
|
+
const { printTable } = require('../table');
|
|
7
|
+
const {
|
|
8
|
+
DATASETS, findDataset, listDatasets, requireText, validateArgs, buildQuery, unknownOptions, ALL_OPTIONS, kebab,
|
|
9
|
+
} = require('../datasets');
|
|
10
|
+
|
|
11
|
+
const data = {
|
|
12
|
+
command: 'data [name]',
|
|
13
|
+
describe: '取报表数据(16 个数据集;scm-agent data --list 查看全部与必填条件)',
|
|
14
|
+
builder: (y) => {
|
|
15
|
+
y.positional('name', {
|
|
16
|
+
describe: '数据集名(见 scm-agent data --list)',
|
|
17
|
+
type: 'string',
|
|
18
|
+
});
|
|
19
|
+
y.option('list', {
|
|
20
|
+
describe: '列出全部数据集、必填条件与用途',
|
|
21
|
+
type: 'boolean',
|
|
22
|
+
});
|
|
23
|
+
// 参数并集来自数据集定义;单个数据集不接受的参数在 handler 里提示,
|
|
24
|
+
// 这样 --help 一处就能看到全部参数,而错误信息仍然具体。
|
|
25
|
+
for (const option of ALL_OPTIONS) {
|
|
26
|
+
y.option(option.name, { describe: option.describe, type: option.type });
|
|
27
|
+
}
|
|
28
|
+
return y;
|
|
29
|
+
},
|
|
30
|
+
handler: async (argv) => {
|
|
31
|
+
try {
|
|
32
|
+
await runData(argv);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.error(`错误:${e.message}`);
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
async function runData(argv) {
|
|
41
|
+
if (argv.list || !argv.name) {
|
|
42
|
+
printDatasetList();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const dataset = findDataset(argv.name);
|
|
47
|
+
if (!dataset) {
|
|
48
|
+
throw new Error(`没有数据集「${argv.name}」。可用:${DATASETS.map((d) => d.name).join('、')}(scm-agent data --list 查看必填条件)`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const extra = unknownOptions(dataset, argv);
|
|
52
|
+
if (extra.length) {
|
|
53
|
+
throw new Error(`数据集 ${dataset.name} 不接受参数 ${extra.join('、')};它支持:${dataset.options.map((o) => '--' + kebab(o.name)).join('、') || '(无参数)'}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const invalid = validateArgs(dataset, argv);
|
|
57
|
+
if (invalid) {
|
|
58
|
+
throw new Error(invalid);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const query = buildQuery(dataset, argv);
|
|
62
|
+
const payload = await call('GET', dataset.path, query);
|
|
63
|
+
|
|
64
|
+
if (argv.json) {
|
|
65
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 分页数据集:{ page, limit, total, list }
|
|
70
|
+
if (dataset.paged) {
|
|
71
|
+
const rows = payload.list || [];
|
|
72
|
+
console.log(`共 ${payload.total} 条,当前显示 ${rows.length} 条`);
|
|
73
|
+
if (!rows.length) {
|
|
74
|
+
console.log('(无数据)');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
printTable(rows, dataset.columns);
|
|
78
|
+
const page = Number(argv.page) || 1;
|
|
79
|
+
const taken = (page - 1) * (Number(argv.limit) || 20) + rows.length;
|
|
80
|
+
if (payload.total > taken) {
|
|
81
|
+
console.log(`\n还有 ${payload.total - taken} 条未取;用 --page ${page + 1} 继续,或加 --limit 100 减少请求次数`);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 数组型单条接口(工序节点、物料占用):一次返回全部行,没有分页
|
|
87
|
+
if (Array.isArray(payload)) {
|
|
88
|
+
console.log(`共 ${payload.length} 条`);
|
|
89
|
+
if (!payload.length) {
|
|
90
|
+
console.log('(无数据)');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
printTable(payload, dataset.columns);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 单对象(BOM、对账 11 表、工序明细、公司配置):字段太多,直接输出 JSON
|
|
98
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** data --list:数据集名、必填条件、用途 */
|
|
102
|
+
function printDatasetList() {
|
|
103
|
+
console.log('数据集清单(字段说明:scm-agent docs api --find <字段名>):');
|
|
104
|
+
printTable(listDatasets(), [
|
|
105
|
+
{ field: 'name', label: '数据集' },
|
|
106
|
+
{ field: 'require', label: '必填条件' },
|
|
107
|
+
{ field: 'describe', label: '用途' },
|
|
108
|
+
]);
|
|
109
|
+
console.log('\n日期类数据集跨度上限 92 天;分页数据集一次只取一页,用 --page 翻页。');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { data, runData, printDatasetList, requireText };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// 文档命令族:把包内附带的说明文档路径与内容交给 AI。
|
|
2
|
+
// 文档随 npm 包发布,AI 的上下文里看不到文件系统,因此需要一条命令拿到绝对路径,
|
|
3
|
+
// 或者按关键词只取需要的那几行,避免把整份文档读进上下文。
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
/** 包内文档清单:命令里用的短名 → 文件名与用途 */
|
|
8
|
+
const DOCS = {
|
|
9
|
+
cli: { file: 'cli.md', describe: '全部命令、参数、错误码与写命令的确认方式' },
|
|
10
|
+
api: { file: 'api.md', describe: '接口返回字段字典:字段含义、取值枚举、单位、为空的原因' },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function docsRoot() {
|
|
14
|
+
return path.join(__dirname, '..', '..', 'docs');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function docPath(name) {
|
|
18
|
+
const entry = DOCS[name];
|
|
19
|
+
if (!entry) {
|
|
20
|
+
throw new Error(`没有文档「${name}」。可用:${Object.keys(DOCS).join('、')}`);
|
|
21
|
+
}
|
|
22
|
+
const filePath = path.join(docsRoot(), entry.file);
|
|
23
|
+
if (!fs.existsSync(filePath)) {
|
|
24
|
+
throw new Error(`文档文件不存在:${filePath}(安装可能不完整,执行 scm-agent update 重新安装)`);
|
|
25
|
+
}
|
|
26
|
+
return filePath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 按关键词筛出文档里相关的行:匹配行本身,加上它上方最近的一级或二级标题,
|
|
31
|
+
* 让 AI 即使只拿到几行也能判断属于哪个接口、哪个层级。
|
|
32
|
+
*/
|
|
33
|
+
function findLines(lines, keyword) {
|
|
34
|
+
const lower = keyword.toLowerCase();
|
|
35
|
+
const hits = [];
|
|
36
|
+
let lastHeading = '';
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
if (line.startsWith('#')) {
|
|
39
|
+
lastHeading = line;
|
|
40
|
+
}
|
|
41
|
+
if (line.toLowerCase().includes(lower)) {
|
|
42
|
+
hits.push({ heading: lastHeading, line });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return hits;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const docs = {
|
|
49
|
+
command: 'docs [name]',
|
|
50
|
+
describe: '查看包内文档的位置与内容(命令说明、接口字段字典)',
|
|
51
|
+
builder: (yargs) => yargs
|
|
52
|
+
.positional('name', {
|
|
53
|
+
describe: `文档名:${Object.keys(DOCS).join(' 或 ')}(不填则列出全部文档)`,
|
|
54
|
+
type: 'string',
|
|
55
|
+
})
|
|
56
|
+
.option('find', {
|
|
57
|
+
describe: '只输出包含该关键词的行(用于查某个字段或命令,避免读入整份文档)',
|
|
58
|
+
type: 'string',
|
|
59
|
+
})
|
|
60
|
+
.option('print', {
|
|
61
|
+
describe: '输出整份文档内容',
|
|
62
|
+
type: 'boolean',
|
|
63
|
+
default: false,
|
|
64
|
+
})
|
|
65
|
+
.example('scm-agent docs', '列出全部文档与文件位置')
|
|
66
|
+
.example('scm-agent docs api --find cutBedState', '查单个字段的含义')
|
|
67
|
+
.example('scm-agent docs api --print', '输出完整字段字典'),
|
|
68
|
+
handler: (argv) => {
|
|
69
|
+
try {
|
|
70
|
+
if (!argv.name) {
|
|
71
|
+
console.log('包内文档(文件随 npm 包安装,路径为本机绝对路径):');
|
|
72
|
+
for (const [name, entry] of Object.entries(DOCS)) {
|
|
73
|
+
const filePath = path.join(docsRoot(), entry.file);
|
|
74
|
+
const exists = fs.existsSync(filePath);
|
|
75
|
+
console.log(` ${name.padEnd(4)} ${entry.describe}`);
|
|
76
|
+
console.log(` ${exists ? filePath : '(缺失,执行 scm-agent update 重新安装)'}`);
|
|
77
|
+
}
|
|
78
|
+
console.log('\n查某个字段或命令:scm-agent docs api --find <关键词>');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const filePath = docPath(argv.name);
|
|
83
|
+
const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
|
|
84
|
+
|
|
85
|
+
if (argv.find) {
|
|
86
|
+
const hits = findLines(lines, argv.find);
|
|
87
|
+
if (!hits.length) {
|
|
88
|
+
console.log(`文档 ${argv.name} 里没有包含「${argv.find}」的内容。`);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
let lastHeading = null;
|
|
93
|
+
for (const hit of hits) {
|
|
94
|
+
// 命中的行本身就是标题时不再重复输出一次标题
|
|
95
|
+
const isHeading = hit.line.startsWith('#');
|
|
96
|
+
if (!isHeading && hit.heading && hit.heading !== lastHeading) {
|
|
97
|
+
console.log(`\n${hit.heading}`);
|
|
98
|
+
lastHeading = hit.heading;
|
|
99
|
+
}
|
|
100
|
+
console.log(hit.line);
|
|
101
|
+
}
|
|
102
|
+
console.log(`\n(以上为 ${path.basename(filePath)} 中匹配「${argv.find}」的内容,共 ${hits.length} 行)`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (argv.print) {
|
|
107
|
+
console.log(lines.join('\n'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 默认只给路径:AI 的上下文有限,整份文档通常不需要一次读完
|
|
112
|
+
console.log(`${path.basename(filePath)}(${lines.length} 行):${filePath}`);
|
|
113
|
+
console.log('查单个字段:加 --find <关键词>;需要全文:加 --print');
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.error(`错误:${e.message}`);
|
|
116
|
+
process.exitCode = 1;
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
module.exports = { docs, DOCS, findLines, docPath };
|
package/cli/commands/query.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// 四组业务命令:商品、物料、生产单、物料采购单。design 命令族查询设计款与 IE
|
|
1
|
+
// 四组业务命令:商品、物料、生产单、物料采购单。design 命令族查询设计款与 IE 核价,
|
|
2
|
+
// design-order 命令族查询版单(旗舰版功能)。
|
|
2
3
|
// 每个命令默认输出人类可读表格;全局 --json 时输出原始 JSON(供 AI 解析)。
|
|
3
4
|
const { call } = require('../api');
|
|
4
5
|
const { printTable } = require('../table');
|
|
@@ -204,6 +205,61 @@ const design = {
|
|
|
204
205
|
},
|
|
205
206
|
};
|
|
206
207
|
|
|
208
|
+
const designOrder = {
|
|
209
|
+
command: 'design-order',
|
|
210
|
+
describe: '查询版单(旗舰版功能)',
|
|
211
|
+
builder(yargs) {
|
|
212
|
+
return yargs
|
|
213
|
+
.command({
|
|
214
|
+
command: 'list',
|
|
215
|
+
describe: '版单列表(至少给一项条件)',
|
|
216
|
+
builder: (y) => y
|
|
217
|
+
.option('sn', { describe: '关键词:款号/名称/版单号/备注/货号', type: 'string' })
|
|
218
|
+
.option('states', { describe: '版单状态(逗号分隔):1未开始 2进行中 3已结束 4废版 9撤销 10审批中 12审批拒绝', type: 'string' })
|
|
219
|
+
.option('review-state', { describe: '审版状态:2未设置审版 3待审 4已审', type: 'number' })
|
|
220
|
+
.option('node', { describe: '当前节点名称,如:纸样、车板', type: 'string' })
|
|
221
|
+
.option('overdue', { describe: '逾期筛选:pattern纸样逾期 sweep车版逾期', type: 'string' })
|
|
222
|
+
.option('dateType', { describe: '日期字段:business下版(默认) create创建 update更新 finish预计完成 delivery交付', type: 'string', default: 'business' })
|
|
223
|
+
.option('from', { describe: '日期起(yyyy-MM-dd)', type: 'string' })
|
|
224
|
+
.option('to', { describe: '日期止(yyyy-MM-dd)', type: 'string' }),
|
|
225
|
+
handler: (argv) => run(argv, async () => {
|
|
226
|
+
const data = await call('GET', '/api/agent/v1/design-order/list', {
|
|
227
|
+
searchKey: argv.sn, states: argv.states, reviewState: argv.reviewState,
|
|
228
|
+
node: argv.node, overdue: argv.overdue, dateType: argv.dateType,
|
|
229
|
+
from: argv.from, to: argv.to, page: argv.page, limit: argv.limit,
|
|
230
|
+
});
|
|
231
|
+
return { list: data.list, total: data.total, columns: [
|
|
232
|
+
{ field: 'designOrderSn', label: '版单号' },
|
|
233
|
+
{ field: 'designSn', label: '款号' },
|
|
234
|
+
{ field: 'designName', label: '名称' },
|
|
235
|
+
{ field: 'stateText', label: '状态' },
|
|
236
|
+
{ field: 'currentProgressState', label: '当前节点' },
|
|
237
|
+
{ field: 'designOrderSerialName', label: '版次' },
|
|
238
|
+
{ field: 'businessTime', label: '下版日期' },
|
|
239
|
+
{ field: 'finishTime', label: '预计完成' },
|
|
240
|
+
{ field: 'totalNumber', label: '打版数' },
|
|
241
|
+
{ field: 'supplierName', label: '供应商' },
|
|
242
|
+
] };
|
|
243
|
+
}),
|
|
244
|
+
})
|
|
245
|
+
.command({
|
|
246
|
+
command: 'detail',
|
|
247
|
+
describe: '版单详情(打版数量、节点进度、BOM、工艺要求、同款全部版单)',
|
|
248
|
+
builder: (y) => y
|
|
249
|
+
.option('sn', { describe: '版单号', type: 'string' })
|
|
250
|
+
.option('id', { describe: '版单id(先用 list 查到)', type: 'number' }),
|
|
251
|
+
handler: (argv) => run(argv, async () => {
|
|
252
|
+
if (!argv.sn && !argv.id) {
|
|
253
|
+
throw new Error('请提供版单号 --sn 或版单 id --id(二选一)');
|
|
254
|
+
}
|
|
255
|
+
const data = await call('GET', '/api/agent/v1/design-order/detail', { sn: argv.sn, id: argv.id });
|
|
256
|
+
return { data, columns: null };
|
|
257
|
+
}),
|
|
258
|
+
})
|
|
259
|
+
.demandCommand(1, '请指定子命令:list 或 detail');
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
|
|
207
263
|
/** 统一执行:异常输出可读文案并以非 0 退出;--json 输出原始数据 */
|
|
208
264
|
async function run(argv, task) {
|
|
209
265
|
try {
|
|
@@ -226,4 +282,4 @@ async function run(argv, task) {
|
|
|
226
282
|
}
|
|
227
283
|
}
|
|
228
284
|
|
|
229
|
-
module.exports = { goods, material, produce, materialPurchase, design };
|
|
285
|
+
module.exports = { goods, material, produce, materialPurchase, design, designOrder };
|