@carllee1983/dbcli 8.0.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2087,7 +2087,7 @@ var package_default;
2087
2087
  var init_package = __esm(() => {
2088
2088
  package_default = {
2089
2089
  name: "@carllee1983/dbcli",
2090
- version: "8.0.0",
2090
+ version: "9.1.0",
2091
2091
  description: "Database CLI for AI agents",
2092
2092
  type: "module",
2093
2093
  publishConfig: {
@@ -2168,15 +2168,15 @@ var init_package = __esm(() => {
2168
2168
  "services:check": "bun run scripts/check-test-services.ts",
2169
2169
  "docs:check": "bun run scripts/check-user-docs.ts",
2170
2170
  "contract:check": "bun run scripts/check-cli-contract.ts",
2171
- "skill:check": "bun run scripts/check-skill-parity.ts",
2172
2171
  "platform:check": "bun run scripts/check-platform-parity.ts",
2173
2172
  "agent-core:check": "bun run scripts/check-agent-core-purity.ts",
2174
2173
  "core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
2175
2174
  "plan:check": "bun run scripts/check-plan-acceptance.ts",
2176
2175
  "forgeflow:check": "bun run scripts/check-forgeflow-adoption.ts && bun run scripts/check-forgeflow-handoff.ts",
2176
+ "forgeflow:contract": "bun run scripts/check-forgeflow-contract.ts",
2177
2177
  typecheck: "tsc --noEmit --pretty false",
2178
2178
  "typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
2179
- "test:perf": "bun test ./tests/perf/*.bench.ts",
2179
+ "test:perf": "bun test --timeout 30000 ./tests/perf/*.bench.ts",
2180
2180
  lint: "eslint src tests scripts --ext .ts --max-warnings=0",
2181
2181
  "format:check": "prettier --check .",
2182
2182
  format: "prettier --write .",
@@ -6407,13 +6407,27 @@ function parseStatementTimeoutOption(value) {
6407
6407
  }
6408
6408
  return parsed;
6409
6409
  }
6410
+ function isSqliteMemoryTarget(value) {
6411
+ const trimmed = value.trim();
6412
+ if (trimmed.toLowerCase() === ":memory:")
6413
+ return true;
6414
+ if (!trimmed.toLowerCase().startsWith("file:"))
6415
+ return false;
6416
+ const withoutScheme = trimmed.slice("file:".length);
6417
+ const queryStart = withoutScheme.indexOf("?");
6418
+ const path = queryStart === -1 ? withoutScheme : withoutScheme.slice(0, queryStart);
6419
+ if (path.toLowerCase() === ":memory:")
6420
+ return true;
6421
+ const query = queryStart === -1 ? "" : withoutScheme.slice(queryStart + 1);
6422
+ return new URLSearchParams(query).get("mode")?.toLowerCase() === "memory";
6423
+ }
6410
6424
  function validateFormat(value, allowedFormats, commandName) {
6411
6425
  if (!allowedFormats.includes(value)) {
6412
6426
  const allowed = allowedFormats.join(", ");
6413
6427
  throw new Error(`Invalid format "${value}" for ${commandName}. Allowed: ${allowed}`);
6414
6428
  }
6415
6429
  }
6416
- var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, OptStringOrEnvRef, OptNumberOrEnvRef, MIN_CONNECTION_TIMEOUT_MS = 100, MAX_CONNECTION_TIMEOUT_MS = 600000, MIN_STATEMENT_TIMEOUT_MS = 0, MAX_STATEMENT_TIMEOUT_MS = 3600000, TimeoutField, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, RedisConnectionConfigSchema, ElasticsearchConnectionConfigSchema, ConnectionConfigSchema, PermissionSchema, EnvironmentLabelSchema, MetadataSchema, BLACKLIST_KEY_ALIASES, BlacklistConfigSchema, RedisMaskRuleSchema, RedisConfigSchema, DEFAULT_AUDIT_ROTATION, AuditRotationConfigSchema, AuditConfigSchema, DbcliConfigSchema, SqlNamedConnectionSchema, MongoDBNamedConnectionSchema, RedisNamedConnectionSchema, ElasticsearchNamedConnectionSchema, NamedConnectionUnion, NamedConnectionSchema, DbcliConfigV2Schema;
6430
+ var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, OptStringOrEnvRef, OptNumberOrEnvRef, MIN_CONNECTION_TIMEOUT_MS = 100, MAX_CONNECTION_TIMEOUT_MS = 600000, MIN_STATEMENT_TIMEOUT_MS = 0, MAX_STATEMENT_TIMEOUT_MS = 3600000, TimeoutField, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, SQLITE_MEMORY_TARGET_MESSAGE, SqliteConnectionConfigSchema, RedisConnectionConfigSchema, ElasticsearchConnectionConfigSchema, ConnectionConfigSchema, PermissionSchema, EnvironmentLabelSchema, MetadataSchema, BLACKLIST_KEY_ALIASES, BlacklistConfigSchema, RedisMaskRuleSchema, RedisConfigSchema, DEFAULT_AUDIT_ROTATION, AuditRotationConfigSchema, AuditConfigSchema, DbcliConfigSchema, SqlNamedConnectionSchema, SqliteNamedConnectionSchema, MongoDBNamedConnectionSchema, RedisNamedConnectionSchema, ElasticsearchNamedConnectionSchema, NamedConnectionUnion, NamedConnectionSchema, DbcliConfigV2Schema;
6417
6431
  var init_validation = __esm(() => {
6418
6432
  init_zod();
6419
6433
  EnvRefSchema = exports_external.object({
@@ -6450,6 +6464,21 @@ var init_validation = __esm(() => {
6450
6464
  database: StringOrEnvRef,
6451
6465
  ...TimeoutField
6452
6466
  });
6467
+ SQLITE_MEMORY_TARGET_MESSAGE = "SQLite in-memory databases are refused: a memory target is a different database on every " + "connection, so it cannot be the connection identity the blacklist, audit log and schema " + "cache are scoped to. Point `file` at a database file on disk. See ADR-0038.";
6468
+ SqliteConnectionConfigSchema = exports_external.object({
6469
+ system: exports_external.literal("sqlite"),
6470
+ file: exports_external.union([exports_external.string().min(1), EnvRefSchema]).superRefine((value, ctx) => {
6471
+ if (typeof value === "string" && isSqliteMemoryTarget(value)) {
6472
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, message: SQLITE_MEMORY_TARGET_MESSAGE });
6473
+ }
6474
+ }),
6475
+ host: OptStringOrEnvRef,
6476
+ port: exports_external.union([exports_external.number().int(), EnvRefSchema]).optional().default(0),
6477
+ user: OptStringOrEnvRef,
6478
+ password: exports_external.union([exports_external.string(), EnvRefSchema]).optional().default(""),
6479
+ database: OptStringOrEnvRef,
6480
+ ...TimeoutField
6481
+ });
6453
6482
  RedisConnectionConfigSchema = exports_external.object({
6454
6483
  system: exports_external.literal("redis"),
6455
6484
  host: StringOrEnvRef,
@@ -6476,6 +6505,7 @@ var init_validation = __esm(() => {
6476
6505
  });
6477
6506
  ConnectionConfigSchema = exports_external.union([
6478
6507
  SqlConnectionConfigSchema,
6508
+ SqliteConnectionConfigSchema,
6479
6509
  MongoDBConnectionConfigSchema,
6480
6510
  RedisConnectionConfigSchema,
6481
6511
  ElasticsearchConnectionConfigSchema
@@ -6546,6 +6576,11 @@ var init_validation = __esm(() => {
6546
6576
  envFile: exports_external.string().optional(),
6547
6577
  environment: EnvironmentLabelSchema
6548
6578
  });
6579
+ SqliteNamedConnectionSchema = SqliteConnectionConfigSchema.extend({
6580
+ permission: PermissionSchema,
6581
+ envFile: exports_external.string().optional(),
6582
+ environment: EnvironmentLabelSchema
6583
+ });
6549
6584
  MongoDBNamedConnectionSchema = MongoDBConnectionConfigSchema.extend({
6550
6585
  permission: PermissionSchema,
6551
6586
  envFile: exports_external.string().optional(),
@@ -6563,6 +6598,7 @@ var init_validation = __esm(() => {
6563
6598
  });
6564
6599
  NamedConnectionUnion = exports_external.union([
6565
6600
  SqlNamedConnectionSchema,
6601
+ SqliteNamedConnectionSchema,
6566
6602
  MongoDBNamedConnectionSchema,
6567
6603
  RedisNamedConnectionSchema,
6568
6604
  ElasticsearchNamedConnectionSchema
@@ -8816,7 +8852,8 @@ var init_messages = __esm(() => {
8816
8852
  connection_not_found: "Connection '{name}' does not exist",
8817
8853
  cannot_remove_last: "Cannot remove the last connection",
8818
8854
  connection_already_exists: "Connection '{name}' already exists",
8819
- rename_invalid_format: "Usage: --rename <old-name>:<new-name>"
8855
+ rename_invalid_format: "Usage: --rename <old-name>:<new-name>",
8856
+ prompt_sqlite_file: "SQLite database file path: "
8820
8857
  },
8821
8858
  inspect: {
8822
8859
  description: "Read-only snapshot of connection, permission, blacklist, objects, snippets, and suggested next commands.",
@@ -8985,7 +9022,8 @@ Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactiv
8985
9022
  env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
8986
9023
  Set {envKey} in .env or environment variables.
8987
9024
  Hint: run 'export {envKey}=<value>' and retry`,
8988
- invalid_output_format: "--format {format} is not supported here \u2014 use text or json"
9025
+ invalid_output_format: "--format {format} is not supported here \u2014 use text or json",
9026
+ sqlite_file_required: "A SQLite connection needs a file path. Pass --file <path> or run init interactively."
8989
9027
  },
8990
9028
  success: {
8991
9029
  inserted: "Successfully inserted {count} row(s)",
@@ -9150,7 +9188,8 @@ var init_messages2 = __esm(() => {
9150
9188
  connection_not_found: "\u9023\u7DDA '{name}' \u4E0D\u5B58\u5728",
9151
9189
  cannot_remove_last: "\u7121\u6CD5\u79FB\u9664\u6700\u5F8C\u4E00\u500B\u9023\u7DDA",
9152
9190
  connection_already_exists: "\u9023\u7DDA '{name}' \u5DF2\u5B58\u5728",
9153
- rename_invalid_format: "\u7528\u6CD5\uFF1A--rename <\u820A\u540D\u7A31>:<\u65B0\u540D\u7A31>"
9191
+ rename_invalid_format: "\u7528\u6CD5\uFF1A--rename <\u820A\u540D\u7A31>:<\u65B0\u540D\u7A31>",
9192
+ prompt_sqlite_file: "SQLite \u8CC7\u6599\u5EAB\u6A94\u6848\u8DEF\u5F91\uFF1A"
9154
9193
  },
9155
9194
  inspect: {
9156
9195
  description: "\u552F\u8B80\u6AA2\u8996\uFF1A\u9023\u7DDA\u3001\u6B0A\u9650\u3001\u9ED1\u540D\u55AE\u3001\u7269\u4EF6\u3001\u7247\u6BB5\u8207\u5EFA\u8B70\u4E0B\u4E00\u6B65\u6307\u4EE4\u3002",
@@ -9319,7 +9358,8 @@ var init_messages2 = __esm(() => {
9319
9358
  env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
9320
9359
  \u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
9321
9360
  \u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`,
9322
- invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json"
9361
+ invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json",
9362
+ sqlite_file_required: "SQLite \u9023\u7DDA\u9700\u8981\u4E00\u500B\u6A94\u6848\u8DEF\u5F91\u3002\u8ACB\u7528 --file <path>\uFF0C\u6216\u4EE5\u4E92\u52D5\u6A21\u5F0F\u57F7\u884C init\u3002"
9323
9363
  },
9324
9364
  success: {
9325
9365
  inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
@@ -10247,16 +10287,23 @@ var init_redaction = __esm(() => {
10247
10287
  });
10248
10288
 
10249
10289
  // src/adapters/types.ts
10250
- var DATABASE_SYSTEMS, ConnectionError;
10290
+ var DATABASE_SYSTEMS, SQL_DATABASE_SYSTEMS, ConnectionError;
10251
10291
  var init_types2 = __esm(() => {
10252
10292
  DATABASE_SYSTEMS = Object.freeze([
10253
10293
  "postgresql",
10254
10294
  "mysql",
10255
10295
  "mariadb",
10296
+ "sqlite",
10256
10297
  "mongodb",
10257
10298
  "redis",
10258
10299
  "elasticsearch"
10259
10300
  ]);
10301
+ SQL_DATABASE_SYSTEMS = Object.freeze([
10302
+ "postgresql",
10303
+ "mysql",
10304
+ "mariadb",
10305
+ "sqlite"
10306
+ ]);
10260
10307
  ConnectionError = class ConnectionError extends Error {
10261
10308
  code;
10262
10309
  hints;
@@ -10417,6 +10464,38 @@ var init_capabilities = __esm(() => {
10417
10464
  ...SQL_BASE,
10418
10465
  check: cap("limited", "readonly", "SQL-only and strongest on MySQL/MariaDB.")
10419
10466
  }),
10467
+ sqlite: Object.freeze({
10468
+ ...SQL_BASE,
10469
+ init: cap("supported", "local-write", "Asks for a file path and a permission; the file must already exist."),
10470
+ use: cap("supported", "local-write", "Switches connections and displays the file path."),
10471
+ schemaFullScan: cap("unsupported", "none", "Full schema cache scan is not implemented for SQLite."),
10472
+ lint: cap("unsupported", "none", "Static lint is not enabled for the SQLite dialect yet."),
10473
+ queries: cap("supported", "local-write", "Snippets may declare `engine: sqlite` and bind with `?` placeholders."),
10474
+ insert: cap("supported", "db-write", "Row insert through the shared data-executor path."),
10475
+ update: cap("supported", "db-write", "Row update with a mandatory --where clause."),
10476
+ delete: cap("supported", "db-write", "Row delete with a mandatory --where clause."),
10477
+ export: cap("supported", "local-write", "Exports SELECT results to json, csv or html through the shared query path."),
10478
+ check: cap("unsupported", "none", "Data health check is not enabled for SQLite."),
10479
+ diff: cap("unsupported", "none", "Schema snapshots are not enabled for SQLite."),
10480
+ migrate: cap("unsupported", "none", "SQLite ALTER TABLE covers few operations and a column change is a table rebuild; no DDL generator exists."),
10481
+ shell: cap("unsupported", "none", "The REPL is not enabled for SQLite."),
10482
+ doctor: cap("supported", "readonly", "Checks the file exists, is readable, and is writable when the permission allows writes."),
10483
+ inspect: cap("unsupported", "none", "Inspection collectors are not enabled for SQLite."),
10484
+ report: cap("unsupported", "none", "Report diagnostics query engine-specific statistics views."),
10485
+ guide: cap("unsupported", "none", "Guides depend on engine-specific EXPLAIN output."),
10486
+ explain: cap("unsupported", "none", "EXPLAIN handling is not enabled for SQLite."),
10487
+ plan: cap("unsupported", "none", "Plan capture is not enabled for SQLite."),
10488
+ impactAssess: cap("unsupported", "none", "Impact assessment is not enabled for SQLite."),
10489
+ assert: cap("unsupported", "none", "Assertions are not enabled for SQLite."),
10490
+ snapshot: cap("unsupported", "none", "Result snapshots are not enabled for SQLite."),
10491
+ verify: cap("unsupported", "none", "Write verification is not enabled for SQLite."),
10492
+ semantic: cap("unsupported", "none", "Semantic context work is not enabled for SQLite."),
10493
+ design: cap("unsupported", "none", "Schema design work is not enabled for SQLite."),
10494
+ proxy: cap("not-applicable", "none", "SQLite has no network protocol to proxy."),
10495
+ proxyAnalyze: cap("not-applicable", "none", "There are no SQLite proxy events to analyse."),
10496
+ password: cap("not-applicable", "none", "A SQLite connection has no credential to store or rotate."),
10497
+ queryLimitGuard: cap("limited", "readonly", "Applies the same LIMIT rewrite as the other SQL engines; under query-only the handle is opened read-only, which a database with an unreplayed WAL cannot satisfy.")
10498
+ }),
10420
10499
  mysql: Object.freeze(SQL_BASE),
10421
10500
  mariadb: Object.freeze(SQL_BASE),
10422
10501
  mongodb: Object.freeze({
@@ -10916,7 +10995,7 @@ var init_encoding = __esm(() => {
10916
10995
 
10917
10996
  // src/adapters/identifier-quote.ts
10918
10997
  function quoteCharFor(dialect) {
10919
- return dialect === "postgresql" ? '"' : "`";
10998
+ return dialect === "postgresql" || dialect === "sqlite" ? '"' : "`";
10920
10999
  }
10921
11000
  function assertQuotable(name, what) {
10922
11001
  if (name.length === 0) {
@@ -11625,6 +11704,208 @@ var init_mysql_adapter = __esm(() => {
11625
11704
  init_connection_error_message();
11626
11705
  });
11627
11706
 
11707
+ // src/adapters/sqlite-adapter.ts
11708
+ import { Database } from "bun:sqlite";
11709
+ function mapSqliteError(error, file, readonly) {
11710
+ if (error instanceof ConnectionError)
11711
+ return error;
11712
+ const message = error instanceof Error ? error.message : String(error);
11713
+ const code = String(error?.code ?? "");
11714
+ if (readonly && (code.startsWith("SQLITE_CANTOPEN") || /unable to open database/i.test(message))) {
11715
+ return new ConnectionError("CONNECTION_REJECTED", `Cannot open '${file}' read-only: the database has an unreplayed write-ahead log, ` + `and replaying it is a write. A query-only connection cannot recover it.`, [
11716
+ "Let the owning application close the database cleanly, which checkpoints the WAL.",
11717
+ "Or connect with a permission above query-only, which opens the file read-write.",
11718
+ `Original driver message: ${message}`
11719
+ ]);
11720
+ }
11721
+ if (/readonly database|attempt to write a readonly/i.test(message)) {
11722
+ return new ConnectionError("CONNECTION_REJECTED", `The SQLite connection is open read-only: ${message}`, [
11723
+ "query-only opens the database file read-only, so no write can reach it.",
11724
+ "Raise the connection permission if the write is intended."
11725
+ ]);
11726
+ }
11727
+ if (/no such table/i.test(message)) {
11728
+ return new ConnectionError("TABLE_NOT_FOUND", message, [
11729
+ "Run `dbcli list` to see the tables this database holds."
11730
+ ]);
11731
+ }
11732
+ if (/no such column/i.test(message)) {
11733
+ return new ConnectionError("COLUMN_NOT_FOUND", message, [
11734
+ "Run `dbcli schema <table>` to see the columns this table holds."
11735
+ ]);
11736
+ }
11737
+ if (/syntax error/i.test(message)) {
11738
+ return new ConnectionError("SQL_SYNTAX_ERROR", message, [
11739
+ "Check the statement against SQLite syntax; it differs from PostgreSQL and MySQL."
11740
+ ]);
11741
+ }
11742
+ return new ConnectionError("UNKNOWN", message, [`SQLite database: ${file}`]);
11743
+ }
11744
+
11745
+ class SQLiteAdapter {
11746
+ db = null;
11747
+ openedReadonly = true;
11748
+ connected = false;
11749
+ file;
11750
+ constructor(options) {
11751
+ if (!options.file) {
11752
+ throw new ConnectionError("UNKNOWN", "SQLite connection is missing its `file` path", [
11753
+ "Set `file` in the connection configuration to the database file path."
11754
+ ]);
11755
+ }
11756
+ this.file = options.file;
11757
+ }
11758
+ async connect() {
11759
+ const target = Bun.file(this.file);
11760
+ if (!await target.exists()) {
11761
+ throw new ConnectionError("ENOTFOUND", `SQLite database file not found: ${this.file}`, [
11762
+ "dbcli never creates a database file; point `file` at one that already exists.",
11763
+ "Check the path in the connection configuration."
11764
+ ]);
11765
+ }
11766
+ let header;
11767
+ try {
11768
+ header = await target.slice(0, SQLITE_HEADER.length).text();
11769
+ } catch (error) {
11770
+ throw new ConnectionError("CONNECTION_REJECTED", `SQLite database file is not readable: ${this.file}`, [
11771
+ "Check the file permissions for the user running dbcli.",
11772
+ `Original error: ${error instanceof Error ? error.message : String(error)}`
11773
+ ]);
11774
+ }
11775
+ if (target.size > 0 && header !== SQLITE_HEADER) {
11776
+ throw new ConnectionError("CONNECTION_REJECTED", `Not a SQLite database: ${this.file} does not start with the SQLite file header`, ["Point `file` at a SQLite database file."]);
11777
+ }
11778
+ this.connected = true;
11779
+ }
11780
+ async disconnect() {
11781
+ try {
11782
+ this.db?.close();
11783
+ } catch {}
11784
+ this.db = null;
11785
+ this.connected = false;
11786
+ }
11787
+ handle(readonly) {
11788
+ if (!this.connected)
11789
+ requireConnected(null);
11790
+ if (this.db && this.openedReadonly === readonly)
11791
+ return this.db;
11792
+ try {
11793
+ this.db?.close();
11794
+ } catch {}
11795
+ this.db = null;
11796
+ try {
11797
+ this.db = readonly ? new Database(this.file, { readonly: true }) : new Database(this.file, { readwrite: true, create: false });
11798
+ } catch (error) {
11799
+ throw mapSqliteError(error, this.file, readonly);
11800
+ }
11801
+ this.openedReadonly = readonly;
11802
+ return this.db;
11803
+ }
11804
+ async execute(sql, params, options) {
11805
+ const readonly = options?.sqlMode === "native-read-only";
11806
+ const db = this.handle(readonly);
11807
+ try {
11808
+ const statement = db.prepare(sql);
11809
+ try {
11810
+ const columnNames = statement.columnNames;
11811
+ if (columnNames.length === 0) {
11812
+ const changes = statement.run(...params ?? []);
11813
+ return {
11814
+ rows: [],
11815
+ affectedRows: changes.changes,
11816
+ lastInsertId: Number(changes.lastInsertRowid)
11817
+ };
11818
+ }
11819
+ const rows = statement.all(...params ?? []);
11820
+ return {
11821
+ rows,
11822
+ affectedRows: rows.length,
11823
+ rowCount: rows.length,
11824
+ columnNames: [...columnNames]
11825
+ };
11826
+ } finally {
11827
+ statement.finalize();
11828
+ }
11829
+ } catch (error) {
11830
+ throw mapSqliteError(error, this.file, readonly);
11831
+ }
11832
+ }
11833
+ async testConnection() {
11834
+ const result = await this.execute("SELECT 1 as ok", [], {
11835
+ sqlMode: "native-read-only"
11836
+ });
11837
+ return result.rows.length > 0;
11838
+ }
11839
+ async getServerVersion() {
11840
+ const result = await this.execute("SELECT sqlite_version() as version", [], {
11841
+ sqlMode: "native-read-only"
11842
+ });
11843
+ return result.rows[0]?.version ?? "unknown";
11844
+ }
11845
+ async listTables() {
11846
+ const result = await this.execute(`SELECT m.name AS name,
11847
+ m.type AS type,
11848
+ (SELECT COUNT(*) FROM pragma_table_info(m.name)) AS column_count
11849
+ FROM sqlite_master m
11850
+ WHERE m.type IN ('table', 'view')
11851
+ AND m.name NOT LIKE 'sqlite\\_%' ESCAPE '\\'
11852
+ ORDER BY m.name`, [], { sqlMode: "native-read-only" });
11853
+ return result.rows.map((row) => ({
11854
+ name: row.name,
11855
+ columns: [],
11856
+ columnCount: row.column_count,
11857
+ rowCount: 0,
11858
+ estimatedRowCount: 0,
11859
+ tableType: row.type === "view" ? "view" : "table"
11860
+ }));
11861
+ }
11862
+ async getTableSchema(tableName, _options) {
11863
+ const info = await this.execute('SELECT cid, name, type, "notnull", dflt_value, pk FROM pragma_table_info(?)', [tableName], { sqlMode: "native-read-only" });
11864
+ if (info.rows.length === 0) {
11865
+ throw new ConnectionError("TABLE_NOT_FOUND", `Table not found: ${tableName}`, [
11866
+ "Run `dbcli list` to see the tables this database holds."
11867
+ ]);
11868
+ }
11869
+ const foreignKeys = await this.execute('SELECT "from", "table", "to" FROM pragma_foreign_key_list(?)', [tableName], { sqlMode: "native-read-only" });
11870
+ const fkByColumn = new Map(foreignKeys.rows.map((fk) => [fk.from, { table: fk.table, column: fk.to ?? "rowid" }]));
11871
+ const ddl = await this.execute("SELECT sql FROM sqlite_master WHERE name = ? AND type = ?", [tableName, "table"], { sqlMode: "native-read-only" });
11872
+ const createSql = ddl.rows[0]?.sql ?? "";
11873
+ const declaresAutoIncrement = /\bAUTOINCREMENT\b/i.test(createSql);
11874
+ const columns = info.rows.map((row) => {
11875
+ const isPrimaryKey = row.pk > 0;
11876
+ const column = {
11877
+ name: row.name,
11878
+ type: row.type || "BLOB",
11879
+ nullable: row.notnull === 0,
11880
+ primaryKey: isPrimaryKey
11881
+ };
11882
+ if (row.dflt_value !== null)
11883
+ column.default = row.dflt_value;
11884
+ const fk = fkByColumn.get(row.name);
11885
+ if (fk)
11886
+ column.foreignKey = fk;
11887
+ if (isPrimaryKey && declaresAutoIncrement && /^INTEGER$/i.test(row.type)) {
11888
+ column.autoIncrement = true;
11889
+ }
11890
+ return column;
11891
+ });
11892
+ const count = await this.execute(`SELECT COUNT(*) AS n FROM ${quoteIdentifier(tableName, "sqlite")}`, [], { sqlMode: "native-read-only" });
11893
+ return {
11894
+ name: tableName,
11895
+ columns,
11896
+ columnCount: columns.length,
11897
+ rowCount: count.rows[0]?.n ?? 0,
11898
+ estimatedRowCount: count.rows[0]?.n ?? 0,
11899
+ tableType: "table"
11900
+ };
11901
+ }
11902
+ }
11903
+ var SQLITE_HEADER = "SQLite format 3\x00";
11904
+ var init_sqlite_adapter = __esm(() => {
11905
+ init_types2();
11906
+ init_sql_adapter_utils();
11907
+ });
11908
+
11628
11909
  // src/adapters/server-side-script.ts
11629
11910
  function isElasticsearchScriptKey(key, parentKey) {
11630
11911
  if (key === "script" || key === "script_fields")
@@ -14268,6 +14549,8 @@ class AdapterFactory {
14268
14549
  case "mysql":
14269
14550
  case "mariadb":
14270
14551
  return new MySQLAdapter(options);
14552
+ case "sqlite":
14553
+ return new SQLiteAdapter(options);
14271
14554
  default:
14272
14555
  throw new Error(`createSqlAdapter requires a SQL system, got: ${options.system}`);
14273
14556
  }
@@ -14291,6 +14574,7 @@ class AdapterFactory {
14291
14574
  case "postgresql":
14292
14575
  case "mysql":
14293
14576
  case "mariadb":
14577
+ case "sqlite":
14294
14578
  return AdapterFactory.createSqlAdapter(options);
14295
14579
  case "redis":
14296
14580
  return AdapterFactory.createRedisAdapter(config);
@@ -14307,6 +14591,7 @@ class AdapterFactory {
14307
14591
  case "postgresql":
14308
14592
  case "mysql":
14309
14593
  case "mariadb":
14594
+ case "sqlite":
14310
14595
  return AdapterFactory.createSqlAdapter(options);
14311
14596
  case "mongodb":
14312
14597
  case "redis":
@@ -14351,6 +14636,7 @@ class AdapterFactory {
14351
14636
  var init_factory = __esm(() => {
14352
14637
  init_postgresql_adapter();
14353
14638
  init_mysql_adapter();
14639
+ init_sqlite_adapter();
14354
14640
  init_mongodb_adapter();
14355
14641
  init_redis_adapter();
14356
14642
  init_elasticsearch_adapter();
@@ -14722,13 +15008,113 @@ var init_init_mongodb = __esm(() => {
14722
15008
  VALID_PERMISSIONS = ["query-only", "read-write", "data-admin", "admin"];
14723
15009
  });
14724
15010
 
15011
+ // src/commands/init-sqlite.ts
15012
+ import { mkdir as mkdir7 } from "fs/promises";
15013
+ function isMemoryTarget(value) {
15014
+ const trimmed = value.trim().toLowerCase();
15015
+ if (trimmed === ":memory:")
15016
+ return true;
15017
+ if (!trimmed.startsWith("file:"))
15018
+ return false;
15019
+ const withoutScheme = trimmed.slice("file:".length);
15020
+ const [path = "", query = ""] = withoutScheme.split("?");
15021
+ if (path === ":memory:")
15022
+ return true;
15023
+ return new URLSearchParams(query).get("mode") === "memory";
15024
+ }
15025
+ async function handleSQLiteInit(ctx) {
15026
+ const { options, configPath, connectionName, isV2Init, existingConfig } = ctx;
15027
+ const isInteractive = options.interactive !== false && process.stdin.isTTY;
15028
+ let file = options.file?.trim();
15029
+ while (!file && isInteractive) {
15030
+ const input = (await promptUser.text(t("init.prompt_sqlite_file"))).trim();
15031
+ if (!input)
15032
+ continue;
15033
+ if (isMemoryTarget(input)) {
15034
+ console.error(SQLITE_MEMORY_TARGET_MESSAGE);
15035
+ continue;
15036
+ }
15037
+ file = input;
15038
+ }
15039
+ if (!file) {
15040
+ throw new Error(t("errors.sqlite_file_required"));
15041
+ }
15042
+ if (isMemoryTarget(file)) {
15043
+ throw new Error(SQLITE_MEMORY_TARGET_MESSAGE);
15044
+ }
15045
+ const sqliteConfig = { system: "sqlite", file };
15046
+ let permission = options.permission || "query-only";
15047
+ if (isInteractive && !options.permission) {
15048
+ permission = await promptUser.select(t("init.prompt_permission"), [
15049
+ "query-only",
15050
+ "read-write",
15051
+ "data-admin",
15052
+ "admin"
15053
+ ]);
15054
+ }
15055
+ if (!VALID_PERMISSIONS2.includes(permission)) {
15056
+ throw new Error(t_vars("errors.invalid_permission", { permission }));
15057
+ }
15058
+ const canProceed = await checkOverwrite(configPath, isInteractive, !!options.force);
15059
+ if (!canProceed)
15060
+ return;
15061
+ if (!options.skipTest) {
15062
+ console.log(t("init.connection_testing"));
15063
+ const adapter = AdapterFactory.createAdapterWithoutRules(sqliteConfig);
15064
+ try {
15065
+ await adapter.connect();
15066
+ await adapter.testConnection();
15067
+ console.log(t("init.connection_success"));
15068
+ } catch (error) {
15069
+ if (error instanceof ConnectionError) {
15070
+ console.error(t_vars("errors.connection_failed", { message: error.message }));
15071
+ console.error(t("init.connection_hints"));
15072
+ error.hints.forEach((hint) => console.error(` \u2022 ${hint}`));
15073
+ process.exit(1);
15074
+ }
15075
+ throw error;
15076
+ } finally {
15077
+ await adapter.disconnect();
15078
+ }
15079
+ }
15080
+ if (isV2Init) {
15081
+ await writeV2InitConfig(configPath, connectionName, sqliteConfig, permission, options.envFile);
15082
+ return;
15083
+ }
15084
+ const newConfig = configModule.merge(existingConfig, {
15085
+ connection: sqliteConfig,
15086
+ permission
15087
+ });
15088
+ const globalConfig = isGlobalConfigPath(configPath);
15089
+ const storagePath = globalConfig ? configPath : getProjectStoragePath(configPath);
15090
+ await mkdir7(storagePath, { recursive: true });
15091
+ await configModule.write(storagePath, newConfig);
15092
+ if (!globalConfig) {
15093
+ await migrateLegacyProjectEnvLocal(configPath, storagePath);
15094
+ await writeProjectBinding(configPath, storagePath);
15095
+ }
15096
+ console.log(t("init.config_saved"));
15097
+ }
15098
+ var VALID_PERMISSIONS2;
15099
+ var init_init_sqlite = __esm(() => {
15100
+ init_message_loader();
15101
+ init_config();
15102
+ init_prompts();
15103
+ init_adapters();
15104
+ init_validation();
15105
+ init_config_binding();
15106
+ init_init_shared();
15107
+ VALID_PERMISSIONS2 = ["query-only", "read-write", "data-admin", "admin"];
15108
+ });
15109
+
14725
15110
  // src/commands/init.ts
14726
15111
  var exports_init = {};
14727
15112
  __export(exports_init, {
14728
- initCommand: () => initCommand
15113
+ initCommand: () => initCommand,
15114
+ INIT_SYSTEM_CHOICES: () => INIT_SYSTEM_CHOICES
14729
15115
  });
14730
15116
  import { join as join11 } from "path";
14731
- import { mkdir as mkdir7 } from "fs/promises";
15117
+ import { mkdir as mkdir8 } from "fs/promises";
14732
15118
  async function handleRemove(configPath, name) {
14733
15119
  const storagePath = await resolveConfigStoragePath(configPath);
14734
15120
  const configFile = Bun.file(join11(storagePath, "config.json"));
@@ -14819,18 +15205,22 @@ async function initCommandHandler(options, command) {
14819
15205
  const systemFromCli = typeof options.system === "string" ? options.system : undefined;
14820
15206
  let system = systemFromCli ?? envConfig?.system ?? "postgresql";
14821
15207
  if (shouldPrompt && !options.system && !envConfig?.system) {
14822
- system = await promptUser.select(t("init.select_system"), [
14823
- "postgresql",
14824
- "mysql",
14825
- "mariadb",
14826
- "mongodb",
14827
- "redis",
14828
- "elasticsearch"
14829
- ]);
15208
+ system = await promptUser.select(t("init.select_system"), [...INIT_SYSTEM_CHOICES]);
14830
15209
  }
14831
- if (!["postgresql", "mysql", "mariadb", "mongodb", "redis", "elasticsearch"].includes(system)) {
15210
+ if (!DATABASE_SYSTEMS.includes(system)) {
14832
15211
  throw new Error(t_vars("errors.invalid_system", { system }));
14833
15212
  }
15213
+ if (system === "sqlite") {
15214
+ await handleSQLiteInit({
15215
+ options,
15216
+ configPath,
15217
+ connectionName,
15218
+ isV2Init,
15219
+ existingConfig,
15220
+ shouldPrompt
15221
+ });
15222
+ return;
15223
+ }
14834
15224
  const defaults = getDefaultsForSystem(system);
14835
15225
  if (system === "mongodb") {
14836
15226
  await handleMongoDBInit({
@@ -14871,7 +15261,7 @@ async function initCommandHandler(options, command) {
14871
15261
  "admin"
14872
15262
  ]);
14873
15263
  }
14874
- if (!VALID_PERMISSIONS2.includes(permission2)) {
15264
+ if (!VALID_PERMISSIONS3.includes(permission2)) {
14875
15265
  throw new Error(t_vars("errors.invalid_permission", { permission: permission2 }));
14876
15266
  }
14877
15267
  const newConfig2 = configModule.merge(existingConfig, {
@@ -14887,7 +15277,7 @@ async function initCommandHandler(options, command) {
14887
15277
  return;
14888
15278
  }
14889
15279
  const storagePath2 = isGlobalConfig ? configPath : getProjectStoragePath(configPath);
14890
- await mkdir7(storagePath2, { recursive: true });
15280
+ await mkdir8(storagePath2, { recursive: true });
14891
15281
  await configModule.write(storagePath2, newConfig2);
14892
15282
  if (!isGlobalConfig) {
14893
15283
  await migrateLegacyProjectEnvLocal(configPath, storagePath2);
@@ -14928,7 +15318,7 @@ async function initCommandHandler(options, command) {
14928
15318
  "admin"
14929
15319
  ]);
14930
15320
  }
14931
- if (!VALID_PERMISSIONS2.includes(permission)) {
15321
+ if (!VALID_PERMISSIONS3.includes(permission)) {
14932
15322
  throw new Error(t_vars("errors.invalid_permission", { permission }));
14933
15323
  }
14934
15324
  configForWrite = connection;
@@ -15006,7 +15396,7 @@ async function initCommandHandler(options, command) {
15006
15396
  return;
15007
15397
  }
15008
15398
  const storagePath = isGlobalConfig ? configPath : getProjectStoragePath(configPath);
15009
- await mkdir7(storagePath, { recursive: true });
15399
+ await mkdir8(storagePath, { recursive: true });
15010
15400
  await configModule.write(storagePath, newConfig);
15011
15401
  if (!isGlobalConfig) {
15012
15402
  await migrateLegacyProjectEnvLocal(configPath, storagePath);
@@ -15014,7 +15404,7 @@ async function initCommandHandler(options, command) {
15014
15404
  }
15015
15405
  console.log(t("init.config_saved"));
15016
15406
  }
15017
- var VALID_PERMISSIONS2, initCommand;
15407
+ var INIT_SYSTEM_CHOICES, VALID_PERMISSIONS3, initCommand;
15018
15408
  var init_init = __esm(() => {
15019
15409
  init_esm();
15020
15410
  init_message_loader();
@@ -15024,12 +15414,15 @@ var init_init = __esm(() => {
15024
15414
  init_prompts();
15025
15415
  init_redaction();
15026
15416
  init_adapters();
15417
+ init_types2();
15027
15418
  init_config_path();
15028
15419
  init_config_binding();
15029
15420
  init_init_shared();
15030
15421
  init_init_mongodb();
15031
- VALID_PERMISSIONS2 = ["query-only", "read-write", "data-admin", "admin"];
15032
- initCommand = new Command("init").description("Initialize dbcli configuration with .env parsing and interactive prompts").option("--host <host>", "Database host").option("--port <port>", "Database port").option("--user <user>", "Database user").option("--password <password>", "Database password").option("--name <name>", "Database name").option("--system <system>", "Database system (postgresql, mysql, mariadb, mongodb, redis, elasticsearch)").option("--cloud-id <id>", "Elasticsearch Cloud ID").option("--api-key <key>", "Elasticsearch API Key").option("--uri <uri>", "MongoDB connection URI (mongodb://user:pass@host:27017/db?authSource=admin)").option("--auth-source <authSource>", "MongoDB auth database (default: admin when user/password are set)").option("--permission <permission>", "Permission level (query-only, read-write, data-admin, admin)", "query-only").option("--use-env-refs", "Store env var references in config instead of actual values (for CI/CD or multi-env)", false).option("--env-host <var>", "Env var name for host (with --use-env-refs)").option("--env-port <var>", "Env var name for port (with --use-env-refs)").option("--env-user <var>", "Env var name for user (with --use-env-refs)").option("--env-password <var>", "Env var name for password (with --use-env-refs)").option("--env-database <var>", "Env var name for database (with --use-env-refs)").option("--skip-test", "Skip database connection test").option("--no-interactive", "Non-interactive mode (requires all values via flags)").option("--force", "Skip overwrite confirmation if .dbcli exists").option("--conn-name <name>", "Connection name (creates v2 multi-connection config)").option("--env-file <path>", "Path to env file for this connection").option("--remove <name>", "Remove a named connection").option("--rename <names>", "Rename a connection (format: old:new)").action(async (options) => {
15422
+ init_init_sqlite();
15423
+ INIT_SYSTEM_CHOICES = DATABASE_SYSTEMS;
15424
+ VALID_PERMISSIONS3 = ["query-only", "read-write", "data-admin", "admin"];
15425
+ initCommand = new Command("init").description("Initialize dbcli configuration with .env parsing and interactive prompts").option("--host <host>", "Database host").option("--port <port>", "Database port").option("--user <user>", "Database user").option("--password <password>", "Database password").option("--name <name>", "Database name").option("--system <system>", "Database system (postgresql, mysql, mariadb, mongodb, redis, elasticsearch)").option("--file <path>", "SQLite database file path (the database must already exist)").option("--cloud-id <id>", "Elasticsearch Cloud ID").option("--api-key <key>", "Elasticsearch API Key").option("--uri <uri>", "MongoDB connection URI (mongodb://user:pass@host:27017/db?authSource=admin)").option("--auth-source <authSource>", "MongoDB auth database (default: admin when user/password are set)").option("--permission <permission>", "Permission level (query-only, read-write, data-admin, admin)", "query-only").option("--use-env-refs", "Store env var references in config instead of actual values (for CI/CD or multi-env)", false).option("--env-host <var>", "Env var name for host (with --use-env-refs)").option("--env-port <var>", "Env var name for port (with --use-env-refs)").option("--env-user <var>", "Env var name for user (with --use-env-refs)").option("--env-password <var>", "Env var name for password (with --use-env-refs)").option("--env-database <var>", "Env var name for database (with --use-env-refs)").option("--skip-test", "Skip database connection test").option("--no-interactive", "Non-interactive mode (requires all values via flags)").option("--force", "Skip overwrite confirmation if .dbcli exists").option("--conn-name <name>", "Connection name (creates v2 multi-connection config)").option("--env-file <path>", "Path to env file for this connection").option("--remove <name>", "Remove a named connection").option("--rename <names>", "Rename a connection (format: old:new)").action(async (options) => {
15033
15426
  try {
15034
15427
  await initCommandHandler(options, initCommand);
15035
15428
  } catch (error) {
@@ -16976,17 +17369,22 @@ var init_formatters = __esm(() => {
16976
17369
  init_query_result_formatter();
16977
17370
  });
16978
17371
 
16979
- // src/commands/list.ts
16980
- var exports_list = {};
16981
- __export(exports_list, {
16982
- listCommand: () => listCommand
16983
- });
17372
+ // src/commands/require-sql-connection.ts
16984
17373
  function requireSqlConnection(connection) {
16985
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
17374
+ if (!SQL_DATABASE_SYSTEMS.includes(connection.system)) {
16986
17375
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
16987
17376
  }
16988
17377
  return connection;
16989
17378
  }
17379
+ var init_require_sql_connection = __esm(() => {
17380
+ init_types2();
17381
+ });
17382
+
17383
+ // src/commands/list.ts
17384
+ var exports_list = {};
17385
+ __export(exports_list, {
17386
+ listCommand: () => listCommand
17387
+ });
16990
17388
  async function listAction(options, command) {
16991
17389
  validateFormat(options.format, ALLOWED_FORMATS, "list");
16992
17390
  const config = await configModule.read(resolveConfigPath(command, options));
@@ -17136,6 +17534,7 @@ var init_list = __esm(() => {
17136
17534
  init_config_path();
17137
17535
  init_validation();
17138
17536
  init_connection_selector();
17537
+ init_require_sql_connection();
17139
17538
  ALLOWED_FORMATS = ["table", "json"];
17140
17539
  listCommand = new Command().name("list").description("List all tables in the database with metadata").option("--format <format>", "Output format: table (default) or json", "table").option("--config <path>", "Path to .dbcli config file", ".dbcli").addOption(createConnectionSelectorOption()).option("--include-system", "Elasticsearch only: include system indices whose names start with dot", false).action(listAction);
17141
17540
  });
@@ -17251,7 +17650,7 @@ var init_artifact = __esm(() => {
17251
17650
  });
17252
17651
 
17253
17652
  // src/core/verification/artifact-writer.ts
17254
- import { mkdir as mkdir8, writeFile as writeFile3, link, unlink as unlink5 } from "fs/promises";
17653
+ import { mkdir as mkdir9, writeFile as writeFile3, link, unlink as unlink5 } from "fs/promises";
17255
17654
  import { join as join12 } from "path";
17256
17655
  function pad(n, len = 2) {
17257
17656
  return String(n).padStart(len, "0");
@@ -17270,7 +17669,7 @@ function verificationArtifactFilename(artifact) {
17270
17669
  }
17271
17670
  async function writeVerificationArtifact(storageDir, artifact) {
17272
17671
  const dir = join12(storageDir, VERIFICATION_DIR_RELATIVE);
17273
- await mkdir8(dir, { recursive: true });
17672
+ await mkdir9(dir, { recursive: true });
17274
17673
  const target = join12(dir, verificationArtifactFilename(artifact));
17275
17674
  const tmp = `${target}.${process.pid}.tmp`;
17276
17675
  try {
@@ -17843,7 +18242,7 @@ var init_legacy = __esm(() => {
17843
18242
 
17844
18243
  // src/core/evidence-receipt/index.ts
17845
18244
  import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
17846
- import { link as link2, lstat as lstat4, mkdir as mkdir9, readFile as readFile3, realpath, unlink as unlink7, writeFile as writeFile4 } from "fs/promises";
18245
+ import { link as link2, lstat as lstat4, mkdir as mkdir10, readFile as readFile3, realpath, unlink as unlink7, writeFile as writeFile4 } from "fs/promises";
17847
18246
  import { basename as basename3, dirname as dirname5, relative, resolve as resolve5, sep as sep3 } from "path";
17848
18247
  function exact(value, keys, label) {
17849
18248
  if (Object.keys(value).length !== keys.length || Object.keys(value).some((key) => !keys.includes(key))) {
@@ -18123,7 +18522,7 @@ async function writeEvidenceReceipt(workspaceRoot, outputPath, receipt) {
18123
18522
  const parent = dirname5(requested);
18124
18523
  if (!inside(workspace, await existingAncestor(parent)))
18125
18524
  throw new EvidenceReceiptValidationError("output path resolves outside the workspace");
18126
- await mkdir9(parent, { recursive: true });
18525
+ await mkdir10(parent, { recursive: true });
18127
18526
  const realParent = await realpath(parent);
18128
18527
  if (!inside(workspace, realParent))
18129
18528
  throw new EvidenceReceiptValidationError("output path resolves outside the workspace");
@@ -18217,8 +18616,16 @@ var init_evidence_receipt = __esm(() => {
18217
18616
  });
18218
18617
 
18219
18618
  // src/core/saved-queries/types.ts
18220
- var SUPPORTED_CHART_TYPES, SavedQueryError;
18619
+ var ENGINE_TAGS, SUPPORTED_CHART_TYPES, SavedQueryError;
18221
18620
  var init_types4 = __esm(() => {
18621
+ ENGINE_TAGS = Object.freeze([
18622
+ "postgres",
18623
+ "mysql",
18624
+ "sqlite",
18625
+ "elasticsearch",
18626
+ "redis",
18627
+ "mongodb"
18628
+ ]);
18222
18629
  SUPPORTED_CHART_TYPES = ["line", "bar", "area", "pie"];
18223
18630
  SavedQueryError = class SavedQueryError extends Error {
18224
18631
  code;
@@ -18777,7 +19184,16 @@ function stripCommentsAndStrings2(sql, options = {}) {
18777
19184
  continue;
18778
19185
  }
18779
19186
  }
18780
- if (mysqlDialect && char === "`") {
19187
+ if (options.dialect === "sqlite" && char === "[") {
19188
+ i++;
19189
+ while (i < sql.length && sql[i] !== "]")
19190
+ i++;
19191
+ if (i < sql.length)
19192
+ i++;
19193
+ result += " ";
19194
+ continue;
19195
+ }
19196
+ if ((mysqlDialect || options.dialect === "sqlite") && char === "`") {
18781
19197
  i++;
18782
19198
  while (i < sql.length) {
18783
19199
  if (sql[i] === "`") {
@@ -18850,8 +19266,13 @@ function extractFirstKeyword(sql) {
18850
19266
  }
18851
19267
  return firstKeyword;
18852
19268
  }
18853
- function mapKeywordToType(keyword) {
19269
+ function isDeclaredAdminOnly(keyword) {
19270
+ return DECLARED_ADMIN_ONLY_KEYWORDS.has(keyword.toUpperCase());
19271
+ }
19272
+ function mapKeywordToType(keyword, dialect) {
18854
19273
  const upper = keyword.toUpperCase();
19274
+ if (upper === "REPLACE" && dialect === "sqlite")
19275
+ return "REPLACE";
18855
19276
  if (upper === "SELECT")
18856
19277
  return "SELECT";
18857
19278
  if (upper === "SHOW")
@@ -18881,7 +19302,7 @@ function mapKeywordToType(keyword) {
18881
19302
  return "UNKNOWN";
18882
19303
  }
18883
19304
  function isDestructiveOperation(type) {
18884
- return ["DELETE", "DROP", "ALTER", "TRUNCATE", "CREATE", "GRANT"].includes(type);
19305
+ return ["DELETE", "REPLACE", "DROP", "ALTER", "TRUNCATE", "CREATE", "GRANT"].includes(type);
18885
19306
  }
18886
19307
  function extractAllKeywords(sql) {
18887
19308
  const upper = sql.toUpperCase();
@@ -18948,8 +19369,10 @@ function determineConfidence(type, _keyword, _sql) {
18948
19369
  function removeParameterMarkers(sql) {
18949
19370
  return sql.replace(/\$\d+/g, "").replace(/\?/g, "");
18950
19371
  }
19372
+ var DECLARED_ADMIN_ONLY_KEYWORDS;
18951
19373
  var init_sql_analysis = __esm(() => {
18952
19374
  init_sql_lexical();
19375
+ DECLARED_ADMIN_ONLY_KEYWORDS = new Set(["PRAGMA", "VACUUM", "REINDEX"]);
18953
19376
  });
18954
19377
 
18955
19378
  // src/core/permission/rank.ts
@@ -18984,11 +19407,12 @@ __export(exports_permission_guard, {
18984
19407
  SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
18985
19408
  SQL_LOCK_CLAUSE: () => SQL_LOCK_CLAUSE,
18986
19409
  SQL_DIALECTS: () => SQL_DIALECTS,
18987
- PermissionError: () => PermissionError
19410
+ PermissionError: () => PermissionError,
19411
+ ConnectionBoundaryError: () => ConnectionBoundaryError
18988
19412
  });
18989
- function classifyStatement(sql) {
19413
+ function classifyStatement(sql, dialect) {
18990
19414
  const normalized = normalizeSQL(sql);
18991
- const stripped = stripCommentsAndStrings2(normalized);
19415
+ const stripped = stripCommentsAndStrings2(normalized, { dialect });
18992
19416
  const upper = stripped.toUpperCase();
18993
19417
  if (/^\s*ANALYZE\s+SELECT\b/i.test(stripped)) {
18994
19418
  return {
@@ -19001,7 +19425,16 @@ function classifyStatement(sql) {
19001
19425
  }
19002
19426
  const composite = detectCompositePatterns(upper);
19003
19427
  const firstKeyword = extractFirstKeyword(stripped);
19004
- const type = mapKeywordToType(firstKeyword);
19428
+ if (isDeclaredAdminOnly(firstKeyword)) {
19429
+ return {
19430
+ type: "UNKNOWN",
19431
+ isDangerous: true,
19432
+ keywords: extractAllKeywords(stripped),
19433
+ isComposite: false,
19434
+ confidence: "HIGH"
19435
+ };
19436
+ }
19437
+ const type = mapKeywordToType(firstKeyword, dialect);
19005
19438
  return {
19006
19439
  type,
19007
19440
  isDangerous: isDestructiveOperation(type),
@@ -19046,8 +19479,21 @@ function escalateHiddenWrite(sql, classification, dialect) {
19046
19479
  escalatedFrom: hidden
19047
19480
  };
19048
19481
  }
19482
+ function reachesAnotherDatabase(sql, dialect) {
19483
+ if (dialect !== "sqlite")
19484
+ return false;
19485
+ return stripCommentsAndStrings2(sql, { dialect }).split(";").some((statement) => SQLITE_CROSS_DATABASE.test(statement));
19486
+ }
19049
19487
  function checkPermission(sql, permission, dialect) {
19050
- const classification = escalateHiddenWrite(sql, classifyStatement(sql), dialect);
19488
+ if (reachesAnotherDatabase(sql, dialect)) {
19489
+ return {
19490
+ allowed: false,
19491
+ reason: "ATTACH and DETACH are refused on a SQLite connection: they reach a database file " + "outside the one this connection names, which the blacklist, audit log and schema " + "cache are all scoped to. Configure a second connection instead.",
19492
+ classification: classifyStatement(sql),
19493
+ boundary: true
19494
+ };
19495
+ }
19496
+ const classification = escalateHiddenWrite(sql, classifyStatement(sql, dialect), dialect);
19051
19497
  if (permission !== "admin" && containsMultipleStatements(sql, dialect)) {
19052
19498
  return {
19053
19499
  allowed: false,
@@ -19133,16 +19579,28 @@ function enforcePermissionForType(type, permission) {
19133
19579
  }
19134
19580
  function enforcePermission(sql, permission, dialect) {
19135
19581
  const result = checkPermission(sql, permission, dialect);
19582
+ if (result.boundary) {
19583
+ throw new ConnectionBoundaryError(result.reason, result.classification);
19584
+ }
19136
19585
  if (!result.allowed) {
19137
19586
  throw new PermissionError(result.reason, result.classification, result.requiredPermission ?? "admin");
19138
19587
  }
19139
19588
  return result.classification;
19140
19589
  }
19141
- var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, TIER_GRANTS;
19590
+ var ConnectionBoundaryError, PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, SQLITE_CROSS_DATABASE, TIER_GRANTS;
19142
19591
  var init_permission_guard = __esm(() => {
19143
19592
  init_message_loader();
19144
19593
  init_sql_analysis();
19145
19594
  init_rank();
19595
+ ConnectionBoundaryError = class ConnectionBoundaryError extends Error {
19596
+ classification;
19597
+ constructor(message, classification) {
19598
+ super(message);
19599
+ this.classification = classification;
19600
+ this.name = "ConnectionBoundaryError";
19601
+ Object.setPrototypeOf(this, ConnectionBoundaryError.prototype);
19602
+ }
19603
+ };
19146
19604
  PermissionError = class PermissionError extends Error {
19147
19605
  classification;
19148
19606
  requiredPermission;
@@ -19154,14 +19612,15 @@ var init_permission_guard = __esm(() => {
19154
19612
  Object.setPrototypeOf(this, PermissionError.prototype);
19155
19613
  }
19156
19614
  };
19157
- SQL_DIALECTS = ["postgresql", "mysql", "mariadb"];
19615
+ SQL_DIALECTS = ["postgresql", "mysql", "mariadb", "sqlite"];
19158
19616
  SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
19159
19617
  SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
19160
19618
  ESCALATABLE_READ_TYPES = new Set(["SELECT", "EXPLAIN", "DESCRIBE"]);
19619
+ SQLITE_CROSS_DATABASE = /^\s*(ATTACH|DETACH)\b/i;
19161
19620
  TIER_GRANTS = [
19162
19621
  { permission: "query-only", types: ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"] },
19163
19622
  { permission: "read-write", types: ["INSERT", "UPDATE"] },
19164
- { permission: "data-admin", types: ["DELETE"] }
19623
+ { permission: "data-admin", types: ["DELETE", "REPLACE"] }
19165
19624
  ];
19166
19625
  });
19167
19626
 
@@ -19311,7 +19770,7 @@ var init_mongodb = __esm(() => {
19311
19770
  });
19312
19771
  // src/core/saved-queries/strategies/index.ts
19313
19772
  function engineFamily(engine) {
19314
- if (engine === "postgres" || engine === "mysql")
19773
+ if (engine === "postgres" || engine === "mysql" || engine === "sqlite")
19315
19774
  return "sql";
19316
19775
  if (engine === "elasticsearch")
19317
19776
  return "es";
@@ -19470,7 +19929,7 @@ function parseScalar(s) {
19470
19929
 
19471
19930
  // src/core/saved-queries/parser.ts
19472
19931
  function familyOf(engine) {
19473
- if (engine === "postgres" || engine === "mysql")
19932
+ if (engine === "postgres" || engine === "mysql" || engine === "sqlite")
19474
19933
  return "sql";
19475
19934
  if (engine === "elasticsearch")
19476
19935
  return "es";
@@ -19796,13 +20255,14 @@ var init_parser = __esm(() => {
19796
20255
  init_types4();
19797
20256
  MAX_BYTES = 64 * 1024;
19798
20257
  VALID_TYPES = ["int", "string", "float", "bool", "date", "datetime"];
19799
- VALID_ENGINES = ["postgres", "mysql", "elasticsearch", "redis", "mongodb"];
20258
+ VALID_ENGINES = ENGINE_TAGS;
19800
20259
  INTENT_RE = /^[a-z][a-z0-9.-]*$/;
19801
20260
  ENGINE_DIALECT = {
19802
20261
  postgres: "postgresql",
19803
20262
  postgresql: "postgresql",
19804
20263
  mysql: "mysql",
19805
- mariadb: "mariadb"
20264
+ mariadb: "mariadb",
20265
+ sqlite: "sqlite"
19806
20266
  };
19807
20267
  });
19808
20268
 
@@ -20045,7 +20505,8 @@ var init_read_only = __esm(() => {
20045
20505
  DIALECT = {
20046
20506
  postgresql: "Postgresql",
20047
20507
  mysql: "MySQL",
20048
- mariadb: "MariaDB"
20508
+ mariadb: "MariaDB",
20509
+ sqlite: "Sqlite"
20049
20510
  };
20050
20511
  WRITE_STATEMENT_TYPES = new Set([
20051
20512
  "insert",
@@ -20391,7 +20852,8 @@ var init_query_draft = __esm(() => {
20391
20852
  DIALECT2 = {
20392
20853
  postgresql: "Postgresql",
20393
20854
  mysql: "MySQL",
20394
- mariadb: "MariaDB"
20855
+ mariadb: "MariaDB",
20856
+ sqlite: "Sqlite"
20395
20857
  };
20396
20858
  HASH = /^[a-f0-9]{64}$/;
20397
20859
  IDENTIFIER = /^[a-z][a-z0-9-]*$/;
@@ -21313,6 +21775,8 @@ function mapSystemToEngine(system) {
21313
21775
  return "postgres";
21314
21776
  if (system === "mysql" || system === "mariadb")
21315
21777
  return "mysql";
21778
+ if (system === "sqlite")
21779
+ return "sqlite";
21316
21780
  if (system === "mongodb")
21317
21781
  return "mongodb";
21318
21782
  if (system === "elasticsearch")
@@ -22545,13 +23009,25 @@ class BlacklistValidator {
22545
23009
  const tables = dedupe2(tableNames);
22546
23010
  const blacklistedColumns = tables.length === 0 ? this.manager.getAllBlacklistedColumns() : Array.from(new Set(tables.flatMap((table) => this.manager.getBlacklistedColumns(table))));
22547
23011
  if (blacklistedColumns.length === 0) {
22548
- return { filteredRows: rows, omittedColumns: [], reachesOmitted: () => false };
23012
+ return {
23013
+ filteredRows: rows,
23014
+ omittedColumns: [],
23015
+ cost: NO_MASKING_COST,
23016
+ reachesOmitted: () => false
23017
+ };
22549
23018
  }
23019
+ let rowsScanned = 0;
23020
+ let keysScanned = 0;
23021
+ let ruleEvaluations = 0;
23022
+ let pathSplits = 0;
23023
+ let nestedProbeRows = 0;
23024
+ let nestedGlobRows = 0;
22550
23025
  const probeNested = blacklistedColumns.some((path2) => path2.includes("."));
22551
23026
  const presentColumns = new Set(columnList);
22552
23027
  const nestedHeads = new Set;
22553
23028
  const collect2 = (record4) => {
22554
23029
  for (const key of Object.getOwnPropertyNames(record4)) {
23030
+ keysScanned += 1;
22555
23031
  presentColumns.add(key);
22556
23032
  if (!probeNested)
22557
23033
  continue;
@@ -22561,6 +23037,7 @@ class BlacklistValidator {
22561
23037
  }
22562
23038
  };
22563
23039
  for (const row of rows) {
23040
+ rowsScanned += 1;
22564
23041
  if (row === null || typeof row !== "object")
22565
23042
  continue;
22566
23043
  if (Array.isArray(row)) {
@@ -22583,6 +23060,7 @@ class BlacklistValidator {
22583
23060
  }
22584
23061
  const omitted = new Set;
22585
23062
  for (const path2 of blacklistedColumns) {
23063
+ ruleEvaluations += 1;
22586
23064
  const present = presentByFolded.get(foldFieldPath(path2));
22587
23065
  if (present !== undefined) {
22588
23066
  for (const name of present)
@@ -22594,8 +23072,17 @@ class BlacklistValidator {
22594
23072
  continue;
22595
23073
  if (!nestedHeads.has(foldFieldPath(path2.slice(0, dot))))
22596
23074
  continue;
23075
+ pathSplits += 1;
22597
23076
  const segments = path2.split(".").map(foldFieldPath);
22598
- if (rows.some((row) => hasFieldPath(row, segments, PRE_FOLDED)))
23077
+ let found = false;
23078
+ for (const row of rows) {
23079
+ nestedProbeRows += 1;
23080
+ if (hasFieldPath(row, segments, PRE_FOLDED)) {
23081
+ found = true;
23082
+ break;
23083
+ }
23084
+ }
23085
+ if (found)
22599
23086
  omitted.add(path2);
22600
23087
  }
22601
23088
  const nestedGlobs = globRules.filter((pattern) => pattern.segments.length > 1);
@@ -22606,6 +23093,7 @@ class BlacklistValidator {
22606
23093
  for (const row of rows) {
22607
23094
  if (pending.size === 0)
22608
23095
  break;
23096
+ nestedGlobRows += 1;
22609
23097
  if (row === null || typeof row !== "object")
22610
23098
  continue;
22611
23099
  collectNestedMatches(row, states, pending, (pattern) => {
@@ -22643,13 +23131,35 @@ class BlacklistValidator {
22643
23131
  }
22644
23132
  const omittedColumns = Array.from(omitted);
22645
23133
  if (omittedColumns.length === 0) {
22646
- return { filteredRows: rows, omittedColumns: [], reachesOmitted: () => false };
23134
+ return {
23135
+ filteredRows: rows,
23136
+ omittedColumns: [],
23137
+ cost: Object.freeze({
23138
+ rowsScanned,
23139
+ keysScanned,
23140
+ ruleEvaluations,
23141
+ pathSplits,
23142
+ nestedProbeRows,
23143
+ nestedGlobRows,
23144
+ nestedGlobMatches: matchedNested.length
23145
+ }),
23146
+ reachesOmitted: () => false
23147
+ };
22647
23148
  }
22648
23149
  const removalPaths = matchedNested.length === 0 ? omittedColumns : omittedColumns.filter((path2) => !matchedNested.some((p) => p.raw === path2));
22649
23150
  const filteredRows = omitFieldPaths(rows, removalPaths, matchedNested.length > 0 ? { ...FOLD_CASE2, patterns: matchedNested } : FOLD_CASE2);
22650
23151
  return {
22651
23152
  filteredRows,
22652
23153
  omittedColumns,
23154
+ cost: Object.freeze({
23155
+ rowsScanned,
23156
+ keysScanned,
23157
+ ruleEvaluations,
23158
+ pathSplits,
23159
+ nestedProbeRows,
23160
+ nestedGlobRows,
23161
+ nestedGlobMatches: matchedNested.length
23162
+ }),
22653
23163
  reachesOmitted: (path2) => {
22654
23164
  if (omittedByName(removalPaths, path2))
22655
23165
  return true;
@@ -22677,7 +23187,7 @@ class BlacklistValidator {
22677
23187
  });
22678
23188
  }
22679
23189
  }
22680
- var FOLD_CASE2, PRE_FOLDED;
23190
+ var FOLD_CASE2, PRE_FOLDED, NO_MASKING_COST;
22681
23191
  var init_blacklist_validator = __esm(() => {
22682
23192
  init_glob();
22683
23193
  init_blacklist();
@@ -22688,6 +23198,15 @@ var init_blacklist_validator = __esm(() => {
22688
23198
  init_es_index_target();
22689
23199
  FOLD_CASE2 = { caseInsensitive: true };
22690
23200
  PRE_FOLDED = { caseInsensitive: true, alreadyFolded: true };
23201
+ NO_MASKING_COST = Object.freeze({
23202
+ rowsScanned: 0,
23203
+ keysScanned: 0,
23204
+ ruleEvaluations: 0,
23205
+ pathSplits: 0,
23206
+ nestedProbeRows: 0,
23207
+ nestedGlobRows: 0,
23208
+ nestedGlobMatches: 0
23209
+ });
22691
23210
  });
22692
23211
 
22693
23212
  // src/core/health-checker.ts
@@ -22840,7 +23359,7 @@ var init_core = __esm(() => {
22840
23359
  // src/core/audit/lock.ts
22841
23360
  import { hostname } from "os";
22842
23361
  import { dirname as dirname7 } from "path";
22843
- import { mkdir as mkdir10, open as open2, rm } from "fs/promises";
23362
+ import { mkdir as mkdir11, open as open2, rm } from "fs/promises";
22844
23363
 
22845
23364
  class AuditLockManager {
22846
23365
  auditFilePath;
@@ -22898,7 +23417,7 @@ class AuditLockManager {
22898
23417
  }
22899
23418
  async tryAcquireLock(operationName) {
22900
23419
  try {
22901
- await mkdir10(dirname7(this.lockPath), { recursive: true });
23420
+ await mkdir11(dirname7(this.lockPath), { recursive: true });
22902
23421
  const lockFile = Bun.file(this.lockPath);
22903
23422
  if (await lockFile.exists()) {
22904
23423
  const lockContent = await lockFile.json();
@@ -22951,7 +23470,7 @@ var init_rotation = __esm(() => {
22951
23470
  });
22952
23471
 
22953
23472
  // src/core/audit/logger.ts
22954
- import { appendFile, mkdir as mkdir11, readFile as readFile4, stat as stat2 } from "fs/promises";
23473
+ import { appendFile, mkdir as mkdir12, readFile as readFile4, stat as stat2 } from "fs/promises";
22955
23474
  import { join as join21 } from "path";
22956
23475
  import { randomUUID as randomUUID3 } from "crypto";
22957
23476
 
@@ -23000,7 +23519,7 @@ class AuditLogger {
23000
23519
  try {
23001
23520
  const sessionId = await this.sessionIdService.resolve();
23002
23521
  this.cachedSessionId = sessionId;
23003
- await mkdir11(this.auditDir, { recursive: true });
23522
+ await mkdir12(this.auditDir, { recursive: true });
23004
23523
  if (!this.writerInitialized) {
23005
23524
  await this.syncCountersFromDisk();
23006
23525
  this.writerInitialized = true;
@@ -23105,7 +23624,7 @@ var init_logger2 = __esm(() => {
23105
23624
  });
23106
23625
 
23107
23626
  // src/core/audit/session-id.ts
23108
- import { mkdir as mkdir12, readFile as readFile5, rename as rename5, stat as stat3, writeFile as writeFile5 } from "fs/promises";
23627
+ import { mkdir as mkdir13, readFile as readFile5, rename as rename5, stat as stat3, writeFile as writeFile5 } from "fs/promises";
23109
23628
  import { randomBytes as randomBytes2 } from "crypto";
23110
23629
  import { dirname as dirname8, join as join22 } from "path";
23111
23630
  function generateSessionId(pid, nowMs) {
@@ -23138,7 +23657,7 @@ async function writeSessionIdFile(storagePath, payload) {
23138
23657
  const target = join22(storagePath, LAST_SESSION_ID_RELATIVE);
23139
23658
  const tmp = `${target}.tmp`;
23140
23659
  try {
23141
- await mkdir12(dirname8(target), { recursive: true });
23660
+ await mkdir13(dirname8(target), { recursive: true });
23142
23661
  await writeFile5(tmp, JSON.stringify(payload, null, 2), "utf8");
23143
23662
  await rename5(tmp, target);
23144
23663
  } catch {}
@@ -23259,6 +23778,18 @@ function tokenize2(sql, dialect, backslashEscapes) {
23259
23778
  }
23260
23779
  continue;
23261
23780
  }
23781
+ if (dialect === "sqlite" && char === "[") {
23782
+ i++;
23783
+ let value = "";
23784
+ while (i < sql.length && sql[i] !== "]") {
23785
+ value += sql[i];
23786
+ i++;
23787
+ }
23788
+ if (i < sql.length)
23789
+ i++;
23790
+ tokens.push({ kind: "identifier", value, quoted: true });
23791
+ continue;
23792
+ }
23262
23793
  if (char === '"' || char === "`") {
23263
23794
  const quote = char;
23264
23795
  i++;
@@ -24809,7 +25340,7 @@ var init_render_markdown = __esm(() => {
24809
25340
  });
24810
25341
 
24811
25342
  // src/core/recovery/last-envelope.ts
24812
- import { writeFile as writeFile6, readFile as readFile6, rename as rename6, mkdir as mkdir13, stat as stat4 } from "fs/promises";
25343
+ import { writeFile as writeFile6, readFile as readFile6, rename as rename6, mkdir as mkdir14, stat as stat4 } from "fs/promises";
24813
25344
  import { dirname as dirname9, join as join23 } from "path";
24814
25345
  import { randomUUID as randomUUID4 } from "crypto";
24815
25346
  function sanitizeCommandSummary(argv) {
@@ -24828,7 +25359,7 @@ async function writeLastEnvelope(cwd, envelope, argv, now = () => new Date, id =
24828
25359
  envelope
24829
25360
  };
24830
25361
  try {
24831
- await mkdir13(dirname9(target), { recursive: true });
25362
+ await mkdir14(dirname9(target), { recursive: true });
24832
25363
  await writeFile6(tmp, JSON.stringify(payload, null, 2), "utf8");
24833
25364
  await rename6(tmp, target);
24834
25365
  } catch {}
@@ -27771,7 +28302,8 @@ var init_write_gate = __esm(() => {
27771
28302
  PARSER_DIALECTS = {
27772
28303
  mysql: "MySQL",
27773
28304
  mariadb: "MariaDB",
27774
- postgresql: "Postgresql"
28305
+ postgresql: "Postgresql",
28306
+ sqlite: "Sqlite"
27775
28307
  };
27776
28308
  WRITE_TYPES = new Set([
27777
28309
  "INSERT",
@@ -28081,12 +28613,6 @@ function mongoCollectionRefs(query) {
28081
28613
  return [];
28082
28614
  }
28083
28615
  }
28084
- function requireSqlConnection2(connection) {
28085
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
28086
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
28087
- }
28088
- return connection;
28089
- }
28090
28616
  async function queryCommand(sql, options, command) {
28091
28617
  return executeQueryCommand(sql, options, command);
28092
28618
  }
@@ -28438,7 +28964,7 @@ function queryAuditTarget(system, query, options) {
28438
28964
  async function sqlQueryBranch(query, options, context, fieldSelection) {
28439
28965
  const { config } = context;
28440
28966
  const effectivePermission = context.effectivePermission ?? config.permission;
28441
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection2(config.connection));
28967
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
28442
28968
  let executionError;
28443
28969
  try {
28444
28970
  await adapter.connect();
@@ -28705,6 +29231,7 @@ var init_query = __esm(() => {
28705
29231
  init_query_fanout();
28706
29232
  init_elasticsearch2();
28707
29233
  init_slow_query_advisory();
29234
+ init_require_sql_connection();
28708
29235
  ALLOWED_FORMATS3 = ["table", "json", "csv"];
28709
29236
  defaultQueryCommandRuntime = {
28710
29237
  loadConfig: (configPath, connectionName) => configModule.read(configPath, connectionName, { loadLayeredSchema: false }),
@@ -29857,7 +30384,7 @@ class DataExecutor {
29857
30384
  return this.dbSystem;
29858
30385
  }
29859
30386
  getQuoteChar() {
29860
- return this.dbSystem === "mysql" ? "`" : '"';
30387
+ return this.dbSystem === "mysql" || this.dbSystem === "mariadb" ? "`" : '"';
29861
30388
  }
29862
30389
  checkBlacklist(operation, tableName2) {
29863
30390
  this.blacklistValidator?.checkTableBlacklist(operation, tableName2, []);
@@ -30847,12 +31374,6 @@ __export(exports_insert, {
30847
31374
  insertCommand: () => insertCommand
30848
31375
  });
30849
31376
  import crypto5 from "crypto";
30850
- function requireSqlConnection3(connection) {
30851
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
30852
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
30853
- }
30854
- return connection;
30855
- }
30856
31377
  async function readStdinJSON() {
30857
31378
  try {
30858
31379
  if (typeof Bun !== "undefined" && Bun.stdin) {
@@ -31034,11 +31555,11 @@ async function insertCommand(table, options, command) {
31034
31555
  await adapter2.disconnect();
31035
31556
  }
31036
31557
  }
31037
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection3(config.connection));
31558
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
31038
31559
  await adapter.connect();
31039
31560
  try {
31040
31561
  const schema = await adapter.getTableSchema(table);
31041
- const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
31562
+ const dbSystem = config.connection.system;
31042
31563
  const blacklistManager = new BlacklistManager(config);
31043
31564
  const blacklistValidator = new BlacklistValidator(blacklistManager);
31044
31565
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
@@ -31115,6 +31636,7 @@ var init_insert = __esm(() => {
31115
31636
  init_dml_plan();
31116
31637
  init_dml_plan4();
31117
31638
  init_integration_helper();
31639
+ init_require_sql_connection();
31118
31640
  });
31119
31641
 
31120
31642
  // src/commands/update.ts
@@ -31123,12 +31645,6 @@ __export(exports_update, {
31123
31645
  updateCommand: () => updateCommand
31124
31646
  });
31125
31647
  import crypto6 from "crypto";
31126
- function requireSqlConnection4(connection) {
31127
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
31128
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
31129
- }
31130
- return connection;
31131
- }
31132
31648
  async function updateCommand(table, options, command) {
31133
31649
  let config;
31134
31650
  try {
@@ -31342,7 +31858,7 @@ async function updateCommand(table, options, command) {
31342
31858
  } catch (error) {
31343
31859
  throw new Error(`WHERE clause parsing failed: ${error.message}`);
31344
31860
  }
31345
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection4(config.connection));
31861
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
31346
31862
  await adapter.connect();
31347
31863
  try {
31348
31864
  const schema = await adapter.getTableSchema(table);
@@ -31360,7 +31876,7 @@ async function updateCommand(table, options, command) {
31360
31876
  await writeAuditEntry(config, "update", options, auditOutcomeForMutation(cancelled, table));
31361
31877
  return;
31362
31878
  }
31363
- const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
31879
+ const dbSystem = config.connection.system;
31364
31880
  const blacklistManager = new BlacklistManager(config);
31365
31881
  const blacklistValidator = new BlacklistValidator(blacklistManager);
31366
31882
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
@@ -31441,6 +31957,7 @@ var init_update = __esm(() => {
31441
31957
  init_config_path();
31442
31958
  init_dml_plan4();
31443
31959
  init_integration_helper();
31960
+ init_require_sql_connection();
31444
31961
  });
31445
31962
 
31446
31963
  // src/commands/delete.ts
@@ -31449,12 +31966,6 @@ __export(exports_delete, {
31449
31966
  deleteCommand: () => deleteCommand
31450
31967
  });
31451
31968
  import crypto7 from "crypto";
31452
- function requireSqlConnection5(connection) {
31453
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
31454
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
31455
- }
31456
- return connection;
31457
- }
31458
31969
  async function deleteCommand(table, options, command) {
31459
31970
  let config;
31460
31971
  try {
@@ -31642,7 +32153,7 @@ async function deleteCommand(table, options, command) {
31642
32153
  } catch (error) {
31643
32154
  throw new Error(`WHERE clause parsing failed: ${error.message}`);
31644
32155
  }
31645
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection5(config.connection));
32156
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
31646
32157
  await adapter.connect();
31647
32158
  try {
31648
32159
  const schema = await adapter.getTableSchema(table);
@@ -31660,7 +32171,7 @@ async function deleteCommand(table, options, command) {
31660
32171
  await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(cancelled, table));
31661
32172
  return;
31662
32173
  }
31663
- const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
32174
+ const dbSystem = config.connection.system;
31664
32175
  const blacklistManager = new BlacklistManager(config);
31665
32176
  const blacklistValidator = new BlacklistValidator(blacklistManager);
31666
32177
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
@@ -31741,6 +32252,7 @@ var init_delete = __esm(() => {
31741
32252
  init_config_path();
31742
32253
  init_dml_plan4();
31743
32254
  init_integration_helper();
32255
+ init_require_sql_connection();
31744
32256
  });
31745
32257
 
31746
32258
  // src/adapters/elasticsearch/scroll-reader.ts
@@ -31783,12 +32295,6 @@ __export(exports_export, {
31783
32295
  buildEsExportRows: () => buildEsExportRows
31784
32296
  });
31785
32297
  import crypto8 from "crypto";
31786
- function requireSqlConnection6(connection) {
31787
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
31788
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
31789
- }
31790
- return connection;
31791
- }
31792
32298
  async function exportCommand(sql, options, command) {
31793
32299
  let config;
31794
32300
  try {
@@ -31819,7 +32325,7 @@ async function exportCommand(sql, options, command) {
31819
32325
  if (options.format === "jsonl") {
31820
32326
  throw new Error("--format jsonl is only supported on MongoDB connections");
31821
32327
  }
31822
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection6(config.connection));
32328
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
31823
32329
  await adapter.connect();
31824
32330
  const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
31825
32331
  const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, {
@@ -32179,6 +32685,7 @@ var init_export = __esm(() => {
32179
32685
  init_field_masker();
32180
32686
  init_scroll_reader();
32181
32687
  init_collection_references();
32688
+ init_require_sql_connection();
32182
32689
  SQL_PATTERN = /^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SHOW|DESCRIBE)\b/i;
32183
32690
  });
32184
32691
 
@@ -33398,15 +33905,9 @@ import { homedir as homedir2 } from "os";
33398
33905
  function looksLikeDbcliSkill(content) {
33399
33906
  return SKILL_SENTINELS.some((sentinel) => content.includes(sentinel));
33400
33907
  }
33401
- function resolveSkillSource(lang) {
33402
- if (lang === "zh-TW")
33403
- return packageAssetPath("SKILL.zh-TW.md");
33404
- return packageAssetPath("SKILL.md");
33405
- }
33406
33908
  async function skillCommand(program2, options) {
33407
33909
  try {
33408
- const lang = options.lang ?? "en";
33409
- const skillSourcePath = resolveSkillSource(lang);
33910
+ const skillSourcePath = packageAssetPath("SKILL.md");
33410
33911
  const skillFile = Bun.file(skillSourcePath);
33411
33912
  if (!await skillFile.exists()) {
33412
33913
  throw new Error(`Skill source not found: ${skillSourcePath}`);
@@ -33444,21 +33945,17 @@ async function skillCommand(program2, options) {
33444
33945
  async function checkSkillUpdates() {
33445
33946
  const outdated = [];
33446
33947
  try {
33447
- const sourceContents = [];
33448
- for (const lang of ["en", "zh-TW"]) {
33449
- const sourceFile = Bun.file(resolveSkillSource(lang));
33450
- if (await sourceFile.exists())
33451
- sourceContents.push(await sourceFile.text());
33452
- }
33453
- if (sourceContents.length === 0)
33948
+ const sourceFile = Bun.file(packageAssetPath("SKILL.md"));
33949
+ if (!await sourceFile.exists())
33454
33950
  return [];
33951
+ const sourceContent = await sourceFile.text();
33455
33952
  for (const platform of SUPPORTED_PLATFORMS) {
33456
33953
  try {
33457
33954
  const installPath = getInstallPath(platform);
33458
33955
  const installedFile = Bun.file(installPath);
33459
33956
  if (await installedFile.exists()) {
33460
33957
  const installedContent = await installedFile.text();
33461
- if (looksLikeDbcliSkill(installedContent) && !sourceContents.includes(installedContent)) {
33958
+ if (looksLikeDbcliSkill(installedContent) && installedContent !== sourceContent) {
33462
33959
  outdated.push(platform);
33463
33960
  }
33464
33961
  }
@@ -33527,15 +34024,15 @@ async function ensureDir(dirPath) {
33527
34024
  await $`mkdir -p ${dirPath}`.quiet();
33528
34025
  } catch {
33529
34026
  try {
33530
- const { mkdir: mkdir14 } = await import("fs/promises");
33531
- await mkdir14(dirPath, { recursive: true });
34027
+ const { mkdir: mkdir15 } = await import("fs/promises");
34028
+ await mkdir15(dirPath, { recursive: true });
33532
34029
  } catch {
33533
34030
  throw new Error(`Cannot create directory: ${dirPath}`);
33534
34031
  }
33535
34032
  }
33536
34033
  }
33537
34034
  function registerSkillCommand(program2) {
33538
- const skill = program2.command("skill").description(t("skill.description")).option("--install <platform>", "Install to platform directory (claude, gemini, antigravity, copilot, cursor, codex, windsurf)").option("--output <path>", "Write skill to file instead of stdout").addOption(new Option("--lang <lang>", "Source language for SKILL content").choices(["en", "zh-TW"]).default("en")).action(async (options) => {
34035
+ const skill = program2.command("skill").description(t("skill.description")).option("--install <platform>", "Install to platform directory (claude, gemini, antigravity, copilot, cursor, codex, windsurf)").option("--output <path>", "Write skill to file instead of stdout").action(async (options) => {
33539
34036
  try {
33540
34037
  await skillCommand(program2, options);
33541
34038
  } catch (error) {
@@ -33584,7 +34081,6 @@ var REFERENCE_SOURCE_PATH, SKILL_SENTINELS, SUPPORTED_PLATFORMS;
33584
34081
  var init_skill = __esm(() => {
33585
34082
  init_message_loader();
33586
34083
  init_package_root();
33587
- init_esm();
33588
34084
  init_context();
33589
34085
  init_context_v2();
33590
34086
  init_config_path();
@@ -35367,7 +35863,7 @@ var exports_check = {};
35367
35863
  __export(exports_check, {
35368
35864
  checkCommand: () => checkCommand
35369
35865
  });
35370
- function requireSqlConnection7(connection) {
35866
+ function requireSqlConnection2(connection) {
35371
35867
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
35372
35868
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
35373
35869
  }
@@ -35379,7 +35875,7 @@ async function checkAction(table, options) {
35379
35875
  if (!config.connection) {
35380
35876
  throw new Error("Database not configured. Run: dbcli init");
35381
35877
  }
35382
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection7(config.connection));
35878
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection2(config.connection));
35383
35879
  await adapter.connect();
35384
35880
  try {
35385
35881
  const checker = new HealthChecker(adapter);
@@ -37209,7 +37705,8 @@ var init_ddl = __esm(() => {
37209
37705
  DIALECT3 = {
37210
37706
  mysql: "MySQL",
37211
37707
  mariadb: "MariaDB",
37212
- postgresql: "Postgresql"
37708
+ postgresql: "Postgresql",
37709
+ sqlite: "Sqlite"
37213
37710
  };
37214
37711
  });
37215
37712
 
@@ -37912,7 +38409,7 @@ __export(exports_diff, {
37912
38409
  ORM_ALIASES: () => ORM_ALIASES
37913
38410
  });
37914
38411
  import crypto9 from "crypto";
37915
- function requireSqlConnection8(connection) {
38412
+ function requireSqlConnection3(connection) {
37916
38413
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
37917
38414
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
37918
38415
  }
@@ -38079,7 +38576,7 @@ async function diffAction(options) {
38079
38576
  console.error("Elasticsearch \u4E0D\u652F\u63F4 diff \u6307\u4EE4\uFF1B\u5982\u9700\u8FFD\u8E64 mapping \u8B8A\u5316\uFF0C\u8ACB\u6539\u7528 schema \u4E26\u81EA\u884C\u7248\u672C\u5316 mapping JSON");
38080
38577
  process.exit(1);
38081
38578
  }
38082
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection8(config.connection));
38579
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection3(config.connection));
38083
38580
  await adapter.connect();
38084
38581
  try {
38085
38582
  const tables = await adapter.listTables();
@@ -38205,9 +38702,11 @@ var init_status2 = __esm(() => {
38205
38702
  const blacklistTables = config.blacklist?.tables ?? [];
38206
38703
  const blacklistColumns = config.blacklist?.columns ?? {};
38207
38704
  const columnCount = Object.values(blacklistColumns).reduce((sum, cols) => sum + cols.length, 0);
38705
+ const file = config.connection.file;
38208
38706
  const status2 = {
38209
38707
  permission: config.permission,
38210
38708
  system: config.connection.system,
38709
+ ...file !== undefined && { file },
38211
38710
  blacklist: {
38212
38711
  tables: blacklistTables.length,
38213
38712
  columns: columnCount
@@ -38217,6 +38716,8 @@ var init_status2 = __esm(() => {
38217
38716
  if (options.format === "text") {
38218
38717
  console.log(`Permission: ${status2.permission}`);
38219
38718
  console.log(`System: ${status2.system}`);
38719
+ if (file !== undefined)
38720
+ console.log(`File: ${file}`);
38220
38721
  console.log(`Blacklist: ${status2.blacklist.tables} table(s), ${status2.blacklist.columns} column(s)`);
38221
38722
  console.log(`Version: ${status2.version}`);
38222
38723
  } else {
@@ -39953,7 +40454,8 @@ var init_parse_sql = __esm(() => {
39953
40454
  DIALECT4 = {
39954
40455
  mysql: "MySQL",
39955
40456
  mariadb: "MariaDB",
39956
- postgresql: "Postgresql"
40457
+ postgresql: "Postgresql",
40458
+ sqlite: "Sqlite"
39957
40459
  };
39958
40460
  });
39959
40461
 
@@ -41833,18 +42335,14 @@ __export(exports_doctor, {
41833
42335
  resolveSchemaLastUpdated: () => resolveSchemaLastUpdated,
41834
42336
  renderMongoSamplingLine: () => renderMongoSamplingLine,
41835
42337
  doctorCommand: () => doctorCommand,
42338
+ collectSQLiteDoctorResults: () => collectSQLiteDoctorResults,
41836
42339
  collectMongoDoctorResults: () => collectMongoDoctorResults,
41837
42340
  collectElasticsearchDoctorResults: () => collectElasticsearchDoctorResults,
41838
42341
  buildDoctorRemediationPlan: () => buildDoctorRemediationPlan
41839
42342
  });
41840
42343
  import { join as join37 } from "path";
41841
42344
  import { resolveSrv as resolveSrv2 } from "dns/promises";
41842
- function requireSqlConnection9(connection) {
41843
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
41844
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
41845
- }
41846
- return connection;
41847
- }
42345
+ import { access, constants } from "fs/promises";
41848
42346
  function renderMongoSamplingLine(meta) {
41849
42347
  if (!meta.sampleMethod)
41850
42348
  return "";
@@ -42084,6 +42582,46 @@ async function collectElasticsearchDoctorResults(config, runtimeOverrides = {})
42084
42582
  }
42085
42583
  return results;
42086
42584
  }
42585
+ async function collectSQLiteDoctorResults(config, runtimeOverrides = {}) {
42586
+ const results = [];
42587
+ const file = config.connection.file ?? "";
42588
+ const fileResults = await runDoctorChecks.checkSQLiteFile(file, config.permission ?? "query-only");
42589
+ results.push(...fileResults);
42590
+ if (fileResults.some((r) => r.status === "error"))
42591
+ return results;
42592
+ const adapter = AdapterFactory.createSqlAdapter(config.connection);
42593
+ try {
42594
+ await adapter.connect();
42595
+ results.push({
42596
+ group: "Connection & Data",
42597
+ label: "Connection",
42598
+ status: "pass",
42599
+ message: `Connected to sqlite ${file}`
42600
+ });
42601
+ try {
42602
+ const tables = await adapter.listTables();
42603
+ const tableColumns = new Map;
42604
+ for (const t2 of tables) {
42605
+ tableColumns.set(t2.name, t2.columns.map((c) => c.name));
42606
+ }
42607
+ if (config.blacklistedColumns) {
42608
+ results.push(runDoctorChecks.checkBlacklistCompleteness(tableColumns, config.blacklistedColumns));
42609
+ }
42610
+ results.push(runDoctorChecks.checkLargeTables(tables, "sqlite"));
42611
+ } catch {}
42612
+ results.push(runDoctorChecks.checkSchemaCacheFreshness(config.metadata?.schemaLastUpdated ?? null));
42613
+ } catch (error) {
42614
+ results.push({
42615
+ group: "Connection & Data",
42616
+ label: "Connection",
42617
+ status: "error",
42618
+ message: `Connection failed: ${error.message}`
42619
+ });
42620
+ } finally {
42621
+ await adapter.disconnect();
42622
+ }
42623
+ return results;
42624
+ }
42087
42625
  var ALLOWED_FORMATS15, SENSITIVE_PATTERNS, runDoctorChecks, defaultDoctorCollectorRuntime, doctorCommand;
42088
42626
  var init_doctor = __esm(() => {
42089
42627
  init_esm();
@@ -42103,6 +42641,7 @@ var init_doctor = __esm(() => {
42103
42641
  init_runtime_info();
42104
42642
  init_config();
42105
42643
  init_integration_helper();
42644
+ init_require_sql_connection();
42106
42645
  ALLOWED_FORMATS15 = ["text", "json"];
42107
42646
  SENSITIVE_PATTERNS = [
42108
42647
  "password",
@@ -42336,6 +42875,83 @@ var init_doctor = __esm(() => {
42336
42875
  }
42337
42876
  }
42338
42877
  },
42878
+ async checkSQLiteFile(file, permission) {
42879
+ const group = "Connection & Data";
42880
+ if (!file) {
42881
+ return [
42882
+ {
42883
+ group,
42884
+ label: "Database file",
42885
+ status: "error",
42886
+ message: 'Connection has no file path \u2014 run "dbcli init --system sqlite"'
42887
+ }
42888
+ ];
42889
+ }
42890
+ try {
42891
+ await access(file, constants.F_OK);
42892
+ } catch {
42893
+ return [
42894
+ {
42895
+ group,
42896
+ label: "Database file",
42897
+ status: "error",
42898
+ message: `Database file not found: ${file}`,
42899
+ details: { file }
42900
+ }
42901
+ ];
42902
+ }
42903
+ try {
42904
+ await access(file, constants.R_OK);
42905
+ } catch {
42906
+ return [
42907
+ {
42908
+ group,
42909
+ label: "Database file",
42910
+ status: "error",
42911
+ message: `Database file is not readable: ${file}`,
42912
+ details: { file }
42913
+ }
42914
+ ];
42915
+ }
42916
+ const results = [
42917
+ {
42918
+ group,
42919
+ label: "Database file",
42920
+ status: "pass",
42921
+ message: `Readable: ${file}`,
42922
+ details: { file }
42923
+ }
42924
+ ];
42925
+ if (permission === "query-only") {
42926
+ results.push({
42927
+ group,
42928
+ label: "Database file writable",
42929
+ status: "pass",
42930
+ message: "Not required \u2014 the connection is query-only",
42931
+ details: { file, permission }
42932
+ });
42933
+ return results;
42934
+ }
42935
+ try {
42936
+ await access(file, constants.W_OK);
42937
+ results.push({
42938
+ group,
42939
+ label: "Database file writable",
42940
+ status: "pass",
42941
+ message: `Writable: ${file}`,
42942
+ details: { file, permission }
42943
+ });
42944
+ } catch {
42945
+ results.push({
42946
+ group,
42947
+ label: "Database file writable",
42948
+ status: "error",
42949
+ message: `Database file is not writable, but permission '${permission}' allows writes: ${file}`,
42950
+ details: { file, permission }
42951
+ });
42952
+ }
42953
+ return results;
42954
+ },
42339
42955
  checkLargeTables(tables, system = "postgresql") {
42340
42956
  const large = tables.filter((t2) => (t2.estimatedRowCount ?? 0) > 1e6);
42341
42957
  if (large.length === 0) {
@@ -42482,13 +43098,18 @@ var init_doctor = __esm(() => {
42482
43098
  ...config,
42483
43099
  blacklistedColumns
42484
43100
  }));
43101
+ } else if (config.connection.system === "sqlite") {
43102
+ results.push(...await collectSQLiteDoctorResults({
43103
+ ...config,
43104
+ blacklistedColumns
43105
+ }));
42485
43106
  } else if (config.connection.system === "elasticsearch") {
42486
43107
  results.push(...await collectElasticsearchDoctorResults({
42487
43108
  ...config,
42488
43109
  blacklistedColumns
42489
43110
  }));
42490
43111
  } else {
42491
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection9(config.connection));
43112
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
42492
43113
  await adapter.connect();
42493
43114
  results.push({
42494
43115
  group: "Connection & Data",
@@ -42804,7 +43425,7 @@ var exports_queries_rename = {};
42804
43425
  __export(exports_queries_rename, {
42805
43426
  queriesRename: () => queriesRename
42806
43427
  });
42807
- import { rename as rename7, mkdir as mkdir14 } from "fs/promises";
43428
+ import { rename as rename7, mkdir as mkdir15 } from "fs/promises";
42808
43429
  import { dirname as dirname12 } from "path";
42809
43430
  async function queriesRename(oldName, newName, options = {}) {
42810
43431
  if (!oldName.startsWith("@") || !newName.startsWith("@")) {
@@ -42826,7 +43447,7 @@ async function queriesRename(oldName, newName, options = {}) {
42826
43447
  for (const v of local) {
42827
43448
  const dst = snippetKeyToFile(cwd, newName, "local");
42828
43449
  const dstWithSuffix = preserveEngineSuffix(v.query.file, dst);
42829
- await mkdir14(dirname12(dstWithSuffix), { recursive: true });
43450
+ await mkdir15(dirname12(dstWithSuffix), { recursive: true });
42830
43451
  await rename7(v.query.file, dstWithSuffix);
42831
43452
  await rewriteFrontmatterName(dstWithSuffix, newName.slice(1));
42832
43453
  console.log(`renamed ${v.query.file} \u2192 ${dstWithSuffix}`);
@@ -42852,7 +43473,7 @@ var exports_queries_copy = {};
42852
43473
  __export(exports_queries_copy, {
42853
43474
  queriesCopy: () => queriesCopy
42854
43475
  });
42855
- import { mkdir as mkdir15, copyFile } from "fs/promises";
43476
+ import { mkdir as mkdir16, copyFile } from "fs/promises";
42856
43477
  import { dirname as dirname13, basename as basename5 } from "path";
42857
43478
  async function queriesCopy(src, dst, options = {}) {
42858
43479
  if (!src.startsWith("@") || !dst.startsWith("@")) {
@@ -42871,7 +43492,7 @@ async function queriesCopy(src, dst, options = {}) {
42871
43492
  }
42872
43493
  for (const v of variants) {
42873
43494
  const dstFile = mapEngineSuffix(v.query.file, snippetKeyToFile(cwd, dst, "local"));
42874
- await mkdir15(dirname13(dstFile), { recursive: true });
43495
+ await mkdir16(dirname13(dstFile), { recursive: true });
42875
43496
  await copyFile(v.query.file, dstFile);
42876
43497
  console.log(`copied ${v.query.file} \u2192 ${dstFile}`);
42877
43498
  }
@@ -42889,7 +43510,7 @@ var exports_queries_import = {};
42889
43510
  __export(exports_queries_import, {
42890
43511
  queriesImport: () => queriesImport
42891
43512
  });
42892
- import { stat as stat8, mkdir as mkdir16, copyFile as copyFile2 } from "fs/promises";
43513
+ import { stat as stat8, mkdir as mkdir17, copyFile as copyFile2 } from "fs/promises";
42893
43514
  import { basename as basename6, join as join38, extname as extname2 } from "path";
42894
43515
  import { confirm as confirm3 } from "@inquirer/prompts";
42895
43516
  async function queriesImport(filePath, options = {}) {
@@ -42903,7 +43524,7 @@ async function queriesImport(filePath, options = {}) {
42903
43524
  const key = "@" + baseName;
42904
43525
  parseSavedQuery({ key, file: filePath, source: "local", text: text4 });
42905
43526
  const targetDir = join38(cwd, ".dbcli/queries");
42906
- await mkdir16(targetDir, { recursive: true });
43527
+ await mkdir17(targetDir, { recursive: true });
42907
43528
  const target = join38(targetDir, basename6(filePath));
42908
43529
  if (await Bun.file(target).exists()) {
42909
43530
  if (!options.force) {
@@ -42968,7 +43589,7 @@ __export(exports_queries, {
42968
43589
  queriesCheck: () => queriesCheck,
42969
43590
  listSnippetsForEngine: () => listSnippetsForEngine
42970
43591
  });
42971
- import { mkdir as mkdir17, writeFile as writeFile8 } from "fs/promises";
43592
+ import { mkdir as mkdir18, writeFile as writeFile8 } from "fs/promises";
42972
43593
  import { dirname as dirname14 } from "path";
42973
43594
  import { spawn } from "child_process";
42974
43595
  async function deriveEngine(command) {
@@ -43089,7 +43710,7 @@ async function queriesNew(name, options) {
43089
43710
  process.exit(1);
43090
43711
  return;
43091
43712
  }
43092
- await mkdir17(dirname14(file), { recursive: true });
43713
+ await mkdir18(dirname14(file), { recursive: true });
43093
43714
  await writeFile8(file, scaffold(name), "utf8");
43094
43715
  console.log(`Created ${file}`);
43095
43716
  if (source === "shared")
@@ -43165,7 +43786,7 @@ async function queriesSearch(keywords, options, command) {
43165
43786
  process.exit(2);
43166
43787
  return;
43167
43788
  }
43168
- const allowed = ["postgres", "mysql", "redis", "elasticsearch", "mongodb", "all"];
43789
+ const allowed = [...ENGINE_TAGS, "all"];
43169
43790
  if (options.engine && !allowed.includes(options.engine)) {
43170
43791
  console.error(`Unknown engine '${options.engine}'. Allowed: ${allowed.join(", ")}.`);
43171
43792
  process.exit(2);
@@ -43257,7 +43878,7 @@ async function queriesSuggest(intent, options, command) {
43257
43878
  process.exit(2);
43258
43879
  return;
43259
43880
  }
43260
- const allowed = ["postgres", "mysql", "redis", "elasticsearch", "mongodb", "all"];
43881
+ const allowed = [...ENGINE_TAGS, "all"];
43261
43882
  if (options.engine && !allowed.includes(options.engine)) {
43262
43883
  console.error(`Unknown engine '${options.engine}'. Allowed: ${allowed.join(", ")}.`);
43263
43884
  process.exit(2);
@@ -43866,7 +44487,8 @@ var init_parse = __esm(() => {
43866
44487
  DIALECT5 = {
43867
44488
  mysql: "MySQL",
43868
44489
  mariadb: "MariaDB",
43869
- postgresql: "Postgresql"
44490
+ postgresql: "Postgresql",
44491
+ sqlite: "Sqlite"
43870
44492
  };
43871
44493
  });
43872
44494
 
@@ -44998,12 +45620,14 @@ var init_not_in_nullable = __esm(() => {
44998
45620
  "VARIANCE",
44999
45621
  "VAR_POP",
45000
45622
  "VAR_SAMP"
45001
- ])
45623
+ ]),
45624
+ sqlite: new Set([...COMMON_NULL_ON_EMPTY_AGGREGATES, "GROUP_CONCAT", "STRING_AGG"])
45002
45625
  };
45003
45626
  NON_NULL_AGGREGATES = {
45004
45627
  postgresql: new Set(["COUNT", "REGR_COUNT"]),
45005
45628
  mysql: new Set(["COUNT", "BIT_AND", "BIT_OR", "BIT_XOR"]),
45006
- mariadb: new Set(["COUNT", "BIT_AND", "BIT_OR", "BIT_XOR"])
45629
+ mariadb: new Set(["COUNT", "BIT_AND", "BIT_OR", "BIT_XOR"]),
45630
+ sqlite: new Set(["COUNT", "TOTAL"])
45007
45631
  };
45008
45632
  notInNullableRule = {
45009
45633
  name: "not-in-nullable",
@@ -45866,7 +46490,7 @@ __export(exports_snapshot, {
45866
46490
  snapshotCommand: () => snapshotCommand
45867
46491
  });
45868
46492
  import { join as join39 } from "path";
45869
- function requireSqlConnection10(connection) {
46493
+ function requireSqlConnection4(connection) {
45870
46494
  if (!SQL_SYSTEMS6.includes(connection.system)) {
45871
46495
  throw new Error(`snapshot currently supports SQL engines only, got: ${connection.system}`);
45872
46496
  }
@@ -45914,7 +46538,7 @@ var init_snapshot = __esm(() => {
45914
46538
  const snippets = await loadSnippets(dirs);
45915
46539
  sql = resolveByName(snippets, query.slice(1), engine).query.sqlBody;
45916
46540
  }
45917
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection10(config.connection));
46541
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection4(config.connection));
45918
46542
  await adapter.connect();
45919
46543
  try {
45920
46544
  const blacklistManager = new BlacklistManager(config);
@@ -46150,7 +46774,7 @@ __export(exports_assert, {
46150
46774
  assertCommand: () => assertCommand
46151
46775
  });
46152
46776
  import { basename as basename7 } from "path";
46153
- function requireSqlConnection11(connection) {
46777
+ function requireSqlConnection5(connection) {
46154
46778
  if (!SQL_SYSTEMS7.includes(connection.system)) {
46155
46779
  throw new Error(`assert currently supports SQL engines only, got: ${connection.system}`);
46156
46780
  }
@@ -46205,7 +46829,7 @@ var init_assert = __esm(() => {
46205
46829
  console.error("Database not configured. Run: dbcli init");
46206
46830
  process.exit(1);
46207
46831
  }
46208
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection11(config.connection));
46832
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection5(config.connection));
46209
46833
  await adapter.connect();
46210
46834
  let auditRef = null;
46211
46835
  let verdict2;
@@ -47590,7 +48214,7 @@ __export(exports_verify, {
47590
48214
  verifyCommand: () => verifyCommand,
47591
48215
  BUILTIN_VERIFY_SCENARIOS: () => BUILTIN_VERIFY_SCENARIOS
47592
48216
  });
47593
- function requireSqlConnection12(connection, scenario2) {
48217
+ function requireSqlConnection6(connection, scenario2) {
47594
48218
  if (!SQL_SYSTEMS8.includes(connection.system)) {
47595
48219
  throw new Error(`verify ${scenario2} currently supports SQL engines only, got: ${connection.system}`);
47596
48220
  }
@@ -48276,7 +48900,7 @@ async function executeScenario(def, options, command) {
48276
48900
  console.error("Database not configured. Run: dbcli init");
48277
48901
  process.exit(1);
48278
48902
  }
48279
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection12(config.connection, def.name));
48903
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection6(config.connection, def.name));
48280
48904
  await adapter.connect();
48281
48905
  const runners = def.createRunners({ adapter, config, options, targetTable: input.table }, input);
48282
48906
  if (!input.afterWrite) {
@@ -50151,7 +50775,7 @@ __export(exports_shell, {
50151
50775
  import { createInterface as createInterface2 } from "readline";
50152
50776
  import { join as join40 } from "path";
50153
50777
  import { homedir as homedir3 } from "os";
50154
- function requireSqlConnection13(connection) {
50778
+ function requireSqlConnection7(connection) {
50155
50779
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
50156
50780
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
50157
50781
  }
@@ -50196,7 +50820,7 @@ async function runShell(options, configPath) {
50196
50820
  const connectionOpts = config.connection;
50197
50821
  const mongoInner = isMongoDB ? AdapterFactory.createMongoDBAdapter(config) : null;
50198
50822
  const redisInner = isRedis ? AdapterFactory.createRedisAdapter(config) : null;
50199
- const adapter = isMongoDB ? new MongoShellAdapter(mongoInner) : isRedis ? new RedisShellAdapter(redisInner) : AdapterFactory.createSqlAdapter(requireSqlConnection13(connectionOpts));
50823
+ const adapter = isMongoDB ? new MongoShellAdapter(mongoInner) : isRedis ? new RedisShellAdapter(redisInner) : AdapterFactory.createSqlAdapter(requireSqlConnection7(connectionOpts));
50200
50824
  try {
50201
50825
  await adapter.connect();
50202
50826
  } catch (error) {
@@ -51011,7 +51635,7 @@ __export(exports_migrate, {
51011
51635
  runDDL: () => runDDL,
51012
51636
  migrateCommand: () => migrateCommand
51013
51637
  });
51014
- function requireSqlConnection14(connection) {
51638
+ function requireSqlConnection8(connection) {
51015
51639
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
51016
51640
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
51017
51641
  }
@@ -51035,7 +51659,7 @@ async function runDDL(operation, opts, command) {
51035
51659
  console.error("\u6B64\u547D\u4EE4\u76EE\u524D\u4E0D\u652F\u63F4 Elasticsearch\uFF1B\u5982\u9700\u5EFA\u7ACB\u7D22\u5F15\u6216\u8ABF\u6574 mapping\uFF0C\u8ACB\u6539\u7528\u5916\u90E8\u5DE5\u5177");
51036
51660
  process.exit(1);
51037
51661
  }
51038
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection14(config.connection));
51662
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection8(config.connection));
51039
51663
  const isDryRun = !opts.execute;
51040
51664
  if (!isDryRun) {
51041
51665
  await adapter.connect();
@@ -51277,17 +51901,23 @@ async function switchDefault(configPath, name, config, options = {}) {
51277
51901
  function listConnectionsForDisplay(config) {
51278
51902
  return Object.entries(config.connections).map(([name, conn]) => {
51279
51903
  const marker = name === config.default ? "*" : " ";
51904
+ const environmentLabel = conn.environment ? ` [${conn.environment}]` : "";
51905
+ const file = conn.file;
51906
+ if (file !== undefined) {
51907
+ const rendered = typeof file === "object" ? `\${${file.$env}}` : file;
51908
+ return `${marker} ${name.padEnd(12)}${environmentLabel} ${conn.system.padEnd(12)} ${rendered}`;
51909
+ }
51280
51910
  const host = typeof conn.host === "object" ? `\${${conn.host.$env}}` : conn.host;
51281
51911
  const port = typeof conn.port === "object" ? `\${${conn.port.$env}}` : conn.port;
51282
51912
  const db = typeof conn.database === "object" ? `\${${conn.database.$env}}` : conn.database;
51283
- const environment = conn.environment ? ` [${conn.environment}]` : "";
51284
- return `${marker} ${name.padEnd(12)}${environment} ${conn.system.padEnd(12)} ${host}:${port}/${db}`;
51913
+ return `${marker} ${name.padEnd(12)}${environmentLabel} ${conn.system.padEnd(12)} ${host}:${port}/${db}`;
51285
51914
  });
51286
51915
  }
51287
51916
  function listConnectionIdentities(config) {
51288
51917
  return Object.entries(config.connections).map(([name, conn]) => {
51289
51918
  const host = typeof conn.host === "string" && conn.host.length > 0 ? conn.host : null;
51290
51919
  const database = typeof conn.database === "string" && conn.database.length > 0 ? conn.database : null;
51920
+ const file = conn.file;
51291
51921
  return {
51292
51922
  name,
51293
51923
  environment: conn.environment ?? null,
@@ -51298,6 +51928,7 @@ function listConnectionIdentities(config) {
51298
51928
  port: host !== null && typeof conn.port === "number" ? conn.port : null
51299
51929
  },
51300
51930
  database,
51931
+ ...typeof file === "string" && { file },
51301
51932
  isDefault: name === config.default
51302
51933
  };
51303
51934
  });
@@ -51398,7 +52029,7 @@ var init_config_v2_mutations = __esm(() => {
51398
52029
  });
51399
52030
 
51400
52031
  // src/core/env-file-writer.ts
51401
- import { chmod as chmod2, mkdir as mkdir18, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
52032
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
51402
52033
  import { dirname as dirname16 } from "path";
51403
52034
  function escapeForRegExp(value) {
51404
52035
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -51411,7 +52042,7 @@ async function upsertEnvVar(envPath, varName, value) {
51411
52042
  const code = cause.code;
51412
52043
  if (code !== "ENOENT")
51413
52044
  throw cause;
51414
- await mkdir18(dirname16(envPath), { recursive: true });
52045
+ await mkdir19(dirname16(envPath), { recursive: true });
51415
52046
  }
51416
52047
  const line = `${varName}="${value}"`;
51417
52048
  const existing = new RegExp(`^${escapeForRegExp(varName)}=.*$`, "m");
@@ -51716,7 +52347,7 @@ var init_sql_metadata = __esm(() => {
51716
52347
  });
51717
52348
 
51718
52349
  // src/proxy/events.ts
51719
- import { appendFile as appendFile2, mkdir as mkdir19, readFile as readFile10, stat as stat9 } from "fs/promises";
52350
+ import { appendFile as appendFile2, mkdir as mkdir20, readFile as readFile10, stat as stat9 } from "fs/promises";
51720
52351
  import { dirname as dirname17 } from "path";
51721
52352
  function hasSql(e) {
51722
52353
  return e.type === "query_observed" || e.type === "query_completed" || e.type === "query_errored";
@@ -51756,7 +52387,7 @@ class EventWriter {
51756
52387
  }
51757
52388
  async writeInternal(event) {
51758
52389
  if (!this.dirEnsured) {
51759
- await mkdir19(dirname17(this.path), { recursive: true });
52390
+ await mkdir20(dirname17(this.path), { recursive: true });
51760
52391
  this.dirEnsured = true;
51761
52392
  }
51762
52393
  if (!this.initialized) {
@@ -54168,7 +54799,7 @@ __export(exports_backfill, {
54168
54799
  backfillCommand: () => backfillCommand
54169
54800
  });
54170
54801
  import { dirname as dirname18, resolve as resolve12 } from "path";
54171
- import { mkdir as mkdir20 } from "fs/promises";
54802
+ import { mkdir as mkdir21 } from "fs/promises";
54172
54803
  function identityFor(config, name) {
54173
54804
  const connection = config.connections[name];
54174
54805
  if (!connection) {
@@ -54218,7 +54849,7 @@ var init_backfill = __esm(() => {
54218
54849
  return;
54219
54850
  }
54220
54851
  const out = resolve12(options.out ?? `.dbcli/backfills/${artifact2.source.sha256.slice(0, 12)}.json`);
54221
- await mkdir20(dirname18(out), { recursive: true });
54852
+ await mkdir21(dirname18(out), { recursive: true });
54222
54853
  await Bun.write(out, JSON.stringify(artifact2, null, 2) + `
54223
54854
  `);
54224
54855
  console.log(JSON.stringify({ path: out, artifact: artifact2 }, null, 2));
@@ -54361,7 +54992,7 @@ var init_legacy2 = __esm(() => {
54361
54992
 
54362
54993
  // src/core/evidence-pack/index.ts
54363
54994
  import { createHash as createHash10, randomUUID as randomUUID6 } from "crypto";
54364
- import { link as link3, lstat as lstat7, mkdir as mkdir21, realpath as realpath4, unlink as unlink8, writeFile as writeFile10 } from "fs/promises";
54995
+ import { link as link3, lstat as lstat7, mkdir as mkdir22, realpath as realpath4, unlink as unlink8, writeFile as writeFile10 } from "fs/promises";
54365
54996
  import { basename as basename9, dirname as dirname19, relative as relative6, resolve as resolve13, sep as sep8 } from "path";
54366
54997
  function isRecord6(value) {
54367
54998
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -54645,7 +55276,7 @@ async function writeEvidencePack(workspaceRoot, outputPath, pack) {
54645
55276
  if (!isInside(workspace, existingAncestor2) && existingAncestor2 !== workspace) {
54646
55277
  throw new EvidencePackValidationError("output path resolves outside the workspace");
54647
55278
  }
54648
- await mkdir21(parent, { recursive: true });
55279
+ await mkdir22(parent, { recursive: true });
54649
55280
  const realParent = await realpath4(parent);
54650
55281
  if (!isInside(workspace, realParent) && realParent !== workspace) {
54651
55282
  throw new EvidencePackValidationError("output path resolves outside the workspace");
@@ -55290,9 +55921,9 @@ function assessImpact(input) {
55290
55921
  ...nodes.map((node) => node.reference),
55291
55922
  ...contractResult.metrics.map((metric) => `metric:${metric.name}`)
55292
55923
  ]);
55293
- const access = dataAccessFindings(change, references, input.dataAccess.value, input.blockedIdentifiers);
55294
- findings.push(...access.findings);
55295
- if (access.redacted)
55924
+ const access2 = dataAccessFindings(change, references, input.dataAccess.value, input.blockedIdentifiers);
55925
+ findings.push(...access2.findings);
55926
+ if (access2.redacted)
55296
55927
  addGap(gaps, "REDACTED_PROTECTED_SUBJECT");
55297
55928
  }
55298
55929
  }
@@ -56087,7 +56718,7 @@ __export(exports_impact, {
56087
56718
  impactCommand: () => impactCommand
56088
56719
  });
56089
56720
  import { randomUUID as randomUUID7 } from "crypto";
56090
- import { link as link4, lstat as lstat9, mkdir as mkdir22, realpath as realpath6, unlink as unlink9, writeFile as writeFile11 } from "fs/promises";
56721
+ import { link as link4, lstat as lstat9, mkdir as mkdir23, realpath as realpath6, unlink as unlink9, writeFile as writeFile11 } from "fs/promises";
56091
56722
  import { basename as basename10, dirname as dirname20, relative as relative8, resolve as resolve15, sep as sep10 } from "path";
56092
56723
  function collectOption3(value, previous) {
56093
56724
  return [...previous, value];
@@ -56308,7 +56939,7 @@ async function resolveOutput(workspaceRoot, outputPath) {
56308
56939
  const existingAncestor2 = await realExistingAncestor2(parent);
56309
56940
  if (!isInside2(workspace, existingAncestor2) && existingAncestor2 !== workspace)
56310
56941
  throw new Error("output path must stay inside the workspace");
56311
- await mkdir22(parent, { recursive: true });
56942
+ await mkdir23(parent, { recursive: true });
56312
56943
  const realParent = await realpath6(parent);
56313
56944
  if (!isInside2(workspace, realParent) && realParent !== workspace)
56314
56945
  throw new Error("output path must stay inside the workspace");
@@ -57178,7 +57809,7 @@ __export(exports_completion, {
57178
57809
  });
57179
57810
  import { join as join46 } from "path";
57180
57811
  import { homedir as homedir4 } from "os";
57181
- import { mkdir as mkdir23 } from "fs/promises";
57812
+ import { mkdir as mkdir24 } from "fs/promises";
57182
57813
  function resolveHome() {
57183
57814
  return process.env.HOME ?? homedir4();
57184
57815
  }
@@ -57451,7 +58082,7 @@ async function installCompletion(shell, script) {
57451
58082
  const targetPath = getInstallPath2(shell);
57452
58083
  if (shell === "fish") {
57453
58084
  const dir = join46(resolveHome(), ".config", "fish", "completions");
57454
- await mkdir23(dir, { recursive: true });
58085
+ await mkdir24(dir, { recursive: true });
57455
58086
  await Bun.file(targetPath).write(script);
57456
58087
  console.log(colors.success(`\u2713 Fish completion installed to ${targetPath}`));
57457
58088
  return;