@ty_krystal/sei-ai 0.1.1 → 0.1.3
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 +21 -0
- package/dist/README.md +21 -0
- package/dist/index.js +532 -120
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,13 @@ sei-ai cli api-docs --keyword login --format json
|
|
|
63
63
|
- `--token`
|
|
64
64
|
- `--timeout`
|
|
65
65
|
|
|
66
|
+
环境变量加载规则:
|
|
67
|
+
|
|
68
|
+
- 启动时会自动读取当前工作目录下的 `.env.local` 和 `.env`
|
|
69
|
+
- 加载顺序为 `.env.local` 再 `.env`
|
|
70
|
+
- 已经存在的进程环境变量不会被 `.env` 覆盖
|
|
71
|
+
- 显式 CLI 参数仍然优先于环境变量
|
|
72
|
+
|
|
66
73
|
载荷输入支持:
|
|
67
74
|
|
|
68
75
|
- `--json '<payload>'`
|
|
@@ -123,6 +130,14 @@ docker compose -f apps/sei-ai-mcp/docker-compose.yml up -d --build
|
|
|
123
130
|
- `SEI_MCP_PATH`
|
|
124
131
|
- `SEI_MCP_HTTP_JSON_RESPONSE`
|
|
125
132
|
|
|
133
|
+
例如可以在项目目录写:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
SEI_BASE_URL=http://127.0.0.1:8081
|
|
137
|
+
SEI_AI_LOGIN_KEY=dev-ai-secret
|
|
138
|
+
SEI_AI_LOGIN_ROLE=admin
|
|
139
|
+
```
|
|
140
|
+
|
|
126
141
|
## 权限模型
|
|
127
142
|
|
|
128
143
|
- 未配置目标时默认拒绝。
|
|
@@ -209,3 +224,9 @@ SEI_AI_MCP_HTTP_ENDPOINT=/mcp
|
|
|
209
224
|
```bash
|
|
210
225
|
http://127.0.0.1:3719/mcp
|
|
211
226
|
```
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
npm version patch --no-git-tag-version
|
|
231
|
+
pnpm run build
|
|
232
|
+
npm publish --access public --cache /tmp/sei-ai-npm-cache
|
package/dist/README.md
CHANGED
|
@@ -63,6 +63,13 @@ sei-ai cli api-docs --keyword login --format json
|
|
|
63
63
|
- `--token`
|
|
64
64
|
- `--timeout`
|
|
65
65
|
|
|
66
|
+
环境变量加载规则:
|
|
67
|
+
|
|
68
|
+
- 启动时会自动读取当前工作目录下的 `.env.local` 和 `.env`
|
|
69
|
+
- 加载顺序为 `.env.local` 再 `.env`
|
|
70
|
+
- 已经存在的进程环境变量不会被 `.env` 覆盖
|
|
71
|
+
- 显式 CLI 参数仍然优先于环境变量
|
|
72
|
+
|
|
66
73
|
载荷输入支持:
|
|
67
74
|
|
|
68
75
|
- `--json '<payload>'`
|
|
@@ -123,6 +130,14 @@ docker compose -f apps/sei-ai-mcp/docker-compose.yml up -d --build
|
|
|
123
130
|
- `SEI_MCP_PATH`
|
|
124
131
|
- `SEI_MCP_HTTP_JSON_RESPONSE`
|
|
125
132
|
|
|
133
|
+
例如可以在项目目录写:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
SEI_BASE_URL=http://127.0.0.1:8081
|
|
137
|
+
SEI_AI_LOGIN_KEY=dev-ai-secret
|
|
138
|
+
SEI_AI_LOGIN_ROLE=admin
|
|
139
|
+
```
|
|
140
|
+
|
|
126
141
|
## 权限模型
|
|
127
142
|
|
|
128
143
|
- 未配置目标时默认拒绝。
|
|
@@ -209,3 +224,9 @@ SEI_AI_MCP_HTTP_ENDPOINT=/mcp
|
|
|
209
224
|
```bash
|
|
210
225
|
http://127.0.0.1:3719/mcp
|
|
211
226
|
```
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
npm version patch --no-git-tag-version
|
|
231
|
+
pnpm run build
|
|
232
|
+
npm publish --access public --cache /tmp/sei-ai-npm-cache
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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
|
+
import { parseEnv } from "node:util";
|
|
6
7
|
import { createServer } from "node:http";
|
|
7
8
|
import { ZodOptional, z } from "zod";
|
|
8
9
|
import process$1 from "node:process";
|
|
@@ -49,6 +50,7 @@ var PACKAGE_NAME = "sei-ai";
|
|
|
49
50
|
var BIN_NAME = "sei-ai";
|
|
50
51
|
var SERVER_NAME = "sei-ai-mcp-server";
|
|
51
52
|
var SERVER_VERSION = "0.2.0";
|
|
53
|
+
var DEFAULT_AI_KEY = "dev-ai-secret";
|
|
52
54
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
53
55
|
var DEFAULT_LOG_LEVEL = "info";
|
|
54
56
|
var MAX_RESPONSE_TEXT = 2e4;
|
|
@@ -58,7 +60,27 @@ var ACCOUNT_CHECK_CODE_PATH = "/api/sei/public/checkCode";
|
|
|
58
60
|
var QUERY_PATH = "/api/sei/data/public/query";
|
|
59
61
|
var SAVE_PATH = "/api/sei/data/public/save";
|
|
60
62
|
var QUERY_SQL_PATH = "/api/sei/data/querySQL";
|
|
63
|
+
var DDL_EXECUTE_PATH = "/api/sei/mcp/ddl/execute";
|
|
61
64
|
var OPENAPI_DOCS_PATH = "/v3/api-docs";
|
|
65
|
+
var DICT_MODULE = "sys_dic";
|
|
66
|
+
var DICT_FIELDS = [
|
|
67
|
+
"_UUID",
|
|
68
|
+
"_PID",
|
|
69
|
+
"_SYSID",
|
|
70
|
+
"_TYPE",
|
|
71
|
+
"_CTYPE",
|
|
72
|
+
"_CODE",
|
|
73
|
+
"_NAME",
|
|
74
|
+
"_ENAME",
|
|
75
|
+
"_DICT",
|
|
76
|
+
"_SORT",
|
|
77
|
+
"_BODY",
|
|
78
|
+
"_MEMO"
|
|
79
|
+
];
|
|
80
|
+
var BASE_SEED_DEFAULT_ADMIN_UID = "admin";
|
|
81
|
+
var BASE_SEED_DEFAULT_ADMIN_ROLE = "admin";
|
|
82
|
+
var BASE_SEED_DEFAULT_ADMIN_PASSWORD = "123456";
|
|
83
|
+
var ACCOUNT_LOGIN_DEFAULT_CHECKCODE = "abcd";
|
|
62
84
|
var ACCOUNT_LOGIN_DEFAULT_TYPE = "login";
|
|
63
85
|
var TOKEN_CACHE_DIR_NAME = "sei-ai";
|
|
64
86
|
var TOKEN_CACHE_FILE_PREFIX = "token-";
|
|
@@ -75,9 +97,235 @@ var SeiMcpError = class extends Error {
|
|
|
75
97
|
this.details = details;
|
|
76
98
|
}
|
|
77
99
|
};
|
|
100
|
+
function isSeiMcpError(error) {
|
|
101
|
+
return error instanceof SeiMcpError;
|
|
102
|
+
}
|
|
103
|
+
function normalizeErrorMessage(error) {
|
|
104
|
+
return error instanceof Error ? error.message : "Unknown error";
|
|
105
|
+
}
|
|
106
|
+
function formatCliError(error) {
|
|
107
|
+
if (error instanceof SeiMcpError) return `[${error.code}] ${error.message}`;
|
|
108
|
+
return `[INTERNAL_ERROR] ${normalizeErrorMessage(error)}`;
|
|
109
|
+
}
|
|
110
|
+
function toErrorLogMeta(error) {
|
|
111
|
+
if (error instanceof SeiMcpError) return {
|
|
112
|
+
code: error.code,
|
|
113
|
+
message: error.message,
|
|
114
|
+
details: toJsonValue(error.details)
|
|
115
|
+
};
|
|
116
|
+
if (error instanceof Error) return {
|
|
117
|
+
message: error.message,
|
|
118
|
+
stack: error.stack ?? ""
|
|
119
|
+
};
|
|
120
|
+
return { message: normalizeErrorMessage(error) };
|
|
121
|
+
}
|
|
122
|
+
function toJsonValue(value) {
|
|
123
|
+
if (value === null) return null;
|
|
124
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
125
|
+
if (Array.isArray(value)) return value.map((item) => toJsonValue(item));
|
|
126
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, toJsonValue(item)]));
|
|
127
|
+
return String(value);
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/cli-helpers.ts
|
|
131
|
+
function buildDictQueryPayload(typeCode, keyword, size = 1e3) {
|
|
132
|
+
return {
|
|
133
|
+
head: { module: DICT_MODULE },
|
|
134
|
+
option: {
|
|
135
|
+
keyField: true,
|
|
136
|
+
privilege: true,
|
|
137
|
+
fields: DICT_FIELDS.join(","),
|
|
138
|
+
filter: buildDictQueryFilter(typeCode, keyword),
|
|
139
|
+
order: "_SORT ASC,_CODE ASC",
|
|
140
|
+
page: 1,
|
|
141
|
+
size
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function buildDictSavePayload(row) {
|
|
146
|
+
return {
|
|
147
|
+
head: { module: DICT_MODULE },
|
|
148
|
+
data: [{
|
|
149
|
+
action: "add",
|
|
150
|
+
option: { keyVal: true },
|
|
151
|
+
rows: [{ row }]
|
|
152
|
+
}]
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function buildDictCategoryRow(input) {
|
|
156
|
+
const typeCode = requiredText(input.typeCode, "字典类别");
|
|
157
|
+
const name = requiredText(input.name, "字典名称");
|
|
158
|
+
const uuid = requiredText(input.uuid || typeCode, "字典编号");
|
|
159
|
+
const code = requiredText(input.code || typeCode, "字典代码");
|
|
160
|
+
return {
|
|
161
|
+
_UUID: uuid,
|
|
162
|
+
_PID: "",
|
|
163
|
+
_SYSID: requiredText(input.sysid || "sys", "所属系统"),
|
|
164
|
+
_TYPE: typeCode,
|
|
165
|
+
_CTYPE: normalizeText$1(input.ctype),
|
|
166
|
+
_CODE: code,
|
|
167
|
+
_NAME: name,
|
|
168
|
+
_ENAME: normalizeText$1(input.ename),
|
|
169
|
+
_DICT: normalizeDictFlag(input.dictFlag),
|
|
170
|
+
_SORT: normalizeSort(input.sort),
|
|
171
|
+
_BODY: normalizeText$1(input.body),
|
|
172
|
+
_MEMO: normalizeText$1(input.memo)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function buildDictItemRow(input) {
|
|
176
|
+
const typeCode = requiredText(input.typeCode, "字典类别");
|
|
177
|
+
const code = requiredText(input.code, "字典代码");
|
|
178
|
+
const name = requiredText(input.name, "字典名称");
|
|
179
|
+
const parent = requiredText(input.parent || typeCode, "父级编号");
|
|
180
|
+
return {
|
|
181
|
+
_UUID: requiredText(input.uuid || `${parent}_${code}`, "字典编号"),
|
|
182
|
+
_PID: parent,
|
|
183
|
+
_SYSID: requiredText(input.sysid || "sys", "所属系统"),
|
|
184
|
+
_TYPE: typeCode,
|
|
185
|
+
_CTYPE: normalizeText$1(input.ctype),
|
|
186
|
+
_CODE: code,
|
|
187
|
+
_NAME: name,
|
|
188
|
+
_ENAME: normalizeText$1(input.ename),
|
|
189
|
+
_DICT: normalizeDictFlag(input.dictFlag),
|
|
190
|
+
_SORT: normalizeSort(input.sort),
|
|
191
|
+
_BODY: normalizeText$1(input.body),
|
|
192
|
+
_MEMO: normalizeText$1(input.memo)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function buildDictTree(rows) {
|
|
196
|
+
if (!Array.isArray(rows)) return [];
|
|
197
|
+
const recordMap = /* @__PURE__ */ new Map();
|
|
198
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
199
|
+
const roots = [];
|
|
200
|
+
for (const row of rows) {
|
|
201
|
+
if (!isObject$5(row)) continue;
|
|
202
|
+
const key = normalizeText$1(row._UUID);
|
|
203
|
+
if (!key) continue;
|
|
204
|
+
recordMap.set(key, { ...row });
|
|
205
|
+
}
|
|
206
|
+
for (const record of recordMap.values()) {
|
|
207
|
+
const parentId = normalizeText$1(record._PID);
|
|
208
|
+
if (!parentId || !recordMap.has(parentId)) {
|
|
209
|
+
roots.push(record);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const children = childrenMap.get(parentId) ?? [];
|
|
213
|
+
children.push(record);
|
|
214
|
+
childrenMap.set(parentId, children);
|
|
215
|
+
}
|
|
216
|
+
for (const [key, record] of recordMap.entries()) {
|
|
217
|
+
const children = childrenMap.get(key);
|
|
218
|
+
if (children && children.length > 0) record.children = children.sort(dictSortKey);
|
|
219
|
+
}
|
|
220
|
+
return roots.sort(dictSortKey);
|
|
221
|
+
}
|
|
222
|
+
function buildBaseSeedSqlVars(input) {
|
|
223
|
+
const adminPassword = input.adminPassword || "123456";
|
|
224
|
+
const passwordHash = createHash("md5").update(adminPassword, "utf8").digest("hex");
|
|
225
|
+
const adminUid = input.adminUid || "admin";
|
|
226
|
+
const adminRole = input.adminRole || "admin";
|
|
227
|
+
const resetSql = input.resetAdminPassword === true ? `UPDATE _SYS_USER\nSET _PWD = ${sqlLiteral(passwordHash)}\nWHERE _UID = ${sqlLiteral(adminUid)};` : "";
|
|
228
|
+
return {
|
|
229
|
+
SYSID: sqlLiteral(input.sysid || "sys"),
|
|
230
|
+
ADMIN_UID: sqlLiteral(adminUid),
|
|
231
|
+
ADMIN_NAME: sqlLiteral(input.adminName || "管理员"),
|
|
232
|
+
ADMIN_ROLE: sqlLiteral(adminRole),
|
|
233
|
+
ADMIN_ROLE_NAME: sqlLiteral(input.adminRoleName || "管理员"),
|
|
234
|
+
ADMIN_PASSWORD_MD5: sqlLiteral(passwordHash),
|
|
235
|
+
RESET_ADMIN_PASSWORD_SQL: resetSql
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function renderBaseSeedSql(template, values) {
|
|
239
|
+
let rendered = template;
|
|
240
|
+
for (const [key, value] of Object.entries(values)) rendered = rendered.replaceAll(`{{${key}}}`, value);
|
|
241
|
+
const missing = [...rendered.matchAll(/\{\{[A-Z0-9_]+\}\}/g)].map((item) => item[0]);
|
|
242
|
+
if (missing.length > 0) throw new SeiMcpError(`初始化 SQL 存在未替换变量:${[...new Set(missing)].join(", ")}`, "INVALID_PARAMS");
|
|
243
|
+
return rendered;
|
|
244
|
+
}
|
|
245
|
+
function removeSqlCommentLines(sql) {
|
|
246
|
+
return sql.split("\n").filter((line) => !line.trimStart().startsWith("--")).join("\n");
|
|
247
|
+
}
|
|
248
|
+
function splitSqlStatements(sql) {
|
|
249
|
+
const statements = [];
|
|
250
|
+
let current = "";
|
|
251
|
+
let quote = null;
|
|
252
|
+
let escaped = false;
|
|
253
|
+
for (const char of sql) {
|
|
254
|
+
if (escaped) {
|
|
255
|
+
current += char;
|
|
256
|
+
escaped = false;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (char === "\\" && quote !== null) {
|
|
260
|
+
current += char;
|
|
261
|
+
escaped = true;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (quote !== null) {
|
|
265
|
+
current += char;
|
|
266
|
+
if (char === quote) quote = null;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (char === "'" || char === "\"" || char === "`") {
|
|
270
|
+
current += char;
|
|
271
|
+
quote = char;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (char === ";") {
|
|
275
|
+
const statement = current.trim();
|
|
276
|
+
if (statement) statements.push(statement);
|
|
277
|
+
current = "";
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
current += char;
|
|
281
|
+
}
|
|
282
|
+
const finalStatement = current.trim();
|
|
283
|
+
if (finalStatement) statements.push(finalStatement);
|
|
284
|
+
return statements;
|
|
285
|
+
}
|
|
286
|
+
function buildDictQueryFilter(typeCode, keyword) {
|
|
287
|
+
const filters = [];
|
|
288
|
+
const normalizedType = normalizeText$1(typeCode);
|
|
289
|
+
const normalizedKeyword = normalizeText$1(keyword);
|
|
290
|
+
if (normalizedType) filters.push({ _TYPE: { "=": normalizedType } });
|
|
291
|
+
if (normalizedKeyword) filters.push({ OR: [
|
|
292
|
+
{ _UUID: { "*LIKE*": normalizedKeyword } },
|
|
293
|
+
{ _TYPE: { "*LIKE*": normalizedKeyword } },
|
|
294
|
+
{ _CODE: { "*LIKE*": normalizedKeyword } },
|
|
295
|
+
{ _NAME: { "*LIKE*": normalizedKeyword } }
|
|
296
|
+
] });
|
|
297
|
+
return filters;
|
|
298
|
+
}
|
|
299
|
+
function dictSortKey(a, b) {
|
|
300
|
+
const sortA = normalizeSort(typeof a._SORT === "number" ? a._SORT : Number(a._SORT ?? 0));
|
|
301
|
+
const sortB = normalizeSort(typeof b._SORT === "number" ? b._SORT : Number(b._SORT ?? 0));
|
|
302
|
+
if (sortA !== sortB) return sortA - sortB;
|
|
303
|
+
return normalizeText$1(a._CODE).localeCompare(normalizeText$1(b._CODE), "zh-CN");
|
|
304
|
+
}
|
|
305
|
+
function normalizeDictFlag(value) {
|
|
306
|
+
return value === 1 ? 1 : 0;
|
|
307
|
+
}
|
|
308
|
+
function normalizeSort(value) {
|
|
309
|
+
return Number.isInteger(value) && Number(value) >= 0 ? Number(value) : 100;
|
|
310
|
+
}
|
|
311
|
+
function requiredText(value, label) {
|
|
312
|
+
const normalized = normalizeText$1(value);
|
|
313
|
+
if (!normalized) throw new SeiMcpError(`${label} 不能为空`, "INVALID_PARAMS");
|
|
314
|
+
return normalized;
|
|
315
|
+
}
|
|
316
|
+
function normalizeText$1(value) {
|
|
317
|
+
return typeof value === "string" ? value.trim() : "";
|
|
318
|
+
}
|
|
319
|
+
function sqlLiteral(value) {
|
|
320
|
+
if (value === null || value === void 0) return "NULL";
|
|
321
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
322
|
+
}
|
|
323
|
+
function isObject$5(value) {
|
|
324
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
325
|
+
}
|
|
78
326
|
//#endregion
|
|
79
327
|
//#region src/utils.ts
|
|
80
|
-
function isObject$
|
|
328
|
+
function isObject$4(value) {
|
|
81
329
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
82
330
|
}
|
|
83
331
|
function toTrimmedString$1(value) {
|
|
@@ -143,12 +391,12 @@ function createSeiClient(config, logger, fetchImpl = fetch) {
|
|
|
143
391
|
function createSeiAuthSettings(config, options = {}) {
|
|
144
392
|
return {
|
|
145
393
|
baseUrl: normalizeBaseUrl$1(options.baseUrl ?? config.baseUrl),
|
|
146
|
-
token:
|
|
147
|
-
aiKey:
|
|
394
|
+
token: firstText(options.token, config.auth.token, process.env.SEI_TOKEN),
|
|
395
|
+
aiKey: firstText(options.aiKey, config.auth.aiKey, process.env.SEI_AI_LOGIN_KEY, DEFAULT_AI_KEY),
|
|
148
396
|
role: resolveRole(options.role, config.auth.role),
|
|
149
|
-
account:
|
|
150
|
-
accountProject:
|
|
151
|
-
accountCheckcode:
|
|
397
|
+
account: firstText(options.account, config.auth.account, process.env.SEI_AI_LOGIN_ACCOUNT),
|
|
398
|
+
accountProject: firstText(options.accountProject, process.env.SEI_AI_LOGIN_ACCOUNT_PROJECT, "sys"),
|
|
399
|
+
accountCheckcode: firstText(options.accountCheckcode, process.env.SEI_AI_LOGIN_ACCOUNT_CHECKCODE, ACCOUNT_LOGIN_DEFAULT_CHECKCODE),
|
|
152
400
|
timeoutMs: normalizeTimeout(options.timeoutMs)
|
|
153
401
|
};
|
|
154
402
|
}
|
|
@@ -169,6 +417,17 @@ function createCliSeiClient(settings, logger, fetchImpl = fetch) {
|
|
|
169
417
|
},
|
|
170
418
|
async readApiDocs() {
|
|
171
419
|
return requestJsonRaw(settings, logger, fetchImpl, "GET", OPENAPI_DOCS_PATH, void 0, { skipAuth: true });
|
|
420
|
+
},
|
|
421
|
+
async executeDdlStatements(statements, options = {}) {
|
|
422
|
+
const results = [];
|
|
423
|
+
for (const [index, statement] of statements.entries()) {
|
|
424
|
+
const response = await requestSeiJson(settings, logger, fetchImpl, auth, "POST", DDL_EXECUTE_PATH, { ddl: statement }, options);
|
|
425
|
+
results.push({
|
|
426
|
+
index: index + 1,
|
|
427
|
+
response
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return results;
|
|
172
431
|
}
|
|
173
432
|
};
|
|
174
433
|
}
|
|
@@ -333,7 +592,7 @@ async function fetchAccountLoginRow(settings, logger, fetchImpl, adminToken, acc
|
|
|
333
592
|
const rows = extractResponseRows(response);
|
|
334
593
|
if (rows.length === 0) throw new SeiMcpError(`未找到账号:${account}`, "INVALID_REQUEST");
|
|
335
594
|
const row = rows[0];
|
|
336
|
-
if (!isObject$
|
|
595
|
+
if (!isObject$3(row)) throw new SeiMcpError(`账号 ${account} 查询结果不是 JSON 对象`, "INVALID_REQUEST");
|
|
337
596
|
return row;
|
|
338
597
|
}
|
|
339
598
|
async function requestCookieHeader(settings, logger, fetchImpl, payload) {
|
|
@@ -370,19 +629,19 @@ async function resolveRequestToken(settings, auth, options) {
|
|
|
370
629
|
}
|
|
371
630
|
function validateSeiEnvelope(response, context, allowFailure, allowEmpty) {
|
|
372
631
|
if (response === null && allowEmpty) return;
|
|
373
|
-
if (!isObject$
|
|
632
|
+
if (!isObject$3(response)) throw new SeiMcpError(`${context} 响应不是 JSON 对象`, "INVALID_REQUEST");
|
|
374
633
|
if (!allowFailure && isSeiFailure(response)) throw new SeiMcpError(`${context} 失败:${String(response.message ?? response.msg ?? response.code ?? "未知错误")}`, "INVALID_REQUEST", { response: safeResponse(response) });
|
|
375
634
|
}
|
|
376
635
|
function extractToken(response) {
|
|
377
636
|
validateSeiEnvelope(response, "login", false, false);
|
|
378
|
-
if (!isObject$
|
|
637
|
+
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
638
|
return response.data.token.trim();
|
|
380
639
|
}
|
|
381
640
|
function extractResponseRows(response) {
|
|
382
|
-
if (!isObject$
|
|
641
|
+
if (!isObject$3(response)) return [];
|
|
383
642
|
const data = response.data;
|
|
384
643
|
if (Array.isArray(data)) return data;
|
|
385
|
-
if (isObject$
|
|
644
|
+
if (isObject$3(data)) for (const key of [
|
|
386
645
|
"rows",
|
|
387
646
|
"records",
|
|
388
647
|
"list",
|
|
@@ -456,24 +715,24 @@ function escapeSqlLiteral(value) {
|
|
|
456
715
|
return String(value).replace(/'/g, "''");
|
|
457
716
|
}
|
|
458
717
|
function isUnauthenticatedResponse(response) {
|
|
459
|
-
if (!isObject$
|
|
718
|
+
if (!isObject$3(response)) return false;
|
|
460
719
|
return Number(response.code) === -2;
|
|
461
720
|
}
|
|
462
721
|
function isSeiFailure(response) {
|
|
463
|
-
if (!isObject$
|
|
722
|
+
if (!isObject$3(response)) return false;
|
|
464
723
|
if (response.success === false) return true;
|
|
465
724
|
const code = Number(response.code);
|
|
466
725
|
return Number.isFinite(code) && code < 1;
|
|
467
726
|
}
|
|
468
727
|
function safeResponse(response) {
|
|
469
|
-
if (!isObject$
|
|
728
|
+
if (!isObject$3(response)) return response;
|
|
470
729
|
return {
|
|
471
730
|
code: response.code,
|
|
472
731
|
success: response.success,
|
|
473
732
|
message: response.message
|
|
474
733
|
};
|
|
475
734
|
}
|
|
476
|
-
function isObject$
|
|
735
|
+
function isObject$3(value) {
|
|
477
736
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
478
737
|
}
|
|
479
738
|
function normalizeTimeout(value) {
|
|
@@ -484,9 +743,14 @@ function normalizeTimeout(value) {
|
|
|
484
743
|
return DEFAULT_TIMEOUT_MS;
|
|
485
744
|
}
|
|
486
745
|
function resolveRole(role, fallback) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
746
|
+
return firstText(role, fallback, process.env.SEI_AI_LOGIN_ROLE).toLowerCase();
|
|
747
|
+
}
|
|
748
|
+
function firstText(...values) {
|
|
749
|
+
for (const value of values) {
|
|
750
|
+
const text = toText(value);
|
|
751
|
+
if (text) return text;
|
|
752
|
+
}
|
|
753
|
+
return "";
|
|
490
754
|
}
|
|
491
755
|
function toText(value) {
|
|
492
756
|
return typeof value === "string" ? value.trim() : "";
|
|
@@ -504,17 +768,17 @@ var OPENAPI_HTTP_METHODS = [
|
|
|
504
768
|
"TRACE"
|
|
505
769
|
];
|
|
506
770
|
function buildOpenApiDocSummary(openapiDoc, keyword) {
|
|
507
|
-
if (!isObject$
|
|
508
|
-
const info = isObject$
|
|
771
|
+
if (!isObject$2(openapiDoc)) throw new SeiMcpError("OpenAPI 文档不是 JSON 对象", "INVALID_REQUEST");
|
|
772
|
+
const info = isObject$2(openapiDoc.info) ? openapiDoc.info : {};
|
|
509
773
|
const rawPaths = openapiDoc.paths;
|
|
510
|
-
if (!isObject$
|
|
774
|
+
if (!isObject$2(rawPaths)) throw new SeiMcpError("OpenAPI 文档缺少 paths 对象", "INVALID_REQUEST");
|
|
511
775
|
const normalizedKeyword = normalizeText(keyword).toLowerCase();
|
|
512
776
|
const items = [];
|
|
513
777
|
for (const [path, rawPathItem] of Object.entries(rawPaths)) {
|
|
514
|
-
if (!isObject$
|
|
778
|
+
if (!isObject$2(rawPathItem)) continue;
|
|
515
779
|
for (const [method, rawOperation] of Object.entries(rawPathItem)) {
|
|
516
780
|
const methodUpper = method.toUpperCase();
|
|
517
|
-
if (!OPENAPI_HTTP_METHODS.includes(methodUpper) || !isObject$
|
|
781
|
+
if (!OPENAPI_HTTP_METHODS.includes(methodUpper) || !isObject$2(rawOperation)) continue;
|
|
518
782
|
const tags = Array.isArray(rawOperation.tags) ? rawOperation.tags.map((item) => normalizeText(item)).filter(Boolean) : [];
|
|
519
783
|
const item = {
|
|
520
784
|
method: methodUpper,
|
|
@@ -559,7 +823,7 @@ function renderOpenApiMarkdown(summary) {
|
|
|
559
823
|
let currentTag = "";
|
|
560
824
|
const items = Array.isArray(summary.items) ? summary.items : [];
|
|
561
825
|
for (const rawItem of items) {
|
|
562
|
-
if (!isObject$
|
|
826
|
+
if (!isObject$2(rawItem)) continue;
|
|
563
827
|
const tag = normalizeText((Array.isArray(rawItem.tags) ? rawItem.tags : [])[0] ?? "未分组") || "未分组";
|
|
564
828
|
if (tag !== currentTag) {
|
|
565
829
|
lines.push("", `## ${tag}`);
|
|
@@ -578,7 +842,7 @@ function renderOpenApiMarkdown(summary) {
|
|
|
578
842
|
if (parameters.length > 0) {
|
|
579
843
|
lines.push("- 参数:");
|
|
580
844
|
for (const rawParameter of parameters) {
|
|
581
|
-
if (!isObject$
|
|
845
|
+
if (!isObject$2(rawParameter)) continue;
|
|
582
846
|
const name = normalizeText(rawParameter.name);
|
|
583
847
|
const location = normalizeText(rawParameter.in);
|
|
584
848
|
const detail = [
|
|
@@ -591,7 +855,7 @@ function renderOpenApiMarkdown(summary) {
|
|
|
591
855
|
}
|
|
592
856
|
const requestBody = Array.isArray(rawItem.requestBody) ? rawItem.requestBody : [];
|
|
593
857
|
if (requestBody.length > 0) {
|
|
594
|
-
const parts = requestBody.filter(isObject$
|
|
858
|
+
const parts = requestBody.filter(isObject$2).map((body) => {
|
|
595
859
|
const contentType = normalizeText(body.contentType);
|
|
596
860
|
const schema = normalizeText(body.schema);
|
|
597
861
|
return schema ? `${contentType}(${schema})` : contentType;
|
|
@@ -600,7 +864,7 @@ function renderOpenApiMarkdown(summary) {
|
|
|
600
864
|
}
|
|
601
865
|
const responses = Array.isArray(rawItem.responses) ? rawItem.responses : [];
|
|
602
866
|
if (responses.length > 0) {
|
|
603
|
-
const parts = responses.filter(isObject$
|
|
867
|
+
const parts = responses.filter(isObject$2).map((response) => `${normalizeText(response.status)} ${normalizeText(response.description)}`.trim()).filter(Boolean);
|
|
604
868
|
if (parts.length > 0) lines.push(`- 响应:${parts.join(", ")}`);
|
|
605
869
|
}
|
|
606
870
|
}
|
|
@@ -609,7 +873,7 @@ function renderOpenApiMarkdown(summary) {
|
|
|
609
873
|
function extractOpenApiParameters(operation) {
|
|
610
874
|
const rawParameters = operation.parameters;
|
|
611
875
|
if (!Array.isArray(rawParameters)) return [];
|
|
612
|
-
return rawParameters.filter(isObject$
|
|
876
|
+
return rawParameters.filter(isObject$2).map((parameter) => ({
|
|
613
877
|
name: normalizeText(parameter.name),
|
|
614
878
|
in: normalizeText(parameter.in),
|
|
615
879
|
required: Boolean(parameter.required),
|
|
@@ -619,22 +883,22 @@ function extractOpenApiParameters(operation) {
|
|
|
619
883
|
}
|
|
620
884
|
function extractOpenApiRequestBody(operation) {
|
|
621
885
|
const requestBody = operation.requestBody;
|
|
622
|
-
if (!isObject$
|
|
886
|
+
if (!isObject$2(requestBody) || !isObject$2(requestBody.content)) return [];
|
|
623
887
|
return Object.entries(requestBody.content).map(([contentType, contentInfo]) => ({
|
|
624
888
|
contentType: normalizeText(contentType),
|
|
625
|
-
schema: summarizeSchema(isObject$
|
|
889
|
+
schema: summarizeSchema(isObject$2(contentInfo) ? contentInfo.schema : void 0)
|
|
626
890
|
})).filter((item) => item.contentType);
|
|
627
891
|
}
|
|
628
892
|
function extractOpenApiResponses(operation) {
|
|
629
893
|
const rawResponses = operation.responses;
|
|
630
|
-
if (!isObject$
|
|
894
|
+
if (!isObject$2(rawResponses)) return [];
|
|
631
895
|
return Object.entries(rawResponses).map(([status, response]) => ({
|
|
632
896
|
status: normalizeText(status),
|
|
633
|
-
description: normalizeText(isObject$
|
|
897
|
+
description: normalizeText(isObject$2(response) ? response.description : "")
|
|
634
898
|
})).filter((item) => item.status);
|
|
635
899
|
}
|
|
636
900
|
function summarizeSchema(schema) {
|
|
637
|
-
if (!isObject$
|
|
901
|
+
if (!isObject$2(schema)) return "";
|
|
638
902
|
const ref = normalizeText(schema.$ref);
|
|
639
903
|
if (ref) return ref.split("/").pop() ?? ref;
|
|
640
904
|
const schemaType = normalizeText(schema.type);
|
|
@@ -647,63 +911,147 @@ function summarizeSchema(schema) {
|
|
|
647
911
|
function normalizeText(value) {
|
|
648
912
|
return typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
649
913
|
}
|
|
650
|
-
function isObject$
|
|
914
|
+
function isObject$2(value) {
|
|
651
915
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
652
916
|
}
|
|
653
917
|
//#endregion
|
|
654
918
|
//#region src/cli.ts
|
|
655
919
|
async function runCli(argv, options) {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
const client = createCliSeiClient(createSeiAuthSettings(options.config, {
|
|
662
|
-
baseUrl: toUndefined(readOption(argv, "--base-url")),
|
|
663
|
-
token: toUndefined(readOption(argv, "--token")),
|
|
664
|
-
aiKey: toUndefined(readOption(argv, "--ai-key")),
|
|
665
|
-
role: toUndefined(readOption(argv, "--role") ?? readOption(argv, "--profile")),
|
|
666
|
-
account: toUndefined(readOption(argv, "--account")),
|
|
667
|
-
accountProject: toUndefined(readOption(argv, "--account-project")),
|
|
668
|
-
accountCheckcode: toUndefined(readOption(argv, "--account-checkcode")),
|
|
669
|
-
timeoutMs: readOptionNumber(argv, "--timeout")
|
|
670
|
-
}), options.logger, options.fetchImpl ?? fetch);
|
|
671
|
-
switch (command) {
|
|
672
|
-
case "api-docs": {
|
|
673
|
-
const summary = buildOpenApiDocSummary(await client.readApiDocs(), readOption(argv, "--keyword") ?? void 0);
|
|
674
|
-
const output = normalizeFormat(readOption(argv, "--format")) === "json" ? `${JSON.stringify(summary, null, 2)}\n` : renderOpenApiMarkdown(summary);
|
|
675
|
-
await writeOutput(readOption(argv, "--output"), output);
|
|
676
|
-
return;
|
|
677
|
-
}
|
|
678
|
-
case "call": {
|
|
679
|
-
const [method, path] = positionalAfter(argv, command, 2);
|
|
680
|
-
if (!method || !path) throw new SeiMcpError("call 需要 METHOD 和 PATH", "INVALID_PARAMS");
|
|
681
|
-
const payload = await loadPayload(argv, {});
|
|
682
|
-
printJson(await client.call(method, path, payload, {
|
|
683
|
-
allowFailure: hasFlag(argv, "--allow-failure"),
|
|
684
|
-
allowEmpty: hasFlag(argv, "--allow-empty"),
|
|
685
|
-
skipAuth: hasFlag(argv, "--no-token")
|
|
686
|
-
}));
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
case "query": {
|
|
690
|
-
const payload = await loadPayload(argv, {});
|
|
691
|
-
printJson(await client.query(payload, { allowFailure: hasFlag(argv, "--allow-failure") }));
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
case "save": {
|
|
695
|
-
const payload = await loadPayload(argv, {});
|
|
696
|
-
printJson(await client.save(payload, { allowFailure: hasFlag(argv, "--allow-failure") }));
|
|
920
|
+
try {
|
|
921
|
+
const command = firstPositional(argv);
|
|
922
|
+
if (!command || isHelpFlag(command) || hasFlag(argv, "--help") || hasFlag(argv, "-h")) {
|
|
923
|
+
process.stdout.write(`${renderCliHelp()}\n`);
|
|
697
924
|
return;
|
|
698
925
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
926
|
+
const settings = createSeiAuthSettings(options.config, {
|
|
927
|
+
baseUrl: toUndefined(readOption(argv, "--base-url")),
|
|
928
|
+
token: toUndefined(readOption(argv, "--token")),
|
|
929
|
+
aiKey: toUndefined(readOption(argv, "--ai-key")),
|
|
930
|
+
role: toUndefined(readOption(argv, "--role") ?? readOption(argv, "--profile")),
|
|
931
|
+
account: toUndefined(readOption(argv, "--account")),
|
|
932
|
+
accountProject: toUndefined(readOption(argv, "--account-project")),
|
|
933
|
+
accountCheckcode: toUndefined(readOption(argv, "--account-checkcode")),
|
|
934
|
+
timeoutMs: readOptionNumber(argv, "--timeout")
|
|
935
|
+
});
|
|
936
|
+
const client = createCliSeiClient(settings, options.logger, options.fetchImpl ?? fetch);
|
|
937
|
+
switch (command) {
|
|
938
|
+
case "api-docs": {
|
|
939
|
+
const summary = buildOpenApiDocSummary(await client.readApiDocs(), readOption(argv, "--keyword") ?? void 0);
|
|
940
|
+
const output = normalizeFormat(readOption(argv, "--format")) === "json" ? `${JSON.stringify(summary, null, 2)}\n` : renderOpenApiMarkdown(summary);
|
|
941
|
+
await writeOutput(readOption(argv, "--output"), output);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
case "call": {
|
|
945
|
+
const [method, path] = positionalAfter(argv, command, 2);
|
|
946
|
+
if (!method || !path) throw new SeiMcpError("call 需要 METHOD 和 PATH", "INVALID_PARAMS");
|
|
947
|
+
const payload = await loadPayload(argv, {});
|
|
948
|
+
printJson(await client.call(method, path, payload, {
|
|
949
|
+
allowFailure: hasFlag(argv, "--allow-failure"),
|
|
950
|
+
allowEmpty: hasFlag(argv, "--allow-empty"),
|
|
951
|
+
skipAuth: hasFlag(argv, "--no-token")
|
|
952
|
+
}));
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
case "query": {
|
|
956
|
+
const payload = await loadPayload(argv, {});
|
|
957
|
+
printJson(await client.query(payload, { allowFailure: hasFlag(argv, "--allow-failure") }));
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
case "save": {
|
|
961
|
+
const payload = await loadPayload(argv, {});
|
|
962
|
+
printJson(await client.save(payload, { allowFailure: hasFlag(argv, "--allow-failure") }));
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
case "query-sql":
|
|
966
|
+
case "sql": {
|
|
967
|
+
const [sql] = positionalAfter(argv, command, 1);
|
|
968
|
+
if (!sql) throw new SeiMcpError("query-sql 需要 SQL 参数", "INVALID_PARAMS");
|
|
969
|
+
printJson(await client.querySql(sql, readOptionNumber(argv, "--page"), readOptionNumber(argv, "--size"), { allowFailure: hasFlag(argv, "--allow-failure") }));
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
case "dict-list": {
|
|
973
|
+
const payload = buildDictQueryPayload(readOption(argv, "--type") ?? void 0, readOption(argv, "--keyword") ?? void 0, readOptionNumber(argv, "--size") ?? 1e3);
|
|
974
|
+
const response = await client.query(payload, { allowFailure: hasFlag(argv, "--allow-failure") });
|
|
975
|
+
if (!hasFlag(argv, "--flat") && isObject$1(response) && isObject$1(response.data) && Array.isArray(response.data.rows)) response.data.tree = buildDictTree(response.data.rows);
|
|
976
|
+
printJson(response);
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
case "dict-add-category": {
|
|
980
|
+
const [typeCode, name] = positionalAfter(argv, command, 2);
|
|
981
|
+
if (!typeCode || !name) throw new SeiMcpError("dict-add-category 需要 type_code 和 name", "INVALID_PARAMS");
|
|
982
|
+
const payload = buildDictSavePayload(buildDictCategoryRow({
|
|
983
|
+
body: readOption(argv, "--body") ?? void 0,
|
|
984
|
+
code: readOption(argv, "--code") ?? void 0,
|
|
985
|
+
ctype: readOption(argv, "--ctype") ?? void 0,
|
|
986
|
+
dictFlag: readOptionInteger(argv, "--dict-flag"),
|
|
987
|
+
ename: readOption(argv, "--ename") ?? void 0,
|
|
988
|
+
memo: readOption(argv, "--memo") ?? void 0,
|
|
989
|
+
name,
|
|
990
|
+
sort: readOptionInteger(argv, "--sort"),
|
|
991
|
+
sysid: readOption(argv, "--sysid") ?? void 0,
|
|
992
|
+
typeCode,
|
|
993
|
+
uuid: readOption(argv, "--uuid") ?? void 0
|
|
994
|
+
}));
|
|
995
|
+
printJson(await client.save(payload, { allowFailure: hasFlag(argv, "--allow-failure") }));
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
case "dict-add-item": {
|
|
999
|
+
const [typeCode, code, name] = positionalAfter(argv, command, 3);
|
|
1000
|
+
if (!typeCode || !code || !name) throw new SeiMcpError("dict-add-item 需要 type_code、code 和 name", "INVALID_PARAMS");
|
|
1001
|
+
const payload = buildDictSavePayload(buildDictItemRow({
|
|
1002
|
+
body: readOption(argv, "--body") ?? void 0,
|
|
1003
|
+
code,
|
|
1004
|
+
ctype: readOption(argv, "--ctype") ?? void 0,
|
|
1005
|
+
dictFlag: readOptionInteger(argv, "--dict-flag"),
|
|
1006
|
+
ename: readOption(argv, "--ename") ?? void 0,
|
|
1007
|
+
memo: readOption(argv, "--memo") ?? void 0,
|
|
1008
|
+
name,
|
|
1009
|
+
parent: readOption(argv, "--parent") ?? void 0,
|
|
1010
|
+
sort: readOptionInteger(argv, "--sort"),
|
|
1011
|
+
sysid: readOption(argv, "--sysid") ?? void 0,
|
|
1012
|
+
typeCode,
|
|
1013
|
+
uuid: readOption(argv, "--uuid") ?? void 0
|
|
1014
|
+
}));
|
|
1015
|
+
printJson(await client.save(payload, { allowFailure: hasFlag(argv, "--allow-failure") }));
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
case "init-base-data": {
|
|
1019
|
+
const sqlFile = resolveBaseSeedSqlPath(readOption(argv, "--sql-file"));
|
|
1020
|
+
const statements = splitSqlStatements(removeSqlCommentLines(renderBaseSeedSql(await readFile(sqlFile, "utf8"), buildBaseSeedSqlVars({
|
|
1021
|
+
adminName: readOption(argv, "--admin-name") ?? void 0,
|
|
1022
|
+
adminPassword: readOption(argv, "--admin-password") ?? void 0,
|
|
1023
|
+
adminRole: readOption(argv, "--admin-role") ?? void 0,
|
|
1024
|
+
adminRoleName: readOption(argv, "--admin-role-name") ?? void 0,
|
|
1025
|
+
adminUid: readOption(argv, "--admin-uid") ?? void 0,
|
|
1026
|
+
resetAdminPassword: hasFlag(argv, "--reset-admin-password"),
|
|
1027
|
+
sysid: readOption(argv, "--sysid") ?? void 0
|
|
1028
|
+
}))));
|
|
1029
|
+
if (statements.length === 0) throw new SeiMcpError("初始化 SQL 文件没有可执行语句", "INVALID_PARAMS");
|
|
1030
|
+
const executed = await client.executeDdlStatements(statements, { allowFailure: hasFlag(argv, "--allow-failure") });
|
|
1031
|
+
await client.call("POST", "/api/sei/data/removeAllCache", {}, { allowFailure: false });
|
|
1032
|
+
printJson({
|
|
1033
|
+
success: true,
|
|
1034
|
+
code: 1,
|
|
1035
|
+
message: "基础源数据初始化完成",
|
|
1036
|
+
data: {
|
|
1037
|
+
executed: executed.length,
|
|
1038
|
+
sqlFile,
|
|
1039
|
+
admin: readOption(argv, "--admin-uid") ?? "admin",
|
|
1040
|
+
role: readOption(argv, "--admin-role") ?? "admin",
|
|
1041
|
+
baseUrl: settings.baseUrl
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
default: throw new SeiMcpError(`未知 CLI 子命令:${command}`, "INVALID_PARAMS");
|
|
705
1047
|
}
|
|
706
|
-
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
options.logger.error("CLI command failed", {
|
|
1050
|
+
argv,
|
|
1051
|
+
error: toErrorLogMeta(error)
|
|
1052
|
+
});
|
|
1053
|
+
process.stderr.write(`${formatCliError(error)}\n`);
|
|
1054
|
+
throw error;
|
|
707
1055
|
}
|
|
708
1056
|
}
|
|
709
1057
|
function renderCliHelp() {
|
|
@@ -716,6 +1064,10 @@ function renderCliHelp() {
|
|
|
716
1064
|
` ${BIN_NAME} cli save [--json <text> | --file <path> | --stdin]`,
|
|
717
1065
|
` ${BIN_NAME} cli query-sql "<sql>" [--page 1] [--size 20]`,
|
|
718
1066
|
` ${BIN_NAME} cli api-docs [--keyword <text>] [--format markdown|json] [--output <path>]`,
|
|
1067
|
+
` ${BIN_NAME} cli dict-list [--type <type>] [--keyword <text>] [--size 1000] [--flat]`,
|
|
1068
|
+
` ${BIN_NAME} cli dict-add-category <type_code> <name> [--code <code>]`,
|
|
1069
|
+
` ${BIN_NAME} cli dict-add-item <type_code> <code> <name> [--parent <uuid>]`,
|
|
1070
|
+
` ${BIN_NAME} cli init-base-data [--sysid sys] [--admin-uid admin]`,
|
|
719
1071
|
"",
|
|
720
1072
|
"Common Options:",
|
|
721
1073
|
" --base-url <url>",
|
|
@@ -727,11 +1079,29 @@ function renderCliHelp() {
|
|
|
727
1079
|
" --token <token>",
|
|
728
1080
|
" --timeout <seconds>",
|
|
729
1081
|
" --allow-failure",
|
|
1082
|
+
" --sysid <sysid>",
|
|
1083
|
+
" --uuid <uuid>",
|
|
1084
|
+
" --ctype <ctype>",
|
|
1085
|
+
" --ename <ename>",
|
|
1086
|
+
" --sort <number>",
|
|
1087
|
+
" --body <json-or-text>",
|
|
1088
|
+
" --memo <text>",
|
|
1089
|
+
" --dict-flag <0|1>",
|
|
730
1090
|
"",
|
|
731
1091
|
"call Options:",
|
|
732
1092
|
" --no-token",
|
|
733
1093
|
" --allow-empty",
|
|
734
1094
|
"",
|
|
1095
|
+
"init-base-data Options:",
|
|
1096
|
+
` --admin-uid <uid> default ${BASE_SEED_DEFAULT_ADMIN_UID}`,
|
|
1097
|
+
" --admin-name <name> default 管理员",
|
|
1098
|
+
` --admin-role <role> default ${BASE_SEED_DEFAULT_ADMIN_ROLE}`,
|
|
1099
|
+
" --admin-role-name <name> default 管理员",
|
|
1100
|
+
` --admin-password <pwd> default ${BASE_SEED_DEFAULT_ADMIN_PASSWORD}`,
|
|
1101
|
+
` --sysid <sysid> default sys`,
|
|
1102
|
+
" --reset-admin-password",
|
|
1103
|
+
" --sql-file <path>",
|
|
1104
|
+
"",
|
|
735
1105
|
"Payload Options:",
|
|
736
1106
|
" --json <text>",
|
|
737
1107
|
" --file <path>",
|
|
@@ -797,6 +1167,13 @@ function readOptionNumber(argv, name) {
|
|
|
797
1167
|
if (!Number.isFinite(parsed) || parsed <= 0) throw new SeiMcpError(`${name} 必须是正数`, "INVALID_PARAMS");
|
|
798
1168
|
return parsed;
|
|
799
1169
|
}
|
|
1170
|
+
function readOptionInteger(argv, name) {
|
|
1171
|
+
const value = readOption(argv, name);
|
|
1172
|
+
if (value === null) return;
|
|
1173
|
+
const parsed = Number.parseInt(value, 10);
|
|
1174
|
+
if (!Number.isFinite(parsed)) throw new SeiMcpError(`${name} 必须是整数`, "INVALID_PARAMS");
|
|
1175
|
+
return parsed;
|
|
1176
|
+
}
|
|
800
1177
|
function firstPositional(argv) {
|
|
801
1178
|
for (const item of argv) if (item && !item.startsWith("-")) return item;
|
|
802
1179
|
return null;
|
|
@@ -824,6 +1201,13 @@ function isHelpFlag(value) {
|
|
|
824
1201
|
function toUndefined(value) {
|
|
825
1202
|
return value ?? void 0;
|
|
826
1203
|
}
|
|
1204
|
+
function resolveBaseSeedSqlPath(path) {
|
|
1205
|
+
if (path) return resolve(path);
|
|
1206
|
+
return resolve(process.cwd(), ".codex/skills/sei-ai/scripts/init-base-data.sql");
|
|
1207
|
+
}
|
|
1208
|
+
function isObject$1(value) {
|
|
1209
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1210
|
+
}
|
|
827
1211
|
async function readStdin() {
|
|
828
1212
|
const chunks = [];
|
|
829
1213
|
for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
@@ -836,10 +1220,10 @@ async function loadConfig(argv = process.argv.slice(2), env = process.env) {
|
|
|
836
1220
|
return normalizeConfig(configPath ? await readConfigFile(configPath) : {}, env);
|
|
837
1221
|
}
|
|
838
1222
|
function normalizeConfig(input = {}, env = process.env) {
|
|
839
|
-
const config = isObject$
|
|
840
|
-
const auth = isObject$
|
|
841
|
-
const tools = isObject$
|
|
842
|
-
const targets = isObject$
|
|
1223
|
+
const config = isObject$4(input) ? input : {};
|
|
1224
|
+
const auth = isObject$4(config.auth) ? config.auth : {};
|
|
1225
|
+
const tools = isObject$4(config.tools) ? config.tools : {};
|
|
1226
|
+
const targets = isObject$4(config.targets) ? config.targets : {};
|
|
843
1227
|
return {
|
|
844
1228
|
baseUrl: normalizeBaseUrl(config.baseUrl, env.SEI_BASE_URL),
|
|
845
1229
|
auth: {
|
|
@@ -896,8 +1280,27 @@ function stringOrEnv(value, envValue) {
|
|
|
896
1280
|
return toTrimmedString$1(envValue);
|
|
897
1281
|
}
|
|
898
1282
|
function toTargetMap(value) {
|
|
899
|
-
if (!isObject$
|
|
900
|
-
return Object.fromEntries(Object.entries(value).filter(([, item]) => isObject$
|
|
1283
|
+
if (!isObject$4(value)) return {};
|
|
1284
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => isObject$4(item)));
|
|
1285
|
+
}
|
|
1286
|
+
//#endregion
|
|
1287
|
+
//#region src/env.ts
|
|
1288
|
+
var DEFAULT_ENV_FILES = [".env.local", ".env"];
|
|
1289
|
+
async function loadWorkingDirEnv(cwd = process.cwd(), env = process.env) {
|
|
1290
|
+
for (const fileName of DEFAULT_ENV_FILES) await loadEnvFile(resolve(cwd, fileName), env);
|
|
1291
|
+
}
|
|
1292
|
+
async function loadEnvFile(path, env) {
|
|
1293
|
+
try {
|
|
1294
|
+
const parsed = parseEnv(await readFile(path, "utf8"));
|
|
1295
|
+
for (const [key, value] of Object.entries(parsed)) if (env[key] === void 0) env[key] = value;
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
if (isMissingFile(error)) return;
|
|
1298
|
+
throw error;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
function isMissingFile(error) {
|
|
1302
|
+
if (!error || typeof error !== "object") return false;
|
|
1303
|
+
return error.code === "ENOENT";
|
|
901
1304
|
}
|
|
902
1305
|
//#endregion
|
|
903
1306
|
//#region src/logger.ts
|
|
@@ -4543,7 +4946,7 @@ var $ZodAsyncError = class extends Error {
|
|
|
4543
4946
|
}
|
|
4544
4947
|
};
|
|
4545
4948
|
var globalConfig = {};
|
|
4546
|
-
function config
|
|
4949
|
+
function config(newConfig) {
|
|
4547
4950
|
if (newConfig) Object.assign(globalConfig, newConfig);
|
|
4548
4951
|
return globalConfig;
|
|
4549
4952
|
}
|
|
@@ -4886,7 +5289,7 @@ var _parse = (_Err) => (schema, value, _ctx, _params) => {
|
|
|
4886
5289
|
}, ctx);
|
|
4887
5290
|
if (result instanceof Promise) throw new $ZodAsyncError();
|
|
4888
5291
|
if (result.issues.length) {
|
|
4889
|
-
const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config
|
|
5292
|
+
const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
|
|
4890
5293
|
captureStackTrace(e, _params?.callee);
|
|
4891
5294
|
throw e;
|
|
4892
5295
|
}
|
|
@@ -4901,7 +5304,7 @@ var _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
|
|
|
4901
5304
|
}, ctx);
|
|
4902
5305
|
if (result instanceof Promise) result = await result;
|
|
4903
5306
|
if (result.issues.length) {
|
|
4904
|
-
const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config
|
|
5307
|
+
const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
|
|
4905
5308
|
captureStackTrace(e, params?.callee);
|
|
4906
5309
|
throw e;
|
|
4907
5310
|
}
|
|
@@ -4920,7 +5323,7 @@ var _safeParse = (_Err) => (schema, value, _ctx) => {
|
|
|
4920
5323
|
if (result instanceof Promise) throw new $ZodAsyncError();
|
|
4921
5324
|
return result.issues.length ? {
|
|
4922
5325
|
success: false,
|
|
4923
|
-
error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config
|
|
5326
|
+
error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
|
|
4924
5327
|
} : {
|
|
4925
5328
|
success: true,
|
|
4926
5329
|
data: result.value
|
|
@@ -4936,7 +5339,7 @@ var _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
|
|
|
4936
5339
|
if (result instanceof Promise) result = await result;
|
|
4937
5340
|
return result.issues.length ? {
|
|
4938
5341
|
success: false,
|
|
4939
|
-
error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config
|
|
5342
|
+
error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
|
|
4940
5343
|
} : {
|
|
4941
5344
|
success: true,
|
|
4942
5345
|
data: result.value
|
|
@@ -5929,7 +6332,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
5929
6332
|
return (payload, ctx) => fn(shape, payload, ctx);
|
|
5930
6333
|
};
|
|
5931
6334
|
let fastpass;
|
|
5932
|
-
const isObject$
|
|
6335
|
+
const isObject$6 = isObject;
|
|
5933
6336
|
const jit = !globalConfig.jitless;
|
|
5934
6337
|
const fastEnabled = jit && allowsEval.value;
|
|
5935
6338
|
const catchall = def.catchall;
|
|
@@ -5937,7 +6340,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
5937
6340
|
inst._zod.parse = (payload, ctx) => {
|
|
5938
6341
|
value ?? (value = _normalized.value);
|
|
5939
6342
|
const input = payload.value;
|
|
5940
|
-
if (!isObject$
|
|
6343
|
+
if (!isObject$6(input)) {
|
|
5941
6344
|
payload.issues.push({
|
|
5942
6345
|
expected: "object",
|
|
5943
6346
|
code: "invalid_type",
|
|
@@ -6004,7 +6407,7 @@ function handleUnionResults(results, final, inst, ctx) {
|
|
|
6004
6407
|
code: "invalid_union",
|
|
6005
6408
|
input: final.value,
|
|
6006
6409
|
inst,
|
|
6007
|
-
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config
|
|
6410
|
+
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
|
|
6008
6411
|
});
|
|
6009
6412
|
return final;
|
|
6010
6413
|
}
|
|
@@ -6233,7 +6636,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
6233
6636
|
payload.issues.push({
|
|
6234
6637
|
origin: "record",
|
|
6235
6638
|
code: "invalid_key",
|
|
6236
|
-
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config
|
|
6639
|
+
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
|
|
6237
6640
|
input: key,
|
|
6238
6641
|
path: [key],
|
|
6239
6642
|
inst
|
|
@@ -6401,7 +6804,7 @@ var $ZodCatch = /* @__PURE__ */ $constructor("$ZodCatch", (inst, def) => {
|
|
|
6401
6804
|
if (result.issues.length) {
|
|
6402
6805
|
payload.value = def.catchValue({
|
|
6403
6806
|
...payload,
|
|
6404
|
-
error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config
|
|
6807
|
+
error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },
|
|
6405
6808
|
input: payload.value
|
|
6406
6809
|
});
|
|
6407
6810
|
payload.issues = [];
|
|
@@ -6412,7 +6815,7 @@ var $ZodCatch = /* @__PURE__ */ $constructor("$ZodCatch", (inst, def) => {
|
|
|
6412
6815
|
if (result.issues.length) {
|
|
6413
6816
|
payload.value = def.catchValue({
|
|
6414
6817
|
...payload,
|
|
6415
|
-
error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config
|
|
6818
|
+
error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },
|
|
6416
6819
|
input: payload.value
|
|
6417
6820
|
});
|
|
6418
6821
|
payload.issues = [];
|
|
@@ -21222,7 +21625,7 @@ function authorizeQuery(config, input) {
|
|
|
21222
21625
|
return {
|
|
21223
21626
|
head,
|
|
21224
21627
|
option: {
|
|
21225
|
-
...isObject$
|
|
21628
|
+
...isObject$4(input.option) ? input.option : {},
|
|
21226
21629
|
fields: effectiveFields.join(","),
|
|
21227
21630
|
limit: toPositiveInteger(input.option?.limit, 20)
|
|
21228
21631
|
},
|
|
@@ -21244,7 +21647,7 @@ function authorizeSave(config, input) {
|
|
|
21244
21647
|
const action = normalizeAction(item?.action);
|
|
21245
21648
|
if (!allowedActions.includes(action)) throw new SeiMcpError(`save 动作不在白名单内: ${action}`, "INVALID_PARAMS");
|
|
21246
21649
|
for (const row of Array.isArray(item?.rows) ? item.rows : []) {
|
|
21247
|
-
const current = isObject$
|
|
21650
|
+
const current = isObject$4(row?.row) ? row.row : {};
|
|
21248
21651
|
ensureFieldsAllowed(Object.keys(current).filter((key) => !RESERVED_SAVE_ROW_KEYS.has(key)), allowedFields, "save");
|
|
21249
21652
|
}
|
|
21250
21653
|
}
|
|
@@ -21272,7 +21675,7 @@ function authorizeQuerySql(config, input) {
|
|
|
21272
21675
|
};
|
|
21273
21676
|
}
|
|
21274
21677
|
function requireHead(head) {
|
|
21275
|
-
if (!isObject$
|
|
21678
|
+
if (!isObject$4(head) || Array.isArray(head)) throw new SeiMcpError("head is required", "INVALID_PARAMS");
|
|
21276
21679
|
const result = {};
|
|
21277
21680
|
for (const [key, value] of Object.entries(head)) {
|
|
21278
21681
|
const trimmed = toTrimmedString$1(value);
|
|
@@ -21312,7 +21715,7 @@ function isTargetAllowed(config, tableName) {
|
|
|
21312
21715
|
];
|
|
21313
21716
|
for (const pool of pools) {
|
|
21314
21717
|
if (pool[normalized]) return true;
|
|
21315
|
-
for (const target of Object.values(pool)) if (isObject$
|
|
21718
|
+
for (const target of Object.values(pool)) if (isObject$4(target)) {
|
|
21316
21719
|
const mainTable = toTrimmedString$1(target.mainTable);
|
|
21317
21720
|
if (mainTable && mainTable === normalized) return true;
|
|
21318
21721
|
}
|
|
@@ -21883,6 +22286,7 @@ function toTrimmedString(value) {
|
|
|
21883
22286
|
}
|
|
21884
22287
|
//#endregion
|
|
21885
22288
|
//#region src/index.ts
|
|
22289
|
+
await loadWorkingDirEnv();
|
|
21886
22290
|
var logger = createLogger(process.env.SEI_MCP_LOG_LEVEL ?? "info");
|
|
21887
22291
|
var argv = process.argv.slice(2);
|
|
21888
22292
|
var runtime = parseRuntimeOptions(argv, process.env);
|
|
@@ -21890,23 +22294,31 @@ if (runtime.command === "help") {
|
|
|
21890
22294
|
process.stdout.write(`${renderHelpText()}\n`);
|
|
21891
22295
|
process.exit(0);
|
|
21892
22296
|
}
|
|
21893
|
-
|
|
21894
|
-
|
|
21895
|
-
|
|
22297
|
+
try {
|
|
22298
|
+
const config = await loadConfig(argv, process.env);
|
|
22299
|
+
if (runtime.command === "cli") {
|
|
22300
|
+
await runCli(runtime.cliArgs, {
|
|
22301
|
+
config,
|
|
22302
|
+
logger
|
|
22303
|
+
});
|
|
22304
|
+
process.exit(0);
|
|
22305
|
+
}
|
|
22306
|
+
const client = createSeiClient(config, logger);
|
|
22307
|
+
if (runtime.transport === "streamable-http") await startHttpServer(() => createMcpServer({
|
|
21896
22308
|
config,
|
|
22309
|
+
client,
|
|
21897
22310
|
logger
|
|
21898
|
-
});
|
|
21899
|
-
|
|
22311
|
+
}), runtime, logger);
|
|
22312
|
+
else await startStdioServer(createMcpServer({
|
|
22313
|
+
config,
|
|
22314
|
+
client,
|
|
22315
|
+
logger
|
|
22316
|
+
}));
|
|
22317
|
+
} catch (error) {
|
|
22318
|
+
if (!isSeiMcpError(error) || runtime.command !== "cli") {
|
|
22319
|
+
logger.error("sei-ai startup failed", toErrorLogMeta(error));
|
|
22320
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
22321
|
+
}
|
|
22322
|
+
process.exit(1);
|
|
21900
22323
|
}
|
|
21901
|
-
var client = createSeiClient(config, logger);
|
|
21902
|
-
if (runtime.transport === "streamable-http") await startHttpServer(() => createMcpServer({
|
|
21903
|
-
config,
|
|
21904
|
-
client,
|
|
21905
|
-
logger
|
|
21906
|
-
}), runtime, logger);
|
|
21907
|
-
else await startStdioServer(createMcpServer({
|
|
21908
|
-
config,
|
|
21909
|
-
client,
|
|
21910
|
-
logger
|
|
21911
|
-
}));
|
|
21912
22324
|
//#endregion
|