@dbx-app/cli 0.4.34 → 0.4.36

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 CHANGED
@@ -17,7 +17,31 @@ brew tap t8y2/dbx
17
17
  brew install dbx-cli
18
18
  ```
19
19
 
20
- Requires Node.js 22.13.0 or newer.
20
+ The npm package installs the native CLI for the current platform automatically. Node.js 18.18.0 or newer is only needed for the npm launcher; direct native distributions do not require Node.js.
21
+
22
+ ### Native downloads
23
+
24
+ The `packages-v*` GitHub Release also provides standalone native CLI archives:
25
+
26
+ | Platform | Archive |
27
+ | --- | --- |
28
+ | macOS Apple Silicon | `dbx-cli-darwin-arm64.tar.gz` |
29
+ | macOS Intel | `dbx-cli-darwin-x64.tar.gz` |
30
+ | Linux glibc ARM64 | `dbx-cli-linux-arm64-gnu.tar.gz` |
31
+ | Linux glibc x64 | `dbx-cli-linux-x64-gnu.tar.gz` |
32
+ | Windows ARM64 | `dbx-cli-win32-arm64.zip` |
33
+ | Windows x64 | `dbx-cli-win32-x64.zip` |
34
+
35
+ Verify the downloaded archive with `CLI-SHA256SUMS`, extract it, and run the native binary directly:
36
+
37
+ ```bash
38
+ tar -xzf dbx-cli-linux-x64-gnu.tar.gz
39
+ chmod +x dbx
40
+ ./dbx --version
41
+ ./dbx connections list --json
42
+ ```
43
+
44
+ Standalone binaries do not require Node.js. They read the same DBX connection storage as the desktop application; set `DBX_DATA_DIR` when using a custom or portable data directory.
21
45
 
22
46
  ## Usage
23
47
 
@@ -90,23 +114,19 @@ Some CLI commands can run without DBX Desktop:
90
114
  - `query`
91
115
  - `context`
92
116
 
93
- Direct execution currently supports PostgreSQL/Redshift, MySQL-compatible databases (MySQL, Doris, StarRocks), and SQLite. Other database types use the DBX Desktop bridge until their drivers are added to `@dbx-app/node-core`.
117
+ Direct execution supports PostgreSQL/Redshift, MySQL-compatible databases (MySQL, Doris, StarRocks), and SQLite. Other database types use the DBX Desktop bridge or DBX Agent/JDBC infrastructure.
94
118
 
95
119
  Use `dbx doctor` to check whether the DBX connection database, connection table, native SQLite loader, and desktop bridge are available. Use `dbx capabilities` to list direct-query and bridge-required database types.
96
120
 
97
- If `dbx doctor` reports a `NODE_MODULE_VERSION` mismatch after switching Node.js versions, rebuild the native dependencies with the Node.js version you use to run `dbx`:
98
-
99
- ```bash
100
- pnpm rebuild better-sqlite3 keytar --pending
101
- ```
102
-
103
- For global npm installs, reinstall the CLI with the same Node.js version:
121
+ If the optional platform package was not installed, reinstall without `--no-optional`:
104
122
 
105
123
  ```bash
106
124
  npm uninstall -g @dbx-app/cli
107
125
  npm install -g @dbx-app/cli
108
126
  ```
109
127
 
128
+ The native CLI does not require `better-sqlite3` and is not coupled to the Node.js ABI.
129
+
110
130
  ## Error Codes
111
131
 
112
132
  CLI JSON errors use stable codes:
package/bin/dbx.js ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from "node:fs";
4
+ import { spawnSync } from "node:child_process";
5
+ import { createRequire } from "node:module";
6
+ import { dirname, join } from "node:path";
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const platformPackages = {
10
+ "darwin-arm64": "@dbx-app/cli-darwin-arm64",
11
+ "darwin-x64": "@dbx-app/cli-darwin-x64",
12
+ "linux-arm64": "@dbx-app/cli-linux-arm64-gnu",
13
+ "linux-x64": "@dbx-app/cli-linux-x64-gnu",
14
+ "win32-arm64": "@dbx-app/cli-win32-arm64",
15
+ "win32-x64": "@dbx-app/cli-win32-x64",
16
+ };
17
+
18
+ const platformKey = `${process.platform}-${process.arch}`;
19
+ const packageName = platformPackages[platformKey];
20
+ if (!packageName) {
21
+ console.error(`Unsupported platform: ${platformKey}`);
22
+ process.exit(1);
23
+ }
24
+
25
+ let binary;
26
+ try {
27
+ const packageJson = require.resolve(`${packageName}/package.json`);
28
+ binary = join(dirname(packageJson), "bin", process.platform === "win32" ? "dbx.exe" : "dbx");
29
+ } catch {
30
+ console.error(`The optional package ${packageName} was not installed. Reinstall @dbx-app/cli without --no-optional.`);
31
+ process.exit(1);
32
+ }
33
+
34
+ if (!existsSync(binary)) {
35
+ console.error(`DBX CLI binary was not found at ${binary}`);
36
+ process.exit(1);
37
+ }
38
+
39
+ const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit", env: process.env });
40
+ if (result.error) {
41
+ console.error(result.error.message);
42
+ process.exit(1);
43
+ }
44
+ process.exit(result.status ?? 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbx-app/cli",
3
- "version": "0.4.34",
3
+ "version": "0.4.36",
4
4
  "description": "Command line interface for DBX database connections, schema, and safe queries",
5
5
  "keywords": [
6
6
  "ai-agent",
@@ -21,26 +21,26 @@
21
21
  "directory": "packages/cli"
22
22
  },
23
23
  "bin": {
24
- "dbx": "dist/cli.js"
24
+ "dbx": "bin/dbx.js"
25
25
  },
26
26
  "files": [
27
- "dist"
27
+ "bin"
28
28
  ],
29
29
  "type": "module",
30
- "dependencies": {
31
- "@dbx-app/node-core": "^0.4.34"
32
- },
33
- "devDependencies": {
34
- "@types/node": "^22.15.21",
35
- "tsx": "^4.19.4",
36
- "typescript": "^5.8.3"
30
+ "optionalDependencies": {
31
+ "@dbx-app/cli-darwin-arm64": "0.4.36",
32
+ "@dbx-app/cli-darwin-x64": "0.4.36",
33
+ "@dbx-app/cli-linux-arm64-gnu": "0.4.36",
34
+ "@dbx-app/cli-linux-x64-gnu": "0.4.36",
35
+ "@dbx-app/cli-win32-arm64": "0.4.36",
36
+ "@dbx-app/cli-win32-x64": "0.4.36"
37
37
  },
38
38
  "engines": {
39
- "node": ">=22.13.0"
39
+ "node": ">=18.18.0"
40
40
  },
41
41
  "scripts": {
42
- "start": "tsx src/cli.ts",
43
- "test": "pnpm --filter @dbx-app/node-core build && vitest run --config vitest.config.ts",
44
- "build": "pnpm --filter @dbx-app/node-core build && tsc"
42
+ "start": "cargo run -p dbx-cli --no-default-features --",
43
+ "test": "cargo test -p dbx-cli --no-default-features",
44
+ "build": "cargo build -p dbx-cli --release --no-default-features"
45
45
  }
46
46
  }
@@ -1,20 +0,0 @@
1
- import { formatCell, mdTable, type ConnectionConfig } from "@dbx-app/node-core";
2
- export { formatCell, mdTable };
3
- export interface ConnectionSummary {
4
- name: string;
5
- type: string;
6
- host: string;
7
- port: number;
8
- database?: string;
9
- }
10
- export declare function connectionSummary(connection: ConnectionConfig): ConnectionSummary;
11
- export interface ErrorPayload {
12
- error: {
13
- code: string;
14
- message: string;
15
- hint?: string;
16
- };
17
- }
18
- export declare function errorPayload(code: string, message: string): ErrorPayload;
19
- export declare function formatErrorMessage(code: string, message: string): string;
20
- export declare function csvTable<T extends object>(headers: string[], rows: T[]): string;
@@ -1,41 +0,0 @@
1
- import { formatCell, mdTable } from "@dbx-app/node-core";
2
- export { formatCell, mdTable };
3
- export function connectionSummary(connection) {
4
- return {
5
- name: connection.name,
6
- type: connection.db_type,
7
- host: connection.host,
8
- port: connection.port,
9
- database: connection.database || undefined,
10
- };
11
- }
12
- export function errorPayload(code, message) {
13
- const hint = errorHint(code, message);
14
- return { error: hint ? { code, message, hint } : { code, message } };
15
- }
16
- export function formatErrorMessage(code, message) {
17
- const hint = errorHint(code, message);
18
- return hint ? `${message}\n\nHint: ${hint}` : message;
19
- }
20
- function errorHint(code, message) {
21
- if (code === "CONNECTION_STORE_ERROR" && /NODE_MODULE_VERSION|compiled against a different Node\.js version/i.test(message)) {
22
- return "Rebuild DBX CLI native dependencies with your active Node.js: pnpm rebuild better-sqlite3 keytar --pending, or reinstall the package with the same Node.js version you use to run dbx.";
23
- }
24
- return undefined;
25
- }
26
- export function csvTable(headers, rows) {
27
- const lines = [headers.map(csvCell).join(",")];
28
- for (const row of rows) {
29
- const values = row;
30
- lines.push(headers.map((header) => csvCell(values[header])).join(","));
31
- }
32
- return `${lines.join("\n")}\n`;
33
- }
34
- function csvCell(value) {
35
- if (value === null || value === undefined)
36
- return "";
37
- const text = typeof value === "object" ? JSON.stringify(value) : String(value);
38
- if (/[",\r\n]/.test(text))
39
- return `"${text.replace(/"/g, '""')}"`;
40
- return text;
41
- }
package/dist/cli.d.ts DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env node
2
- import { type Backend, type DbxDiagnostics } from "@dbx-app/node-core";
3
- export interface CliResult {
4
- exitCode: number;
5
- stdout: string;
6
- stderr: string;
7
- }
8
- interface RunOptions {
9
- backend?: Backend;
10
- backendFactory?: (env?: NodeJS.ProcessEnv) => Promise<Backend>;
11
- env?: NodeJS.ProcessEnv;
12
- diagnostics?: () => Promise<DbxDiagnostics>;
13
- }
14
- export declare function runCli(argv: string[], options?: RunOptions): Promise<CliResult>;
15
- export {};
package/dist/cli.js DELETED
@@ -1,352 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readFile } from "node:fs/promises";
3
- import { buildSchemaContext, createBackend, DIRECT_QUERY_TYPES, BRIDGE_REQUIRED_TYPES, evaluateSqlSafety, formatSchemaContext, getDbxDiagnostics, isMainModule, postBridge, supportsHashLineComments } from "@dbx-app/node-core";
4
- import { connectionSummary, csvTable, errorPayload, formatCell, formatErrorMessage, mdTable } from "./cli-format.js";
5
- class CliError extends Error {
6
- code;
7
- constructor(code, message) {
8
- super(message);
9
- this.code = code;
10
- }
11
- }
12
- export async function runCli(argv, options = {}) {
13
- const env = options.env ?? process.env;
14
- let ownedBackend;
15
- try {
16
- const flags = parseFlags(argv);
17
- const args = flags.args;
18
- if (flags.version) {
19
- return ok(`${await packageVersion()}\n`);
20
- }
21
- if (args.length === 0 || flags.help || args[0] === "help") {
22
- return ok(`${usage()}\n`);
23
- }
24
- const backendFactory = options.backendFactory ?? createBackend;
25
- const backend = options.backend ?? (ownedBackend = await backendFactory(env));
26
- if (args[0] === "doctor") {
27
- ensureArgCount(args, 1, "dbx doctor");
28
- const diagnostics = await (options.diagnostics ?? getDbxDiagnostics)();
29
- if (flags.format === "json")
30
- return okJson(diagnostics);
31
- if (flags.format === "csv") {
32
- return ok(csvTable(["check", "value"], [
33
- { check: "appDataDir", value: diagnostics.appDataDir },
34
- { check: "dbPath", value: diagnostics.dbPath },
35
- { check: "dbPathExists", value: diagnostics.dbPathExists },
36
- { check: "connectionsTableExists", value: diagnostics.connectionsTableExists },
37
- { check: "connectionRowCount", value: diagnostics.connectionRowCount },
38
- { check: "loadConnectionsOk", value: diagnostics.loadConnectionsOk },
39
- { check: "loadedConnectionCount", value: diagnostics.loadedConnectionCount },
40
- { check: "loadConnectionsError", value: diagnostics.loadConnectionsError ?? "" },
41
- { check: "loadConnectionsHint", value: diagnostics.loadConnectionsHint ?? "" },
42
- { check: "bridgePortFile", value: diagnostics.bridgePortFile },
43
- { check: "bridgePortFileExists", value: diagnostics.bridgePortFileExists },
44
- { check: "bridgeUrl", value: diagnostics.bridgeUrl ?? "" },
45
- ]));
46
- }
47
- return ok(formatDoctor(diagnostics));
48
- }
49
- if (args[0] === "capabilities") {
50
- ensureArgCount(args, 1, "dbx capabilities");
51
- const payload = {
52
- directQueryTypes: [...DIRECT_QUERY_TYPES],
53
- bridgeRequiredTypes: [...BRIDGE_REQUIRED_TYPES],
54
- };
55
- if (flags.format === "json")
56
- return okJson(payload);
57
- if (flags.format === "csv") {
58
- return ok(csvTable(["mode", "type"], [...payload.directQueryTypes.map((type) => ({ mode: "direct", type })), ...payload.bridgeRequiredTypes.map((type) => ({ mode: "bridge", type }))]));
59
- }
60
- return ok(`${mdTable(["Mode", "Types"], [
61
- ["Direct", payload.directQueryTypes.join(", ")],
62
- ["Requires DBX Desktop", payload.bridgeRequiredTypes.join(", ")],
63
- ])}\n`);
64
- }
65
- if (args[0] === "connections" && args[1] === "list") {
66
- ensureArgCount(args, 2, "dbx connections list");
67
- const connections = (await backend.loadConnections()).map(connectionSummary);
68
- if (flags.format === "json")
69
- return okJson({ connections });
70
- if (flags.format === "csv")
71
- return ok(csvTable(["name", "type", "host", "port", "database"], connections));
72
- return ok(`${mdTable(["Name", "Type", "Host", "Port", "Database"], connections.map((c) => [c.name, c.type, c.host, String(c.port), c.database ?? ""]))}\n`);
73
- }
74
- if (args[0] === "schema" && args[1] === "list") {
75
- ensureArgCount(args, 3, "dbx schema list");
76
- const connectionName = required(args[2], "Connection name is required.");
77
- const config = await findConnectionOrThrow(backend, connectionName);
78
- const tables = await backend.listTables(config, flags.schema);
79
- if (flags.format === "json")
80
- return okJson({ connection: connectionName, schema: flags.schema, tables });
81
- if (flags.format === "csv")
82
- return ok(csvTable(["name", "type"], tables));
83
- return ok(`${mdTable(["Table", "Type"], tables.map((t) => [t.name, t.type]))}\n`);
84
- }
85
- if (args[0] === "schema" && args[1] === "describe") {
86
- ensureArgCount(args, 4, "dbx schema describe");
87
- const connectionName = required(args[2], "Connection name is required.");
88
- const table = required(args[3], "Table name is required.");
89
- const config = await findConnectionOrThrow(backend, connectionName);
90
- const columns = await backend.describeTable(config, table, flags.schema);
91
- if (flags.format === "json")
92
- return okJson({ connection: connectionName, schema: flags.schema, table, columns });
93
- if (flags.format === "csv") {
94
- return ok(csvTable(["name", "data_type", "is_nullable", "is_primary_key", "column_default", "comment"], columns));
95
- }
96
- return ok(`${mdTable(["Column", "Type", "Nullable", "Default", "Comment"], columns.map((c) => [c.is_primary_key ? `${c.name} (PK)` : c.name, c.data_type, c.is_nullable ? "YES" : "NO", c.column_default ?? "", c.comment ?? ""]))}\n`);
97
- }
98
- if (args[0] === "query") {
99
- const usesDefaultConnection = !!env.DBX_CONNECTION && args.length === (flags.file ? 1 : 2);
100
- ensureArgCount(args, usesDefaultConnection ? (flags.file ? 1 : 2) : flags.file ? 2 : 3, "dbx query");
101
- const connectionName = usesDefaultConnection ? env.DBX_CONNECTION : required(args[1], "Connection name is required.");
102
- if (flags.file && args[2]) {
103
- throw new CliError("INVALID_ARGUMENT", "Provide SQL either inline or with --file, not both.");
104
- }
105
- const sqlArg = usesDefaultConnection ? args[1] : args[2];
106
- const sql = flags.file ? await readFile(flags.file, "utf-8") : required(sqlArg, "SQL string or --file is required.");
107
- const config = await findConnectionOrThrow(backend, connectionName);
108
- const envSafety = sqlSafetyFromCliEnv(env);
109
- if (flags.allowDangerous && !flags.allowWrites && !envSafety.allowWrites) {
110
- throw new CliError("INVALID_OPTION", "--allow-dangerous-sql requires --allow-writes.");
111
- }
112
- const safetyOptions = {
113
- allowWrites: flags.allowWrites || envSafety.allowWrites,
114
- allowDangerous: flags.allowDangerous || envSafety.allowDangerous,
115
- hashLineComments: supportsHashLineComments(config.db_type),
116
- };
117
- const safety = evaluateSqlSafety(sql, safetyOptions);
118
- if (!safety.allowed)
119
- return fail("SQL_BLOCKED", safety.reason ?? "SQL blocked.", flags.json);
120
- const result = await backend.executeQuery(config, sql, { maxRows: flags.maxRows, timeoutMs: flags.timeoutMs });
121
- if (flags.format === "json") {
122
- return okJson({ connection: connectionName, columns: result.columns, rows: result.rows, row_count: result.row_count });
123
- }
124
- if (flags.format === "csv")
125
- return ok(csvTable(result.columns, result.rows));
126
- if (result.columns.length === 0)
127
- return ok(`Query executed. ${result.row_count} row(s) affected.\n`);
128
- return ok(`${mdTable(result.columns, result.rows.map((row) => result.columns.map((column) => formatCell(row[column]))))}\n\n${result.row_count} row(s)\n`);
129
- }
130
- if (args[0] === "context") {
131
- const usesDefaultConnection = !!env.DBX_CONNECTION && args.length === 1;
132
- ensureArgCount(args, usesDefaultConnection ? 1 : 2, "dbx context");
133
- const connectionName = usesDefaultConnection ? env.DBX_CONNECTION : required(args[1], "Connection name is required.");
134
- const config = await findConnectionOrThrow(backend, connectionName);
135
- const context = await buildSchemaContext(backend, config, {
136
- schema: flags.schema,
137
- tables: flags.tables,
138
- maxTables: flags.maxTables,
139
- });
140
- if (flags.format === "json")
141
- return okJson(context);
142
- if (flags.format === "csv")
143
- throw new CliError("INVALID_OPTION", "CSV format is not supported for dbx context.");
144
- return ok(`${formatSchemaContext(context)}\n`);
145
- }
146
- if (args[0] === "open") {
147
- ensureArgCount(args, 3, "dbx open");
148
- const connectionName = required(args[1], "Connection name is required.");
149
- const table = required(args[2], "Table name is required.");
150
- const response = await postBridge("/open-table", {
151
- connection_name: connectionName,
152
- table,
153
- schema: flags.schema,
154
- database: flags.database,
155
- });
156
- if (!response.ok) {
157
- return fail("DBX_NOT_RUNNING", response.text || "DBX is not running. Please start DBX first.", flags.json);
158
- }
159
- if (flags.format === "json")
160
- return okJson({ opened: true, connection: connectionName, table, schema: flags.schema, database: flags.database });
161
- if (flags.format === "csv")
162
- throw new CliError("INVALID_OPTION", "CSV format is not supported for dbx open.");
163
- return ok(`Opened ${table} in DBX\n`);
164
- }
165
- return fail("USAGE", usage(), flags.json);
166
- }
167
- catch (error) {
168
- const message = error instanceof Error ? error.message : String(error);
169
- const code = error instanceof CliError ? error.code : typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "ERROR";
170
- const wantsJson = argv.includes("--json");
171
- return fail(code, message, wantsJson);
172
- }
173
- finally {
174
- await ownedBackend?.close?.().catch(() => { });
175
- }
176
- }
177
- function parseFlags(argv) {
178
- const args = [];
179
- const flags = {
180
- args,
181
- json: false,
182
- format: "table",
183
- allowWrites: false,
184
- allowDangerous: false,
185
- help: false,
186
- version: false,
187
- };
188
- for (let i = 0; i < argv.length; i++) {
189
- const arg = argv[i];
190
- if (arg === "--") {
191
- args.push(...argv.slice(i + 1));
192
- break;
193
- }
194
- if (arg === "--json") {
195
- flags.json = true;
196
- flags.format = "json";
197
- }
198
- else if (arg === "--format")
199
- flags.format = parseFormat(readOptionValue(argv, ++i, "--format"));
200
- else if (arg === "--help" || arg === "-h")
201
- flags.help = true;
202
- else if (arg === "--version" || arg === "-V")
203
- flags.version = true;
204
- else if (arg === "--schema")
205
- flags.schema = readOptionValue(argv, ++i, "--schema");
206
- else if (arg === "--database")
207
- flags.database = readOptionValue(argv, ++i, "--database");
208
- else if (arg === "--tables")
209
- flags.tables = splitCsv(readOptionValue(argv, ++i, "--tables"));
210
- else if (arg === "--max-tables")
211
- flags.maxTables = parsePositiveInt(readOptionValue(argv, ++i, "--max-tables"), "--max-tables");
212
- else if (arg === "--limit")
213
- flags.maxRows = parsePositiveInt(readOptionValue(argv, ++i, "--limit"), "--limit");
214
- else if (arg === "--timeout")
215
- flags.timeoutMs = parseDurationMs(readOptionValue(argv, ++i, "--timeout"), "--timeout");
216
- else if (arg === "--file")
217
- flags.file = readOptionValue(argv, ++i, "--file");
218
- else if (arg === "--allow-writes")
219
- flags.allowWrites = true;
220
- else if (arg === "--allow-dangerous-sql")
221
- flags.allowDangerous = true;
222
- else if (arg.startsWith("-"))
223
- throw new CliError("UNKNOWN_OPTION", `Unknown option: ${arg}`);
224
- else
225
- args.push(arg);
226
- }
227
- return flags;
228
- }
229
- function parseFormat(value) {
230
- if (value === "table" || value === "json" || value === "csv")
231
- return value;
232
- throw new CliError("INVALID_OPTION", "--format must be one of: table, json, csv.");
233
- }
234
- function ensureArgCount(args, count, command) {
235
- if (args.length !== count) {
236
- throw new CliError("INVALID_ARGUMENT", `${command} expects ${count - 1} argument(s); received ${args.length - 1}.`);
237
- }
238
- }
239
- function readOptionValue(argv, index, option) {
240
- const value = argv[index];
241
- if (!value || value.startsWith("-")) {
242
- throw new CliError("INVALID_OPTION", `${option} requires a value.`);
243
- }
244
- return value;
245
- }
246
- function parsePositiveInt(value, option) {
247
- const parsed = Number(value);
248
- if (!Number.isInteger(parsed) || parsed < 1) {
249
- throw new CliError("INVALID_OPTION", `${option} must be a positive integer.`);
250
- }
251
- return parsed;
252
- }
253
- function parseDurationMs(value, option) {
254
- const match = value.match(/^(\d+)(ms|s|m)?$/);
255
- if (!match) {
256
- throw new CliError("INVALID_OPTION", `${option} must be a positive duration such as 500ms, 10s, or 1m.`);
257
- }
258
- const amount = Number(match[1]);
259
- if (!Number.isInteger(amount) || amount < 1) {
260
- throw new CliError("INVALID_OPTION", `${option} must be a positive duration such as 500ms, 10s, or 1m.`);
261
- }
262
- const unit = match[2] ?? "ms";
263
- if (unit === "ms")
264
- return amount;
265
- if (unit === "s")
266
- return amount * 1000;
267
- return amount * 60_000;
268
- }
269
- function parseBooleanEnv(value) {
270
- if (value === undefined)
271
- return false;
272
- const normalized = value.trim().toLowerCase();
273
- return normalized === "1" || normalized === "true";
274
- }
275
- function sqlSafetyFromCliEnv(env) {
276
- return {
277
- allowWrites: parseBooleanEnv(env.DBX_MCP_ALLOW_WRITES),
278
- allowDangerous: parseBooleanEnv(env.DBX_MCP_ALLOW_DANGEROUS_SQL),
279
- };
280
- }
281
- function splitCsv(value) {
282
- return (value ?? "")
283
- .split(",")
284
- .map((part) => part.trim())
285
- .filter(Boolean);
286
- }
287
- async function findConnectionOrThrow(backend, name) {
288
- const config = await backend.findConnection(name);
289
- if (!config)
290
- throw new CliError("CONNECTION_NOT_FOUND", `Connection "${name}" not found.`);
291
- return config;
292
- }
293
- function required(value, message) {
294
- if (!value)
295
- throw new Error(message);
296
- return value;
297
- }
298
- function ok(stdout) {
299
- return { exitCode: 0, stdout, stderr: "" };
300
- }
301
- function okJson(payload) {
302
- return ok(`${JSON.stringify(payload, null, 2)}\n`);
303
- }
304
- function fail(code, message, json) {
305
- const text = json ? `${JSON.stringify(errorPayload(code, message), null, 2)}\n` : `${formatErrorMessage(code, message)}\n`;
306
- return { exitCode: 1, stdout: "", stderr: text };
307
- }
308
- function usage() {
309
- return [
310
- "Usage:",
311
- " dbx doctor [--json]",
312
- " dbx capabilities [--json]",
313
- " dbx connections list [--json]",
314
- " dbx schema list <connection> [--schema name] [--json]",
315
- " dbx schema describe <connection> <table> [--schema name] [--json]",
316
- " dbx query <connection> <sql> [--file path] [--limit n] [--timeout 10s] [--allow-writes] [--allow-dangerous-sql] [--json]",
317
- " dbx context <connection> [--schema name] [--tables a,b] [--max-tables n] [--json]",
318
- " dbx open <connection> <table> [--schema name] [--database name] [--json]",
319
- ].join("\n");
320
- }
321
- function formatDoctor(diagnostics) {
322
- const rows = [
323
- ["App data directory", diagnostics.appDataDir],
324
- ["DBX database", diagnostics.dbPathExists ? `found (${diagnostics.dbPath})` : `missing (${diagnostics.dbPath})`],
325
- ["Connections table", diagnostics.connectionsTableExists ? `${diagnostics.connectionRowCount} row(s)` : "missing"],
326
- ["Connection loading", diagnostics.loadConnectionsOk ? `ok (${diagnostics.loadedConnectionCount} loaded)` : `failed (${diagnostics.loadConnectionsError ?? "unknown error"})`],
327
- ...(diagnostics.loadConnectionsHint ? [["Connection fix", diagnostics.loadConnectionsHint]] : []),
328
- ["Desktop bridge", diagnostics.bridgePortFileExists ? `available (${diagnostics.bridgeUrl ?? diagnostics.bridgePortFile})` : "not running"],
329
- ["Direct query types", diagnostics.directQueryTypes.join(", ")],
330
- ["Bridge-required types", diagnostics.bridgeRequiredTypes.join(", ")],
331
- ];
332
- return `${mdTable(["Check", "Value"], rows)}\n`;
333
- }
334
- async function packageVersion() {
335
- const packageJson = await readFile(new URL("../package.json", import.meta.url), "utf-8");
336
- const parsed = JSON.parse(packageJson);
337
- return parsed.version ?? "0.0.0";
338
- }
339
- async function main() {
340
- const result = await runCli(process.argv.slice(2));
341
- if (result.stdout)
342
- process.stdout.write(result.stdout);
343
- if (result.stderr)
344
- process.stderr.write(result.stderr);
345
- process.exitCode = result.exitCode;
346
- }
347
- if (isMainModule(import.meta.url, process.argv[1])) {
348
- main().catch((error) => {
349
- console.error(error instanceof Error ? error.message : String(error));
350
- process.exitCode = 1;
351
- });
352
- }