@ty_krystal/sei-ai 0.1.0 → 0.1.2
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 +6 -0
- package/dist/README.md +6 -0
- package/dist/index.js +379 -41
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
3
|
import { dirname, resolve } from "node:path";
|
|
5
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
6
|
import { createServer } from "node:http";
|
|
7
7
|
import { ZodOptional, z } from "zod";
|
|
8
8
|
import process$1 from "node:process";
|
|
@@ -58,7 +58,26 @@ var ACCOUNT_CHECK_CODE_PATH = "/api/sei/public/checkCode";
|
|
|
58
58
|
var QUERY_PATH = "/api/sei/data/public/query";
|
|
59
59
|
var SAVE_PATH = "/api/sei/data/public/save";
|
|
60
60
|
var QUERY_SQL_PATH = "/api/sei/data/querySQL";
|
|
61
|
+
var DDL_EXECUTE_PATH = "/api/sei/mcp/ddl/execute";
|
|
61
62
|
var OPENAPI_DOCS_PATH = "/v3/api-docs";
|
|
63
|
+
var DICT_MODULE = "sys_dic";
|
|
64
|
+
var DICT_FIELDS = [
|
|
65
|
+
"_UUID",
|
|
66
|
+
"_PID",
|
|
67
|
+
"_SYSID",
|
|
68
|
+
"_TYPE",
|
|
69
|
+
"_CTYPE",
|
|
70
|
+
"_CODE",
|
|
71
|
+
"_NAME",
|
|
72
|
+
"_ENAME",
|
|
73
|
+
"_DICT",
|
|
74
|
+
"_SORT",
|
|
75
|
+
"_BODY",
|
|
76
|
+
"_MEMO"
|
|
77
|
+
];
|
|
78
|
+
var BASE_SEED_DEFAULT_ADMIN_UID = "admin";
|
|
79
|
+
var BASE_SEED_DEFAULT_ADMIN_ROLE = "admin";
|
|
80
|
+
var BASE_SEED_DEFAULT_ADMIN_PASSWORD = "123456";
|
|
62
81
|
var ACCOUNT_LOGIN_DEFAULT_TYPE = "login";
|
|
63
82
|
var TOKEN_CACHE_DIR_NAME = "sei-ai";
|
|
64
83
|
var TOKEN_CACHE_FILE_PREFIX = "token-";
|
|
@@ -76,8 +95,205 @@ var SeiMcpError = class extends Error {
|
|
|
76
95
|
}
|
|
77
96
|
};
|
|
78
97
|
//#endregion
|
|
98
|
+
//#region src/cli-helpers.ts
|
|
99
|
+
function buildDictQueryPayload(typeCode, keyword, size = 1e3) {
|
|
100
|
+
return {
|
|
101
|
+
head: { module: DICT_MODULE },
|
|
102
|
+
option: {
|
|
103
|
+
keyField: true,
|
|
104
|
+
privilege: true,
|
|
105
|
+
fields: DICT_FIELDS.join(","),
|
|
106
|
+
filter: buildDictQueryFilter(typeCode, keyword),
|
|
107
|
+
order: "_SORT ASC,_CODE ASC",
|
|
108
|
+
page: 1,
|
|
109
|
+
size
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function buildDictSavePayload(row) {
|
|
114
|
+
return {
|
|
115
|
+
head: { module: DICT_MODULE },
|
|
116
|
+
data: [{
|
|
117
|
+
action: "add",
|
|
118
|
+
option: { keyVal: true },
|
|
119
|
+
rows: [{ row }]
|
|
120
|
+
}]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function buildDictCategoryRow(input) {
|
|
124
|
+
const typeCode = requiredText(input.typeCode, "字典类别");
|
|
125
|
+
const name = requiredText(input.name, "字典名称");
|
|
126
|
+
const uuid = requiredText(input.uuid || typeCode, "字典编号");
|
|
127
|
+
const code = requiredText(input.code || typeCode, "字典代码");
|
|
128
|
+
return {
|
|
129
|
+
_UUID: uuid,
|
|
130
|
+
_PID: "",
|
|
131
|
+
_SYSID: requiredText(input.sysid || "sys", "所属系统"),
|
|
132
|
+
_TYPE: typeCode,
|
|
133
|
+
_CTYPE: normalizeText$1(input.ctype),
|
|
134
|
+
_CODE: code,
|
|
135
|
+
_NAME: name,
|
|
136
|
+
_ENAME: normalizeText$1(input.ename),
|
|
137
|
+
_DICT: normalizeDictFlag(input.dictFlag),
|
|
138
|
+
_SORT: normalizeSort(input.sort),
|
|
139
|
+
_BODY: normalizeText$1(input.body),
|
|
140
|
+
_MEMO: normalizeText$1(input.memo)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function buildDictItemRow(input) {
|
|
144
|
+
const typeCode = requiredText(input.typeCode, "字典类别");
|
|
145
|
+
const code = requiredText(input.code, "字典代码");
|
|
146
|
+
const name = requiredText(input.name, "字典名称");
|
|
147
|
+
const parent = requiredText(input.parent || typeCode, "父级编号");
|
|
148
|
+
return {
|
|
149
|
+
_UUID: requiredText(input.uuid || `${parent}_${code}`, "字典编号"),
|
|
150
|
+
_PID: parent,
|
|
151
|
+
_SYSID: requiredText(input.sysid || "sys", "所属系统"),
|
|
152
|
+
_TYPE: typeCode,
|
|
153
|
+
_CTYPE: normalizeText$1(input.ctype),
|
|
154
|
+
_CODE: code,
|
|
155
|
+
_NAME: name,
|
|
156
|
+
_ENAME: normalizeText$1(input.ename),
|
|
157
|
+
_DICT: normalizeDictFlag(input.dictFlag),
|
|
158
|
+
_SORT: normalizeSort(input.sort),
|
|
159
|
+
_BODY: normalizeText$1(input.body),
|
|
160
|
+
_MEMO: normalizeText$1(input.memo)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function buildDictTree(rows) {
|
|
164
|
+
if (!Array.isArray(rows)) return [];
|
|
165
|
+
const recordMap = /* @__PURE__ */ new Map();
|
|
166
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
167
|
+
const roots = [];
|
|
168
|
+
for (const row of rows) {
|
|
169
|
+
if (!isObject$5(row)) continue;
|
|
170
|
+
const key = normalizeText$1(row._UUID);
|
|
171
|
+
if (!key) continue;
|
|
172
|
+
recordMap.set(key, { ...row });
|
|
173
|
+
}
|
|
174
|
+
for (const record of recordMap.values()) {
|
|
175
|
+
const parentId = normalizeText$1(record._PID);
|
|
176
|
+
if (!parentId || !recordMap.has(parentId)) {
|
|
177
|
+
roots.push(record);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const children = childrenMap.get(parentId) ?? [];
|
|
181
|
+
children.push(record);
|
|
182
|
+
childrenMap.set(parentId, children);
|
|
183
|
+
}
|
|
184
|
+
for (const [key, record] of recordMap.entries()) {
|
|
185
|
+
const children = childrenMap.get(key);
|
|
186
|
+
if (children && children.length > 0) record.children = children.sort(dictSortKey);
|
|
187
|
+
}
|
|
188
|
+
return roots.sort(dictSortKey);
|
|
189
|
+
}
|
|
190
|
+
function buildBaseSeedSqlVars(input) {
|
|
191
|
+
const adminPassword = input.adminPassword || "123456";
|
|
192
|
+
const passwordHash = createHash("md5").update(adminPassword, "utf8").digest("hex");
|
|
193
|
+
const adminUid = input.adminUid || "admin";
|
|
194
|
+
const adminRole = input.adminRole || "admin";
|
|
195
|
+
const resetSql = input.resetAdminPassword === true ? `UPDATE _SYS_USER\nSET _PWD = ${sqlLiteral(passwordHash)}\nWHERE _UID = ${sqlLiteral(adminUid)};` : "";
|
|
196
|
+
return {
|
|
197
|
+
SYSID: sqlLiteral(input.sysid || "sys"),
|
|
198
|
+
ADMIN_UID: sqlLiteral(adminUid),
|
|
199
|
+
ADMIN_NAME: sqlLiteral(input.adminName || "管理员"),
|
|
200
|
+
ADMIN_ROLE: sqlLiteral(adminRole),
|
|
201
|
+
ADMIN_ROLE_NAME: sqlLiteral(input.adminRoleName || "管理员"),
|
|
202
|
+
ADMIN_PASSWORD_MD5: sqlLiteral(passwordHash),
|
|
203
|
+
RESET_ADMIN_PASSWORD_SQL: resetSql
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function renderBaseSeedSql(template, values) {
|
|
207
|
+
let rendered = template;
|
|
208
|
+
for (const [key, value] of Object.entries(values)) rendered = rendered.replaceAll(`{{${key}}}`, value);
|
|
209
|
+
const missing = [...rendered.matchAll(/\{\{[A-Z0-9_]+\}\}/g)].map((item) => item[0]);
|
|
210
|
+
if (missing.length > 0) throw new SeiMcpError(`初始化 SQL 存在未替换变量:${[...new Set(missing)].join(", ")}`, "INVALID_PARAMS");
|
|
211
|
+
return rendered;
|
|
212
|
+
}
|
|
213
|
+
function removeSqlCommentLines(sql) {
|
|
214
|
+
return sql.split("\n").filter((line) => !line.trimStart().startsWith("--")).join("\n");
|
|
215
|
+
}
|
|
216
|
+
function splitSqlStatements(sql) {
|
|
217
|
+
const statements = [];
|
|
218
|
+
let current = "";
|
|
219
|
+
let quote = null;
|
|
220
|
+
let escaped = false;
|
|
221
|
+
for (const char of sql) {
|
|
222
|
+
if (escaped) {
|
|
223
|
+
current += char;
|
|
224
|
+
escaped = false;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (char === "\\" && quote !== null) {
|
|
228
|
+
current += char;
|
|
229
|
+
escaped = true;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (quote !== null) {
|
|
233
|
+
current += char;
|
|
234
|
+
if (char === quote) quote = null;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (char === "'" || char === "\"" || char === "`") {
|
|
238
|
+
current += char;
|
|
239
|
+
quote = char;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (char === ";") {
|
|
243
|
+
const statement = current.trim();
|
|
244
|
+
if (statement) statements.push(statement);
|
|
245
|
+
current = "";
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
current += char;
|
|
249
|
+
}
|
|
250
|
+
const finalStatement = current.trim();
|
|
251
|
+
if (finalStatement) statements.push(finalStatement);
|
|
252
|
+
return statements;
|
|
253
|
+
}
|
|
254
|
+
function buildDictQueryFilter(typeCode, keyword) {
|
|
255
|
+
const filters = [];
|
|
256
|
+
const normalizedType = normalizeText$1(typeCode);
|
|
257
|
+
const normalizedKeyword = normalizeText$1(keyword);
|
|
258
|
+
if (normalizedType) filters.push({ _TYPE: { "=": normalizedType } });
|
|
259
|
+
if (normalizedKeyword) filters.push({ OR: [
|
|
260
|
+
{ _UUID: { "*LIKE*": normalizedKeyword } },
|
|
261
|
+
{ _TYPE: { "*LIKE*": normalizedKeyword } },
|
|
262
|
+
{ _CODE: { "*LIKE*": normalizedKeyword } },
|
|
263
|
+
{ _NAME: { "*LIKE*": normalizedKeyword } }
|
|
264
|
+
] });
|
|
265
|
+
return filters;
|
|
266
|
+
}
|
|
267
|
+
function dictSortKey(a, b) {
|
|
268
|
+
const sortA = normalizeSort(typeof a._SORT === "number" ? a._SORT : Number(a._SORT ?? 0));
|
|
269
|
+
const sortB = normalizeSort(typeof b._SORT === "number" ? b._SORT : Number(b._SORT ?? 0));
|
|
270
|
+
if (sortA !== sortB) return sortA - sortB;
|
|
271
|
+
return normalizeText$1(a._CODE).localeCompare(normalizeText$1(b._CODE), "zh-CN");
|
|
272
|
+
}
|
|
273
|
+
function normalizeDictFlag(value) {
|
|
274
|
+
return value === 1 ? 1 : 0;
|
|
275
|
+
}
|
|
276
|
+
function normalizeSort(value) {
|
|
277
|
+
return Number.isInteger(value) && Number(value) >= 0 ? Number(value) : 100;
|
|
278
|
+
}
|
|
279
|
+
function requiredText(value, label) {
|
|
280
|
+
const normalized = normalizeText$1(value);
|
|
281
|
+
if (!normalized) throw new SeiMcpError(`${label} 不能为空`, "INVALID_PARAMS");
|
|
282
|
+
return normalized;
|
|
283
|
+
}
|
|
284
|
+
function normalizeText$1(value) {
|
|
285
|
+
return typeof value === "string" ? value.trim() : "";
|
|
286
|
+
}
|
|
287
|
+
function sqlLiteral(value) {
|
|
288
|
+
if (value === null || value === void 0) return "NULL";
|
|
289
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
290
|
+
}
|
|
291
|
+
function isObject$5(value) {
|
|
292
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
293
|
+
}
|
|
294
|
+
//#endregion
|
|
79
295
|
//#region src/utils.ts
|
|
80
|
-
function isObject$
|
|
296
|
+
function isObject$4(value) {
|
|
81
297
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
82
298
|
}
|
|
83
299
|
function toTrimmedString$1(value) {
|
|
@@ -169,6 +385,17 @@ function createCliSeiClient(settings, logger, fetchImpl = fetch) {
|
|
|
169
385
|
},
|
|
170
386
|
async readApiDocs() {
|
|
171
387
|
return requestJsonRaw(settings, logger, fetchImpl, "GET", OPENAPI_DOCS_PATH, void 0, { skipAuth: true });
|
|
388
|
+
},
|
|
389
|
+
async executeDdlStatements(statements, options = {}) {
|
|
390
|
+
const results = [];
|
|
391
|
+
for (const [index, statement] of statements.entries()) {
|
|
392
|
+
const response = await requestSeiJson(settings, logger, fetchImpl, auth, "POST", DDL_EXECUTE_PATH, { ddl: statement }, options);
|
|
393
|
+
results.push({
|
|
394
|
+
index: index + 1,
|
|
395
|
+
response
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
return results;
|
|
172
399
|
}
|
|
173
400
|
};
|
|
174
401
|
}
|
|
@@ -333,7 +560,7 @@ async function fetchAccountLoginRow(settings, logger, fetchImpl, adminToken, acc
|
|
|
333
560
|
const rows = extractResponseRows(response);
|
|
334
561
|
if (rows.length === 0) throw new SeiMcpError(`未找到账号:${account}`, "INVALID_REQUEST");
|
|
335
562
|
const row = rows[0];
|
|
336
|
-
if (!isObject$
|
|
563
|
+
if (!isObject$3(row)) throw new SeiMcpError(`账号 ${account} 查询结果不是 JSON 对象`, "INVALID_REQUEST");
|
|
337
564
|
return row;
|
|
338
565
|
}
|
|
339
566
|
async function requestCookieHeader(settings, logger, fetchImpl, payload) {
|
|
@@ -370,19 +597,19 @@ async function resolveRequestToken(settings, auth, options) {
|
|
|
370
597
|
}
|
|
371
598
|
function validateSeiEnvelope(response, context, allowFailure, allowEmpty) {
|
|
372
599
|
if (response === null && allowEmpty) return;
|
|
373
|
-
if (!isObject$
|
|
600
|
+
if (!isObject$3(response)) throw new SeiMcpError(`${context} 响应不是 JSON 对象`, "INVALID_REQUEST");
|
|
374
601
|
if (!allowFailure && isSeiFailure(response)) throw new SeiMcpError(`${context} 失败:${String(response.message ?? response.msg ?? response.code ?? "未知错误")}`, "INVALID_REQUEST", { response: safeResponse(response) });
|
|
375
602
|
}
|
|
376
603
|
function extractToken(response) {
|
|
377
604
|
validateSeiEnvelope(response, "login", false, false);
|
|
378
|
-
if (!isObject$
|
|
605
|
+
if (!isObject$3(response) || !isObject$3(response.data) || typeof response.data.token !== "string" || !response.data.token.trim()) throw new SeiMcpError("登录响应中没有 data.token", "INVALID_REQUEST");
|
|
379
606
|
return response.data.token.trim();
|
|
380
607
|
}
|
|
381
608
|
function extractResponseRows(response) {
|
|
382
|
-
if (!isObject$
|
|
609
|
+
if (!isObject$3(response)) return [];
|
|
383
610
|
const data = response.data;
|
|
384
611
|
if (Array.isArray(data)) return data;
|
|
385
|
-
if (isObject$
|
|
612
|
+
if (isObject$3(data)) for (const key of [
|
|
386
613
|
"rows",
|
|
387
614
|
"records",
|
|
388
615
|
"list",
|
|
@@ -456,24 +683,24 @@ function escapeSqlLiteral(value) {
|
|
|
456
683
|
return String(value).replace(/'/g, "''");
|
|
457
684
|
}
|
|
458
685
|
function isUnauthenticatedResponse(response) {
|
|
459
|
-
if (!isObject$
|
|
686
|
+
if (!isObject$3(response)) return false;
|
|
460
687
|
return Number(response.code) === -2;
|
|
461
688
|
}
|
|
462
689
|
function isSeiFailure(response) {
|
|
463
|
-
if (!isObject$
|
|
690
|
+
if (!isObject$3(response)) return false;
|
|
464
691
|
if (response.success === false) return true;
|
|
465
692
|
const code = Number(response.code);
|
|
466
693
|
return Number.isFinite(code) && code < 1;
|
|
467
694
|
}
|
|
468
695
|
function safeResponse(response) {
|
|
469
|
-
if (!isObject$
|
|
696
|
+
if (!isObject$3(response)) return response;
|
|
470
697
|
return {
|
|
471
698
|
code: response.code,
|
|
472
699
|
success: response.success,
|
|
473
700
|
message: response.message
|
|
474
701
|
};
|
|
475
702
|
}
|
|
476
|
-
function isObject$
|
|
703
|
+
function isObject$3(value) {
|
|
477
704
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
478
705
|
}
|
|
479
706
|
function normalizeTimeout(value) {
|
|
@@ -504,17 +731,17 @@ var OPENAPI_HTTP_METHODS = [
|
|
|
504
731
|
"TRACE"
|
|
505
732
|
];
|
|
506
733
|
function buildOpenApiDocSummary(openapiDoc, keyword) {
|
|
507
|
-
if (!isObject$
|
|
508
|
-
const info = isObject$
|
|
734
|
+
if (!isObject$2(openapiDoc)) throw new Error("OpenAPI 文档不是 JSON 对象");
|
|
735
|
+
const info = isObject$2(openapiDoc.info) ? openapiDoc.info : {};
|
|
509
736
|
const rawPaths = openapiDoc.paths;
|
|
510
|
-
if (!isObject$
|
|
737
|
+
if (!isObject$2(rawPaths)) throw new Error("OpenAPI 文档缺少 paths 对象");
|
|
511
738
|
const normalizedKeyword = normalizeText(keyword).toLowerCase();
|
|
512
739
|
const items = [];
|
|
513
740
|
for (const [path, rawPathItem] of Object.entries(rawPaths)) {
|
|
514
|
-
if (!isObject$
|
|
741
|
+
if (!isObject$2(rawPathItem)) continue;
|
|
515
742
|
for (const [method, rawOperation] of Object.entries(rawPathItem)) {
|
|
516
743
|
const methodUpper = method.toUpperCase();
|
|
517
|
-
if (!OPENAPI_HTTP_METHODS.includes(methodUpper) || !isObject$
|
|
744
|
+
if (!OPENAPI_HTTP_METHODS.includes(methodUpper) || !isObject$2(rawOperation)) continue;
|
|
518
745
|
const tags = Array.isArray(rawOperation.tags) ? rawOperation.tags.map((item) => normalizeText(item)).filter(Boolean) : [];
|
|
519
746
|
const item = {
|
|
520
747
|
method: methodUpper,
|
|
@@ -559,7 +786,7 @@ function renderOpenApiMarkdown(summary) {
|
|
|
559
786
|
let currentTag = "";
|
|
560
787
|
const items = Array.isArray(summary.items) ? summary.items : [];
|
|
561
788
|
for (const rawItem of items) {
|
|
562
|
-
if (!isObject$
|
|
789
|
+
if (!isObject$2(rawItem)) continue;
|
|
563
790
|
const tag = normalizeText((Array.isArray(rawItem.tags) ? rawItem.tags : [])[0] ?? "未分组") || "未分组";
|
|
564
791
|
if (tag !== currentTag) {
|
|
565
792
|
lines.push("", `## ${tag}`);
|
|
@@ -578,7 +805,7 @@ function renderOpenApiMarkdown(summary) {
|
|
|
578
805
|
if (parameters.length > 0) {
|
|
579
806
|
lines.push("- 参数:");
|
|
580
807
|
for (const rawParameter of parameters) {
|
|
581
|
-
if (!isObject$
|
|
808
|
+
if (!isObject$2(rawParameter)) continue;
|
|
582
809
|
const name = normalizeText(rawParameter.name);
|
|
583
810
|
const location = normalizeText(rawParameter.in);
|
|
584
811
|
const detail = [
|
|
@@ -591,7 +818,7 @@ function renderOpenApiMarkdown(summary) {
|
|
|
591
818
|
}
|
|
592
819
|
const requestBody = Array.isArray(rawItem.requestBody) ? rawItem.requestBody : [];
|
|
593
820
|
if (requestBody.length > 0) {
|
|
594
|
-
const parts = requestBody.filter(isObject$
|
|
821
|
+
const parts = requestBody.filter(isObject$2).map((body) => {
|
|
595
822
|
const contentType = normalizeText(body.contentType);
|
|
596
823
|
const schema = normalizeText(body.schema);
|
|
597
824
|
return schema ? `${contentType}(${schema})` : contentType;
|
|
@@ -600,7 +827,7 @@ function renderOpenApiMarkdown(summary) {
|
|
|
600
827
|
}
|
|
601
828
|
const responses = Array.isArray(rawItem.responses) ? rawItem.responses : [];
|
|
602
829
|
if (responses.length > 0) {
|
|
603
|
-
const parts = responses.filter(isObject$
|
|
830
|
+
const parts = responses.filter(isObject$2).map((response) => `${normalizeText(response.status)} ${normalizeText(response.description)}`.trim()).filter(Boolean);
|
|
604
831
|
if (parts.length > 0) lines.push(`- 响应:${parts.join(", ")}`);
|
|
605
832
|
}
|
|
606
833
|
}
|
|
@@ -609,7 +836,7 @@ function renderOpenApiMarkdown(summary) {
|
|
|
609
836
|
function extractOpenApiParameters(operation) {
|
|
610
837
|
const rawParameters = operation.parameters;
|
|
611
838
|
if (!Array.isArray(rawParameters)) return [];
|
|
612
|
-
return rawParameters.filter(isObject$
|
|
839
|
+
return rawParameters.filter(isObject$2).map((parameter) => ({
|
|
613
840
|
name: normalizeText(parameter.name),
|
|
614
841
|
in: normalizeText(parameter.in),
|
|
615
842
|
required: Boolean(parameter.required),
|
|
@@ -619,22 +846,22 @@ function extractOpenApiParameters(operation) {
|
|
|
619
846
|
}
|
|
620
847
|
function extractOpenApiRequestBody(operation) {
|
|
621
848
|
const requestBody = operation.requestBody;
|
|
622
|
-
if (!isObject$
|
|
849
|
+
if (!isObject$2(requestBody) || !isObject$2(requestBody.content)) return [];
|
|
623
850
|
return Object.entries(requestBody.content).map(([contentType, contentInfo]) => ({
|
|
624
851
|
contentType: normalizeText(contentType),
|
|
625
|
-
schema: summarizeSchema(isObject$
|
|
852
|
+
schema: summarizeSchema(isObject$2(contentInfo) ? contentInfo.schema : void 0)
|
|
626
853
|
})).filter((item) => item.contentType);
|
|
627
854
|
}
|
|
628
855
|
function extractOpenApiResponses(operation) {
|
|
629
856
|
const rawResponses = operation.responses;
|
|
630
|
-
if (!isObject$
|
|
857
|
+
if (!isObject$2(rawResponses)) return [];
|
|
631
858
|
return Object.entries(rawResponses).map(([status, response]) => ({
|
|
632
859
|
status: normalizeText(status),
|
|
633
|
-
description: normalizeText(isObject$
|
|
860
|
+
description: normalizeText(isObject$2(response) ? response.description : "")
|
|
634
861
|
})).filter((item) => item.status);
|
|
635
862
|
}
|
|
636
863
|
function summarizeSchema(schema) {
|
|
637
|
-
if (!isObject$
|
|
864
|
+
if (!isObject$2(schema)) return "";
|
|
638
865
|
const ref = normalizeText(schema.$ref);
|
|
639
866
|
if (ref) return ref.split("/").pop() ?? ref;
|
|
640
867
|
const schemaType = normalizeText(schema.type);
|
|
@@ -647,7 +874,7 @@ function summarizeSchema(schema) {
|
|
|
647
874
|
function normalizeText(value) {
|
|
648
875
|
return typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
649
876
|
}
|
|
650
|
-
function isObject$
|
|
877
|
+
function isObject$2(value) {
|
|
651
878
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
652
879
|
}
|
|
653
880
|
//#endregion
|
|
@@ -658,7 +885,7 @@ async function runCli(argv, options) {
|
|
|
658
885
|
process.stdout.write(`${renderCliHelp()}\n`);
|
|
659
886
|
return;
|
|
660
887
|
}
|
|
661
|
-
const
|
|
888
|
+
const settings = createSeiAuthSettings(options.config, {
|
|
662
889
|
baseUrl: toUndefined(readOption(argv, "--base-url")),
|
|
663
890
|
token: toUndefined(readOption(argv, "--token")),
|
|
664
891
|
aiKey: toUndefined(readOption(argv, "--ai-key")),
|
|
@@ -667,7 +894,8 @@ async function runCli(argv, options) {
|
|
|
667
894
|
accountProject: toUndefined(readOption(argv, "--account-project")),
|
|
668
895
|
accountCheckcode: toUndefined(readOption(argv, "--account-checkcode")),
|
|
669
896
|
timeoutMs: readOptionNumber(argv, "--timeout")
|
|
670
|
-
})
|
|
897
|
+
});
|
|
898
|
+
const client = createCliSeiClient(settings, options.logger, options.fetchImpl ?? fetch);
|
|
671
899
|
switch (command) {
|
|
672
900
|
case "api-docs": {
|
|
673
901
|
const summary = buildOpenApiDocSummary(await client.readApiDocs(), readOption(argv, "--keyword") ?? void 0);
|
|
@@ -703,6 +931,80 @@ async function runCli(argv, options) {
|
|
|
703
931
|
printJson(await client.querySql(sql, readOptionNumber(argv, "--page"), readOptionNumber(argv, "--size"), { allowFailure: hasFlag(argv, "--allow-failure") }));
|
|
704
932
|
return;
|
|
705
933
|
}
|
|
934
|
+
case "dict-list": {
|
|
935
|
+
const payload = buildDictQueryPayload(readOption(argv, "--type") ?? void 0, readOption(argv, "--keyword") ?? void 0, readOptionNumber(argv, "--size") ?? 1e3);
|
|
936
|
+
const response = await client.query(payload, { allowFailure: hasFlag(argv, "--allow-failure") });
|
|
937
|
+
if (!hasFlag(argv, "--flat") && isObject$1(response) && isObject$1(response.data) && Array.isArray(response.data.rows)) response.data.tree = buildDictTree(response.data.rows);
|
|
938
|
+
printJson(response);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
case "dict-add-category": {
|
|
942
|
+
const [typeCode, name] = positionalAfter(argv, command, 2);
|
|
943
|
+
if (!typeCode || !name) throw new SeiMcpError("dict-add-category 需要 type_code 和 name", "INVALID_PARAMS");
|
|
944
|
+
const payload = buildDictSavePayload(buildDictCategoryRow({
|
|
945
|
+
body: readOption(argv, "--body") ?? void 0,
|
|
946
|
+
code: readOption(argv, "--code") ?? void 0,
|
|
947
|
+
ctype: readOption(argv, "--ctype") ?? void 0,
|
|
948
|
+
dictFlag: readOptionInteger(argv, "--dict-flag"),
|
|
949
|
+
ename: readOption(argv, "--ename") ?? void 0,
|
|
950
|
+
memo: readOption(argv, "--memo") ?? void 0,
|
|
951
|
+
name,
|
|
952
|
+
sort: readOptionInteger(argv, "--sort"),
|
|
953
|
+
sysid: readOption(argv, "--sysid") ?? void 0,
|
|
954
|
+
typeCode,
|
|
955
|
+
uuid: readOption(argv, "--uuid") ?? void 0
|
|
956
|
+
}));
|
|
957
|
+
printJson(await client.save(payload, { allowFailure: hasFlag(argv, "--allow-failure") }));
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
case "dict-add-item": {
|
|
961
|
+
const [typeCode, code, name] = positionalAfter(argv, command, 3);
|
|
962
|
+
if (!typeCode || !code || !name) throw new SeiMcpError("dict-add-item 需要 type_code、code 和 name", "INVALID_PARAMS");
|
|
963
|
+
const payload = buildDictSavePayload(buildDictItemRow({
|
|
964
|
+
body: readOption(argv, "--body") ?? void 0,
|
|
965
|
+
code,
|
|
966
|
+
ctype: readOption(argv, "--ctype") ?? void 0,
|
|
967
|
+
dictFlag: readOptionInteger(argv, "--dict-flag"),
|
|
968
|
+
ename: readOption(argv, "--ename") ?? void 0,
|
|
969
|
+
memo: readOption(argv, "--memo") ?? void 0,
|
|
970
|
+
name,
|
|
971
|
+
parent: readOption(argv, "--parent") ?? void 0,
|
|
972
|
+
sort: readOptionInteger(argv, "--sort"),
|
|
973
|
+
sysid: readOption(argv, "--sysid") ?? void 0,
|
|
974
|
+
typeCode,
|
|
975
|
+
uuid: readOption(argv, "--uuid") ?? void 0
|
|
976
|
+
}));
|
|
977
|
+
printJson(await client.save(payload, { allowFailure: hasFlag(argv, "--allow-failure") }));
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
case "init-base-data": {
|
|
981
|
+
const sqlFile = resolveBaseSeedSqlPath(readOption(argv, "--sql-file"));
|
|
982
|
+
const statements = splitSqlStatements(removeSqlCommentLines(renderBaseSeedSql(await readFile(sqlFile, "utf8"), buildBaseSeedSqlVars({
|
|
983
|
+
adminName: readOption(argv, "--admin-name") ?? void 0,
|
|
984
|
+
adminPassword: readOption(argv, "--admin-password") ?? void 0,
|
|
985
|
+
adminRole: readOption(argv, "--admin-role") ?? void 0,
|
|
986
|
+
adminRoleName: readOption(argv, "--admin-role-name") ?? void 0,
|
|
987
|
+
adminUid: readOption(argv, "--admin-uid") ?? void 0,
|
|
988
|
+
resetAdminPassword: hasFlag(argv, "--reset-admin-password"),
|
|
989
|
+
sysid: readOption(argv, "--sysid") ?? void 0
|
|
990
|
+
}))));
|
|
991
|
+
if (statements.length === 0) throw new SeiMcpError("初始化 SQL 文件没有可执行语句", "INVALID_PARAMS");
|
|
992
|
+
const executed = await client.executeDdlStatements(statements, { allowFailure: hasFlag(argv, "--allow-failure") });
|
|
993
|
+
await client.call("POST", "/api/sei/data/removeAllCache", {}, { allowFailure: false });
|
|
994
|
+
printJson({
|
|
995
|
+
success: true,
|
|
996
|
+
code: 1,
|
|
997
|
+
message: "基础源数据初始化完成",
|
|
998
|
+
data: {
|
|
999
|
+
executed: executed.length,
|
|
1000
|
+
sqlFile,
|
|
1001
|
+
admin: readOption(argv, "--admin-uid") ?? "admin",
|
|
1002
|
+
role: readOption(argv, "--admin-role") ?? "admin",
|
|
1003
|
+
baseUrl: settings.baseUrl
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
706
1008
|
default: throw new SeiMcpError(`未知 CLI 子命令:${command}`, "INVALID_PARAMS");
|
|
707
1009
|
}
|
|
708
1010
|
}
|
|
@@ -716,6 +1018,10 @@ function renderCliHelp() {
|
|
|
716
1018
|
` ${BIN_NAME} cli save [--json <text> | --file <path> | --stdin]`,
|
|
717
1019
|
` ${BIN_NAME} cli query-sql "<sql>" [--page 1] [--size 20]`,
|
|
718
1020
|
` ${BIN_NAME} cli api-docs [--keyword <text>] [--format markdown|json] [--output <path>]`,
|
|
1021
|
+
` ${BIN_NAME} cli dict-list [--type <type>] [--keyword <text>] [--size 1000] [--flat]`,
|
|
1022
|
+
` ${BIN_NAME} cli dict-add-category <type_code> <name> [--code <code>]`,
|
|
1023
|
+
` ${BIN_NAME} cli dict-add-item <type_code> <code> <name> [--parent <uuid>]`,
|
|
1024
|
+
` ${BIN_NAME} cli init-base-data [--sysid sys] [--admin-uid admin]`,
|
|
719
1025
|
"",
|
|
720
1026
|
"Common Options:",
|
|
721
1027
|
" --base-url <url>",
|
|
@@ -727,11 +1033,29 @@ function renderCliHelp() {
|
|
|
727
1033
|
" --token <token>",
|
|
728
1034
|
" --timeout <seconds>",
|
|
729
1035
|
" --allow-failure",
|
|
1036
|
+
" --sysid <sysid>",
|
|
1037
|
+
" --uuid <uuid>",
|
|
1038
|
+
" --ctype <ctype>",
|
|
1039
|
+
" --ename <ename>",
|
|
1040
|
+
" --sort <number>",
|
|
1041
|
+
" --body <json-or-text>",
|
|
1042
|
+
" --memo <text>",
|
|
1043
|
+
" --dict-flag <0|1>",
|
|
730
1044
|
"",
|
|
731
1045
|
"call Options:",
|
|
732
1046
|
" --no-token",
|
|
733
1047
|
" --allow-empty",
|
|
734
1048
|
"",
|
|
1049
|
+
"init-base-data Options:",
|
|
1050
|
+
` --admin-uid <uid> default ${BASE_SEED_DEFAULT_ADMIN_UID}`,
|
|
1051
|
+
" --admin-name <name> default 管理员",
|
|
1052
|
+
` --admin-role <role> default ${BASE_SEED_DEFAULT_ADMIN_ROLE}`,
|
|
1053
|
+
" --admin-role-name <name> default 管理员",
|
|
1054
|
+
` --admin-password <pwd> default ${BASE_SEED_DEFAULT_ADMIN_PASSWORD}`,
|
|
1055
|
+
` --sysid <sysid> default sys`,
|
|
1056
|
+
" --reset-admin-password",
|
|
1057
|
+
" --sql-file <path>",
|
|
1058
|
+
"",
|
|
735
1059
|
"Payload Options:",
|
|
736
1060
|
" --json <text>",
|
|
737
1061
|
" --file <path>",
|
|
@@ -797,6 +1121,13 @@ function readOptionNumber(argv, name) {
|
|
|
797
1121
|
if (!Number.isFinite(parsed) || parsed <= 0) throw new SeiMcpError(`${name} 必须是正数`, "INVALID_PARAMS");
|
|
798
1122
|
return parsed;
|
|
799
1123
|
}
|
|
1124
|
+
function readOptionInteger(argv, name) {
|
|
1125
|
+
const value = readOption(argv, name);
|
|
1126
|
+
if (value === null) return;
|
|
1127
|
+
const parsed = Number.parseInt(value, 10);
|
|
1128
|
+
if (!Number.isFinite(parsed)) throw new SeiMcpError(`${name} 必须是整数`, "INVALID_PARAMS");
|
|
1129
|
+
return parsed;
|
|
1130
|
+
}
|
|
800
1131
|
function firstPositional(argv) {
|
|
801
1132
|
for (const item of argv) if (item && !item.startsWith("-")) return item;
|
|
802
1133
|
return null;
|
|
@@ -824,6 +1155,13 @@ function isHelpFlag(value) {
|
|
|
824
1155
|
function toUndefined(value) {
|
|
825
1156
|
return value ?? void 0;
|
|
826
1157
|
}
|
|
1158
|
+
function resolveBaseSeedSqlPath(path) {
|
|
1159
|
+
if (path) return resolve(path);
|
|
1160
|
+
return resolve(process.cwd(), ".codex/skills/sei-ai/scripts/init-base-data.sql");
|
|
1161
|
+
}
|
|
1162
|
+
function isObject$1(value) {
|
|
1163
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1164
|
+
}
|
|
827
1165
|
async function readStdin() {
|
|
828
1166
|
const chunks = [];
|
|
829
1167
|
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
@@ -836,10 +1174,10 @@ async function loadConfig(argv = process.argv.slice(2), env = process.env) {
|
|
|
836
1174
|
return normalizeConfig(configPath ? await readConfigFile(configPath) : {}, env);
|
|
837
1175
|
}
|
|
838
1176
|
function normalizeConfig(input = {}, env = process.env) {
|
|
839
|
-
const config = isObject$
|
|
840
|
-
const auth = isObject$
|
|
841
|
-
const tools = isObject$
|
|
842
|
-
const targets = isObject$
|
|
1177
|
+
const config = isObject$4(input) ? input : {};
|
|
1178
|
+
const auth = isObject$4(config.auth) ? config.auth : {};
|
|
1179
|
+
const tools = isObject$4(config.tools) ? config.tools : {};
|
|
1180
|
+
const targets = isObject$4(config.targets) ? config.targets : {};
|
|
843
1181
|
return {
|
|
844
1182
|
baseUrl: normalizeBaseUrl(config.baseUrl, env.SEI_BASE_URL),
|
|
845
1183
|
auth: {
|
|
@@ -896,8 +1234,8 @@ function stringOrEnv(value, envValue) {
|
|
|
896
1234
|
return toTrimmedString$1(envValue);
|
|
897
1235
|
}
|
|
898
1236
|
function toTargetMap(value) {
|
|
899
|
-
if (!isObject$
|
|
900
|
-
return Object.fromEntries(Object.entries(value).filter(([, item]) => isObject$
|
|
1237
|
+
if (!isObject$4(value)) return {};
|
|
1238
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => isObject$4(item)));
|
|
901
1239
|
}
|
|
902
1240
|
//#endregion
|
|
903
1241
|
//#region src/logger.ts
|
|
@@ -5929,7 +6267,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
5929
6267
|
return (payload, ctx) => fn(shape, payload, ctx);
|
|
5930
6268
|
};
|
|
5931
6269
|
let fastpass;
|
|
5932
|
-
const isObject$
|
|
6270
|
+
const isObject$6 = isObject;
|
|
5933
6271
|
const jit = !globalConfig.jitless;
|
|
5934
6272
|
const fastEnabled = jit && allowsEval.value;
|
|
5935
6273
|
const catchall = def.catchall;
|
|
@@ -5937,7 +6275,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
5937
6275
|
inst._zod.parse = (payload, ctx) => {
|
|
5938
6276
|
value ?? (value = _normalized.value);
|
|
5939
6277
|
const input = payload.value;
|
|
5940
|
-
if (!isObject$
|
|
6278
|
+
if (!isObject$6(input)) {
|
|
5941
6279
|
payload.issues.push({
|
|
5942
6280
|
expected: "object",
|
|
5943
6281
|
code: "invalid_type",
|
|
@@ -21222,7 +21560,7 @@ function authorizeQuery(config, input) {
|
|
|
21222
21560
|
return {
|
|
21223
21561
|
head,
|
|
21224
21562
|
option: {
|
|
21225
|
-
...isObject$
|
|
21563
|
+
...isObject$4(input.option) ? input.option : {},
|
|
21226
21564
|
fields: effectiveFields.join(","),
|
|
21227
21565
|
limit: toPositiveInteger(input.option?.limit, 20)
|
|
21228
21566
|
},
|
|
@@ -21244,7 +21582,7 @@ function authorizeSave(config, input) {
|
|
|
21244
21582
|
const action = normalizeAction(item?.action);
|
|
21245
21583
|
if (!allowedActions.includes(action)) throw new SeiMcpError(`save 动作不在白名单内: ${action}`, "INVALID_PARAMS");
|
|
21246
21584
|
for (const row of Array.isArray(item?.rows) ? item.rows : []) {
|
|
21247
|
-
const current = isObject$
|
|
21585
|
+
const current = isObject$4(row?.row) ? row.row : {};
|
|
21248
21586
|
ensureFieldsAllowed(Object.keys(current).filter((key) => !RESERVED_SAVE_ROW_KEYS.has(key)), allowedFields, "save");
|
|
21249
21587
|
}
|
|
21250
21588
|
}
|
|
@@ -21272,7 +21610,7 @@ function authorizeQuerySql(config, input) {
|
|
|
21272
21610
|
};
|
|
21273
21611
|
}
|
|
21274
21612
|
function requireHead(head) {
|
|
21275
|
-
if (!isObject$
|
|
21613
|
+
if (!isObject$4(head) || Array.isArray(head)) throw new SeiMcpError("head is required", "INVALID_PARAMS");
|
|
21276
21614
|
const result = {};
|
|
21277
21615
|
for (const [key, value] of Object.entries(head)) {
|
|
21278
21616
|
const trimmed = toTrimmedString$1(value);
|
|
@@ -21312,7 +21650,7 @@ function isTargetAllowed(config, tableName) {
|
|
|
21312
21650
|
];
|
|
21313
21651
|
for (const pool of pools) {
|
|
21314
21652
|
if (pool[normalized]) return true;
|
|
21315
|
-
for (const target of Object.values(pool)) if (isObject$
|
|
21653
|
+
for (const target of Object.values(pool)) if (isObject$4(target)) {
|
|
21316
21654
|
const mainTable = toTrimmedString$1(target.mainTable);
|
|
21317
21655
|
if (mainTable && mainTable === normalized) return true;
|
|
21318
21656
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ty_krystal/sei-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "SEI MCP server and developer CLI for query, save, query-sql, and api-docs workflows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sei",
|
|
@@ -52,6 +52,6 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
54
54
|
"tsx": "^4.22.4",
|
|
55
|
-
"zod": "
|
|
55
|
+
"zod": "^3.25.76"
|
|
56
56
|
}
|
|
57
57
|
}
|