agent-database-cli 0.2.7

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.
Files changed (73) hide show
  1. package/AI_INSTALL.md +72 -0
  2. package/LICENSE +21 -0
  3. package/README.md +340 -0
  4. package/README_EN.md +341 -0
  5. package/SKILL.md +252 -0
  6. package/config/docker-test.json +40 -0
  7. package/dist/adapters/base-sql.d.ts +12 -0
  8. package/dist/adapters/base-sql.js +22 -0
  9. package/dist/adapters/base-sql.js.map +1 -0
  10. package/dist/adapters/factory.d.ts +2 -0
  11. package/dist/adapters/factory.js +26 -0
  12. package/dist/adapters/factory.js.map +1 -0
  13. package/dist/adapters/mongodb.d.ts +14 -0
  14. package/dist/adapters/mongodb.js +137 -0
  15. package/dist/adapters/mongodb.js.map +1 -0
  16. package/dist/adapters/mysql.d.ts +12 -0
  17. package/dist/adapters/mysql.js +38 -0
  18. package/dist/adapters/mysql.js.map +1 -0
  19. package/dist/adapters/oracle-sqlcl.d.ts +19 -0
  20. package/dist/adapters/oracle-sqlcl.js +334 -0
  21. package/dist/adapters/oracle-sqlcl.js.map +1 -0
  22. package/dist/adapters/oracle.d.ts +13 -0
  23. package/dist/adapters/oracle.js +47 -0
  24. package/dist/adapters/oracle.js.map +1 -0
  25. package/dist/adapters/postgres.d.ts +12 -0
  26. package/dist/adapters/postgres.js +40 -0
  27. package/dist/adapters/postgres.js.map +1 -0
  28. package/dist/adapters/redis.d.ts +15 -0
  29. package/dist/adapters/redis.js +91 -0
  30. package/dist/adapters/redis.js.map +1 -0
  31. package/dist/cli.d.ts +2 -0
  32. package/dist/cli.js +118 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/config.d.ts +8 -0
  35. package/dist/config.js +147 -0
  36. package/dist/config.js.map +1 -0
  37. package/dist/connection-manager.d.ts +24 -0
  38. package/dist/connection-manager.js +91 -0
  39. package/dist/connection-manager.js.map +1 -0
  40. package/dist/daemon/client.d.ts +3 -0
  41. package/dist/daemon/client.js +33 -0
  42. package/dist/daemon/client.js.map +1 -0
  43. package/dist/daemon/config-manager.d.ts +15 -0
  44. package/dist/daemon/config-manager.js +44 -0
  45. package/dist/daemon/config-manager.js.map +1 -0
  46. package/dist/daemon/control.d.ts +9 -0
  47. package/dist/daemon/control.js +53 -0
  48. package/dist/daemon/control.js.map +1 -0
  49. package/dist/daemon/paths.d.ts +5 -0
  50. package/dist/daemon/paths.js +18 -0
  51. package/dist/daemon/paths.js.map +1 -0
  52. package/dist/daemon/server.d.ts +1 -0
  53. package/dist/daemon/server.js +119 -0
  54. package/dist/daemon/server.js.map +1 -0
  55. package/dist/output.d.ts +2 -0
  56. package/dist/output.js +39 -0
  57. package/dist/output.js.map +1 -0
  58. package/dist/runtime.d.ts +5 -0
  59. package/dist/runtime.js +25 -0
  60. package/dist/runtime.js.map +1 -0
  61. package/dist/security.d.ts +8 -0
  62. package/dist/security.js +239 -0
  63. package/dist/security.js.map +1 -0
  64. package/dist/ssh-tunnel.d.ts +8 -0
  65. package/dist/ssh-tunnel.js +231 -0
  66. package/dist/ssh-tunnel.js.map +1 -0
  67. package/dist/types.d.ts +69 -0
  68. package/dist/types.js +2 -0
  69. package/dist/types.js.map +1 -0
  70. package/dist/utils/masking.d.ts +2 -0
  71. package/dist/utils/masking.js +14 -0
  72. package/dist/utils/masking.js.map +1 -0
  73. package/package.json +50 -0
@@ -0,0 +1,239 @@
1
+ const SQL_READ_COMMANDS = new Set(["select", "show", "describe", "desc", "explain", "with"]);
2
+ const SQL_WRITE_COMMANDS = [
3
+ "insert",
4
+ "update",
5
+ "delete",
6
+ "merge",
7
+ "replace",
8
+ "drop",
9
+ "truncate",
10
+ "alter",
11
+ "create",
12
+ "grant",
13
+ "revoke"
14
+ ];
15
+ const REDIS_READ_COMMANDS = new Set([
16
+ "get",
17
+ "mget",
18
+ "exists",
19
+ "ttl",
20
+ "pttl",
21
+ "type",
22
+ "strlen",
23
+ "keys",
24
+ "scan",
25
+ "hget",
26
+ "hgetall",
27
+ "hmget",
28
+ "hexists",
29
+ "hlen",
30
+ "hkeys",
31
+ "hvals",
32
+ "lrange",
33
+ "llen",
34
+ "lindex",
35
+ "smembers",
36
+ "scard",
37
+ "sismember",
38
+ "zrange",
39
+ "zrevrange",
40
+ "zcard",
41
+ "zscore"
42
+ ]);
43
+ const MONGO_READ_COMMANDS = new Set([
44
+ "find",
45
+ "findOne",
46
+ "aggregate",
47
+ "count",
48
+ "countDocuments",
49
+ "estimatedDocumentCount",
50
+ "distinct"
51
+ ]);
52
+ export class SecurityError extends Error {
53
+ constructor(message) {
54
+ super(message);
55
+ this.name = "SecurityError";
56
+ }
57
+ }
58
+ export function normalizeCommand(command) {
59
+ return command.trim().replace(/\s+/g, " ");
60
+ }
61
+ export function getCommandHead(command, type) {
62
+ const normalized = normalizeCommand(command);
63
+ if (type === "mongodb") {
64
+ return getMongoCommandName(normalized).toLowerCase();
65
+ }
66
+ return normalized.split(/\s+/)[0]?.replace(/;$/, "").toLowerCase() || "";
67
+ }
68
+ export function assertCommandAllowed(config, command) {
69
+ const normalized = normalizeCommand(command);
70
+ const head = getCommandHead(normalized, config.type);
71
+ assertNotBlacklisted(config, normalized, head);
72
+ // 默认开启只读,只有显式配置 readonly: false 才允许写操作。
73
+ if (isReadonlyEnabled(config) && !isReadOnlyCommand(config.type, normalized)) {
74
+ throw new SecurityError(`只读模式拒绝执行命令: ${head || normalized}`);
75
+ }
76
+ }
77
+ function assertNotBlacklisted(config, normalized, head) {
78
+ const commandForBlacklist = isSqlDatabase(config.type) ? stripSqlLiteralsAndComments(normalized) : normalized;
79
+ for (const item of config.blacklist || []) {
80
+ const black = normalizeCommand(item).toLowerCase();
81
+ if (!black) {
82
+ continue;
83
+ }
84
+ if (head === black || hasBlacklistedKeyword(commandForBlacklist, black)) {
85
+ throw new SecurityError(`黑名单拒绝执行命令: ${item}`);
86
+ }
87
+ }
88
+ }
89
+ function hasBlacklistedKeyword(command, keyword) {
90
+ const normalized = command.toLowerCase();
91
+ const escaped = escapeRegExp(keyword).replace(/\s+/g, "\\s+");
92
+ // 黑名单按完整命令关键字匹配,避免 FCREATETIME 这类字段名误命中 create。
93
+ return new RegExp(`(?<![\\p{L}\\p{N}_$])${escaped}(?![\\p{L}\\p{N}_$])`, "u").test(normalized);
94
+ }
95
+ function escapeRegExp(value) {
96
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
97
+ }
98
+ function stripSqlLiteralsAndComments(command) {
99
+ let result = "";
100
+ let index = 0;
101
+ while (index < command.length) {
102
+ const char = command[index];
103
+ const next = command[index + 1];
104
+ if ((char === "q" || char === "Q") && next === "'") {
105
+ const endIndex = findOracleQuotedLiteralEnd(command, index);
106
+ if (endIndex !== -1) {
107
+ result += " ".repeat(endIndex - index);
108
+ index = endIndex;
109
+ continue;
110
+ }
111
+ }
112
+ if (char === "-" && next === "-") {
113
+ const endIndex = findLineEnd(command, index + 2);
114
+ result += " ".repeat(endIndex - index);
115
+ index = endIndex;
116
+ continue;
117
+ }
118
+ if (char === "#") {
119
+ const endIndex = findLineEnd(command, index + 1);
120
+ result += " ".repeat(endIndex - index);
121
+ index = endIndex;
122
+ continue;
123
+ }
124
+ if (char === "/" && next === "*") {
125
+ const endIndex = findBlockCommentEnd(command, index + 2);
126
+ result += " ".repeat(endIndex - index);
127
+ index = endIndex;
128
+ continue;
129
+ }
130
+ if (char === "'") {
131
+ const endIndex = findQuotedTokenEnd(command, index, "'", "'");
132
+ result += " ".repeat(endIndex - index);
133
+ index = endIndex;
134
+ continue;
135
+ }
136
+ if (char === '"') {
137
+ const endIndex = findQuotedTokenEnd(command, index, '"', '"');
138
+ result += " ".repeat(endIndex - index);
139
+ index = endIndex;
140
+ continue;
141
+ }
142
+ if (char === "`") {
143
+ const endIndex = findQuotedTokenEnd(command, index, "`", "`");
144
+ result += " ".repeat(endIndex - index);
145
+ index = endIndex;
146
+ continue;
147
+ }
148
+ if (char === "[") {
149
+ const endIndex = findQuotedTokenEnd(command, index, "[", "]");
150
+ result += " ".repeat(endIndex - index);
151
+ index = endIndex;
152
+ continue;
153
+ }
154
+ result += char;
155
+ index += 1;
156
+ }
157
+ return result;
158
+ }
159
+ function findLineEnd(command, start) {
160
+ const lineEnd = command.indexOf("\n", start);
161
+ return lineEnd === -1 ? command.length : lineEnd;
162
+ }
163
+ function findBlockCommentEnd(command, start) {
164
+ const commentEnd = command.indexOf("*/", start);
165
+ return commentEnd === -1 ? command.length : commentEnd + 2;
166
+ }
167
+ function findQuotedTokenEnd(command, start, open, close) {
168
+ let index = start + open.length;
169
+ while (index < command.length) {
170
+ if (command[index] === close) {
171
+ if (command[index + close.length] === close) {
172
+ index += close.length * 2;
173
+ continue;
174
+ }
175
+ return index + close.length;
176
+ }
177
+ if (command[index] === "\\" && close !== "]") {
178
+ index += 2;
179
+ continue;
180
+ }
181
+ index += 1;
182
+ }
183
+ return command.length;
184
+ }
185
+ function findOracleQuotedLiteralEnd(command, start) {
186
+ const open = command[start + 2];
187
+ if (!open) {
188
+ return -1;
189
+ }
190
+ const close = getOracleQuotedLiteralClose(open);
191
+ const closeSequence = `${close}'`;
192
+ const contentStart = start + 3;
193
+ const closeIndex = command.indexOf(closeSequence, contentStart);
194
+ return closeIndex === -1 ? -1 : closeIndex + closeSequence.length;
195
+ }
196
+ function getOracleQuotedLiteralClose(open) {
197
+ if (open === "[") {
198
+ return "]";
199
+ }
200
+ if (open === "(") {
201
+ return ")";
202
+ }
203
+ if (open === "{") {
204
+ return "}";
205
+ }
206
+ if (open === "<") {
207
+ return ">";
208
+ }
209
+ return open;
210
+ }
211
+ function isSqlDatabase(type) {
212
+ return type === "mysql" || type === "postgres" || type === "oracle";
213
+ }
214
+ export function isReadOnlyCommand(type, command) {
215
+ const head = getCommandHead(command, type);
216
+ if (type === "redis") {
217
+ return REDIS_READ_COMMANDS.has(head);
218
+ }
219
+ if (type === "mongodb") {
220
+ return MONGO_READ_COMMANDS.has(getMongoCommandName(command));
221
+ }
222
+ if (!SQL_READ_COMMANDS.has(head)) {
223
+ return false;
224
+ }
225
+ const sanitizedCommand = stripSqlLiteralsAndComments(command);
226
+ return !SQL_WRITE_COMMANDS.some((keyword) => hasBlacklistedKeyword(sanitizedCommand, keyword));
227
+ }
228
+ function isReadonlyEnabled(config) {
229
+ return config.readonly !== false;
230
+ }
231
+ function getMongoCommandName(command) {
232
+ const parsed = JSON.parse(command);
233
+ const firstKey = Object.keys(parsed)[0];
234
+ if (!firstKey) {
235
+ throw new SecurityError("MongoDB 命令 JSON 不能为空");
236
+ }
237
+ return firstKey;
238
+ }
239
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { DatabaseConfig, DatabaseType, RedisClusterConnectionConfig } from "./types.js";
2
+ export interface StartedSshTunnel {
3
+ url: string;
4
+ redisCluster?: RedisClusterConnectionConfig;
5
+ close(): Promise<void>;
6
+ }
7
+ export declare function startSshTunnel(config: DatabaseConfig): Promise<StartedSshTunnel | undefined>;
8
+ export declare function rewriteDatabaseUrl(type: DatabaseType, url: string, host: string, port: number): string;
@@ -0,0 +1,231 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { isAbsolute, join } from "node:path";
4
+ import net from "node:net";
5
+ import { Client } from "ssh2";
6
+ const DEFAULT_PORTS = {
7
+ mysql: 3306,
8
+ postgres: 5432,
9
+ redis: 6379,
10
+ oracle: 1521,
11
+ mongodb: 27017
12
+ };
13
+ export async function startSshTunnel(config) {
14
+ if (!config.sshTunnel) {
15
+ return undefined;
16
+ }
17
+ if (config.type === "redis" && config.redisCluster) {
18
+ return startRedisClusterSshTunnel(config);
19
+ }
20
+ const endpoint = parseDatabaseEndpoint(config.type, config.url);
21
+ const ssh = new Client();
22
+ const server = net.createServer((socket) => {
23
+ ssh.forwardOut(socket.localAddress || "127.0.0.1", socket.localPort || 0, endpoint.host, endpoint.port, (error, stream) => {
24
+ if (error) {
25
+ socket.destroy(error);
26
+ return;
27
+ }
28
+ socket.pipe(stream).pipe(socket);
29
+ });
30
+ });
31
+ try {
32
+ await connectSshClient(ssh, config.sshTunnel);
33
+ const localPort = await listenLocal(server);
34
+ const url = rewriteDatabaseUrl(config.type, config.url, "127.0.0.1", localPort);
35
+ return {
36
+ url,
37
+ async close() {
38
+ await closeServer(server);
39
+ ssh.end();
40
+ }
41
+ };
42
+ }
43
+ catch (error) {
44
+ await closeServer(server);
45
+ ssh.end();
46
+ throw error;
47
+ }
48
+ }
49
+ async function startRedisClusterSshTunnel(config) {
50
+ const ssh = new Client();
51
+ const servers = [];
52
+ try {
53
+ await connectSshClient(ssh, config.sshTunnel);
54
+ const nodeAddressMap = {};
55
+ const localNodes = [];
56
+ for (const nodeUrl of config.redisCluster.nodes) {
57
+ const endpoint = parseRedisClusterNode(nodeUrl);
58
+ const server = createForwardServer(ssh, endpoint.host, endpoint.port);
59
+ servers.push(server);
60
+ const localPort = await listenLocal(server);
61
+ localNodes.push(rewriteDatabaseUrl("redis", nodeUrl, "127.0.0.1", localPort));
62
+ nodeAddressMap[`${endpoint.host}:${endpoint.port}`] = { host: "127.0.0.1", port: localPort };
63
+ }
64
+ return {
65
+ url: rewriteDatabaseUrl("redis", config.url, "127.0.0.1", extractPort(localNodes[0])),
66
+ redisCluster: {
67
+ nodes: localNodes,
68
+ nodeAddressMap
69
+ },
70
+ async close() {
71
+ await Promise.all(servers.map((server) => closeServer(server)));
72
+ ssh.end();
73
+ }
74
+ };
75
+ }
76
+ catch (error) {
77
+ await Promise.all(servers.map((server) => closeServer(server).catch(() => undefined)));
78
+ ssh.end();
79
+ throw error;
80
+ }
81
+ }
82
+ function createForwardServer(ssh, host, port) {
83
+ return net.createServer((socket) => {
84
+ ssh.forwardOut(socket.localAddress || "127.0.0.1", socket.localPort || 0, host, port, (error, stream) => {
85
+ if (error) {
86
+ socket.destroy(error);
87
+ return;
88
+ }
89
+ socket.pipe(stream).pipe(socket);
90
+ });
91
+ });
92
+ }
93
+ export function rewriteDatabaseUrl(type, url, host, port) {
94
+ if (type === "mongodb") {
95
+ return rewriteMongoUrl(url, host, port);
96
+ }
97
+ const parsed = new URL(url);
98
+ parsed.hostname = host;
99
+ parsed.port = String(port);
100
+ return parsed.toString();
101
+ }
102
+ function rewriteMongoUrl(url, host, port) {
103
+ if (isMongoMultiHostUrl(url)) {
104
+ throw new Error("SSH 隧道暂不支持 MongoDB 多 host URL");
105
+ }
106
+ const parsed = new URL(url);
107
+ parsed.hostname = host;
108
+ parsed.port = String(port);
109
+ return parsed.toString();
110
+ }
111
+ function parseDatabaseEndpoint(type, url) {
112
+ if (type === "mongodb" && isMongoMultiHostUrl(url)) {
113
+ throw new Error("SSH 隧道暂不支持 MongoDB 多 host URL");
114
+ }
115
+ const parsed = new URL(url);
116
+ const host = parsed.hostname;
117
+ if (!host) {
118
+ throw new Error("数据库 URL 必须包含 host 才能建立 SSH 隧道");
119
+ }
120
+ return {
121
+ host,
122
+ port: parsed.port ? Number(parsed.port) : DEFAULT_PORTS[type]
123
+ };
124
+ }
125
+ function parseRedisClusterNode(url) {
126
+ const parsed = new URL(url);
127
+ if (!parsed.hostname) {
128
+ throw new Error("Redis Cluster 节点 URL 必须包含 host");
129
+ }
130
+ return {
131
+ host: parsed.hostname,
132
+ port: parsed.port ? Number(parsed.port) : DEFAULT_PORTS.redis
133
+ };
134
+ }
135
+ function extractPort(url) {
136
+ const parsed = new URL(url);
137
+ return parsed.port ? Number(parsed.port) : DEFAULT_PORTS.redis;
138
+ }
139
+ async function connectSshClient(client, tunnel) {
140
+ const connectConfig = await buildConnectConfig(tunnel);
141
+ await new Promise((resolve, reject) => {
142
+ const onReady = () => {
143
+ cleanup();
144
+ resolve();
145
+ };
146
+ const onError = (error) => {
147
+ cleanup();
148
+ reject(error);
149
+ };
150
+ const cleanup = () => {
151
+ client.off("ready", onReady);
152
+ client.off("error", onError);
153
+ };
154
+ client.once("ready", onReady);
155
+ client.once("error", onError);
156
+ client.connect(connectConfig);
157
+ });
158
+ }
159
+ async function buildConnectConfig(tunnel) {
160
+ const connectConfig = {
161
+ host: tunnel.host,
162
+ port: tunnel.port ?? 22,
163
+ username: tunnel.username,
164
+ readyTimeout: tunnel.readyTimeout
165
+ };
166
+ if (tunnel.password) {
167
+ connectConfig.password = tunnel.password;
168
+ }
169
+ if (tunnel.privateKeyPath) {
170
+ connectConfig.privateKey = await readFile(resolveHomePath(tunnel.privateKeyPath), "utf8");
171
+ }
172
+ else if (tunnel.privateKey) {
173
+ connectConfig.privateKey = tunnel.privateKey;
174
+ }
175
+ if (tunnel.passphrase) {
176
+ connectConfig.passphrase = tunnel.passphrase;
177
+ }
178
+ return connectConfig;
179
+ }
180
+ function listenLocal(server) {
181
+ return new Promise((resolve, reject) => {
182
+ const onError = (error) => {
183
+ server.off("listening", onListening);
184
+ reject(error);
185
+ };
186
+ const onListening = () => {
187
+ server.off("error", onError);
188
+ const address = server.address();
189
+ if (!address || typeof address === "string") {
190
+ reject(new Error("本地 SSH 隧道端口监听失败"));
191
+ return;
192
+ }
193
+ resolve(address.port);
194
+ };
195
+ server.once("error", onError);
196
+ server.once("listening", onListening);
197
+ server.listen(0, "127.0.0.1");
198
+ });
199
+ }
200
+ function closeServer(server) {
201
+ if (!server.listening) {
202
+ return Promise.resolve();
203
+ }
204
+ return new Promise((resolve, reject) => {
205
+ server.close((error) => {
206
+ if (error) {
207
+ reject(error);
208
+ return;
209
+ }
210
+ resolve();
211
+ });
212
+ });
213
+ }
214
+ function resolveHomePath(path) {
215
+ if (path === "~") {
216
+ return homedir();
217
+ }
218
+ if (path.startsWith("~/")) {
219
+ return join(homedir(), path.slice(2));
220
+ }
221
+ return isAbsolute(path) ? path : path;
222
+ }
223
+ function isMongoMultiHostUrl(url) {
224
+ if (!url.startsWith("mongodb://")) {
225
+ return false;
226
+ }
227
+ const authority = url.slice("mongodb://".length).split(/[/?#]/, 1)[0] ?? "";
228
+ const hosts = authority.includes("@") ? authority.slice(authority.lastIndexOf("@") + 1) : authority;
229
+ return hosts.includes(",");
230
+ }
231
+ //# sourceMappingURL=ssh-tunnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-tunnel.js","sourceRoot":"","sources":["../src/ssh-tunnel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ export type DatabaseType = "mysql" | "postgres" | "redis" | "oracle" | "mongodb";
2
+ export type OutputFormat = "json" | "table";
3
+ export interface DatabaseConfig {
4
+ type: DatabaseType;
5
+ url: string;
6
+ redisCluster?: RedisClusterConfig;
7
+ sshTunnel?: SshTunnelConfig;
8
+ database?: string;
9
+ oracleDriver?: "oracledb" | "sqlcl";
10
+ sqlclPath?: string;
11
+ javaHome?: string;
12
+ readonly?: boolean;
13
+ blacklist?: string[];
14
+ keepAliveSeconds?: number;
15
+ }
16
+ export interface RedisClusterConfig {
17
+ nodes: string[];
18
+ }
19
+ export interface RedisClusterConnectionConfig {
20
+ nodes: string[];
21
+ nodeAddressMap?: Record<string, RedisNodeAddress>;
22
+ }
23
+ export interface RedisNodeAddress {
24
+ host: string;
25
+ port: number;
26
+ }
27
+ export interface SshTunnelConfig {
28
+ host: string;
29
+ port?: number;
30
+ username: string;
31
+ password?: string;
32
+ privateKeyPath?: string;
33
+ privateKey?: string;
34
+ passphrase?: string;
35
+ readyTimeout?: number;
36
+ }
37
+ export interface AppConfig {
38
+ databases: Record<string, DatabaseConfig>;
39
+ }
40
+ export interface MetadataRequest {
41
+ type: "tables" | "columns" | "collections" | "keys";
42
+ table?: string;
43
+ pattern?: string;
44
+ }
45
+ export interface QueryResult {
46
+ rows: unknown[];
47
+ fields?: string[];
48
+ rowCount?: number;
49
+ }
50
+ export interface DatabaseAdapter {
51
+ connect(): Promise<void>;
52
+ disconnect(): Promise<void>;
53
+ test(): Promise<void>;
54
+ execute(command: string): Promise<QueryResult>;
55
+ metadata(request: MetadataRequest): Promise<QueryResult>;
56
+ }
57
+ export type DaemonAction = "test" | "execute" | "metadata" | "reset" | "status" | "stop";
58
+ export interface DaemonRequest {
59
+ action: DaemonAction;
60
+ db?: string;
61
+ command?: string;
62
+ metadata?: MetadataRequest;
63
+ configPath?: string;
64
+ }
65
+ export interface DaemonResponse {
66
+ ok: boolean;
67
+ data?: unknown;
68
+ error?: string;
69
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export declare function maskSecret(input: string): string;
2
+ export declare function toErrorMessage(error: unknown): string;
@@ -0,0 +1,14 @@
1
+ export function maskSecret(input) {
2
+ return input
3
+ .replace(/(password=)([^&\s]+)/gi, "$1****")
4
+ .replace(/(token=)([^&\s]+)/gi, "$1****")
5
+ .replace(/(secret=)([^&\s]+)/gi, "$1****")
6
+ .replace(/:\/\/([^:\s/@]+):([^@\s]+)@/g, "://$1:****@");
7
+ }
8
+ export function toErrorMessage(error) {
9
+ if (error instanceof Error) {
10
+ return maskSecret(error.message);
11
+ }
12
+ return maskSecret(String(error));
13
+ }
14
+ //# sourceMappingURL=masking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"masking.js","sourceRoot":"","sources":["../../src/utils/masking.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK;SACT,OAAO,CAAC,wBAAwB,EAAE,QAAQ,CAAC;SAC3C,OAAO,CAAC,qBAAqB,EAAE,QAAQ,CAAC;SACxC,OAAO,CAAC,sBAAsB,EAAE,QAAQ,CAAC;SACzC,OAAO,CAAC,8BAA8B,EAAE,aAAa,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACnC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "agent-database-cli",
3
+ "version": "0.2.7",
4
+ "description": "统一数据库命令行工具,支持只读、黑名单和本地连接守护进程。",
5
+ "type": "module",
6
+ "bin": {
7
+ "agent-database-cli": "dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc -p tsconfig.json",
11
+ "dev": "tsx src/cli.ts",
12
+ "prepack": "npm run build",
13
+ "test": "vitest run",
14
+ "test:unit": "vitest run tests/unit",
15
+ "test:integration": "AGENT_DATABASE_CLI_CONFIG=config/docker-test.json vitest run tests/integration --exclude tests/integration/oracle.test.ts",
16
+ "test:integration:oracle": "AGENT_DATABASE_CLI_CONFIG=config/docker-test.json vitest run tests/integration/oracle.test.ts"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "README_EN.md",
22
+ "AI_INSTALL.md",
23
+ "SKILL.md",
24
+ "LICENSE",
25
+ "config/docker-test.json"
26
+ ],
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "dependencies": {
31
+ "commander": "^12.1.0",
32
+ "mongodb": "^6.20.0",
33
+ "mysql2": "^3.15.3",
34
+ "oracledb": "^6.10.0",
35
+ "pg": "^8.16.3",
36
+ "redis": "^4.7.1",
37
+ "ssh2": "^1.17.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.10.2",
41
+ "@types/pg": "^8.15.5",
42
+ "@types/ssh2": "^1.15.5",
43
+ "tsx": "^4.20.6",
44
+ "typescript": "^5.9.3",
45
+ "vitest": "^2.1.9"
46
+ },
47
+ "engines": {
48
+ "node": ">=20"
49
+ }
50
+ }