@carllee1983/dbcli 9.0.0 → 9.1.1

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: "9.0.0",
2090
+ version: "9.1.1",
2091
2091
  description: "Database CLI for AI agents",
2092
2092
  type: "module",
2093
2093
  publishConfig: {
@@ -2173,9 +2173,10 @@ var init_package = __esm(() => {
2173
2173
  "core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
2174
2174
  "plan:check": "bun run scripts/check-plan-acceptance.ts",
2175
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",
2176
2177
  typecheck: "tsc --noEmit --pretty false",
2177
2178
  "typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
2178
- "test:perf": "bun test ./tests/perf/*.bench.ts",
2179
+ "test:perf": "bun test --timeout 30000 ./tests/perf/*.bench.ts",
2179
2180
  lint: "eslint src tests scripts --ext .ts --max-warnings=0",
2180
2181
  "format:check": "prettier --check .",
2181
2182
  format: "prettier --write .",
@@ -6406,13 +6407,27 @@ function parseStatementTimeoutOption(value) {
6406
6407
  }
6407
6408
  return parsed;
6408
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
+ }
6409
6424
  function validateFormat(value, allowedFormats, commandName) {
6410
6425
  if (!allowedFormats.includes(value)) {
6411
6426
  const allowed = allowedFormats.join(", ");
6412
6427
  throw new Error(`Invalid format "${value}" for ${commandName}. Allowed: ${allowed}`);
6413
6428
  }
6414
6429
  }
6415
- 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;
6416
6431
  var init_validation = __esm(() => {
6417
6432
  init_zod();
6418
6433
  EnvRefSchema = exports_external.object({
@@ -6449,6 +6464,21 @@ var init_validation = __esm(() => {
6449
6464
  database: StringOrEnvRef,
6450
6465
  ...TimeoutField
6451
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
+ });
6452
6482
  RedisConnectionConfigSchema = exports_external.object({
6453
6483
  system: exports_external.literal("redis"),
6454
6484
  host: StringOrEnvRef,
@@ -6475,6 +6505,7 @@ var init_validation = __esm(() => {
6475
6505
  });
6476
6506
  ConnectionConfigSchema = exports_external.union([
6477
6507
  SqlConnectionConfigSchema,
6508
+ SqliteConnectionConfigSchema,
6478
6509
  MongoDBConnectionConfigSchema,
6479
6510
  RedisConnectionConfigSchema,
6480
6511
  ElasticsearchConnectionConfigSchema
@@ -6545,6 +6576,11 @@ var init_validation = __esm(() => {
6545
6576
  envFile: exports_external.string().optional(),
6546
6577
  environment: EnvironmentLabelSchema
6547
6578
  });
6579
+ SqliteNamedConnectionSchema = SqliteConnectionConfigSchema.extend({
6580
+ permission: PermissionSchema,
6581
+ envFile: exports_external.string().optional(),
6582
+ environment: EnvironmentLabelSchema
6583
+ });
6548
6584
  MongoDBNamedConnectionSchema = MongoDBConnectionConfigSchema.extend({
6549
6585
  permission: PermissionSchema,
6550
6586
  envFile: exports_external.string().optional(),
@@ -6562,6 +6598,7 @@ var init_validation = __esm(() => {
6562
6598
  });
6563
6599
  NamedConnectionUnion = exports_external.union([
6564
6600
  SqlNamedConnectionSchema,
6601
+ SqliteNamedConnectionSchema,
6565
6602
  MongoDBNamedConnectionSchema,
6566
6603
  RedisNamedConnectionSchema,
6567
6604
  ElasticsearchNamedConnectionSchema
@@ -8815,7 +8852,8 @@ var init_messages = __esm(() => {
8815
8852
  connection_not_found: "Connection '{name}' does not exist",
8816
8853
  cannot_remove_last: "Cannot remove the last connection",
8817
8854
  connection_already_exists: "Connection '{name}' already exists",
8818
- 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: "
8819
8857
  },
8820
8858
  inspect: {
8821
8859
  description: "Read-only snapshot of connection, permission, blacklist, objects, snippets, and suggested next commands.",
@@ -8959,7 +8997,7 @@ var init_messages = __esm(() => {
8959
8997
  elasticsearch_requires_level: "Elasticsearch {type} operation requires {minimum} permission or higher (current level: {permission})",
8960
8998
  escalated_write_requires_admin: "This statement opens as a read but contains an executable {keyword}. A write hidden inside a read statement requires admin permission (current level: {permission}).",
8961
8999
  multiple_statements_refused: "SQL containing multiple statements is refused below admin permission, because only the first statement determines the permission check. Run each statement separately.",
8962
- unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires read-write+ for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.",
9000
+ unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires admin for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.",
8963
9001
  permission_denied_reason: "Permission denied: {reason}",
8964
9002
  table_not_found: "Table not found: {table}",
8965
9003
  unsupported_lang: "Language '{lang}' not supported, using English",
@@ -8984,7 +9022,8 @@ Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactiv
8984
9022
  env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
8985
9023
  Set {envKey} in .env or environment variables.
8986
9024
  Hint: run 'export {envKey}=<value>' and retry`,
8987
- 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."
8988
9027
  },
8989
9028
  success: {
8990
9029
  inserted: "Successfully inserted {count} row(s)",
@@ -9149,7 +9188,8 @@ var init_messages2 = __esm(() => {
9149
9188
  connection_not_found: "\u9023\u7DDA '{name}' \u4E0D\u5B58\u5728",
9150
9189
  cannot_remove_last: "\u7121\u6CD5\u79FB\u9664\u6700\u5F8C\u4E00\u500B\u9023\u7DDA",
9151
9190
  connection_already_exists: "\u9023\u7DDA '{name}' \u5DF2\u5B58\u5728",
9152
- 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"
9153
9193
  },
9154
9194
  inspect: {
9155
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",
@@ -9293,7 +9333,7 @@ var init_messages2 = __esm(() => {
9293
9333
  elasticsearch_requires_level: "Elasticsearch {type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
9294
9334
  escalated_write_requires_admin: "\u6B64\u8A9E\u53E5\u8868\u9762\u4E0A\u662F\u8B80\u53D6\uFF0C\u4F46\u5BE6\u969B\u5305\u542B\u53EF\u57F7\u884C\u7684 {keyword}\u3002\u8B80\u53D6\u8A9E\u53E5\u4E2D\u96B1\u85CF\u5BEB\u5165\u64CD\u4F5C\u9700\u8981 admin \u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09\u3002",
9295
9335
  multiple_statements_refused: "\u4F4E\u65BC admin \u6B0A\u9650\u6642\u62D2\u7D55\u57F7\u884C\u5305\u542B\u591A\u500B\u8A9E\u53E5\u7684 SQL\uFF0C\u56E0\u70BA\u6B0A\u9650\u6AA2\u67E5\u53EA\u6703\u4F9D\u64DA\u7B2C\u4E00\u500B\u8A9E\u53E5\u5224\u65B7\u3002\u8ACB\u5C07\u5404\u8A9E\u53E5\u5206\u958B\u57F7\u884C\u3002",
9296
- unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\u81F3\u5C11\u9700\u8981 read-write \u4EE5\u4E0A\u6B0A\u9650\u3002\u82E5\u9019\u5176\u5BE6\u662F\u5408\u6CD5\u7684\u552F\u8B80\u8A9E\u53E5\uFF0C\u8ACB\u81F3 https://github.com/CarlLee1983/dbcli/issues \u63D0\u51FA\u8B70\u984C\u3002",
9336
+ unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\u9700\u8981 admin \u6B0A\u9650\u3002\u82E5\u9019\u5176\u5BE6\u662F\u5408\u6CD5\u7684\u552F\u8B80\u8A9E\u53E5\uFF0C\u8ACB\u81F3 https://github.com/CarlLee1983/dbcli/issues \u63D0\u51FA\u8B70\u984C\u3002",
9297
9337
  permission_denied_reason: "\u6B0A\u9650\u88AB\u62D2\uFF1A{reason}",
9298
9338
  table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
9299
9339
  unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
@@ -9318,7 +9358,8 @@ var init_messages2 = __esm(() => {
9318
9358
  env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
9319
9359
  \u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
9320
9360
  \u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`,
9321
- 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"
9322
9363
  },
9323
9364
  success: {
9324
9365
  inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
@@ -10246,16 +10287,23 @@ var init_redaction = __esm(() => {
10246
10287
  });
10247
10288
 
10248
10289
  // src/adapters/types.ts
10249
- var DATABASE_SYSTEMS, ConnectionError;
10290
+ var DATABASE_SYSTEMS, SQL_DATABASE_SYSTEMS, ConnectionError;
10250
10291
  var init_types2 = __esm(() => {
10251
10292
  DATABASE_SYSTEMS = Object.freeze([
10252
10293
  "postgresql",
10253
10294
  "mysql",
10254
10295
  "mariadb",
10296
+ "sqlite",
10255
10297
  "mongodb",
10256
10298
  "redis",
10257
10299
  "elasticsearch"
10258
10300
  ]);
10301
+ SQL_DATABASE_SYSTEMS = Object.freeze([
10302
+ "postgresql",
10303
+ "mysql",
10304
+ "mariadb",
10305
+ "sqlite"
10306
+ ]);
10259
10307
  ConnectionError = class ConnectionError extends Error {
10260
10308
  code;
10261
10309
  hints;
@@ -10416,6 +10464,38 @@ var init_capabilities = __esm(() => {
10416
10464
  ...SQL_BASE,
10417
10465
  check: cap("limited", "readonly", "SQL-only and strongest on MySQL/MariaDB.")
10418
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
+ }),
10419
10499
  mysql: Object.freeze(SQL_BASE),
10420
10500
  mariadb: Object.freeze(SQL_BASE),
10421
10501
  mongodb: Object.freeze({
@@ -10915,7 +10995,7 @@ var init_encoding = __esm(() => {
10915
10995
 
10916
10996
  // src/adapters/identifier-quote.ts
10917
10997
  function quoteCharFor(dialect) {
10918
- return dialect === "postgresql" ? '"' : "`";
10998
+ return dialect === "postgresql" || dialect === "sqlite" ? '"' : "`";
10919
10999
  }
10920
11000
  function assertQuotable(name, what) {
10921
11001
  if (name.length === 0) {
@@ -11624,6 +11704,208 @@ var init_mysql_adapter = __esm(() => {
11624
11704
  init_connection_error_message();
11625
11705
  });
11626
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
+
11627
11909
  // src/adapters/server-side-script.ts
11628
11910
  function isElasticsearchScriptKey(key, parentKey) {
11629
11911
  if (key === "script" || key === "script_fields")
@@ -14267,6 +14549,8 @@ class AdapterFactory {
14267
14549
  case "mysql":
14268
14550
  case "mariadb":
14269
14551
  return new MySQLAdapter(options);
14552
+ case "sqlite":
14553
+ return new SQLiteAdapter(options);
14270
14554
  default:
14271
14555
  throw new Error(`createSqlAdapter requires a SQL system, got: ${options.system}`);
14272
14556
  }
@@ -14290,6 +14574,7 @@ class AdapterFactory {
14290
14574
  case "postgresql":
14291
14575
  case "mysql":
14292
14576
  case "mariadb":
14577
+ case "sqlite":
14293
14578
  return AdapterFactory.createSqlAdapter(options);
14294
14579
  case "redis":
14295
14580
  return AdapterFactory.createRedisAdapter(config);
@@ -14306,6 +14591,7 @@ class AdapterFactory {
14306
14591
  case "postgresql":
14307
14592
  case "mysql":
14308
14593
  case "mariadb":
14594
+ case "sqlite":
14309
14595
  return AdapterFactory.createSqlAdapter(options);
14310
14596
  case "mongodb":
14311
14597
  case "redis":
@@ -14350,6 +14636,7 @@ class AdapterFactory {
14350
14636
  var init_factory = __esm(() => {
14351
14637
  init_postgresql_adapter();
14352
14638
  init_mysql_adapter();
14639
+ init_sqlite_adapter();
14353
14640
  init_mongodb_adapter();
14354
14641
  init_redis_adapter();
14355
14642
  init_elasticsearch_adapter();
@@ -14721,13 +15008,113 @@ var init_init_mongodb = __esm(() => {
14721
15008
  VALID_PERMISSIONS = ["query-only", "read-write", "data-admin", "admin"];
14722
15009
  });
14723
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
+
14724
15110
  // src/commands/init.ts
14725
15111
  var exports_init = {};
14726
15112
  __export(exports_init, {
14727
- initCommand: () => initCommand
15113
+ initCommand: () => initCommand,
15114
+ INIT_SYSTEM_CHOICES: () => INIT_SYSTEM_CHOICES
14728
15115
  });
14729
15116
  import { join as join11 } from "path";
14730
- import { mkdir as mkdir7 } from "fs/promises";
15117
+ import { mkdir as mkdir8 } from "fs/promises";
14731
15118
  async function handleRemove(configPath, name) {
14732
15119
  const storagePath = await resolveConfigStoragePath(configPath);
14733
15120
  const configFile = Bun.file(join11(storagePath, "config.json"));
@@ -14818,18 +15205,22 @@ async function initCommandHandler(options, command) {
14818
15205
  const systemFromCli = typeof options.system === "string" ? options.system : undefined;
14819
15206
  let system = systemFromCli ?? envConfig?.system ?? "postgresql";
14820
15207
  if (shouldPrompt && !options.system && !envConfig?.system) {
14821
- system = await promptUser.select(t("init.select_system"), [
14822
- "postgresql",
14823
- "mysql",
14824
- "mariadb",
14825
- "mongodb",
14826
- "redis",
14827
- "elasticsearch"
14828
- ]);
15208
+ system = await promptUser.select(t("init.select_system"), [...INIT_SYSTEM_CHOICES]);
14829
15209
  }
14830
- if (!["postgresql", "mysql", "mariadb", "mongodb", "redis", "elasticsearch"].includes(system)) {
15210
+ if (!DATABASE_SYSTEMS.includes(system)) {
14831
15211
  throw new Error(t_vars("errors.invalid_system", { system }));
14832
15212
  }
15213
+ if (system === "sqlite") {
15214
+ await handleSQLiteInit({
15215
+ options,
15216
+ configPath,
15217
+ connectionName,
15218
+ isV2Init,
15219
+ existingConfig,
15220
+ shouldPrompt
15221
+ });
15222
+ return;
15223
+ }
14833
15224
  const defaults = getDefaultsForSystem(system);
14834
15225
  if (system === "mongodb") {
14835
15226
  await handleMongoDBInit({
@@ -14870,7 +15261,7 @@ async function initCommandHandler(options, command) {
14870
15261
  "admin"
14871
15262
  ]);
14872
15263
  }
14873
- if (!VALID_PERMISSIONS2.includes(permission2)) {
15264
+ if (!VALID_PERMISSIONS3.includes(permission2)) {
14874
15265
  throw new Error(t_vars("errors.invalid_permission", { permission: permission2 }));
14875
15266
  }
14876
15267
  const newConfig2 = configModule.merge(existingConfig, {
@@ -14886,7 +15277,7 @@ async function initCommandHandler(options, command) {
14886
15277
  return;
14887
15278
  }
14888
15279
  const storagePath2 = isGlobalConfig ? configPath : getProjectStoragePath(configPath);
14889
- await mkdir7(storagePath2, { recursive: true });
15280
+ await mkdir8(storagePath2, { recursive: true });
14890
15281
  await configModule.write(storagePath2, newConfig2);
14891
15282
  if (!isGlobalConfig) {
14892
15283
  await migrateLegacyProjectEnvLocal(configPath, storagePath2);
@@ -14927,7 +15318,7 @@ async function initCommandHandler(options, command) {
14927
15318
  "admin"
14928
15319
  ]);
14929
15320
  }
14930
- if (!VALID_PERMISSIONS2.includes(permission)) {
15321
+ if (!VALID_PERMISSIONS3.includes(permission)) {
14931
15322
  throw new Error(t_vars("errors.invalid_permission", { permission }));
14932
15323
  }
14933
15324
  configForWrite = connection;
@@ -15005,7 +15396,7 @@ async function initCommandHandler(options, command) {
15005
15396
  return;
15006
15397
  }
15007
15398
  const storagePath = isGlobalConfig ? configPath : getProjectStoragePath(configPath);
15008
- await mkdir7(storagePath, { recursive: true });
15399
+ await mkdir8(storagePath, { recursive: true });
15009
15400
  await configModule.write(storagePath, newConfig);
15010
15401
  if (!isGlobalConfig) {
15011
15402
  await migrateLegacyProjectEnvLocal(configPath, storagePath);
@@ -15013,7 +15404,7 @@ async function initCommandHandler(options, command) {
15013
15404
  }
15014
15405
  console.log(t("init.config_saved"));
15015
15406
  }
15016
- var VALID_PERMISSIONS2, initCommand;
15407
+ var INIT_SYSTEM_CHOICES, VALID_PERMISSIONS3, initCommand;
15017
15408
  var init_init = __esm(() => {
15018
15409
  init_esm();
15019
15410
  init_message_loader();
@@ -15023,12 +15414,15 @@ var init_init = __esm(() => {
15023
15414
  init_prompts();
15024
15415
  init_redaction();
15025
15416
  init_adapters();
15417
+ init_types2();
15026
15418
  init_config_path();
15027
15419
  init_config_binding();
15028
15420
  init_init_shared();
15029
15421
  init_init_mongodb();
15030
- VALID_PERMISSIONS2 = ["query-only", "read-write", "data-admin", "admin"];
15031
- 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) => {
15032
15426
  try {
15033
15427
  await initCommandHandler(options, initCommand);
15034
15428
  } catch (error) {
@@ -16975,17 +17369,22 @@ var init_formatters = __esm(() => {
16975
17369
  init_query_result_formatter();
16976
17370
  });
16977
17371
 
16978
- // src/commands/list.ts
16979
- var exports_list = {};
16980
- __export(exports_list, {
16981
- listCommand: () => listCommand
16982
- });
17372
+ // src/commands/require-sql-connection.ts
16983
17373
  function requireSqlConnection(connection) {
16984
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
17374
+ if (!SQL_DATABASE_SYSTEMS.includes(connection.system)) {
16985
17375
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
16986
17376
  }
16987
17377
  return connection;
16988
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
+ });
16989
17388
  async function listAction(options, command) {
16990
17389
  validateFormat(options.format, ALLOWED_FORMATS, "list");
16991
17390
  const config = await configModule.read(resolveConfigPath(command, options));
@@ -17135,6 +17534,7 @@ var init_list = __esm(() => {
17135
17534
  init_config_path();
17136
17535
  init_validation();
17137
17536
  init_connection_selector();
17537
+ init_require_sql_connection();
17138
17538
  ALLOWED_FORMATS = ["table", "json"];
17139
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);
17140
17540
  });
@@ -17250,7 +17650,7 @@ var init_artifact = __esm(() => {
17250
17650
  });
17251
17651
 
17252
17652
  // src/core/verification/artifact-writer.ts
17253
- 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";
17254
17654
  import { join as join12 } from "path";
17255
17655
  function pad(n, len = 2) {
17256
17656
  return String(n).padStart(len, "0");
@@ -17269,7 +17669,7 @@ function verificationArtifactFilename(artifact) {
17269
17669
  }
17270
17670
  async function writeVerificationArtifact(storageDir, artifact) {
17271
17671
  const dir = join12(storageDir, VERIFICATION_DIR_RELATIVE);
17272
- await mkdir8(dir, { recursive: true });
17672
+ await mkdir9(dir, { recursive: true });
17273
17673
  const target = join12(dir, verificationArtifactFilename(artifact));
17274
17674
  const tmp = `${target}.${process.pid}.tmp`;
17275
17675
  try {
@@ -17842,7 +18242,7 @@ var init_legacy = __esm(() => {
17842
18242
 
17843
18243
  // src/core/evidence-receipt/index.ts
17844
18244
  import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
17845
- 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";
17846
18246
  import { basename as basename3, dirname as dirname5, relative, resolve as resolve5, sep as sep3 } from "path";
17847
18247
  function exact(value, keys, label) {
17848
18248
  if (Object.keys(value).length !== keys.length || Object.keys(value).some((key) => !keys.includes(key))) {
@@ -18122,7 +18522,7 @@ async function writeEvidenceReceipt(workspaceRoot, outputPath, receipt) {
18122
18522
  const parent = dirname5(requested);
18123
18523
  if (!inside(workspace, await existingAncestor(parent)))
18124
18524
  throw new EvidenceReceiptValidationError("output path resolves outside the workspace");
18125
- await mkdir9(parent, { recursive: true });
18525
+ await mkdir10(parent, { recursive: true });
18126
18526
  const realParent = await realpath(parent);
18127
18527
  if (!inside(workspace, realParent))
18128
18528
  throw new EvidenceReceiptValidationError("output path resolves outside the workspace");
@@ -18216,8 +18616,16 @@ var init_evidence_receipt = __esm(() => {
18216
18616
  });
18217
18617
 
18218
18618
  // src/core/saved-queries/types.ts
18219
- var SUPPORTED_CHART_TYPES, SavedQueryError;
18619
+ var ENGINE_TAGS, SUPPORTED_CHART_TYPES, SavedQueryError;
18220
18620
  var init_types4 = __esm(() => {
18621
+ ENGINE_TAGS = Object.freeze([
18622
+ "postgres",
18623
+ "mysql",
18624
+ "sqlite",
18625
+ "elasticsearch",
18626
+ "redis",
18627
+ "mongodb"
18628
+ ]);
18221
18629
  SUPPORTED_CHART_TYPES = ["line", "bar", "area", "pie"];
18222
18630
  SavedQueryError = class SavedQueryError extends Error {
18223
18631
  code;
@@ -18311,6 +18719,7 @@ function paramErr(spec, raw, expected) {
18311
18719
  function rewriteToBind(sqlBody, params, engine) {
18312
18720
  const masked = stripCommentsAndStrings(sqlBody);
18313
18721
  const order = [];
18722
+ const occurrences = [];
18314
18723
  const indexByName = new Map;
18315
18724
  let m;
18316
18725
  NAME_RE.lastIndex = 0;
@@ -18329,19 +18738,18 @@ function rewriteToBind(sqlBody, params, engine) {
18329
18738
  const start = m.index;
18330
18739
  const end = start + m[0].length;
18331
18740
  outSql += sqlBody.slice(cursor, start);
18332
- const idx = indexByName.get(name);
18333
- outSql += engine === "postgres" ? `$${idx}` : "?";
18741
+ if (engine === "postgres") {
18742
+ outSql += `$${indexByName.get(name)}`;
18743
+ } else {
18744
+ outSql += "?";
18745
+ occurrences.push(name);
18746
+ }
18334
18747
  cursor = end;
18335
18748
  }
18336
18749
  outSql += sqlBody.slice(cursor);
18337
- const undeclared = [];
18338
- const values = order.map((n) => {
18339
- if (!Object.prototype.hasOwnProperty.call(params, n)) {
18340
- undeclared.push(n);
18341
- return null;
18342
- }
18343
- return params[n];
18344
- });
18750
+ const undeclared = order.filter((n) => !Object.prototype.hasOwnProperty.call(params, n));
18751
+ const bindOrder = engine === "postgres" ? order : occurrences;
18752
+ const values = bindOrder.map((n) => Object.prototype.hasOwnProperty.call(params, n) ? params[n] : null);
18345
18753
  return { sql: outSql, values, undeclared };
18346
18754
  }
18347
18755
  var NAME_RE;
@@ -18776,7 +19184,16 @@ function stripCommentsAndStrings2(sql, options = {}) {
18776
19184
  continue;
18777
19185
  }
18778
19186
  }
18779
- 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 === "`") {
18780
19197
  i++;
18781
19198
  while (i < sql.length) {
18782
19199
  if (sql[i] === "`") {
@@ -18849,8 +19266,13 @@ function extractFirstKeyword(sql) {
18849
19266
  }
18850
19267
  return firstKeyword;
18851
19268
  }
18852
- function mapKeywordToType(keyword) {
19269
+ function isDeclaredAdminOnly(keyword) {
19270
+ return DECLARED_ADMIN_ONLY_KEYWORDS.has(keyword.toUpperCase());
19271
+ }
19272
+ function mapKeywordToType(keyword, dialect) {
18853
19273
  const upper = keyword.toUpperCase();
19274
+ if (upper === "REPLACE" && dialect === "sqlite")
19275
+ return "REPLACE";
18854
19276
  if (upper === "SELECT")
18855
19277
  return "SELECT";
18856
19278
  if (upper === "SHOW")
@@ -18880,7 +19302,7 @@ function mapKeywordToType(keyword) {
18880
19302
  return "UNKNOWN";
18881
19303
  }
18882
19304
  function isDestructiveOperation(type) {
18883
- return ["DELETE", "DROP", "ALTER", "TRUNCATE", "CREATE", "GRANT"].includes(type);
19305
+ return ["DELETE", "REPLACE", "DROP", "ALTER", "TRUNCATE", "CREATE", "GRANT"].includes(type);
18884
19306
  }
18885
19307
  function extractAllKeywords(sql) {
18886
19308
  const upper = sql.toUpperCase();
@@ -18947,8 +19369,10 @@ function determineConfidence(type, _keyword, _sql) {
18947
19369
  function removeParameterMarkers(sql) {
18948
19370
  return sql.replace(/\$\d+/g, "").replace(/\?/g, "");
18949
19371
  }
19372
+ var DECLARED_ADMIN_ONLY_KEYWORDS;
18950
19373
  var init_sql_analysis = __esm(() => {
18951
19374
  init_sql_lexical();
19375
+ DECLARED_ADMIN_ONLY_KEYWORDS = new Set(["PRAGMA", "VACUUM", "REINDEX"]);
18952
19376
  });
18953
19377
 
18954
19378
  // src/core/permission/rank.ts
@@ -18983,11 +19407,12 @@ __export(exports_permission_guard, {
18983
19407
  SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
18984
19408
  SQL_LOCK_CLAUSE: () => SQL_LOCK_CLAUSE,
18985
19409
  SQL_DIALECTS: () => SQL_DIALECTS,
18986
- PermissionError: () => PermissionError
19410
+ PermissionError: () => PermissionError,
19411
+ ConnectionBoundaryError: () => ConnectionBoundaryError
18987
19412
  });
18988
- function classifyStatement(sql) {
19413
+ function classifyStatement(sql, dialect) {
18989
19414
  const normalized = normalizeSQL(sql);
18990
- const stripped = stripCommentsAndStrings2(normalized);
19415
+ const stripped = stripCommentsAndStrings2(normalized, { dialect });
18991
19416
  const upper = stripped.toUpperCase();
18992
19417
  if (/^\s*ANALYZE\s+SELECT\b/i.test(stripped)) {
18993
19418
  return {
@@ -19000,7 +19425,16 @@ function classifyStatement(sql) {
19000
19425
  }
19001
19426
  const composite = detectCompositePatterns(upper);
19002
19427
  const firstKeyword = extractFirstKeyword(stripped);
19003
- 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);
19004
19438
  return {
19005
19439
  type,
19006
19440
  isDangerous: isDestructiveOperation(type),
@@ -19045,8 +19479,21 @@ function escalateHiddenWrite(sql, classification, dialect) {
19045
19479
  escalatedFrom: hidden
19046
19480
  };
19047
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
+ }
19048
19487
  function checkPermission(sql, permission, dialect) {
19049
- 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);
19050
19497
  if (permission !== "admin" && containsMultipleStatements(sql, dialect)) {
19051
19498
  return {
19052
19499
  allowed: false,
@@ -19101,7 +19548,7 @@ function checkPermissionForClassification(classification, permission) {
19101
19548
  allowed: false,
19102
19549
  reason: isUnknown ? t("errors.unknown_statement_query_only") : refusalReason(classification.type, permission),
19103
19550
  classification,
19104
- requiredPermission: isUnknown ? "read-write" : minimumPermissionFor(classification.type)
19551
+ requiredPermission: minimumPermissionFor(classification.type)
19105
19552
  };
19106
19553
  }
19107
19554
  return {
@@ -19132,16 +19579,28 @@ function enforcePermissionForType(type, permission) {
19132
19579
  }
19133
19580
  function enforcePermission(sql, permission, dialect) {
19134
19581
  const result = checkPermission(sql, permission, dialect);
19582
+ if (result.boundary) {
19583
+ throw new ConnectionBoundaryError(result.reason, result.classification);
19584
+ }
19135
19585
  if (!result.allowed) {
19136
19586
  throw new PermissionError(result.reason, result.classification, result.requiredPermission ?? "admin");
19137
19587
  }
19138
19588
  return result.classification;
19139
19589
  }
19140
- 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;
19141
19591
  var init_permission_guard = __esm(() => {
19142
19592
  init_message_loader();
19143
19593
  init_sql_analysis();
19144
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
+ };
19145
19604
  PermissionError = class PermissionError extends Error {
19146
19605
  classification;
19147
19606
  requiredPermission;
@@ -19153,14 +19612,15 @@ var init_permission_guard = __esm(() => {
19153
19612
  Object.setPrototypeOf(this, PermissionError.prototype);
19154
19613
  }
19155
19614
  };
19156
- SQL_DIALECTS = ["postgresql", "mysql", "mariadb"];
19615
+ SQL_DIALECTS = ["postgresql", "mysql", "mariadb", "sqlite"];
19157
19616
  SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
19158
19617
  SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
19159
19618
  ESCALATABLE_READ_TYPES = new Set(["SELECT", "EXPLAIN", "DESCRIBE"]);
19619
+ SQLITE_CROSS_DATABASE = /^\s*(ATTACH|DETACH)\b/i;
19160
19620
  TIER_GRANTS = [
19161
19621
  { permission: "query-only", types: ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"] },
19162
19622
  { permission: "read-write", types: ["INSERT", "UPDATE"] },
19163
- { permission: "data-admin", types: ["DELETE"] }
19623
+ { permission: "data-admin", types: ["DELETE", "REPLACE"] }
19164
19624
  ];
19165
19625
  });
19166
19626
 
@@ -19310,7 +19770,7 @@ var init_mongodb = __esm(() => {
19310
19770
  });
19311
19771
  // src/core/saved-queries/strategies/index.ts
19312
19772
  function engineFamily(engine) {
19313
- if (engine === "postgres" || engine === "mysql")
19773
+ if (engine === "postgres" || engine === "mysql" || engine === "sqlite")
19314
19774
  return "sql";
19315
19775
  if (engine === "elasticsearch")
19316
19776
  return "es";
@@ -19469,7 +19929,7 @@ function parseScalar(s) {
19469
19929
 
19470
19930
  // src/core/saved-queries/parser.ts
19471
19931
  function familyOf(engine) {
19472
- if (engine === "postgres" || engine === "mysql")
19932
+ if (engine === "postgres" || engine === "mysql" || engine === "sqlite")
19473
19933
  return "sql";
19474
19934
  if (engine === "elasticsearch")
19475
19935
  return "es";
@@ -19795,13 +20255,14 @@ var init_parser = __esm(() => {
19795
20255
  init_types4();
19796
20256
  MAX_BYTES = 64 * 1024;
19797
20257
  VALID_TYPES = ["int", "string", "float", "bool", "date", "datetime"];
19798
- VALID_ENGINES = ["postgres", "mysql", "elasticsearch", "redis", "mongodb"];
20258
+ VALID_ENGINES = ENGINE_TAGS;
19799
20259
  INTENT_RE = /^[a-z][a-z0-9.-]*$/;
19800
20260
  ENGINE_DIALECT = {
19801
20261
  postgres: "postgresql",
19802
20262
  postgresql: "postgresql",
19803
20263
  mysql: "mysql",
19804
- mariadb: "mariadb"
20264
+ mariadb: "mariadb",
20265
+ sqlite: "sqlite"
19805
20266
  };
19806
20267
  });
19807
20268
 
@@ -20044,7 +20505,8 @@ var init_read_only = __esm(() => {
20044
20505
  DIALECT = {
20045
20506
  postgresql: "Postgresql",
20046
20507
  mysql: "MySQL",
20047
- mariadb: "MariaDB"
20508
+ mariadb: "MariaDB",
20509
+ sqlite: "Sqlite"
20048
20510
  };
20049
20511
  WRITE_STATEMENT_TYPES = new Set([
20050
20512
  "insert",
@@ -20390,7 +20852,8 @@ var init_query_draft = __esm(() => {
20390
20852
  DIALECT2 = {
20391
20853
  postgresql: "Postgresql",
20392
20854
  mysql: "MySQL",
20393
- mariadb: "MariaDB"
20855
+ mariadb: "MariaDB",
20856
+ sqlite: "Sqlite"
20394
20857
  };
20395
20858
  HASH = /^[a-f0-9]{64}$/;
20396
20859
  IDENTIFIER = /^[a-z][a-z0-9-]*$/;
@@ -21312,6 +21775,8 @@ function mapSystemToEngine(system) {
21312
21775
  return "postgres";
21313
21776
  if (system === "mysql" || system === "mariadb")
21314
21777
  return "mysql";
21778
+ if (system === "sqlite")
21779
+ return "sqlite";
21315
21780
  if (system === "mongodb")
21316
21781
  return "mongodb";
21317
21782
  if (system === "elasticsearch")
@@ -22544,13 +23009,25 @@ class BlacklistValidator {
22544
23009
  const tables = dedupe2(tableNames);
22545
23010
  const blacklistedColumns = tables.length === 0 ? this.manager.getAllBlacklistedColumns() : Array.from(new Set(tables.flatMap((table) => this.manager.getBlacklistedColumns(table))));
22546
23011
  if (blacklistedColumns.length === 0) {
22547
- return { filteredRows: rows, omittedColumns: [], reachesOmitted: () => false };
23012
+ return {
23013
+ filteredRows: rows,
23014
+ omittedColumns: [],
23015
+ cost: NO_MASKING_COST,
23016
+ reachesOmitted: () => false
23017
+ };
22548
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;
22549
23025
  const probeNested = blacklistedColumns.some((path2) => path2.includes("."));
22550
23026
  const presentColumns = new Set(columnList);
22551
23027
  const nestedHeads = new Set;
22552
23028
  const collect2 = (record4) => {
22553
23029
  for (const key of Object.getOwnPropertyNames(record4)) {
23030
+ keysScanned += 1;
22554
23031
  presentColumns.add(key);
22555
23032
  if (!probeNested)
22556
23033
  continue;
@@ -22560,6 +23037,7 @@ class BlacklistValidator {
22560
23037
  }
22561
23038
  };
22562
23039
  for (const row of rows) {
23040
+ rowsScanned += 1;
22563
23041
  if (row === null || typeof row !== "object")
22564
23042
  continue;
22565
23043
  if (Array.isArray(row)) {
@@ -22582,6 +23060,7 @@ class BlacklistValidator {
22582
23060
  }
22583
23061
  const omitted = new Set;
22584
23062
  for (const path2 of blacklistedColumns) {
23063
+ ruleEvaluations += 1;
22585
23064
  const present = presentByFolded.get(foldFieldPath(path2));
22586
23065
  if (present !== undefined) {
22587
23066
  for (const name of present)
@@ -22593,8 +23072,17 @@ class BlacklistValidator {
22593
23072
  continue;
22594
23073
  if (!nestedHeads.has(foldFieldPath(path2.slice(0, dot))))
22595
23074
  continue;
23075
+ pathSplits += 1;
22596
23076
  const segments = path2.split(".").map(foldFieldPath);
22597
- 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)
22598
23086
  omitted.add(path2);
22599
23087
  }
22600
23088
  const nestedGlobs = globRules.filter((pattern) => pattern.segments.length > 1);
@@ -22605,6 +23093,7 @@ class BlacklistValidator {
22605
23093
  for (const row of rows) {
22606
23094
  if (pending.size === 0)
22607
23095
  break;
23096
+ nestedGlobRows += 1;
22608
23097
  if (row === null || typeof row !== "object")
22609
23098
  continue;
22610
23099
  collectNestedMatches(row, states, pending, (pattern) => {
@@ -22642,13 +23131,35 @@ class BlacklistValidator {
22642
23131
  }
22643
23132
  const omittedColumns = Array.from(omitted);
22644
23133
  if (omittedColumns.length === 0) {
22645
- 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
+ };
22646
23148
  }
22647
23149
  const removalPaths = matchedNested.length === 0 ? omittedColumns : omittedColumns.filter((path2) => !matchedNested.some((p) => p.raw === path2));
22648
23150
  const filteredRows = omitFieldPaths(rows, removalPaths, matchedNested.length > 0 ? { ...FOLD_CASE2, patterns: matchedNested } : FOLD_CASE2);
22649
23151
  return {
22650
23152
  filteredRows,
22651
23153
  omittedColumns,
23154
+ cost: Object.freeze({
23155
+ rowsScanned,
23156
+ keysScanned,
23157
+ ruleEvaluations,
23158
+ pathSplits,
23159
+ nestedProbeRows,
23160
+ nestedGlobRows,
23161
+ nestedGlobMatches: matchedNested.length
23162
+ }),
22652
23163
  reachesOmitted: (path2) => {
22653
23164
  if (omittedByName(removalPaths, path2))
22654
23165
  return true;
@@ -22676,7 +23187,7 @@ class BlacklistValidator {
22676
23187
  });
22677
23188
  }
22678
23189
  }
22679
- var FOLD_CASE2, PRE_FOLDED;
23190
+ var FOLD_CASE2, PRE_FOLDED, NO_MASKING_COST;
22680
23191
  var init_blacklist_validator = __esm(() => {
22681
23192
  init_glob();
22682
23193
  init_blacklist();
@@ -22687,6 +23198,15 @@ var init_blacklist_validator = __esm(() => {
22687
23198
  init_es_index_target();
22688
23199
  FOLD_CASE2 = { caseInsensitive: true };
22689
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
+ });
22690
23210
  });
22691
23211
 
22692
23212
  // src/core/health-checker.ts
@@ -22839,7 +23359,7 @@ var init_core = __esm(() => {
22839
23359
  // src/core/audit/lock.ts
22840
23360
  import { hostname } from "os";
22841
23361
  import { dirname as dirname7 } from "path";
22842
- import { mkdir as mkdir10, open as open2, rm } from "fs/promises";
23362
+ import { mkdir as mkdir11, open as open2, rm } from "fs/promises";
22843
23363
 
22844
23364
  class AuditLockManager {
22845
23365
  auditFilePath;
@@ -22897,7 +23417,7 @@ class AuditLockManager {
22897
23417
  }
22898
23418
  async tryAcquireLock(operationName) {
22899
23419
  try {
22900
- await mkdir10(dirname7(this.lockPath), { recursive: true });
23420
+ await mkdir11(dirname7(this.lockPath), { recursive: true });
22901
23421
  const lockFile = Bun.file(this.lockPath);
22902
23422
  if (await lockFile.exists()) {
22903
23423
  const lockContent = await lockFile.json();
@@ -22950,7 +23470,7 @@ var init_rotation = __esm(() => {
22950
23470
  });
22951
23471
 
22952
23472
  // src/core/audit/logger.ts
22953
- 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";
22954
23474
  import { join as join21 } from "path";
22955
23475
  import { randomUUID as randomUUID3 } from "crypto";
22956
23476
 
@@ -22999,7 +23519,7 @@ class AuditLogger {
22999
23519
  try {
23000
23520
  const sessionId = await this.sessionIdService.resolve();
23001
23521
  this.cachedSessionId = sessionId;
23002
- await mkdir11(this.auditDir, { recursive: true });
23522
+ await mkdir12(this.auditDir, { recursive: true });
23003
23523
  if (!this.writerInitialized) {
23004
23524
  await this.syncCountersFromDisk();
23005
23525
  this.writerInitialized = true;
@@ -23104,7 +23624,7 @@ var init_logger2 = __esm(() => {
23104
23624
  });
23105
23625
 
23106
23626
  // src/core/audit/session-id.ts
23107
- 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";
23108
23628
  import { randomBytes as randomBytes2 } from "crypto";
23109
23629
  import { dirname as dirname8, join as join22 } from "path";
23110
23630
  function generateSessionId(pid, nowMs) {
@@ -23137,7 +23657,7 @@ async function writeSessionIdFile(storagePath, payload) {
23137
23657
  const target = join22(storagePath, LAST_SESSION_ID_RELATIVE);
23138
23658
  const tmp = `${target}.tmp`;
23139
23659
  try {
23140
- await mkdir12(dirname8(target), { recursive: true });
23660
+ await mkdir13(dirname8(target), { recursive: true });
23141
23661
  await writeFile5(tmp, JSON.stringify(payload, null, 2), "utf8");
23142
23662
  await rename5(tmp, target);
23143
23663
  } catch {}
@@ -23258,6 +23778,18 @@ function tokenize2(sql, dialect, backslashEscapes) {
23258
23778
  }
23259
23779
  continue;
23260
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
+ }
23261
23793
  if (char === '"' || char === "`") {
23262
23794
  const quote = char;
23263
23795
  i++;
@@ -24808,7 +25340,7 @@ var init_render_markdown = __esm(() => {
24808
25340
  });
24809
25341
 
24810
25342
  // src/core/recovery/last-envelope.ts
24811
- 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";
24812
25344
  import { dirname as dirname9, join as join23 } from "path";
24813
25345
  import { randomUUID as randomUUID4 } from "crypto";
24814
25346
  function sanitizeCommandSummary(argv) {
@@ -24827,7 +25359,7 @@ async function writeLastEnvelope(cwd, envelope, argv, now = () => new Date, id =
24827
25359
  envelope
24828
25360
  };
24829
25361
  try {
24830
- await mkdir13(dirname9(target), { recursive: true });
25362
+ await mkdir14(dirname9(target), { recursive: true });
24831
25363
  await writeFile6(tmp, JSON.stringify(payload, null, 2), "utf8");
24832
25364
  await rename6(tmp, target);
24833
25365
  } catch {}
@@ -27770,7 +28302,8 @@ var init_write_gate = __esm(() => {
27770
28302
  PARSER_DIALECTS = {
27771
28303
  mysql: "MySQL",
27772
28304
  mariadb: "MariaDB",
27773
- postgresql: "Postgresql"
28305
+ postgresql: "Postgresql",
28306
+ sqlite: "Sqlite"
27774
28307
  };
27775
28308
  WRITE_TYPES = new Set([
27776
28309
  "INSERT",
@@ -28080,12 +28613,6 @@ function mongoCollectionRefs(query) {
28080
28613
  return [];
28081
28614
  }
28082
28615
  }
28083
- function requireSqlConnection2(connection) {
28084
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
28085
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
28086
- }
28087
- return connection;
28088
- }
28089
28616
  async function queryCommand(sql, options, command) {
28090
28617
  return executeQueryCommand(sql, options, command);
28091
28618
  }
@@ -28437,7 +28964,7 @@ function queryAuditTarget(system, query, options) {
28437
28964
  async function sqlQueryBranch(query, options, context, fieldSelection) {
28438
28965
  const { config } = context;
28439
28966
  const effectivePermission = context.effectivePermission ?? config.permission;
28440
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection2(config.connection));
28967
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
28441
28968
  let executionError;
28442
28969
  try {
28443
28970
  await adapter.connect();
@@ -28704,6 +29231,7 @@ var init_query = __esm(() => {
28704
29231
  init_query_fanout();
28705
29232
  init_elasticsearch2();
28706
29233
  init_slow_query_advisory();
29234
+ init_require_sql_connection();
28707
29235
  ALLOWED_FORMATS3 = ["table", "json", "csv"];
28708
29236
  defaultQueryCommandRuntime = {
28709
29237
  loadConfig: (configPath, connectionName) => configModule.read(configPath, connectionName, { loadLayeredSchema: false }),
@@ -29856,7 +30384,7 @@ class DataExecutor {
29856
30384
  return this.dbSystem;
29857
30385
  }
29858
30386
  getQuoteChar() {
29859
- return this.dbSystem === "mysql" ? "`" : '"';
30387
+ return this.dbSystem === "mysql" || this.dbSystem === "mariadb" ? "`" : '"';
29860
30388
  }
29861
30389
  checkBlacklist(operation, tableName2) {
29862
30390
  this.blacklistValidator?.checkTableBlacklist(operation, tableName2, []);
@@ -30846,12 +31374,6 @@ __export(exports_insert, {
30846
31374
  insertCommand: () => insertCommand
30847
31375
  });
30848
31376
  import crypto5 from "crypto";
30849
- function requireSqlConnection3(connection) {
30850
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
30851
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
30852
- }
30853
- return connection;
30854
- }
30855
31377
  async function readStdinJSON() {
30856
31378
  try {
30857
31379
  if (typeof Bun !== "undefined" && Bun.stdin) {
@@ -31033,11 +31555,11 @@ async function insertCommand(table, options, command) {
31033
31555
  await adapter2.disconnect();
31034
31556
  }
31035
31557
  }
31036
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection3(config.connection));
31558
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
31037
31559
  await adapter.connect();
31038
31560
  try {
31039
31561
  const schema = await adapter.getTableSchema(table);
31040
- const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
31562
+ const dbSystem = config.connection.system;
31041
31563
  const blacklistManager = new BlacklistManager(config);
31042
31564
  const blacklistValidator = new BlacklistValidator(blacklistManager);
31043
31565
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
@@ -31114,6 +31636,7 @@ var init_insert = __esm(() => {
31114
31636
  init_dml_plan();
31115
31637
  init_dml_plan4();
31116
31638
  init_integration_helper();
31639
+ init_require_sql_connection();
31117
31640
  });
31118
31641
 
31119
31642
  // src/commands/update.ts
@@ -31122,12 +31645,6 @@ __export(exports_update, {
31122
31645
  updateCommand: () => updateCommand
31123
31646
  });
31124
31647
  import crypto6 from "crypto";
31125
- function requireSqlConnection4(connection) {
31126
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
31127
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
31128
- }
31129
- return connection;
31130
- }
31131
31648
  async function updateCommand(table, options, command) {
31132
31649
  let config;
31133
31650
  try {
@@ -31341,7 +31858,7 @@ async function updateCommand(table, options, command) {
31341
31858
  } catch (error) {
31342
31859
  throw new Error(`WHERE clause parsing failed: ${error.message}`);
31343
31860
  }
31344
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection4(config.connection));
31861
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
31345
31862
  await adapter.connect();
31346
31863
  try {
31347
31864
  const schema = await adapter.getTableSchema(table);
@@ -31359,7 +31876,7 @@ async function updateCommand(table, options, command) {
31359
31876
  await writeAuditEntry(config, "update", options, auditOutcomeForMutation(cancelled, table));
31360
31877
  return;
31361
31878
  }
31362
- const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
31879
+ const dbSystem = config.connection.system;
31363
31880
  const blacklistManager = new BlacklistManager(config);
31364
31881
  const blacklistValidator = new BlacklistValidator(blacklistManager);
31365
31882
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
@@ -31440,6 +31957,7 @@ var init_update = __esm(() => {
31440
31957
  init_config_path();
31441
31958
  init_dml_plan4();
31442
31959
  init_integration_helper();
31960
+ init_require_sql_connection();
31443
31961
  });
31444
31962
 
31445
31963
  // src/commands/delete.ts
@@ -31448,12 +31966,6 @@ __export(exports_delete, {
31448
31966
  deleteCommand: () => deleteCommand
31449
31967
  });
31450
31968
  import crypto7 from "crypto";
31451
- function requireSqlConnection5(connection) {
31452
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
31453
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
31454
- }
31455
- return connection;
31456
- }
31457
31969
  async function deleteCommand(table, options, command) {
31458
31970
  let config;
31459
31971
  try {
@@ -31641,7 +32153,7 @@ async function deleteCommand(table, options, command) {
31641
32153
  } catch (error) {
31642
32154
  throw new Error(`WHERE clause parsing failed: ${error.message}`);
31643
32155
  }
31644
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection5(config.connection));
32156
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
31645
32157
  await adapter.connect();
31646
32158
  try {
31647
32159
  const schema = await adapter.getTableSchema(table);
@@ -31659,7 +32171,7 @@ async function deleteCommand(table, options, command) {
31659
32171
  await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(cancelled, table));
31660
32172
  return;
31661
32173
  }
31662
- const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
32174
+ const dbSystem = config.connection.system;
31663
32175
  const blacklistManager = new BlacklistManager(config);
31664
32176
  const blacklistValidator = new BlacklistValidator(blacklistManager);
31665
32177
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
@@ -31740,6 +32252,7 @@ var init_delete = __esm(() => {
31740
32252
  init_config_path();
31741
32253
  init_dml_plan4();
31742
32254
  init_integration_helper();
32255
+ init_require_sql_connection();
31743
32256
  });
31744
32257
 
31745
32258
  // src/adapters/elasticsearch/scroll-reader.ts
@@ -31782,12 +32295,6 @@ __export(exports_export, {
31782
32295
  buildEsExportRows: () => buildEsExportRows
31783
32296
  });
31784
32297
  import crypto8 from "crypto";
31785
- function requireSqlConnection6(connection) {
31786
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
31787
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
31788
- }
31789
- return connection;
31790
- }
31791
32298
  async function exportCommand(sql, options, command) {
31792
32299
  let config;
31793
32300
  try {
@@ -31818,7 +32325,7 @@ async function exportCommand(sql, options, command) {
31818
32325
  if (options.format === "jsonl") {
31819
32326
  throw new Error("--format jsonl is only supported on MongoDB connections");
31820
32327
  }
31821
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection6(config.connection));
32328
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
31822
32329
  await adapter.connect();
31823
32330
  const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
31824
32331
  const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, {
@@ -32178,6 +32685,7 @@ var init_export = __esm(() => {
32178
32685
  init_field_masker();
32179
32686
  init_scroll_reader();
32180
32687
  init_collection_references();
32688
+ init_require_sql_connection();
32181
32689
  SQL_PATTERN = /^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SHOW|DESCRIBE)\b/i;
32182
32690
  });
32183
32691
 
@@ -33516,8 +34024,8 @@ async function ensureDir(dirPath) {
33516
34024
  await $`mkdir -p ${dirPath}`.quiet();
33517
34025
  } catch {
33518
34026
  try {
33519
- const { mkdir: mkdir14 } = await import("fs/promises");
33520
- await mkdir14(dirPath, { recursive: true });
34027
+ const { mkdir: mkdir15 } = await import("fs/promises");
34028
+ await mkdir15(dirPath, { recursive: true });
33521
34029
  } catch {
33522
34030
  throw new Error(`Cannot create directory: ${dirPath}`);
33523
34031
  }
@@ -35355,7 +35863,7 @@ var exports_check = {};
35355
35863
  __export(exports_check, {
35356
35864
  checkCommand: () => checkCommand
35357
35865
  });
35358
- function requireSqlConnection7(connection) {
35866
+ function requireSqlConnection2(connection) {
35359
35867
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
35360
35868
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
35361
35869
  }
@@ -35367,7 +35875,7 @@ async function checkAction(table, options) {
35367
35875
  if (!config.connection) {
35368
35876
  throw new Error("Database not configured. Run: dbcli init");
35369
35877
  }
35370
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection7(config.connection));
35878
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection2(config.connection));
35371
35879
  await adapter.connect();
35372
35880
  try {
35373
35881
  const checker = new HealthChecker(adapter);
@@ -37197,7 +37705,8 @@ var init_ddl = __esm(() => {
37197
37705
  DIALECT3 = {
37198
37706
  mysql: "MySQL",
37199
37707
  mariadb: "MariaDB",
37200
- postgresql: "Postgresql"
37708
+ postgresql: "Postgresql",
37709
+ sqlite: "Sqlite"
37201
37710
  };
37202
37711
  });
37203
37712
 
@@ -37900,7 +38409,7 @@ __export(exports_diff, {
37900
38409
  ORM_ALIASES: () => ORM_ALIASES
37901
38410
  });
37902
38411
  import crypto9 from "crypto";
37903
- function requireSqlConnection8(connection) {
38412
+ function requireSqlConnection3(connection) {
37904
38413
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
37905
38414
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
37906
38415
  }
@@ -38067,7 +38576,7 @@ async function diffAction(options) {
38067
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");
38068
38577
  process.exit(1);
38069
38578
  }
38070
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection8(config.connection));
38579
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection3(config.connection));
38071
38580
  await adapter.connect();
38072
38581
  try {
38073
38582
  const tables = await adapter.listTables();
@@ -38193,9 +38702,11 @@ var init_status2 = __esm(() => {
38193
38702
  const blacklistTables = config.blacklist?.tables ?? [];
38194
38703
  const blacklistColumns = config.blacklist?.columns ?? {};
38195
38704
  const columnCount = Object.values(blacklistColumns).reduce((sum, cols) => sum + cols.length, 0);
38705
+ const file = config.connection.file;
38196
38706
  const status2 = {
38197
38707
  permission: config.permission,
38198
38708
  system: config.connection.system,
38709
+ ...file !== undefined && { file },
38199
38710
  blacklist: {
38200
38711
  tables: blacklistTables.length,
38201
38712
  columns: columnCount
@@ -38205,6 +38716,8 @@ var init_status2 = __esm(() => {
38205
38716
  if (options.format === "text") {
38206
38717
  console.log(`Permission: ${status2.permission}`);
38207
38718
  console.log(`System: ${status2.system}`);
38719
+ if (file !== undefined)
38720
+ console.log(`File: ${file}`);
38208
38721
  console.log(`Blacklist: ${status2.blacklist.tables} table(s), ${status2.blacklist.columns} column(s)`);
38209
38722
  console.log(`Version: ${status2.version}`);
38210
38723
  } else {
@@ -39941,7 +40454,8 @@ var init_parse_sql = __esm(() => {
39941
40454
  DIALECT4 = {
39942
40455
  mysql: "MySQL",
39943
40456
  mariadb: "MariaDB",
39944
- postgresql: "Postgresql"
40457
+ postgresql: "Postgresql",
40458
+ sqlite: "Sqlite"
39945
40459
  };
39946
40460
  });
39947
40461
 
@@ -41821,18 +42335,14 @@ __export(exports_doctor, {
41821
42335
  resolveSchemaLastUpdated: () => resolveSchemaLastUpdated,
41822
42336
  renderMongoSamplingLine: () => renderMongoSamplingLine,
41823
42337
  doctorCommand: () => doctorCommand,
42338
+ collectSQLiteDoctorResults: () => collectSQLiteDoctorResults,
41824
42339
  collectMongoDoctorResults: () => collectMongoDoctorResults,
41825
42340
  collectElasticsearchDoctorResults: () => collectElasticsearchDoctorResults,
41826
42341
  buildDoctorRemediationPlan: () => buildDoctorRemediationPlan
41827
42342
  });
41828
42343
  import { join as join37 } from "path";
41829
42344
  import { resolveSrv as resolveSrv2 } from "dns/promises";
41830
- function requireSqlConnection9(connection) {
41831
- if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
41832
- throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
41833
- }
41834
- return connection;
41835
- }
42345
+ import { access, constants } from "fs/promises";
41836
42346
  function renderMongoSamplingLine(meta) {
41837
42347
  if (!meta.sampleMethod)
41838
42348
  return "";
@@ -42072,6 +42582,46 @@ async function collectElasticsearchDoctorResults(config, runtimeOverrides = {})
42072
42582
  }
42073
42583
  return results;
42074
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
+ }
42075
42625
  var ALLOWED_FORMATS15, SENSITIVE_PATTERNS, runDoctorChecks, defaultDoctorCollectorRuntime, doctorCommand;
42076
42626
  var init_doctor = __esm(() => {
42077
42627
  init_esm();
@@ -42091,6 +42641,7 @@ var init_doctor = __esm(() => {
42091
42641
  init_runtime_info();
42092
42642
  init_config();
42093
42643
  init_integration_helper();
42644
+ init_require_sql_connection();
42094
42645
  ALLOWED_FORMATS15 = ["text", "json"];
42095
42646
  SENSITIVE_PATTERNS = [
42096
42647
  "password",
@@ -42324,6 +42875,83 @@ var init_doctor = __esm(() => {
42324
42875
  }
42325
42876
  }
42326
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
+ },
42327
42955
  checkLargeTables(tables, system = "postgresql") {
42328
42956
  const large = tables.filter((t2) => (t2.estimatedRowCount ?? 0) > 1e6);
42329
42957
  if (large.length === 0) {
@@ -42470,13 +43098,18 @@ var init_doctor = __esm(() => {
42470
43098
  ...config,
42471
43099
  blacklistedColumns
42472
43100
  }));
43101
+ } else if (config.connection.system === "sqlite") {
43102
+ results.push(...await collectSQLiteDoctorResults({
43103
+ ...config,
43104
+ blacklistedColumns
43105
+ }));
42473
43106
  } else if (config.connection.system === "elasticsearch") {
42474
43107
  results.push(...await collectElasticsearchDoctorResults({
42475
43108
  ...config,
42476
43109
  blacklistedColumns
42477
43110
  }));
42478
43111
  } else {
42479
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection9(config.connection));
43112
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
42480
43113
  await adapter.connect();
42481
43114
  results.push({
42482
43115
  group: "Connection & Data",
@@ -42792,7 +43425,7 @@ var exports_queries_rename = {};
42792
43425
  __export(exports_queries_rename, {
42793
43426
  queriesRename: () => queriesRename
42794
43427
  });
42795
- import { rename as rename7, mkdir as mkdir14 } from "fs/promises";
43428
+ import { rename as rename7, mkdir as mkdir15 } from "fs/promises";
42796
43429
  import { dirname as dirname12 } from "path";
42797
43430
  async function queriesRename(oldName, newName, options = {}) {
42798
43431
  if (!oldName.startsWith("@") || !newName.startsWith("@")) {
@@ -42814,7 +43447,7 @@ async function queriesRename(oldName, newName, options = {}) {
42814
43447
  for (const v of local) {
42815
43448
  const dst = snippetKeyToFile(cwd, newName, "local");
42816
43449
  const dstWithSuffix = preserveEngineSuffix(v.query.file, dst);
42817
- await mkdir14(dirname12(dstWithSuffix), { recursive: true });
43450
+ await mkdir15(dirname12(dstWithSuffix), { recursive: true });
42818
43451
  await rename7(v.query.file, dstWithSuffix);
42819
43452
  await rewriteFrontmatterName(dstWithSuffix, newName.slice(1));
42820
43453
  console.log(`renamed ${v.query.file} \u2192 ${dstWithSuffix}`);
@@ -42840,7 +43473,7 @@ var exports_queries_copy = {};
42840
43473
  __export(exports_queries_copy, {
42841
43474
  queriesCopy: () => queriesCopy
42842
43475
  });
42843
- import { mkdir as mkdir15, copyFile } from "fs/promises";
43476
+ import { mkdir as mkdir16, copyFile } from "fs/promises";
42844
43477
  import { dirname as dirname13, basename as basename5 } from "path";
42845
43478
  async function queriesCopy(src, dst, options = {}) {
42846
43479
  if (!src.startsWith("@") || !dst.startsWith("@")) {
@@ -42859,7 +43492,7 @@ async function queriesCopy(src, dst, options = {}) {
42859
43492
  }
42860
43493
  for (const v of variants) {
42861
43494
  const dstFile = mapEngineSuffix(v.query.file, snippetKeyToFile(cwd, dst, "local"));
42862
- await mkdir15(dirname13(dstFile), { recursive: true });
43495
+ await mkdir16(dirname13(dstFile), { recursive: true });
42863
43496
  await copyFile(v.query.file, dstFile);
42864
43497
  console.log(`copied ${v.query.file} \u2192 ${dstFile}`);
42865
43498
  }
@@ -42877,7 +43510,7 @@ var exports_queries_import = {};
42877
43510
  __export(exports_queries_import, {
42878
43511
  queriesImport: () => queriesImport
42879
43512
  });
42880
- 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";
42881
43514
  import { basename as basename6, join as join38, extname as extname2 } from "path";
42882
43515
  import { confirm as confirm3 } from "@inquirer/prompts";
42883
43516
  async function queriesImport(filePath, options = {}) {
@@ -42891,7 +43524,7 @@ async function queriesImport(filePath, options = {}) {
42891
43524
  const key = "@" + baseName;
42892
43525
  parseSavedQuery({ key, file: filePath, source: "local", text: text4 });
42893
43526
  const targetDir = join38(cwd, ".dbcli/queries");
42894
- await mkdir16(targetDir, { recursive: true });
43527
+ await mkdir17(targetDir, { recursive: true });
42895
43528
  const target = join38(targetDir, basename6(filePath));
42896
43529
  if (await Bun.file(target).exists()) {
42897
43530
  if (!options.force) {
@@ -42956,7 +43589,7 @@ __export(exports_queries, {
42956
43589
  queriesCheck: () => queriesCheck,
42957
43590
  listSnippetsForEngine: () => listSnippetsForEngine
42958
43591
  });
42959
- import { mkdir as mkdir17, writeFile as writeFile8 } from "fs/promises";
43592
+ import { mkdir as mkdir18, writeFile as writeFile8 } from "fs/promises";
42960
43593
  import { dirname as dirname14 } from "path";
42961
43594
  import { spawn } from "child_process";
42962
43595
  async function deriveEngine(command) {
@@ -43077,7 +43710,7 @@ async function queriesNew(name, options) {
43077
43710
  process.exit(1);
43078
43711
  return;
43079
43712
  }
43080
- await mkdir17(dirname14(file), { recursive: true });
43713
+ await mkdir18(dirname14(file), { recursive: true });
43081
43714
  await writeFile8(file, scaffold(name), "utf8");
43082
43715
  console.log(`Created ${file}`);
43083
43716
  if (source === "shared")
@@ -43153,7 +43786,7 @@ async function queriesSearch(keywords, options, command) {
43153
43786
  process.exit(2);
43154
43787
  return;
43155
43788
  }
43156
- const allowed = ["postgres", "mysql", "redis", "elasticsearch", "mongodb", "all"];
43789
+ const allowed = [...ENGINE_TAGS, "all"];
43157
43790
  if (options.engine && !allowed.includes(options.engine)) {
43158
43791
  console.error(`Unknown engine '${options.engine}'. Allowed: ${allowed.join(", ")}.`);
43159
43792
  process.exit(2);
@@ -43245,7 +43878,7 @@ async function queriesSuggest(intent, options, command) {
43245
43878
  process.exit(2);
43246
43879
  return;
43247
43880
  }
43248
- const allowed = ["postgres", "mysql", "redis", "elasticsearch", "mongodb", "all"];
43881
+ const allowed = [...ENGINE_TAGS, "all"];
43249
43882
  if (options.engine && !allowed.includes(options.engine)) {
43250
43883
  console.error(`Unknown engine '${options.engine}'. Allowed: ${allowed.join(", ")}.`);
43251
43884
  process.exit(2);
@@ -43854,7 +44487,8 @@ var init_parse = __esm(() => {
43854
44487
  DIALECT5 = {
43855
44488
  mysql: "MySQL",
43856
44489
  mariadb: "MariaDB",
43857
- postgresql: "Postgresql"
44490
+ postgresql: "Postgresql",
44491
+ sqlite: "Sqlite"
43858
44492
  };
43859
44493
  });
43860
44494
 
@@ -44986,12 +45620,14 @@ var init_not_in_nullable = __esm(() => {
44986
45620
  "VARIANCE",
44987
45621
  "VAR_POP",
44988
45622
  "VAR_SAMP"
44989
- ])
45623
+ ]),
45624
+ sqlite: new Set([...COMMON_NULL_ON_EMPTY_AGGREGATES, "GROUP_CONCAT", "STRING_AGG"])
44990
45625
  };
44991
45626
  NON_NULL_AGGREGATES = {
44992
45627
  postgresql: new Set(["COUNT", "REGR_COUNT"]),
44993
45628
  mysql: new Set(["COUNT", "BIT_AND", "BIT_OR", "BIT_XOR"]),
44994
- 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"])
44995
45631
  };
44996
45632
  notInNullableRule = {
44997
45633
  name: "not-in-nullable",
@@ -45854,7 +46490,7 @@ __export(exports_snapshot, {
45854
46490
  snapshotCommand: () => snapshotCommand
45855
46491
  });
45856
46492
  import { join as join39 } from "path";
45857
- function requireSqlConnection10(connection) {
46493
+ function requireSqlConnection4(connection) {
45858
46494
  if (!SQL_SYSTEMS6.includes(connection.system)) {
45859
46495
  throw new Error(`snapshot currently supports SQL engines only, got: ${connection.system}`);
45860
46496
  }
@@ -45902,7 +46538,7 @@ var init_snapshot = __esm(() => {
45902
46538
  const snippets = await loadSnippets(dirs);
45903
46539
  sql = resolveByName(snippets, query.slice(1), engine).query.sqlBody;
45904
46540
  }
45905
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection10(config.connection));
46541
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection4(config.connection));
45906
46542
  await adapter.connect();
45907
46543
  try {
45908
46544
  const blacklistManager = new BlacklistManager(config);
@@ -46138,7 +46774,7 @@ __export(exports_assert, {
46138
46774
  assertCommand: () => assertCommand
46139
46775
  });
46140
46776
  import { basename as basename7 } from "path";
46141
- function requireSqlConnection11(connection) {
46777
+ function requireSqlConnection5(connection) {
46142
46778
  if (!SQL_SYSTEMS7.includes(connection.system)) {
46143
46779
  throw new Error(`assert currently supports SQL engines only, got: ${connection.system}`);
46144
46780
  }
@@ -46193,7 +46829,7 @@ var init_assert = __esm(() => {
46193
46829
  console.error("Database not configured. Run: dbcli init");
46194
46830
  process.exit(1);
46195
46831
  }
46196
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection11(config.connection));
46832
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection5(config.connection));
46197
46833
  await adapter.connect();
46198
46834
  let auditRef = null;
46199
46835
  let verdict2;
@@ -47578,7 +48214,7 @@ __export(exports_verify, {
47578
48214
  verifyCommand: () => verifyCommand,
47579
48215
  BUILTIN_VERIFY_SCENARIOS: () => BUILTIN_VERIFY_SCENARIOS
47580
48216
  });
47581
- function requireSqlConnection12(connection, scenario2) {
48217
+ function requireSqlConnection6(connection, scenario2) {
47582
48218
  if (!SQL_SYSTEMS8.includes(connection.system)) {
47583
48219
  throw new Error(`verify ${scenario2} currently supports SQL engines only, got: ${connection.system}`);
47584
48220
  }
@@ -48264,7 +48900,7 @@ async function executeScenario(def, options, command) {
48264
48900
  console.error("Database not configured. Run: dbcli init");
48265
48901
  process.exit(1);
48266
48902
  }
48267
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection12(config.connection, def.name));
48903
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection6(config.connection, def.name));
48268
48904
  await adapter.connect();
48269
48905
  const runners = def.createRunners({ adapter, config, options, targetTable: input.table }, input);
48270
48906
  if (!input.afterWrite) {
@@ -50139,7 +50775,7 @@ __export(exports_shell, {
50139
50775
  import { createInterface as createInterface2 } from "readline";
50140
50776
  import { join as join40 } from "path";
50141
50777
  import { homedir as homedir3 } from "os";
50142
- function requireSqlConnection13(connection) {
50778
+ function requireSqlConnection7(connection) {
50143
50779
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
50144
50780
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
50145
50781
  }
@@ -50184,7 +50820,7 @@ async function runShell(options, configPath) {
50184
50820
  const connectionOpts = config.connection;
50185
50821
  const mongoInner = isMongoDB ? AdapterFactory.createMongoDBAdapter(config) : null;
50186
50822
  const redisInner = isRedis ? AdapterFactory.createRedisAdapter(config) : null;
50187
- 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));
50188
50824
  try {
50189
50825
  await adapter.connect();
50190
50826
  } catch (error) {
@@ -50999,7 +51635,7 @@ __export(exports_migrate, {
50999
51635
  runDDL: () => runDDL,
51000
51636
  migrateCommand: () => migrateCommand
51001
51637
  });
51002
- function requireSqlConnection14(connection) {
51638
+ function requireSqlConnection8(connection) {
51003
51639
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
51004
51640
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
51005
51641
  }
@@ -51023,7 +51659,7 @@ async function runDDL(operation, opts, command) {
51023
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");
51024
51660
  process.exit(1);
51025
51661
  }
51026
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection14(config.connection));
51662
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection8(config.connection));
51027
51663
  const isDryRun = !opts.execute;
51028
51664
  if (!isDryRun) {
51029
51665
  await adapter.connect();
@@ -51265,17 +51901,23 @@ async function switchDefault(configPath, name, config, options = {}) {
51265
51901
  function listConnectionsForDisplay(config) {
51266
51902
  return Object.entries(config.connections).map(([name, conn]) => {
51267
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
+ }
51268
51910
  const host = typeof conn.host === "object" ? `\${${conn.host.$env}}` : conn.host;
51269
51911
  const port = typeof conn.port === "object" ? `\${${conn.port.$env}}` : conn.port;
51270
51912
  const db = typeof conn.database === "object" ? `\${${conn.database.$env}}` : conn.database;
51271
- const environment = conn.environment ? ` [${conn.environment}]` : "";
51272
- 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}`;
51273
51914
  });
51274
51915
  }
51275
51916
  function listConnectionIdentities(config) {
51276
51917
  return Object.entries(config.connections).map(([name, conn]) => {
51277
51918
  const host = typeof conn.host === "string" && conn.host.length > 0 ? conn.host : null;
51278
51919
  const database = typeof conn.database === "string" && conn.database.length > 0 ? conn.database : null;
51920
+ const file = conn.file;
51279
51921
  return {
51280
51922
  name,
51281
51923
  environment: conn.environment ?? null,
@@ -51286,6 +51928,7 @@ function listConnectionIdentities(config) {
51286
51928
  port: host !== null && typeof conn.port === "number" ? conn.port : null
51287
51929
  },
51288
51930
  database,
51931
+ ...typeof file === "string" && { file },
51289
51932
  isDefault: name === config.default
51290
51933
  };
51291
51934
  });
@@ -51386,7 +52029,7 @@ var init_config_v2_mutations = __esm(() => {
51386
52029
  });
51387
52030
 
51388
52031
  // src/core/env-file-writer.ts
51389
- 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";
51390
52033
  import { dirname as dirname16 } from "path";
51391
52034
  function escapeForRegExp(value) {
51392
52035
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -51399,7 +52042,7 @@ async function upsertEnvVar(envPath, varName, value) {
51399
52042
  const code = cause.code;
51400
52043
  if (code !== "ENOENT")
51401
52044
  throw cause;
51402
- await mkdir18(dirname16(envPath), { recursive: true });
52045
+ await mkdir19(dirname16(envPath), { recursive: true });
51403
52046
  }
51404
52047
  const line = `${varName}="${value}"`;
51405
52048
  const existing = new RegExp(`^${escapeForRegExp(varName)}=.*$`, "m");
@@ -51704,7 +52347,7 @@ var init_sql_metadata = __esm(() => {
51704
52347
  });
51705
52348
 
51706
52349
  // src/proxy/events.ts
51707
- 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";
51708
52351
  import { dirname as dirname17 } from "path";
51709
52352
  function hasSql(e) {
51710
52353
  return e.type === "query_observed" || e.type === "query_completed" || e.type === "query_errored";
@@ -51744,7 +52387,7 @@ class EventWriter {
51744
52387
  }
51745
52388
  async writeInternal(event) {
51746
52389
  if (!this.dirEnsured) {
51747
- await mkdir19(dirname17(this.path), { recursive: true });
52390
+ await mkdir20(dirname17(this.path), { recursive: true });
51748
52391
  this.dirEnsured = true;
51749
52392
  }
51750
52393
  if (!this.initialized) {
@@ -54156,7 +54799,7 @@ __export(exports_backfill, {
54156
54799
  backfillCommand: () => backfillCommand
54157
54800
  });
54158
54801
  import { dirname as dirname18, resolve as resolve12 } from "path";
54159
- import { mkdir as mkdir20 } from "fs/promises";
54802
+ import { mkdir as mkdir21 } from "fs/promises";
54160
54803
  function identityFor(config, name) {
54161
54804
  const connection = config.connections[name];
54162
54805
  if (!connection) {
@@ -54206,7 +54849,7 @@ var init_backfill = __esm(() => {
54206
54849
  return;
54207
54850
  }
54208
54851
  const out = resolve12(options.out ?? `.dbcli/backfills/${artifact2.source.sha256.slice(0, 12)}.json`);
54209
- await mkdir20(dirname18(out), { recursive: true });
54852
+ await mkdir21(dirname18(out), { recursive: true });
54210
54853
  await Bun.write(out, JSON.stringify(artifact2, null, 2) + `
54211
54854
  `);
54212
54855
  console.log(JSON.stringify({ path: out, artifact: artifact2 }, null, 2));
@@ -54349,7 +54992,7 @@ var init_legacy2 = __esm(() => {
54349
54992
 
54350
54993
  // src/core/evidence-pack/index.ts
54351
54994
  import { createHash as createHash10, randomUUID as randomUUID6 } from "crypto";
54352
- 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";
54353
54996
  import { basename as basename9, dirname as dirname19, relative as relative6, resolve as resolve13, sep as sep8 } from "path";
54354
54997
  function isRecord6(value) {
54355
54998
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -54633,7 +55276,7 @@ async function writeEvidencePack(workspaceRoot, outputPath, pack) {
54633
55276
  if (!isInside(workspace, existingAncestor2) && existingAncestor2 !== workspace) {
54634
55277
  throw new EvidencePackValidationError("output path resolves outside the workspace");
54635
55278
  }
54636
- await mkdir21(parent, { recursive: true });
55279
+ await mkdir22(parent, { recursive: true });
54637
55280
  const realParent = await realpath4(parent);
54638
55281
  if (!isInside(workspace, realParent) && realParent !== workspace) {
54639
55282
  throw new EvidencePackValidationError("output path resolves outside the workspace");
@@ -55278,9 +55921,9 @@ function assessImpact(input) {
55278
55921
  ...nodes.map((node) => node.reference),
55279
55922
  ...contractResult.metrics.map((metric) => `metric:${metric.name}`)
55280
55923
  ]);
55281
- const access = dataAccessFindings(change, references, input.dataAccess.value, input.blockedIdentifiers);
55282
- findings.push(...access.findings);
55283
- if (access.redacted)
55924
+ const access2 = dataAccessFindings(change, references, input.dataAccess.value, input.blockedIdentifiers);
55925
+ findings.push(...access2.findings);
55926
+ if (access2.redacted)
55284
55927
  addGap(gaps, "REDACTED_PROTECTED_SUBJECT");
55285
55928
  }
55286
55929
  }
@@ -56075,7 +56718,7 @@ __export(exports_impact, {
56075
56718
  impactCommand: () => impactCommand
56076
56719
  });
56077
56720
  import { randomUUID as randomUUID7 } from "crypto";
56078
- 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";
56079
56722
  import { basename as basename10, dirname as dirname20, relative as relative8, resolve as resolve15, sep as sep10 } from "path";
56080
56723
  function collectOption3(value, previous) {
56081
56724
  return [...previous, value];
@@ -56296,7 +56939,7 @@ async function resolveOutput(workspaceRoot, outputPath) {
56296
56939
  const existingAncestor2 = await realExistingAncestor2(parent);
56297
56940
  if (!isInside2(workspace, existingAncestor2) && existingAncestor2 !== workspace)
56298
56941
  throw new Error("output path must stay inside the workspace");
56299
- await mkdir22(parent, { recursive: true });
56942
+ await mkdir23(parent, { recursive: true });
56300
56943
  const realParent = await realpath6(parent);
56301
56944
  if (!isInside2(workspace, realParent) && realParent !== workspace)
56302
56945
  throw new Error("output path must stay inside the workspace");
@@ -57166,7 +57809,7 @@ __export(exports_completion, {
57166
57809
  });
57167
57810
  import { join as join46 } from "path";
57168
57811
  import { homedir as homedir4 } from "os";
57169
- import { mkdir as mkdir23 } from "fs/promises";
57812
+ import { mkdir as mkdir24 } from "fs/promises";
57170
57813
  function resolveHome() {
57171
57814
  return process.env.HOME ?? homedir4();
57172
57815
  }
@@ -57439,7 +58082,7 @@ async function installCompletion(shell, script) {
57439
58082
  const targetPath = getInstallPath2(shell);
57440
58083
  if (shell === "fish") {
57441
58084
  const dir = join46(resolveHome(), ".config", "fish", "completions");
57442
- await mkdir23(dir, { recursive: true });
58085
+ await mkdir24(dir, { recursive: true });
57443
58086
  await Bun.file(targetPath).write(script);
57444
58087
  console.log(colors.success(`\u2713 Fish completion installed to ${targetPath}`));
57445
58088
  return;