@carllee1983/dbcli 12.0.0 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1618,7 +1618,7 @@ var package_default;
1618
1618
  var init_package = __esm(() => {
1619
1619
  package_default = {
1620
1620
  name: "@carllee1983/dbcli",
1621
- version: "12.0.0",
1621
+ version: "12.1.0",
1622
1622
  description: "Database CLI for AI agents",
1623
1623
  type: "module",
1624
1624
  publishConfig: {
@@ -1687,6 +1687,7 @@ var init_package = __esm(() => {
1687
1687
  prepublishOnly: "bun run build",
1688
1688
  audit: "bun run scripts/audit.ts",
1689
1689
  "release:check": "bash scripts/release-check.sh",
1690
+ "release:extras": "bash scripts/release-extras.sh",
1690
1691
  "plugin:sync": "bun run scripts/sync-plugin-assets.ts --write",
1691
1692
  "plugin:check": "bun run scripts/sync-plugin-assets.ts",
1692
1693
  "manifest:sync": "bun run scripts/check-plugin-manifests.ts --write",
@@ -1935,6 +1936,1028 @@ async function checkForUpdate(currentVersion, cachePath, existingCache) {
1935
1936
  };
1936
1937
  }
1937
1938
 
1939
+ // resources/lang/en/messages.json
1940
+ var messages_default;
1941
+ var init_messages = __esm(() => {
1942
+ messages_default = {
1943
+ cli: {
1944
+ agent_output_option: "Emit one Operation Envelope v1 (capabilities and capabilities check only)"
1945
+ },
1946
+ init: {
1947
+ description: "Initialize dbcli configuration with .env parsing and interactive prompts",
1948
+ welcome: "Welcome to dbcli",
1949
+ prompt_connection_name: "Connection name",
1950
+ connection_name_required: "Connection name is required. Provide --conn-name <name> (this is separate from the database --name).",
1951
+ select_system: "Select database system:",
1952
+ prompt_host: "Database host: ",
1953
+ prompt_port: "Database port: ",
1954
+ prompt_user: "Database user: ",
1955
+ prompt_password: "Database password: ",
1956
+ secret_alternatives_sql: "pass --password, supply the credential through .env or the process environment, or store a reference with --use-env-refs",
1957
+ secret_alternatives_mongo_password: "pass --password, supply the credential through .env or the process environment, or paste the connection string with --uri",
1958
+ secret_alternatives_mongo_uri: "pass the connection string with --uri",
1959
+ mongo_uri_conflicts_fields: "--uri cannot be combined with {flags}: the connection string already carries those settings. Use one or the other (--name may be combined to override the database name).",
1960
+ mongo_uri_with_env_refs: "--uri cannot be combined with --use-env-refs: the connection string is decomposed into config fields, and its credentials are not moved into environment variables. Either remove --use-env-refs so dbcli decomposes the string into the field config, or use the --env-* flags (--env-host, --env-user, --env-password, ...) to name one variable per field.",
1961
+ mongo_uri_fallback_warning: "\u26A0\uFE0F This connection string {reasons}, so it cannot become fields. It is stored as-is in uri, and its credentials will sit in plaintext in the config file.",
1962
+ mongo_uri_reason_unix_socket: "uses a Unix domain socket",
1963
+ mongo_uri_reason_multi_host: "contains more than one host (a field config holds only one)",
1964
+ mongo_uri_reason_secret_option: "contains secret option(s) ({keys}) that would land in plaintext options",
1965
+ mongo_uri_reason_duplicate_key: "repeats the query key(s) {keys} (a field config can hold only one value per key)",
1966
+ mongo_uri_reason_non_scram: "uses the non-SCRAM auth mechanism {mechanism} (field config cannot express it)",
1967
+ mongo_decomposed_header: "Connection string decomposed into these fields (password hidden):",
1968
+ mongo_paste_env_refs_warning: "\u26A0\uFE0F --use-env-refs does not apply to a pasted connection string: its credentials are written to the config file as they are, not as environment-variable references.",
1969
+ mongo_setup_mode_paste: "Paste a connection string (copied from Atlas / Compass)",
1970
+ mongo_string_invalid: "Not a valid MongoDB connection string (it is not echoed because it may carry credentials).",
1971
+ mongo_string_bad_host: "The connection string has an invalid host or port (the port must be an integer from 1 to 65535).",
1972
+ mongo_string_bad_credentials: "The connection string's credentials contain an unescaped reserved character such as @, : or /. Percent-encode it (for example @ as %40).",
1973
+ mongo_string_tls_conflict: "The connection string sets tls and ssl to different values.",
1974
+ mongo_string_bad_tls: "The connection string's {key} must be true or false.",
1975
+ mongo_string_empty_option: "The connection string sets {key} but leaves it empty. Give it a value or remove it.",
1976
+ prompt_name: "Database name: ",
1977
+ prompt_permission: "Permission level (query-only, read-write, data-admin, admin): ",
1978
+ env_parse_note: "Note: Unable to parse .env configuration, using interactive prompts",
1979
+ connection_testing: "Testing database connection...",
1980
+ connection_success: "\u2713 Database connection successful",
1981
+ connection_failed: "\u2717 Database connection failed",
1982
+ config_saved: "Configuration saved to .dbcli",
1983
+ config_saved_global: "Global configuration saved to {path}",
1984
+ config_exists_overwrite: "Configuration file .dbcli already exists. Overwrite? (y/n): ",
1985
+ cancelled: "Cancelled. Configuration not changed.",
1986
+ skip_test_env_ref: "Skipping connection test in env-ref mode",
1987
+ skip_test: "Skipping connection test (--skip-test)",
1988
+ connection_hints: "Hints:",
1989
+ config_exists_use_force: ".dbcli exists. Use --force option to overwrite.",
1990
+ connection_added: "Connection '{name}' added",
1991
+ connection_removed: "Connection '{name}' removed",
1992
+ connection_removed_default_cleared: "Connection '{name}' removed. Default connection cleared. Specify --use <name> or DBCLI_CONNECTION for database commands.",
1993
+ connection_renamed: "Connection '{oldName}' renamed to '{newName}'",
1994
+ v1_migration_prompt: "Detected old config format. Create new format and import existing connection as 'default'?",
1995
+ config_saved_v2: "V2 configuration saved to .dbcli",
1996
+ config_not_found: "Configuration file not found",
1997
+ requires_v2_remove: "Removing a connection requires V2 config format",
1998
+ requires_v2_rename: "Renaming a connection requires V2 config format",
1999
+ connection_not_found: "Connection '{name}' does not exist",
2000
+ cannot_remove_last: "Cannot remove the last connection",
2001
+ connection_already_exists: "Connection '{name}' already exists",
2002
+ rename_invalid_format: "Usage: --rename <old-name>:<new-name>",
2003
+ prompt_sqlite_file: "SQLite database file path: ",
2004
+ sql_tls_help: "Verified SQL TLS mode (verify-full; PostgreSQL/MySQL/MariaDB)",
2005
+ sql_tls_ca_help: "PEM CA bundle for --sql-tls verify-full"
2006
+ },
2007
+ inspect: {
2008
+ description: "Read-only snapshot of connection, permission, blacklist, objects, snippets, and suggested next commands.",
2009
+ no_config: "No dbcli configuration found. Run `dbcli init` to start."
2010
+ },
2011
+ report: {
2012
+ description: "Diagnostic report (health/capacity/perf) built from built-in snippets. Read-only, bounded.",
2013
+ no_config: "No dbcli configuration found. Run `dbcli init` to start.",
2014
+ section_invalid: "Unknown report section '{name}'. Allowed: health, capacity, perf"
2015
+ },
2016
+ guide: {
2017
+ description: "Deterministic next-command planner for a fixed set of database goals.",
2018
+ no_config: "No dbcli configuration found. Run `dbcli init` to start.",
2019
+ missing_goal: "Missing goal. Try `dbcli guide --list` to see available goals.",
2020
+ goal_invalid: "Unknown guide goal '{name}'. Run `dbcli guide --list` for valid IDs."
2021
+ },
2022
+ recovery: {
2023
+ description: "Look up the structured recovery envelope for a known dbcli error code, or list all codes.",
2024
+ missing_code: "Missing --code. Try `dbcli recovery --list` to see available codes.",
2025
+ code_invalid: "Unknown recovery code '{name}'. Run `dbcli recovery --list` for valid codes.",
2026
+ no_config: "No dbcli configuration found. Run `dbcli init` to start."
2027
+ },
2028
+ use: {
2029
+ description: "Switch or display the default database connection",
2030
+ switched: "Switched default connection to {name}",
2031
+ current: "Current default connection: {name}",
2032
+ current_none: "No default connection. Specify --use <name> or DBCLI_CONNECTION.",
2033
+ unset: "Default connection cleared. Specify --use <name> or DBCLI_CONNECTION for database commands.",
2034
+ unset_conflict: "--unset cannot be combined with a connection name, --list, --confirm-production, or --format json",
2035
+ requires_v2: "This feature requires v2 config format. Use 'dbcli init --conn-name <name>' to create one",
2036
+ available: "Available connections"
2037
+ },
2038
+ schema: {
2039
+ description: "Retrieve table structure or list all tables",
2040
+ fetching: "Fetching schema...",
2041
+ success: "Schema updated",
2042
+ not_found: "Table not found in database",
2043
+ refresh_prompt: "Schema has changes. Apply? (y/n): ",
2044
+ columns_header: "Columns",
2045
+ scanning_database: "\uD83D\uDD0D Scanning database schema...",
2046
+ tables_found: "\uD83D\uDCCD Found {count} table(s). Fetching schema details...",
2047
+ processing_tables: " Processed {processed}/{total} table(s)",
2048
+ schema_exists_warning: "\u26A0\uFE0F Database schema already exists in .dbcli",
2049
+ use_force_to_override: " Use --force to override without confirmation"
2050
+ },
2051
+ list: {
2052
+ description: "List all tables in the database",
2053
+ no_tables: "No tables found in database"
2054
+ },
2055
+ query: {
2056
+ description: "Execute SQL query",
2057
+ executing: "Executing query...",
2058
+ no_results: "No results returned",
2059
+ result_count: "Results: {count} row(s)"
2060
+ },
2061
+ q: {
2062
+ description: "Run a saved query snippet by @name",
2063
+ missing_param: "Missing required parameters for snippet '{name}'",
2064
+ engine_mismatch: "Engine mismatch for snippet '{name}'",
2065
+ snippet_not_found: "Snippet not found: {name}",
2066
+ dry_run_header: "Dry-run preview (no execution):"
2067
+ },
2068
+ audit: {
2069
+ description: "Inspect, look up, clear, or check the health of the audit log",
2070
+ tail: {
2071
+ description: "Show recent audit entries (default: last 10 from current connection)"
2072
+ },
2073
+ show: {
2074
+ description: "Look up a single audit entry by id (UUID prefix >=4) or --recovery-ref"
2075
+ },
2076
+ clear: {
2077
+ description: "Delete audit log files (.jsonl + .jsonl.1) for the current connection",
2078
+ prompt_header: "About to clear audit log for connection '{conn}':",
2079
+ prompt_file_line: " {file} \u2014 {entries} entries, {size}",
2080
+ prompt_continue: "Continue? [y/N] ",
2081
+ requires_tty_or_yes: "Cannot prompt for confirmation in non-interactive session. Use --yes to clear without prompt.",
2082
+ summary_cleared: "Cleared {count} entries from '{conn}'.",
2083
+ summary_nothing: "Nothing to clear.",
2084
+ summary_failed: "Failed to clear: {message}."
2085
+ },
2086
+ health: {
2087
+ description: "Show AuditLogger health snapshot (size, entries, lock state, last write)"
2088
+ },
2089
+ write_gate: {
2090
+ description: "Summarize tier-two write-gate decisions (ADR 0010's measurement)",
2091
+ header: "Write-gate summary \u2014 connection: {conn}",
2092
+ window: "Audit entries scanned: {scanned}, {from} \u2192 {to}",
2093
+ decisions: "Tier-two evaluations: {count}, {from} \u2192 {to}",
2094
+ tier_one: "Tier one, counted apart: {count} ({outcomes})",
2095
+ no_decisions: "Tier two was never reached in this window. Per ADR 0010 that falsifies the criterion, not the gate: the shapes it looks for are not the shapes people write.",
2096
+ no_entries: "The audit log is empty, so this says nothing about the gate either way. Run some writes, or check `dbcli audit health`."
2097
+ },
2098
+ disabled_hint: "Audit is disabled (audit.enabled = false in .dbcli). Use 'dbcli audit health' for details.",
2099
+ no_entries: "No audit entries.",
2100
+ show_no_match: "No audit entry matches '{prefix}'.",
2101
+ show_ambiguous: "Ambiguous prefix '{prefix}': matches {count} entries. Please use a longer prefix.",
2102
+ show_prefix_too_short: "Prefix must be at least 4 characters.",
2103
+ show_recovery_no_match: "No audit entry has recovery_ref '{ref}'.",
2104
+ show_recovery_ambiguous: "Multiple entries reference recovery_ref '{ref}': matches {count}. This should be rare; inspect file directly.",
2105
+ show_mutex_violation: "Provide either <id> argument or --recovery-ref, not both.",
2106
+ n_capped_warning: "--n value {requested} exceeds max {max}; capped to {max}.",
2107
+ n_must_be_positive: "--n must be a positive integer."
2108
+ },
2109
+ queries: {
2110
+ description: "Manage saved query snippets",
2111
+ list_description: "List saved snippets",
2112
+ show_description: "Show a snippet (frontmatter + SQL)",
2113
+ new_description: "Create a new snippet file",
2114
+ edit_description: "Open a snippet in $EDITOR",
2115
+ check_description: "Validate all snippets",
2116
+ delete_description: "Delete a local snippet file",
2117
+ rename_description: "Rename a local snippet",
2118
+ copy_description: "Copy a snippet from any tier into local",
2119
+ import_description: "Import an external .sql file into local",
2120
+ export_description: "Export a snippet's raw SQL to stdout or file",
2121
+ created: "Created {file}",
2122
+ first_run_hint: "Tip: commit .dbcli-shared/ to share snippets with your team.",
2123
+ no_snippets: "No snippets found. Create one with: dbcli queries new @<name>",
2124
+ check_ok: "\u2713 {count} snippets parsed successfully",
2125
+ check_failed: "\u2717 {count} snippet(s) have errors",
2126
+ search_description: "Search snippets by keywords (fuzzy ranking)",
2127
+ suggest_description: "Suggest snippets by intent prefix",
2128
+ search_no_keywords: "Provide at least one keyword, or try 'queries list' / 'queries suggest'.",
2129
+ search_no_results: "No snippets matched. Try 'queries list' or 'queries suggest <intent>'.",
2130
+ suggest_missing_intent: "Provide an intent (or prefix). Available: {available}",
2131
+ suggest_invalid_intent: "Invalid intent '{value}'. Must match ^[a-z][a-z0-9.-]*$",
2132
+ suggest_no_results: "No snippets with intent prefix '{intent}'. Available: {available}",
2133
+ no_active_connection_hint: "No active connection; showing snippets across all engines."
2134
+ },
2135
+ errors: {
2136
+ message: "Error: {message}",
2137
+ query_only_boundary_failed: "Could not establish the query-only database boundary for {system}: {detail}",
2138
+ query_only_boundary_verify: "Verify that {system} supports read-only transactions and that this account may start one",
2139
+ query_only_boundary_not_executed: "The target statement was not executed; fix the database setting or connection before retrying",
2140
+ query_only_cleanup_completed: "The query-only target completed, but transaction cleanup failed for {system}: {detail}",
2141
+ query_only_cleanup_uncertain: "The query-only target failed and its outcome is uncertain because transaction cleanup also failed for {system}: {detail}",
2142
+ query_only_cleanup_completed_retry: "The target statement ran; do not retry it unless repeating its external effects is safe",
2143
+ query_only_cleanup_uncertain_retry: "The target may have produced effects before failing; do not retry it unless repeating them is safe",
2144
+ query_only_cleanup_reconnect: "Reconnect before running another statement because this connection was discarded",
2145
+ invalid_config: "Invalid configuration: {field}",
2146
+ connection_failed: "Failed to connect to database: {message}",
2147
+ permission_denied: "Permission denied (required: {required})",
2148
+ permission_requires_level: "{type} operation requires {minimum} permission or higher (current level: {permission})",
2149
+ elasticsearch_requires_level: "Elasticsearch {type} operation requires {minimum} permission or higher (current level: {permission})",
2150
+ 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}).",
2151
+ 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.",
2152
+ 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.",
2153
+ permission_denied_reason: "Permission denied: {reason}",
2154
+ table_not_found: "Table not found: {table}",
2155
+ unsupported_lang: "Language '{lang}' not supported, using English",
2156
+ invalid_json: "Invalid JSON: {message}",
2157
+ table_blacklisted: "Error: Table '{table}' is blacklisted for {operation} operations",
2158
+ redis_target_blacklisted: "Redis command refused by blacklist",
2159
+ column_blacklisted_write: "Error: {operation} on table '{table}' touches blacklisted columns: {columns}",
2160
+ table_already_blacklisted: "Error: Table '{table}' is already blacklisted",
2161
+ table_not_in_blacklist: "Error: Table '{table}' is not in the blacklist",
2162
+ column_already_blacklisted: "Error: Column '{table}.{column}' is already blacklisted",
2163
+ column_not_in_blacklist: "Error: Column '{table}.{column}' is not in the blacklist",
2164
+ invalid_table_name: "Error: Invalid table name: {table}",
2165
+ invalid_column_format: "Error: Invalid column format. Use 'table.column'",
2166
+ invalid_system: "Invalid database system: {system}",
2167
+ invalid_permission: "Invalid permission level: {permission}",
2168
+ invalid_port: "Invalid port: {port}",
2169
+ require_user: "Non-interactive mode requires --user option",
2170
+ require_name: "Non-interactive mode requires --name option",
2171
+ env_refs_missing_options: `When using --use-env-refs, environment variable names must be specified.
2172
+ Provide options: --env-host, --env-port, --env-user, --env-password, --env-database
2173
+ Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactive`,
2174
+ env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
2175
+ Set {envKey} in .env or environment variables.
2176
+ Hint: run 'export {envKey}=<value>' and retry`,
2177
+ invalid_output_format: "--format {format} is not supported here \u2014 use text or json",
2178
+ sqlite_file_required: "A SQLite connection needs a file path. Pass --file <path> or run init interactively.",
2179
+ sql_tls_unsupported: "Explicit SQL TLS is supported only for PostgreSQL, MySQL and MariaDB",
2180
+ sql_tls_options_required: "Provide both --sql-tls verify-full and --sql-tls-ca <path>",
2181
+ sql_tls_change_engine: "Explicit SQL TLS is supported only for PostgreSQL, MySQL and MariaDB; cannot discard it when editing a profile",
2182
+ sql_tls_legacy_import: "SQL TLS legacy single-file import is unsupported. Keep the original profile; create a verified named profile in a separate config directory before switching. Do not remove SQL TLS.",
2183
+ sql_tls_ca_absolute: "SQL TLS CA path must be absolute",
2184
+ sql_tls_network_host: "Explicit SQL TLS requires a DNS or IP host, not a Unix socket",
2185
+ sql_tls_ca_empty: "CA bundle contains no PEM certificates",
2186
+ sql_tls_ca_load: "Cannot load SQL TLS CA bundle: {detail}",
2187
+ sql_tls_ca_restore: "Restore a readable, valid PEM CA bundle at sqlTls.caPath; keep verify-full enabled.",
2188
+ sql_tls_handshake: "TLS handshake failed: {detail}",
2189
+ sql_tls_ca_hint: "Set sqlTls.caPath to the correct readable PEM CA bundle.",
2190
+ sql_tls_identity_hint: "Verify the certificate validity and DNS/IP identity for {host}.",
2191
+ sql_tls_no_downgrade: "Keep verify-full enabled; do not remove SQL TLS or retry without verification.",
2192
+ sql_tls_driver_identity: "SQL TLS identity verification is unavailable in this driver",
2193
+ sql_tls_peer_unverified: "SQL TLS peer is not authenticated",
2194
+ sql_tls_peer_missing: "SQL TLS peer certificate is unavailable",
2195
+ sql_tls_recovery_config: "TLS recovery requires an explicit --config <path> or --global and the intended TLS profile; defaults cannot identify the failed target.",
2196
+ sql_tls_recovery_profile: "TLS recovery requires --use <name> (or DBCLI_CONNECTION); it cannot fall back to the default profile.",
2197
+ sql_tls_recovery_required: "TLS recovery requires a profile with explicit sqlTls verify-full; plaintext recovery is refused.",
2198
+ sql_tls_recovery_target: "TLS recovery requires an explicitly selected, verified TLS profile."
2199
+ },
2200
+ success: {
2201
+ inserted: "Successfully inserted {count} row(s)",
2202
+ updated: "Successfully updated {count} row(s)",
2203
+ deleted: "Successfully deleted {count} row(s)",
2204
+ no_changes: "No changes to apply"
2205
+ },
2206
+ insert: {
2207
+ description: "Insert data into a table",
2208
+ prompt_data: "Enter JSON data to insert: ",
2209
+ confirm: "Insert {count} row(s) into {table}? (y/n): ",
2210
+ elasticsearch_unsupported: "Elasticsearch does not support the insert command; for now, use an external tool (e.g. curl or Kibana DevTools) to write documents"
2211
+ },
2212
+ update: {
2213
+ description: "Update data in a table",
2214
+ prompt_where: "Enter WHERE clause (e.g., id=1): ",
2215
+ prompt_set: 'Enter JSON updates (e.g., {"status": "active"}): ',
2216
+ confirm: "Update rows in {table}? (y/n): ",
2217
+ elasticsearch_unsupported: "Elasticsearch does not support the update command; for now, use an external tool (e.g. curl or Kibana DevTools) to update documents"
2218
+ },
2219
+ delete: {
2220
+ description: "Delete data from a table",
2221
+ prompt_where: "Enter WHERE clause (e.g., id=1): ",
2222
+ confirm: "Delete rows from {table}? (y/n): ",
2223
+ admin_only: "Delete command requires data-admin or admin permission",
2224
+ elasticsearch_unsupported: "Elasticsearch does not support the delete command; for now, use an external tool (e.g. curl or Kibana DevTools) to delete documents"
2225
+ },
2226
+ export: {
2227
+ description: "Export query results",
2228
+ formats: "Supported formats: json, csv",
2229
+ exported: "Exported {count} rows to {file}"
2230
+ },
2231
+ upgrade: {
2232
+ description: "Check for updates and upgrade dbcli to the latest version",
2233
+ checking: "Checking for updates...",
2234
+ already_up_to_date: "\u2713 Already up to date (v{version})",
2235
+ new_version_available: "New version available: v{version}",
2236
+ current_version: "Current version : v{version}",
2237
+ latest_version: "Latest version : v{version}",
2238
+ update_hint: '[INFO] dbcli v{version} available. Run "dbcli upgrade" to upgrade.',
2239
+ install_hint: 'Run "dbcli upgrade" to install the update.',
2240
+ upgrading: "Upgrading...",
2241
+ success: "\u2713 Successfully upgraded to v{version}",
2242
+ skill_recheck_hint: 'Hint: If you have installed AI skills, run "dbcli skill --install <platform>" to ensure they are up to date.',
2243
+ failed: "\u2717 Upgrade failed. Try running manually:",
2244
+ manual_hint: "bun add -g @carllee1983/dbcli@latest",
2245
+ network_error: "\u26A0 Could not check for updates (network error or registry unavailable)"
2246
+ },
2247
+ skill: {
2248
+ description: "Generate AI skill documentation",
2249
+ context_description: "Generate AI-friendly database schema and query snippet context",
2250
+ installed: `Skill: {path}
2251
+ Reference: {referencePath}`,
2252
+ update_available: "Skill updates available for the following platforms:",
2253
+ update_hint: 'Run "dbcli skill --install <platform>" to update.'
2254
+ },
2255
+ blacklist: {
2256
+ description: "Manage sensitive data blacklist to prevent AI access",
2257
+ list_title: "Current Blacklist Configuration",
2258
+ tables_label: "Blacklisted tables",
2259
+ columns_label: "Blacklisted columns",
2260
+ none: "No tables or columns are currently blacklisted",
2261
+ table_added: "\u2713 Table '{table}' added to blacklist",
2262
+ table_removed: "\u2713 Table '{table}' removed from blacklist",
2263
+ column_added: "\u2713 Column '{table}.{column}' added to blacklist",
2264
+ column_removed: "\u2713 Column '{table}.{column}' removed from blacklist",
2265
+ refuse_wildcard: "Refused: '{table}' contains wildcard characters, which are matched literally on {system} \u2014 the entry would never match anything. Write the exact name."
2266
+ },
2267
+ security: {
2268
+ columns_omitted: "Security: {count} column(s) were omitted based on your blacklist"
2269
+ },
2270
+ warnings: {
2271
+ blacklist_override_used: "Warning: Blacklist override enabled (DBCLI_OVERRIDE_BLACKLIST=true). Executing {operation} on blacklisted table '{table}'",
2272
+ redis_blacklist_override_active: "Warning: Redis blacklist enforcement disabled by DBCLI_OVERRIDE_BLACKLIST=true"
2273
+ },
2274
+ version: {
2275
+ unsupported_warning: "{system} {version} is below minimum supported version {minVersion}. Some features may not work correctly.",
2276
+ doctor_pass: "{system} {version} (meets >= {minVersion})",
2277
+ doctor_warn: "{system} {version} is below supported >= {minVersion}"
2278
+ },
2279
+ migrate: {
2280
+ description: "Schema DDL operations (CREATE/DROP/ALTER TABLE, INDEX, CONSTRAINT, ENUM)",
2281
+ dry_run_preview: "[DRY RUN] Generated SQL (not executed):",
2282
+ executing: "Executing DDL...",
2283
+ success: "DDL executed successfully",
2284
+ confirm_drop: "This is a destructive operation ({operation}). Proceed?",
2285
+ permission_required: "DDL operations require admin permission (current: {permission})",
2286
+ blacklist_blocked: 'Table "{table}" is blacklisted \u2014 DDL operations are blocked',
2287
+ schema_refreshed: 'Schema cache updated for table "{table}"',
2288
+ no_sql_generated: "No SQL generated (operation not applicable for this database)",
2289
+ create_description: "Create a new table",
2290
+ drop_description: "Drop a table",
2291
+ add_column_description: "Add a column to a table",
2292
+ drop_column_description: "Drop a column from a table",
2293
+ alter_column_description: "Alter a column in a table",
2294
+ add_index_description: "Add an index to a table",
2295
+ drop_index_description: "Drop an index",
2296
+ add_constraint_description: "Add a constraint to a table",
2297
+ drop_constraint_description: "Drop a constraint from a table",
2298
+ add_enum_description: "Create an enum type (PostgreSQL only)",
2299
+ alter_enum_description: "Add a value to an enum type",
2300
+ drop_enum_description: "Drop an enum type"
2301
+ },
2302
+ password: {
2303
+ description: "Change the password of a single connection, leaving every other setting untouched",
2304
+ arg_connection: "Connection name (defaults to the current default connection)",
2305
+ opt_stdin: "Read the new password from stdin (for rotation scripts)",
2306
+ opt_password: "New password given inline (visible in shell history and process list)",
2307
+ opt_skip_test: "Skip verifying the new password against the database before saving",
2308
+ prompt: "New password for connection '{name}'",
2309
+ source_conflict: "--password and --stdin cannot be used together; pick one source.",
2310
+ needs_source: "No terminal available for a masked prompt. Pass the value with --stdin or --password.",
2311
+ empty: "The new password is empty; nothing was changed.",
2312
+ updated: "Password for connection '{name}' updated \u2192 {envFile} ({varName})",
2313
+ converted: 'The literal password in config.json was replaced by a {{ "$env": "{varName}" }} reference; later rotations only touch the env file.',
2314
+ skipped_test: "Verification skipped (--skip-test): the new password was not checked against the database."
2315
+ }
2316
+ };
2317
+ });
2318
+
2319
+ // resources/lang/zh-TW/messages.json
2320
+ var messages_default2;
2321
+ var init_messages2 = __esm(() => {
2322
+ messages_default2 = {
2323
+ cli: {
2324
+ agent_output_option: "\u8F38\u51FA\u4E00\u4EFD Operation Envelope v1\uFF08\u50C5\u652F\u63F4 capabilities \u8207 capabilities check\uFF09"
2325
+ },
2326
+ init: {
2327
+ description: "\u521D\u59CB\u5316 dbcli \u914D\u7F6E\uFF0C\u89E3\u6790 .env \u4E26\u9032\u884C\u4E92\u52D5\u5F0F\u63D0\u793A",
2328
+ welcome: "\u6B61\u8FCE\u4F7F\u7528 dbcli",
2329
+ prompt_connection_name: "\u9023\u7DDA\u540D\u7A31",
2330
+ connection_name_required: "\u9023\u7DDA\u540D\u7A31\u4E0D\u53EF\u7A7A\u767D\uFF0C\u8ACB\u63D0\u4F9B --conn-name <name>\uFF08\u8207\u8CC7\u6599\u5EAB\u540D\u7A31 --name \u4E0D\u540C\uFF09\u3002",
2331
+ select_system: "\u9078\u64C7\u8CC7\u6599\u5EAB\u7CFB\u7D71\uFF1A",
2332
+ prompt_host: "\u8CC7\u6599\u5EAB\u4E3B\u6A5F\uFF1A",
2333
+ prompt_port: "\u8CC7\u6599\u5EAB\u57E0\u865F\uFF1A",
2334
+ prompt_user: "\u8CC7\u6599\u5EAB\u4F7F\u7528\u8005\uFF1A",
2335
+ prompt_password: "\u8CC7\u6599\u5EAB\u5BC6\u78BC\uFF1A",
2336
+ secret_alternatives_sql: "\u6539\u7528 --password\u3001\u628A\u6191\u8B49\u653E\u9032 .env \u6216\u884C\u7A0B\u74B0\u5883\u8B8A\u6578\uFF0C\u6216\u4EE5 --use-env-refs \u5B58\u6210\u53C3\u7167",
2337
+ secret_alternatives_mongo_password: "\u6539\u7528 --password\u3001\u628A\u6191\u8B49\u653E\u9032 .env \u6216\u884C\u7A0B\u74B0\u5883\u8B8A\u6578\uFF0C\u6216\u4EE5 --uri \u8CBC\u4E0A\u9023\u7DDA\u5B57\u4E32",
2338
+ secret_alternatives_mongo_uri: "\u4EE5 --uri \u50B3\u5165\u9023\u7DDA\u5B57\u4E32",
2339
+ mongo_uri_conflicts_fields: "--uri \u4E0D\u80FD\u8207 {flags} \u4E26\u7528\uFF1A\u9023\u7DDA\u5B57\u4E32\u5DF2\u5305\u542B\u9019\u4E9B\u8A2D\u5B9A\u3002\u8ACB\u64C7\u4E00\u4F7F\u7528\uFF08--name \u53EF\u4E26\u7528\uFF0C\u7528\u4F86\u8986\u84CB\u8CC7\u6599\u5EAB\u540D\u7A31\uFF09\u3002",
2340
+ mongo_uri_with_env_refs: "--uri \u4E0D\u80FD\u8207 --use-env-refs \u4E26\u7528\uFF1A\u9023\u7DDA\u5B57\u4E32\u6703\u88AB\u62C6\u89E3\u6210\u6B04\u4F4D\u8A2D\u5B9A\u5BEB\u5165\uFF0C\u5E33\u5BC6\u4E0D\u6703\u88AB\u62BD\u6210\u74B0\u5883\u8B8A\u6578\u53C3\u7167\u3002\u8ACB\u6539\u8D70\u4E0B\u5217\u4EFB\u4E00\u505A\u6CD5\uFF1A\u62FF\u6389 --use-env-refs\uFF0C\u8B93 dbcli \u62C6\u89E3\u5B57\u4E32\u5BEB\u5165\u6B04\u4F4D\u8A2D\u5B9A\uFF1B\u6216\u6539\u7528 --env-* flag\uFF08--env-host\u3001--env-user\u3001--env-password \u7B49\uFF09\u9010\u6B04\u7D66\u8B8A\u6578\u540D\u7A31\u3002",
2341
+ mongo_uri_fallback_warning: "\u26A0\uFE0F \u6B64\u9023\u7DDA\u5B57\u4E32{reasons}\uFF0C\u7121\u6CD5\u62C6\u6210\u6B04\u4F4D\u8A2D\u5B9A\uFF0C\u5DF2\u539F\u6A23\u5B58\u5165 uri\uFF1B\u5E33\u5BC6\u5C07\u660E\u6587\u5B58\u653E\u65BC\u8A2D\u5B9A\u6A94\u3002",
2342
+ mongo_uri_reason_unix_socket: "\u4F7F\u7528 Unix domain socket\uFF08\u672C\u6A5F\u901A\u8A0A\u7AEF\uFF09",
2343
+ mongo_uri_reason_multi_host: "\u542B\u591A\u7D44 host\uFF08\u6B04\u4F4D\u8A2D\u5B9A\u53EA\u80FD\u8868\u9054\u4E00\u7D44\uFF09",
2344
+ mongo_uri_reason_secret_option: "\u542B\u6A5F\u5BC6 option\uFF08{keys}\uFF09\uFF0C\u62C6\u9032 options \u6703\u660E\u6587\u843D\u76E4",
2345
+ mongo_uri_reason_duplicate_key: "\u542B\u91CD\u8907\u7684 query \u9375\uFF08{keys}\uFF09\uFF08\u6B04\u4F4D\u8A2D\u5B9A\u6BCF\u500B\u9375\u53EA\u80FD\u5B58\u4E00\u500B\u503C\uFF09",
2346
+ mongo_uri_reason_non_scram: "\u4F7F\u7528\u975E SCRAM \u7684\u8A8D\u8B49\u6A5F\u5236 {mechanism}\uFF08\u6B04\u4F4D\u8A2D\u5B9A\u7121\u6CD5\u8868\u9054\uFF09",
2347
+ mongo_decomposed_header: "\u9023\u7DDA\u5B57\u4E32\u5DF2\u62C6\u89E3\u6210\u4E0B\u5217\u6B04\u4F4D\uFF08\u5BC6\u78BC\u5DF2\u906E\u853D\uFF09\uFF1A",
2348
+ mongo_paste_env_refs_warning: "\u26A0\uFE0F --use-env-refs \u4E0D\u9069\u7528\u65BC\u8CBC\u4E0A\u7684\u9023\u7DDA\u5B57\u4E32\uFF1A\u5E33\u5BC6\u6703\u4EE5\u5B57\u9762\u503C\u5BEB\u5165\u8A2D\u5B9A\u6A94\uFF0C\u4E0D\u6703\u8B8A\u6210\u74B0\u5883\u8B8A\u6578\u53C3\u7167\u3002",
2349
+ mongo_setup_mode_paste: "\u8CBC\u4E0A\u9023\u7DDA\u5B57\u4E32\uFF08Atlas / Compass \u8907\u88FD\u7684\uFF09",
2350
+ mongo_string_invalid: "\u4E0D\u662F\u5408\u6CD5\u7684 MongoDB \u9023\u7DDA\u5B57\u4E32\uFF08\u5B57\u4E32\u53EF\u80FD\u5E36\u6709\u5E33\u5BC6\uFF0C\u6240\u4EE5\u4E0D\u5370\u51FA\uFF09\u3002",
2351
+ mongo_string_bad_host: "\u9023\u7DDA\u5B57\u4E32\u7684 host \u6216\u57E0\u865F\u4E0D\u5408\u6CD5\uFF08\u57E0\u865F\u9808\u70BA 1 \u81F3 65535 \u7684\u6574\u6578\uFF09\u3002",
2352
+ mongo_string_bad_credentials: "\u9023\u7DDA\u5B57\u4E32\u7684\u5E33\u865F\u6216\u5BC6\u78BC\u542B\u6709\u672A\u8DF3\u812B\u7684\u4FDD\u7559\u5B57\u5143\uFF08\u5982 @\u3001:\u3001/\uFF09\u3002\u8ACB\u505A\u767E\u5206\u6BD4\u7DE8\u78BC\uFF08\u4F8B\u5982 @ \u5BEB\u6210 %40\uFF09\u3002",
2353
+ mongo_string_tls_conflict: "\u9023\u7DDA\u5B57\u4E32\u540C\u6642\u6307\u5B9A\u4E86 tls \u8207 ssl \u4E14\u503C\u4E0D\u540C\u3002",
2354
+ mongo_string_bad_tls: "\u9023\u7DDA\u5B57\u4E32\u7684 {key} \u53EA\u63A5\u53D7 true \u6216 false\u3002",
2355
+ mongo_string_empty_option: "\u9023\u7DDA\u5B57\u4E32\u6307\u5B9A\u4E86 {key} \u537B\u6C92\u6709\u503C\u3002\u8ACB\u7D66\u503C\u6216\u79FB\u9664\u3002",
2356
+ prompt_name: "\u8CC7\u6599\u5EAB\u540D\u7A31\uFF1A",
2357
+ prompt_permission: "\u6B0A\u9650\u7B49\u7D1A\uFF08query-only, read-write, data-admin, admin\uFF09\uFF1A",
2358
+ env_parse_note: "\u63D0\u793A\uFF1A\u7121\u6CD5\u89E3\u6790 .env \u914D\u7F6E\uFF0C\u4F7F\u7528\u4E92\u52D5\u5F0F\u63D0\u793A",
2359
+ connection_testing: "\u6B63\u5728\u6E2C\u8A66\u8CC7\u6599\u5EAB\u9023\u63A5...",
2360
+ connection_success: "\u2713 \u8CC7\u6599\u5EAB\u9023\u63A5\u6210\u529F",
2361
+ connection_failed: "\u2717 \u8CC7\u6599\u5EAB\u9023\u63A5\u5931\u6557",
2362
+ config_saved: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u81F3 .dbcli",
2363
+ config_saved_global: "\u5168\u57DF\u8A2D\u5B9A\u5DF2\u5132\u5B58\u81F3 {path}",
2364
+ config_exists_overwrite: "\u914D\u7F6E\u6A94\u6848 .dbcli \u5DF2\u5B58\u5728\u3002\u662F\u5426\u8986\u84CB\uFF1F (y/n)\uFF1A",
2365
+ cancelled: "\u5DF2\u53D6\u6D88\u3002\u914D\u7F6E\u672A\u66F4\u6539\u3002",
2366
+ skip_test_env_ref: "\u8DF3\u904E\u9023\u7DDA\u6E2C\u8A66\uFF08\u74B0\u5883\u8B8A\u6578\u53C3\u7167\u6A21\u5F0F\uFF09",
2367
+ skip_test: "\u8DF3\u904E\u9023\u7DDA\u6E2C\u8A66\uFF08--skip-test\uFF09",
2368
+ connection_hints: "\u63D0\u793A\uFF1A",
2369
+ config_exists_use_force: ".dbcli \u5DF2\u5B58\u5728\u3002\u4F7F\u7528 --force \u9078\u9805\u8986\u84CB\u3002",
2370
+ connection_added: "\u5DF2\u65B0\u589E\u9023\u7DDA '{name}'",
2371
+ connection_removed: "\u5DF2\u79FB\u9664\u9023\u7DDA '{name}'",
2372
+ connection_removed_default_cleared: "\u5DF2\u79FB\u9664\u9023\u7DDA '{name}'\uFF0C\u9810\u8A2D\u9023\u7DDA\u5DF2\u6E05\u7A7A\u3002\u57F7\u884C\u8CC7\u6599\u5EAB\u6307\u4EE4\u6642\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION\u3002",
2373
+ connection_renamed: "\u5DF2\u5C07\u9023\u7DDA '{oldName}' \u91CD\u65B0\u547D\u540D\u70BA '{newName}'",
2374
+ v1_migration_prompt: "\u5075\u6E2C\u5230\u820A\u683C\u5F0F\u8A2D\u5B9A\uFF0C\u5C07\u5EFA\u7ACB\u65B0\u683C\u5F0F\u4E26\u5C07\u73FE\u6709\u9023\u7DDA\u532F\u5165\u70BA 'default'\uFF0C\u662F\u5426\u7E7C\u7E8C\uFF1F",
2375
+ config_saved_v2: "V2 \u8A2D\u5B9A\u5DF2\u5132\u5B58\u81F3 .dbcli",
2376
+ config_not_found: "\u627E\u4E0D\u5230\u8A2D\u5B9A\u6A94",
2377
+ requires_v2_remove: "\u79FB\u9664\u9023\u7DDA\u9700\u8981 V2 \u683C\u5F0F\u8A2D\u5B9A",
2378
+ requires_v2_rename: "\u91CD\u65B0\u547D\u540D\u9023\u7DDA\u9700\u8981 V2 \u683C\u5F0F\u8A2D\u5B9A",
2379
+ connection_not_found: "\u9023\u7DDA '{name}' \u4E0D\u5B58\u5728",
2380
+ cannot_remove_last: "\u7121\u6CD5\u79FB\u9664\u6700\u5F8C\u4E00\u500B\u9023\u7DDA",
2381
+ connection_already_exists: "\u9023\u7DDA '{name}' \u5DF2\u5B58\u5728",
2382
+ rename_invalid_format: "\u7528\u6CD5\uFF1A--rename <\u820A\u540D\u7A31>:<\u65B0\u540D\u7A31>",
2383
+ prompt_sqlite_file: "SQLite \u8CC7\u6599\u5EAB\u6A94\u6848\u8DEF\u5F91\uFF1A",
2384
+ sql_tls_help: "\u9A57\u8B49\u5F0F SQL TLS \u6A21\u5F0F\uFF08verify-full\uFF1BPostgreSQL\uFF0FMySQL\uFF0FMariaDB\uFF09",
2385
+ sql_tls_ca_help: "\u4F9B --sql-tls verify-full \u4F7F\u7528\u7684 PEM CA bundle"
2386
+ },
2387
+ inspect: {
2388
+ 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",
2389
+ no_config: "\u627E\u4E0D\u5230 dbcli \u8A2D\u5B9A\u6A94\u3002\u8ACB\u5148\u57F7\u884C `dbcli init`\u3002"
2390
+ },
2391
+ report: {
2392
+ description: "\u8A3A\u65B7\u5831\u544A\uFF08health/capacity/perf\uFF09\uFF1A\u4EE5\u5167\u5EFA\u7247\u6BB5\u7522\u751F\u552F\u8B80\u3001\u6709\u754C\u7684\u8A3A\u65B7\u5FEB\u7167\u3002",
2393
+ no_config: "\u627E\u4E0D\u5230 dbcli \u8A2D\u5B9A\u6A94\u3002\u8ACB\u5148\u57F7\u884C `dbcli init`\u3002",
2394
+ section_invalid: "\u672A\u77E5\u7684 report section '{name}'\u3002\u5141\u8A31\u503C\uFF1Ahealth\u3001capacity\u3001perf"
2395
+ },
2396
+ guide: {
2397
+ description: "\u91DD\u5C0D\u56FA\u5B9A\u8CC7\u6599\u5EAB\u76EE\u6A19\u6E05\u55AE\uFF0C\u8F38\u51FA\u53EF\u9810\u6E2C\u7684\u4E0B\u4E00\u6B65\u6307\u4EE4\u8A08\u756B\u3002",
2398
+ no_config: "\u627E\u4E0D\u5230 dbcli \u8A2D\u5B9A\u6A94\u3002\u8ACB\u5148\u57F7\u884C `dbcli init`\u3002",
2399
+ missing_goal: "\u7F3A\u5C11 goal \u53C3\u6578\u3002\u8ACB\u57F7\u884C `dbcli guide --list` \u67E5\u770B\u53EF\u7528\u76EE\u6A19\u3002",
2400
+ goal_invalid: "\u672A\u77E5\u7684 guide goal '{name}'\u3002\u8ACB\u57F7\u884C `dbcli guide --list` \u53D6\u5F97\u6709\u6548\u76EE\u6A19 ID\u3002"
2401
+ },
2402
+ recovery: {
2403
+ description: "\u67E5\u8A62\u5DF2\u77E5 dbcli \u932F\u8AA4\u78BC\u5C0D\u61C9\u7684\u7D50\u69CB\u5316\u6062\u5FA9\u65B9\u6848\uFF0C\u6216\u5217\u51FA\u6240\u6709\u652F\u63F4\u7684\u932F\u8AA4\u78BC\u3002",
2404
+ missing_code: "\u7F3A\u5C11 --code \u53C3\u6578\u3002\u8ACB\u57F7\u884C `dbcli recovery --list` \u67E5\u770B\u53EF\u7528\u4EE3\u78BC\u3002",
2405
+ code_invalid: "\u672A\u77E5\u7684 recovery code '{name}'\u3002\u8ACB\u57F7\u884C `dbcli recovery --list` \u53D6\u5F97\u6709\u6548\u4EE3\u78BC\u3002",
2406
+ no_config: "\u627E\u4E0D\u5230 dbcli \u8A2D\u5B9A\u6A94\u3002\u8ACB\u5148\u57F7\u884C `dbcli init`\u3002"
2407
+ },
2408
+ use: {
2409
+ description: "\u5207\u63DB\u6216\u986F\u793A\u9810\u8A2D\u8CC7\u6599\u5EAB\u9023\u7DDA",
2410
+ switched: "\u5DF2\u5207\u63DB\u9810\u8A2D\u9023\u7DDA\u70BA {name}",
2411
+ current: "\u76EE\u524D\u7684\u9810\u8A2D\u9023\u7DDA\uFF1A{name}",
2412
+ current_none: "\u76EE\u524D\u6C92\u6709\u9810\u8A2D\u9023\u7DDA\u3002\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION\u3002",
2413
+ unset: "\u9810\u8A2D\u9023\u7DDA\u5DF2\u6E05\u7A7A\u3002\u57F7\u884C\u8CC7\u6599\u5EAB\u6307\u4EE4\u6642\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION\u3002",
2414
+ unset_conflict: "--unset \u4E0D\u53EF\u8207\u9023\u7DDA\u540D\u7A31\u3001--list\u3001--confirm-production \u6216 --format json \u540C\u6642\u4F7F\u7528",
2415
+ requires_v2: "\u6B64\u529F\u80FD\u9700\u8981\u65B0\u683C\u5F0F\u8A2D\u5B9A\u3002\u8ACB\u4F7F\u7528 dbcli init --conn-name <\u540D\u7A31> \u5EFA\u7ACB\u591A\u9023\u7DDA\u8A2D\u5B9A",
2416
+ available: "\u53EF\u7528\u9023\u7DDA"
2417
+ },
2418
+ schema: {
2419
+ description: "\u6AA2\u7D22\u8868\u683C\u7D50\u69CB\u6216\u5217\u51FA\u6240\u6709\u8868\u683C",
2420
+ fetching: "\u6B63\u5728\u6293\u53D6\u6A21\u5F0F...",
2421
+ success: "\u6A21\u5F0F\u5DF2\u66F4\u65B0",
2422
+ not_found: "\u8CC7\u6599\u5EAB\u4E2D\u627E\u4E0D\u5230\u8868\u683C",
2423
+ refresh_prompt: "\u6A21\u5F0F\u6709\u8B8A\u66F4\u3002\u662F\u5426\u5957\u7528\uFF1F (y/n)\uFF1A",
2424
+ columns_header: "\u6B04\u4F4D",
2425
+ scanning_database: "\uD83D\uDD0D \u6B63\u5728\u6383\u63CF\u8CC7\u6599\u5EAB\u67B6\u69CB...",
2426
+ tables_found: "\uD83D\uDCCD \u627E\u5230 {count} \u500B\u8868\u683C\u3002\u6B63\u5728\u7372\u53D6\u67B6\u69CB\u8A73\u60C5...",
2427
+ processing_tables: " \u8655\u7406\u4E86 {processed}/{total} \u500B\u8868\u683C",
2428
+ schema_exists_warning: "\u26A0\uFE0F \u8CC7\u6599\u5EAB\u67B6\u69CB\u5DF2\u5B58\u5728\u65BC .dbcli",
2429
+ use_force_to_override: " \u4F7F\u7528 --force \u9032\u884C\u8986\u84CB\u800C\u7121\u9700\u78BA\u8A8D"
2430
+ },
2431
+ list: {
2432
+ description: "\u5217\u51FA\u8CC7\u6599\u5EAB\u4E2D\u7684\u6240\u6709\u8868\u683C",
2433
+ no_tables: "\u8CC7\u6599\u5EAB\u4E2D\u672A\u627E\u5230\u8868\u683C"
2434
+ },
2435
+ query: {
2436
+ description: "\u57F7\u884C SQL \u67E5\u8A62",
2437
+ executing: "\u6B63\u5728\u57F7\u884C\u67E5\u8A62...",
2438
+ no_results: "\u672A\u8FD4\u56DE\u4EFB\u4F55\u7D50\u679C",
2439
+ result_count: "\u7D50\u679C\uFF1A{count} \u5217"
2440
+ },
2441
+ q: {
2442
+ description: "\u4F9D @name \u57F7\u884C\u5DF2\u4FDD\u5B58\u7684\u67E5\u8A62\u7247\u6BB5",
2443
+ missing_param: "\u7247\u6BB5 '{name}' \u7F3A\u5C11\u5FC5\u8981\u53C3\u6578",
2444
+ engine_mismatch: "\u7247\u6BB5 '{name}' \u5F15\u64CE\u4E0D\u7B26",
2445
+ snippet_not_found: "\u627E\u4E0D\u5230\u7247\u6BB5\uFF1A{name}",
2446
+ dry_run_header: "Dry-run \u9810\u89BD\uFF08\u4E0D\u6703\u57F7\u884C\uFF09\uFF1A"
2447
+ },
2448
+ audit: {
2449
+ description: "\u6AA2\u8996\u3001\u67E5\u8A62\u3001\u6E05\u7A7A\u6216\u6AA2\u67E5 audit log \u5065\u5EB7\u72C0\u614B",
2450
+ tail: {
2451
+ description: "\u986F\u793A\u6700\u8FD1\u7684 audit \u7D00\u9304\uFF08\u9810\u8A2D\uFF1A\u7576\u524D\u9023\u7DDA\u6700\u5F8C 10 \u7B46\uFF09"
2452
+ },
2453
+ show: {
2454
+ description: "\u4F9D id\uFF08UUID \u524D\u7DB4 \u22654 \u78BC\uFF09\u6216 --recovery-ref \u67E5\u8A62\u55AE\u7B46 audit \u7D00\u9304"
2455
+ },
2456
+ clear: {
2457
+ description: "\u522A\u9664\u7576\u524D\u9023\u7DDA\u7684 audit log \u6A94\u6848\uFF08.jsonl \u8207 .jsonl.1\uFF09",
2458
+ prompt_header: "\u5373\u5C07\u6E05\u7A7A\u9023\u7DDA '{conn}' \u7684 audit log\uFF1A",
2459
+ prompt_file_line: " {file} \u2014 {entries} \u7B46\uFF0C{size}",
2460
+ prompt_continue: "\u78BA\u8A8D\u57F7\u884C\uFF1F[y/N] ",
2461
+ requires_tty_or_yes: "\u975E\u4E92\u52D5 session \u7121\u6CD5\u986F\u793A\u78BA\u8A8D\u63D0\u793A\u3002\u8ACB\u4F7F\u7528 --yes \u76F4\u63A5\u6E05\u7A7A\u3002",
2462
+ summary_cleared: "\u5DF2\u5F9E '{conn}' \u6E05\u9664 {count} \u7B46 audit \u7D00\u9304\u3002",
2463
+ summary_nothing: "\u6C92\u6709\u53EF\u6E05\u9664\u7684\u5167\u5BB9\u3002",
2464
+ summary_failed: "\u6E05\u9664\u5931\u6557\uFF1A{message}\u3002"
2465
+ },
2466
+ health: {
2467
+ description: "\u986F\u793A AuditLogger \u5065\u5EB7\u72C0\u614B\uFF08\u6A94\u6848\u5927\u5C0F\u3001\u7B46\u6578\u3001lock \u72C0\u614B\u3001\u6700\u5F8C\u5BEB\u5165\u7D50\u679C\uFF09"
2468
+ },
2469
+ write_gate: {
2470
+ description: "\u7D71\u8A08\u7B2C\u4E8C\u7D1A\u5BEB\u5165\u9598\u9580\u7684\u5224\u5B9A\uFF08ADR 0010 \u62BC\u6CE8\u7684\u90A3\u500B\u91CF\u6E2C\uFF09",
2471
+ header: "\u5BEB\u5165\u9598\u9580\u6458\u8981 \u2014 \u9023\u7DDA\uFF1A{conn}",
2472
+ window: "\u6383\u904E\u7684\u7A3D\u6838\u7D00\u9304\uFF1A{scanned} \u7B46\uFF0C{from} \u2192 {to}",
2473
+ decisions: "\u7B2C\u4E8C\u7D1A\u5224\u5B9A\uFF1A{count} \u6B21\uFF0C{from} \u2192 {to}",
2474
+ tier_one: "\u7B2C\u4E00\u7D1A\uFF0C\u5206\u958B\u8A08\u7B97\uFF1A{count} \u6B21\uFF08{outcomes}\uFF09",
2475
+ no_decisions: "\u9019\u6BB5\u5340\u9593\u5167\u7B2C\u4E8C\u7D1A\u4E00\u6B21\u90FD\u6C92\u88AB\u89F8\u53CA\u3002\u4F9D ADR 0010\uFF0C\u9019\u63A8\u7FFB\u7684\u662F\u5224\u6E96\u800C\u4E0D\u662F\u9598\u9580\uFF1A\u5B83\u5728\u627E\u7684\u5F62\u72C0\u4E0D\u662F\u5927\u5BB6\u5BE6\u969B\u6703\u5BEB\u7684\u5F62\u72C0\u3002",
2476
+ no_entries: "\u7A3D\u6838\u7D00\u9304\u662F\u7A7A\u7684\uFF0C\u6240\u4EE5\u9019\u5C0D\u9598\u9580\u6C92\u6709\u4EFB\u4F55\u7D50\u8AD6\u3002\u5148\u8DD1\u4E00\u4E9B\u5BEB\u5165\uFF0C\u6216\u7528 `dbcli audit health` \u6AA2\u67E5\u3002"
2477
+ },
2478
+ disabled_hint: "Audit \u5DF2\u505C\u7528\uFF08.dbcli \u4E2D audit.enabled = false\uFF09\u3002\u57F7\u884C `dbcli audit health` \u67E5\u770B\u8A73\u60C5\u3002",
2479
+ no_entries: "\u6C92\u6709 audit \u7D00\u9304\u3002",
2480
+ show_no_match: "\u627E\u4E0D\u5230\u7B26\u5408 '{prefix}' \u7684 audit \u7D00\u9304\u3002",
2481
+ show_ambiguous: "\u524D\u7DB4 '{prefix}' \u4E0D\u5920\u7CBE\u78BA\uFF1A\u5339\u914D\u5230 {count} \u7B46\u7D00\u9304\uFF0C\u8ACB\u6539\u7528\u66F4\u9577\u7684\u524D\u7DB4\u3002",
2482
+ show_prefix_too_short: "\u524D\u7DB4\u81F3\u5C11\u9700 4 \u500B\u5B57\u5143\u3002",
2483
+ show_recovery_no_match: "\u6C92\u6709\u4EFB\u4F55 audit \u7D00\u9304\u7684 recovery_ref \u70BA '{ref}'\u3002",
2484
+ show_recovery_ambiguous: "\u6709 {count} \u7B46\u7D00\u9304\u7684 recovery_ref \u70BA '{ref}'\uFF0C\u9019\u61C9\u5C6C\u7F55\u898B\u60C5\u6CC1\uFF1B\u8ACB\u76F4\u63A5\u6AA2\u8996\u6A94\u6848\u3002",
2485
+ show_mutex_violation: "\u8ACB\u64C7\u4E00\u63D0\u4F9B <id> \u53C3\u6578\u6216 --recovery-ref\uFF0C\u5169\u8005\u4E0D\u53EF\u540C\u6642\u4F7F\u7528\u3002",
2486
+ n_capped_warning: "--n \u503C {requested} \u8D85\u904E\u4E0A\u9650 {max}\uFF0C\u5DF2\u81EA\u52D5\u7E2E\u6E1B\u70BA {max}\u3002",
2487
+ n_must_be_positive: "--n \u5FC5\u9808\u70BA\u6B63\u6574\u6578\u3002"
2488
+ },
2489
+ queries: {
2490
+ description: "\u7BA1\u7406\u5DF2\u4FDD\u5B58\u7684\u67E5\u8A62\u7247\u6BB5",
2491
+ list_description: "\u5217\u51FA\u6240\u6709\u7247\u6BB5",
2492
+ show_description: "\u986F\u793A\u7247\u6BB5\u5167\u5BB9\uFF08frontmatter + SQL\uFF09",
2493
+ new_description: "\u5EFA\u7ACB\u65B0\u7684\u7247\u6BB5\u6A94",
2494
+ edit_description: "\u4EE5 $EDITOR \u958B\u555F\u7247\u6BB5",
2495
+ check_description: "\u9A57\u8B49\u6240\u6709\u7247\u6BB5",
2496
+ delete_description: "\u522A\u9664 local snippet \u6A94\u6848",
2497
+ rename_description: "\u91CD\u65B0\u547D\u540D local snippet",
2498
+ copy_description: "\u5C07\u4EFB\u4E00\u5C64\u7D1A\u7684 snippet \u8907\u88FD\u5230 local",
2499
+ import_description: "\u532F\u5165\u5916\u90E8 .sql \u6A94\u6848\u5230 local",
2500
+ export_description: "\u5C07 snippet \u539F\u59CB SQL \u8F38\u51FA\u5230 stdout \u6216\u6A94\u6848",
2501
+ created: "\u5DF2\u5EFA\u7ACB {file}",
2502
+ first_run_hint: "\u63D0\u793A\uFF1A\u5C07 .dbcli-shared/ commit \u9032\u7248\u63A7\u53EF\u8207\u5718\u968A\u5171\u4EAB\u7247\u6BB5\u3002",
2503
+ no_snippets: "\u5C1A\u7121\u4EFB\u4F55\u7247\u6BB5\u3002\u57F7\u884C dbcli queries new @<name> \u5EFA\u7ACB\u3002",
2504
+ check_ok: "\u2713 \u6210\u529F\u89E3\u6790 {count} \u500B\u7247\u6BB5",
2505
+ check_failed: "\u2717 \u6709 {count} \u500B\u7247\u6BB5\u89E3\u6790\u5931\u6557",
2506
+ search_description: "\u4F9D\u95DC\u9375\u5B57\u641C\u5C0B snippet\uFF08fuzzy \u6392\u5E8F\uFF09",
2507
+ suggest_description: "\u4F9D intent prefix \u63A8\u85A6 snippet",
2508
+ search_no_keywords: "\u81F3\u5C11\u63D0\u4F9B\u4E00\u500B\u95DC\u9375\u5B57\uFF0C\u6216\u8A66\u8A66 'queries list' / 'queries suggest'\u3002",
2509
+ search_no_results: "\u6C92\u6709\u547D\u4E2D\u7684 snippet\u3002\u8A66\u8A66 'queries list' \u6216 'queries suggest <intent>'\u3002",
2510
+ suggest_missing_intent: "\u8ACB\u63D0\u4F9B intent\uFF08\u6216 prefix\uFF09\u3002\u53EF\u7528\uFF1A{available}",
2511
+ suggest_invalid_intent: "Intent '{value}' \u683C\u5F0F\u932F\u8AA4\uFF0C\u5FC5\u9808\u7B26\u5408 ^[a-z][a-z0-9.-]*$",
2512
+ suggest_no_results: "\u6C92\u6709 intent prefix \u70BA '{intent}' \u7684 snippet\u3002\u53EF\u7528\uFF1A{available}",
2513
+ no_active_connection_hint: "\u5C1A\u672A\u8A2D\u5B9A connection\uFF1B\u5217\u51FA\u6240\u6709\u5F15\u64CE\u7684 snippet\u3002"
2514
+ },
2515
+ errors: {
2516
+ message: "\u932F\u8AA4\uFF1A{message}",
2517
+ query_only_boundary_failed: "\u7121\u6CD5\u70BA {system} \u5EFA\u7ACB query-only \u8CC7\u6599\u5EAB\u908A\u754C\uFF1A{detail}",
2518
+ query_only_boundary_verify: "\u8ACB\u78BA\u8A8D {system} \u652F\u63F4\u552F\u8B80\u4EA4\u6613\uFF0C\u4E14\u6B64\u5E33\u865F\u53EF\u4EE5\u555F\u52D5\u4EA4\u6613",
2519
+ query_only_boundary_not_executed: "\u76EE\u6A19\u8A9E\u53E5\u5C1A\u672A\u57F7\u884C\uFF1B\u8ACB\u4FEE\u6B63\u8CC7\u6599\u5EAB\u8A2D\u5B9A\u6216\u9023\u7DDA\u5F8C\u518D\u8A66",
2520
+ query_only_cleanup_completed: "query-only \u76EE\u6A19\u5DF2\u5B8C\u6210\uFF0C\u4F46 {system} \u7684\u4EA4\u6613\u6E05\u7406\u5931\u6557\uFF1A{detail}",
2521
+ query_only_cleanup_uncertain: "query-only \u76EE\u6A19\u5931\u6557\uFF0C\u4E14 {system} \u7684\u4EA4\u6613\u6E05\u7406\u4E5F\u5931\u6557\uFF0C\u56E0\u6B64\u7D50\u679C\u4E0D\u78BA\u5B9A\uFF1A{detail}",
2522
+ query_only_cleanup_completed_retry: "\u76EE\u6A19\u8A9E\u53E5\u5DF2\u57F7\u884C\uFF1B\u9664\u975E\u53EF\u5B89\u5168\u91CD\u8907\u5176\u5916\u90E8\u526F\u4F5C\u7528\uFF0C\u5426\u5247\u4E0D\u8981\u91CD\u8A66",
2523
+ query_only_cleanup_uncertain_retry: "\u76EE\u6A19\u8A9E\u53E5\u53EF\u80FD\u5728\u5931\u6557\u524D\u7522\u751F\u526F\u4F5C\u7528\uFF1B\u9664\u975E\u53EF\u5B89\u5168\u91CD\u8907\u9019\u4E9B\u526F\u4F5C\u7528\uFF0C\u5426\u5247\u4E0D\u8981\u91CD\u8A66",
2524
+ query_only_cleanup_reconnect: "\u6B64\u9023\u7DDA\u5DF2\u4E1F\u68C4\uFF1B\u57F7\u884C\u4E0B\u4E00\u53E5\u524D\u8ACB\u5148\u91CD\u65B0\u9023\u7DDA",
2525
+ invalid_config: "\u914D\u7F6E\u7121\u6548\uFF1A{field}",
2526
+ connection_failed: "\u7121\u6CD5\u9023\u63A5\u5230\u8CC7\u6599\u5EAB\uFF1A{message}",
2527
+ permission_denied: "\u6B0A\u9650\u88AB\u62D2\uFF08\u9700\u8981\uFF1A{required}\uFF09",
2528
+ permission_requires_level: "{type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
2529
+ elasticsearch_requires_level: "Elasticsearch {type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
2530
+ 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",
2531
+ 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",
2532
+ 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",
2533
+ permission_denied_reason: "\u6B0A\u9650\u88AB\u62D2\uFF1A{reason}",
2534
+ table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
2535
+ unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
2536
+ invalid_json: "\u7121\u6548\u7684 JSON\uFF1A{message}",
2537
+ table_blacklisted: "\u932F\u8AA4: \u8868\u683C '{table}' \u7684 {operation} \u64CD\u4F5C\u5DF2\u88AB\u9ED1\u540D\u55AE",
2538
+ redis_target_blacklisted: "Redis \u6307\u4EE4\u5DF2\u88AB\u9ED1\u540D\u55AE\u62D2\u7D55",
2539
+ column_blacklisted_write: "\u932F\u8AA4: \u8868\u683C '{table}' \u7684 {operation} \u64CD\u4F5C\u5305\u542B\u9ED1\u540D\u55AE\u6B04\u4F4D: {columns}",
2540
+ table_already_blacklisted: "\u932F\u8AA4: \u8868\u683C '{table}' \u5DF2\u5728\u9ED1\u540D\u55AE\u4E2D",
2541
+ table_not_in_blacklist: "\u932F\u8AA4: \u8868\u683C '{table}' \u4E0D\u5728\u9ED1\u540D\u55AE\u4E2D",
2542
+ column_already_blacklisted: "\u932F\u8AA4: \u6B04\u4F4D '{table}.{column}' \u5DF2\u5728\u9ED1\u540D\u55AE\u4E2D",
2543
+ column_not_in_blacklist: "\u932F\u8AA4: \u6B04\u4F4D '{table}.{column}' \u4E0D\u5728\u9ED1\u540D\u55AE\u4E2D",
2544
+ invalid_table_name: "\u932F\u8AA4: \u7121\u6548\u7684\u8868\u683C\u540D\u7A31: {table}",
2545
+ invalid_column_format: "\u932F\u8AA4: \u7121\u6548\u7684\u6B04\u4F4D\u683C\u5F0F\u3002\u4F7F\u7528 'table.column'",
2546
+ invalid_system: "\u7121\u6548\u7684\u8CC7\u6599\u5EAB\u7CFB\u7D71\uFF1A{system}",
2547
+ invalid_permission: "\u7121\u6548\u7684\u6B0A\u9650\u7B49\u7D1A\uFF1A{permission}",
2548
+ invalid_port: "\u7121\u6548\u7684\u57E0\u865F\uFF1A{port}",
2549
+ require_user: "\u975E\u4E92\u52D5\u6A21\u5F0F\u9700\u8981 --user \u9078\u9805",
2550
+ require_name: "\u975E\u4E92\u52D5\u6A21\u5F0F\u9700\u8981 --name \u9078\u9805",
2551
+ env_refs_missing_options: `\u4F7F\u7528 --use-env-refs \u6642\uFF0C\u5FC5\u9808\u6307\u5B9A\u74B0\u5883\u8B8A\u6578\u540D\u7A31\u3002
2552
+ \u8ACB\u63D0\u4F9B\u9078\u9805\uFF1A--env-host\u3001--env-port\u3001--env-user\u3001--env-password\u3001--env-database
2553
+ \u6216\u4F7F\u7528\u4E92\u52D5\u6A21\u5F0F\uFF1A\u57F7\u884C "dbcli init --use-env-refs"\uFF08\u4E0D\u52A0 --no-interactive\uFF09`,
2554
+ env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
2555
+ \u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
2556
+ \u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`,
2557
+ invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json",
2558
+ 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",
2559
+ sql_tls_unsupported: "\u660E\u78BA SQL TLS \u76EE\u524D\u50C5\u652F\u63F4 PostgreSQL\u3001MySQL \u8207 MariaDB",
2560
+ sql_tls_options_required: "\u8ACB\u540C\u6642\u63D0\u4F9B --sql-tls verify-full \u8207 --sql-tls-ca <path>",
2561
+ sql_tls_change_engine: "\u660E\u78BA SQL TLS \u76EE\u524D\u50C5\u652F\u63F4 PostgreSQL\u3001MySQL \u8207 MariaDB\uFF1B\u7DE8\u8F2F\u9023\u7DDA\u6642\u4E0D\u80FD\u6368\u68C4\u6B64\u8981\u6C42",
2562
+ sql_tls_legacy_import: "\u4E0D\u652F\u63F4\u5E36\u6709 SQL TLS \u7684\u820A\u7248\u55AE\u6A94\u81EA\u52D5\u532F\u5165\u3002\u8ACB\u4FDD\u7559\u539F\u9023\u7DDA\uFF0C\u5728\u53E6\u4E00\u500B\u8A2D\u5B9A\u76EE\u9304\u5EFA\u7ACB\u4E26\u9A57\u8B49\u5177\u540D\u9023\u7DDA\u5F8C\u518D\u5207\u63DB\uFF1B\u4E0D\u8981\u79FB\u9664 SQL TLS\u3002",
2563
+ sql_tls_ca_absolute: "SQL TLS CA \u8DEF\u5F91\u5FC5\u9808\u70BA\u7D55\u5C0D\u8DEF\u5F91",
2564
+ sql_tls_network_host: "\u660E\u78BA SQL TLS \u5FC5\u9808\u6307\u5B9A DNS \u6216 IP \u4E3B\u6A5F\uFF0C\u4E0D\u63A5\u53D7 Unix socket",
2565
+ sql_tls_ca_empty: "CA bundle \u4E0D\u542B PEM \u6191\u8B49",
2566
+ sql_tls_ca_load: "\u7121\u6CD5\u8F09\u5165 SQL TLS CA bundle\uFF1A{detail}",
2567
+ sql_tls_ca_restore: "\u8ACB\u6062\u5FA9 sqlTls.caPath \u6307\u5411\u7684\u53EF\u8B80\u6709\u6548 PEM CA bundle\uFF0C\u4E26\u4FDD\u6301 verify-full\u3002",
2568
+ sql_tls_handshake: "TLS \u63E1\u624B\u5931\u6557\uFF1A{detail}",
2569
+ sql_tls_ca_hint: "\u8ACB\u5C07 sqlTls.caPath \u8A2D\u70BA\u6B63\u78BA\u4E14\u53EF\u8B80\u7684 PEM CA bundle\u3002",
2570
+ sql_tls_identity_hint: "\u8ACB\u78BA\u8A8D\u6191\u8B49\u6709\u6548\u671F\u53CA {host} \u7684 DNS\uFF0FIP \u8EAB\u5206\u76F8\u7B26\u3002",
2571
+ sql_tls_no_downgrade: "\u8ACB\u4FDD\u6301 verify-full\uFF1B\u4E0D\u8981\u79FB\u9664 SQL TLS\uFF0C\u4E5F\u4E0D\u8981\u95DC\u9589\u9A57\u8B49\u5F8C\u91CD\u8A66\u3002",
2572
+ sql_tls_driver_identity: "\u6B64\u9A45\u52D5\u7A0B\u5F0F\u7121\u6CD5\u57F7\u884C SQL TLS \u8EAB\u5206\u9A57\u8B49",
2573
+ sql_tls_peer_unverified: "SQL TLS \u5C0D\u7AEF\u5C1A\u672A\u901A\u904E\u9A57\u8B49",
2574
+ sql_tls_peer_missing: "\u7121\u6CD5\u53D6\u5F97 SQL TLS \u5C0D\u7AEF\u6191\u8B49",
2575
+ sql_tls_recovery_config: "TLS \u5FA9\u539F\u9808\u660E\u78BA\u6307\u5B9A --config <path> \u6216 --global \u8207\u9810\u671F\u7684 TLS profile\uFF1B\u9810\u8A2D\u503C\u7121\u6CD5\u8B58\u5225\u5931\u6557\u7684\u76EE\u6A19\u3002",
2576
+ sql_tls_recovery_profile: "TLS \u5FA9\u539F\u9808\u6307\u5B9A --use <name>\uFF08\u6216 DBCLI_CONNECTION\uFF09\uFF1B\u4E0D\u80FD\u9000\u56DE\u9810\u8A2D profile\u3002",
2577
+ sql_tls_recovery_required: "TLS \u5FA9\u539F\u9808\u4F7F\u7528\u660E\u78BA\u8A2D\u5B9A sqlTls verify-full \u7684 profile\uFF1B\u62D2\u7D55\u660E\u6587\u5FA9\u539F\u3002",
2578
+ sql_tls_recovery_target: "TLS \u5FA9\u539F\u9808\u660E\u78BA\u9078\u53D6\u5DF2\u8A2D\u5B9A\u9A57\u8B49\u5F0F TLS \u7684 profile\u3002"
2579
+ },
2580
+ success: {
2581
+ inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
2582
+ updated: "\u6210\u529F\u66F4\u65B0 {count} \u5217",
2583
+ deleted: "\u6210\u529F\u522A\u9664 {count} \u5217",
2584
+ no_changes: "\u6C92\u6709\u8B8A\u66F4\u8981\u5957\u7528"
2585
+ },
2586
+ insert: {
2587
+ description: "\u63D2\u5165\u8CC7\u6599\u5230\u8868\u683C",
2588
+ prompt_data: "\u8F38\u5165\u8981\u63D2\u5165\u7684 JSON \u8CC7\u6599\uFF1A",
2589
+ confirm: "\u5C07 {count} \u5217\u63D2\u5165\u5230 {table}\uFF1F (y/n)\uFF1A",
2590
+ elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 insert \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u5BEB\u5165"
2591
+ },
2592
+ update: {
2593
+ description: "\u66F4\u65B0\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
2594
+ prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
2595
+ prompt_set: '\u8F38\u5165 JSON \u66F4\u65B0 (\u4F8B\u5982\uFF1A{"status": "active"})\uFF1A',
2596
+ confirm: "\u66F4\u65B0 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
2597
+ elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 update \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u66F4\u65B0"
2598
+ },
2599
+ delete: {
2600
+ description: "\u522A\u9664\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
2601
+ prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
2602
+ confirm: "\u522A\u9664 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
2603
+ admin_only: "\u522A\u9664\u547D\u4EE4\u9700\u8981 data-admin \u6216 admin \u6B0A\u9650",
2604
+ elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 delete \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u522A\u9664"
2605
+ },
2606
+ export: {
2607
+ description: "\u532F\u51FA\u67E5\u8A62\u7D50\u679C",
2608
+ formats: "\u652F\u63F4\u7684\u683C\u5F0F\uFF1Ajson\u3001csv",
2609
+ exported: "\u5DF2\u5C07 {count} \u5217\u532F\u51FA\u81F3 {file}"
2610
+ },
2611
+ upgrade: {
2612
+ description: "\u6AA2\u67E5\u66F4\u65B0\u4E26\u5C07 dbcli \u5347\u7D1A\u81F3\u6700\u65B0\u7248\u672C",
2613
+ checking: "\u6B63\u5728\u6AA2\u67E5\u66F4\u65B0...",
2614
+ already_up_to_date: "\u2713 \u5DF2\u7D93\u662F\u6700\u65B0\u7248\u672C (v{version})",
2615
+ new_version_available: "\u767C\u73FE\u65B0\u7248\u672C\uFF1Av{version}",
2616
+ current_version: "\u76EE\u524D\u7248\u672C\uFF1Av{version}",
2617
+ latest_version: "\u6700\u65B0\u7248\u672C\uFF1Av{version}",
2618
+ update_hint: '[\u63D0\u793A] dbcli v{version} \u5DF2\u767C\u5E03\u3002\u57F7\u884C "dbcli upgrade" \u9032\u884C\u5347\u7D1A\u3002',
2619
+ install_hint: '\u57F7\u884C "dbcli upgrade" \u5B89\u88DD\u66F4\u65B0\u3002',
2620
+ upgrading: "\u6B63\u5728\u5347\u7D1A...",
2621
+ success: "\u2713 \u6210\u529F\u5347\u7D1A\u81F3 v{version}",
2622
+ skill_recheck_hint: '\u63D0\u793A\uFF1A\u82E5\u5DF2\u5B89\u88DD AI \u6280\u80FD\uFF0C\u8ACB\u57F7\u884C "dbcli skill --install <platform>" \u78BA\u8A8D\u6280\u80FD\u70BA\u6700\u65B0\u7248\u672C\u3002',
2623
+ failed: "\u2717 \u5347\u7D1A\u5931\u6557\u3002\u8ACB\u5617\u8A66\u624B\u52D5\u57F7\u884C\uFF1A",
2624
+ manual_hint: "bun add -g @carllee1983/dbcli@latest",
2625
+ network_error: "\u26A0 \u7121\u6CD5\u6AA2\u67E5\u66F4\u65B0\uFF08\u7DB2\u8DEF\u932F\u8AA4\u6216\u8A3B\u518A\u8868\u4E0D\u53EF\u7528\uFF09"
2626
+ },
2627
+ skill: {
2628
+ description: "\u751F\u6210 AI \u6280\u80FD\u6587\u6A94",
2629
+ context_description: "\u751F\u6210 AI \u5354\u4F5C\u6240\u9700\u7684\u8CC7\u6599\u5EAB\u67B6\u69CB\u8207\u67E5\u8A62\u7247\u6BB5\u4E0A\u4E0B\u6587",
2630
+ installed: `\u6280\u80FD: {path}
2631
+ \u53C3\u8003: {referencePath}`,
2632
+ update_available: "\u4EE5\u4E0B\u5E73\u53F0\u7684\u6280\u80FD\u9700\u8981\u66F4\u65B0\uFF1A",
2633
+ update_hint: '\u57F7\u884C "dbcli skill --install <platform>" \u9032\u884C\u66F4\u65B0\u3002'
2634
+ },
2635
+ blacklist: {
2636
+ description: "\u7BA1\u7406\u654F\u611F\u8CC7\u6599\u9ED1\u540D\u55AE\u4EE5\u9632\u6B62 AI \u5B58\u53D6",
2637
+ list_title: "\u76EE\u524D\u9ED1\u540D\u55AE\u914D\u7F6E",
2638
+ tables_label: "\u5DF2\u9ED1\u540D\u55AE\u7684\u8868\u683C",
2639
+ columns_label: "\u5DF2\u9ED1\u540D\u55AE\u7684\u6B04\u4F4D",
2640
+ none: "\u76EE\u524D\u6C92\u6709\u9ED1\u540D\u55AE\u8868\u683C\u6216\u6B04\u4F4D",
2641
+ table_added: "\u2713 \u8868\u683C '{table}' \u5DF2\u65B0\u589E\u81F3\u9ED1\u540D\u55AE",
2642
+ table_removed: "\u2713 \u8868\u683C '{table}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664",
2643
+ column_added: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u65B0\u589E\u81F3\u9ED1\u540D\u55AE",
2644
+ column_removed: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664",
2645
+ refuse_wildcard: "\u5DF2\u62D2\u7D55\uFF1A'{table}' \u542B\u6709\u842C\u7528\u5B57\u5143\uFF0C\u800C {system} \u4E0A\u7684\u6BD4\u5C0D\u662F\u9010\u5B57\u9032\u884C\u7684\u2014\u2014\u9019\u7B46\u8A2D\u5B9A\u6C38\u9060\u4E0D\u6703\u547D\u4E2D\u4EFB\u4F55\u6771\u897F\u3002\u8ACB\u5BEB\u51FA\u78BA\u5207\u7684\u540D\u7A31\u3002"
2646
+ },
2647
+ security: {
2648
+ columns_omitted: "\u5B89\u5168: {count} \u500B\u6B04\u4F4D\u6839\u64DA\u4F60\u7684\u9ED1\u540D\u55AE\u5DF2\u88AB\u96B1\u85CF"
2649
+ },
2650
+ warnings: {
2651
+ blacklist_override_used: "\u8B66\u544A: \u5DF2\u555F\u7528\u9ED1\u540D\u55AE\u8986\u84CB (DBCLI_OVERRIDE_BLACKLIST=true)\u3002\u57F7\u884C {operation} \u5728\u5DF2\u9ED1\u540D\u55AE\u7684\u8868\u683C '{table}' \u4E0A",
2652
+ redis_blacklist_override_active: "\u8B66\u544A: DBCLI_OVERRIDE_BLACKLIST=true \u5DF2\u505C\u7528 Redis \u9ED1\u540D\u55AE\u5F37\u5236\u57F7\u884C"
2653
+ },
2654
+ version: {
2655
+ unsupported_warning: "{system} {version} \u4F4E\u65BC\u6700\u4F4E\u652F\u63F4\u7248\u672C {minVersion}\u3002\u90E8\u5206\u529F\u80FD\u53EF\u80FD\u7121\u6CD5\u6B63\u5E38\u904B\u4F5C\u3002",
2656
+ doctor_pass: "{system} {version}\uFF08\u7B26\u5408 >= {minVersion}\uFF09",
2657
+ doctor_warn: "{system} {version} \u4F4E\u65BC\u652F\u63F4\u7248\u672C >= {minVersion}"
2658
+ },
2659
+ migrate: {
2660
+ description: "Schema DDL \u64CD\u4F5C\uFF08CREATE/DROP/ALTER TABLE\u3001INDEX\u3001CONSTRAINT\u3001ENUM\uFF09",
2661
+ dry_run_preview: "[\u9810\u89BD] \u7522\u751F\u7684 SQL\uFF08\u672A\u57F7\u884C\uFF09\uFF1A",
2662
+ executing: "\u57F7\u884C DDL \u4E2D...",
2663
+ success: "DDL \u57F7\u884C\u6210\u529F",
2664
+ confirm_drop: "\u9019\u662F\u7834\u58DE\u6027\u64CD\u4F5C\uFF08{operation}\uFF09\uFF0C\u78BA\u5B9A\u7E7C\u7E8C\uFF1F",
2665
+ permission_required: "DDL \u64CD\u4F5C\u9700\u8981 admin \u6B0A\u9650\uFF08\u76EE\u524D\uFF1A{permission}\uFF09",
2666
+ blacklist_blocked: '\u8868\u683C "{table}" \u5DF2\u88AB\u5217\u5165\u9ED1\u540D\u55AE \u2014 DDL \u64CD\u4F5C\u88AB\u963B\u64CB',
2667
+ schema_refreshed: '\u5DF2\u66F4\u65B0\u8868\u683C "{table}" \u7684 Schema \u5FEB\u53D6',
2668
+ no_sql_generated: "\u672A\u7522\u751F SQL\uFF08\u6B64\u64CD\u4F5C\u4E0D\u9069\u7528\u65BC\u6B64\u8CC7\u6599\u5EAB\uFF09",
2669
+ create_description: "\u5EFA\u7ACB\u65B0\u8868\u683C",
2670
+ drop_description: "\u522A\u9664\u8868\u683C",
2671
+ add_column_description: "\u65B0\u589E\u6B04\u4F4D\u81F3\u8868\u683C",
2672
+ drop_column_description: "\u522A\u9664\u8868\u683C\u6B04\u4F4D",
2673
+ alter_column_description: "\u4FEE\u6539\u8868\u683C\u6B04\u4F4D",
2674
+ add_index_description: "\u65B0\u589E\u7D22\u5F15\u81F3\u8868\u683C",
2675
+ drop_index_description: "\u522A\u9664\u7D22\u5F15",
2676
+ add_constraint_description: "\u65B0\u589E\u7D04\u675F\u81F3\u8868\u683C",
2677
+ drop_constraint_description: "\u522A\u9664\u8868\u683C\u7D04\u675F",
2678
+ add_enum_description: "\u5EFA\u7ACB\u5217\u8209\u578B\u5225\uFF08\u50C5 PostgreSQL\uFF09",
2679
+ alter_enum_description: "\u65B0\u589E\u5217\u8209\u503C",
2680
+ drop_enum_description: "\u522A\u9664\u5217\u8209\u578B\u5225"
2681
+ },
2682
+ password: {
2683
+ description: "\u53EA\u8B8A\u66F4\u55AE\u4E00\u9023\u7DDA\u7684\u5BC6\u78BC\uFF0C\u5176\u9918\u8A2D\u5B9A\u539F\u5C01\u4E0D\u52D5",
2684
+ arg_connection: "\u9023\u7DDA\u540D\u7A31\uFF08\u7701\u7565\u6642\u6539\u9810\u8A2D\u9023\u7DDA\uFF09",
2685
+ opt_stdin: "\u5F9E stdin \u8B80\u53D6\u65B0\u5BC6\u78BC\uFF08\u4F9B\u8F2A\u66FF\u8173\u672C\u4F7F\u7528\uFF09",
2686
+ opt_password: "\u76F4\u63A5\u5E36\u5165\u65B0\u5BC6\u78BC\uFF08\u6703\u7559\u5728 shell \u6B77\u53F2\u8207\u884C\u7A0B\u5217\u8868\uFF09",
2687
+ opt_skip_test: "\u5B58\u6A94\u524D\u4E0D\u5148\u7528\u65B0\u5BC6\u78BC\u9023\u7DDA\u9A57\u8B49",
2688
+ prompt: "\u9023\u7DDA '{name}' \u7684\u65B0\u5BC6\u78BC",
2689
+ source_conflict: "--password \u8207 --stdin \u53EA\u80FD\u64C7\u4E00\u3002",
2690
+ needs_source: "\u6C92\u6709\u7D42\u7AEF\u6A5F\u53EF\u505A\u906E\u853D\u8F38\u5165\uFF0C\u8ACB\u6539\u7528 --stdin \u6216 --password \u50B3\u5165\u3002",
2691
+ empty: "\u65B0\u5BC6\u78BC\u662F\u7A7A\u7684\uFF0C\u672A\u505A\u4EFB\u4F55\u8B8A\u66F4\u3002",
2692
+ updated: "\u5DF2\u66F4\u65B0\u9023\u7DDA '{name}' \u7684\u5BC6\u78BC \u2192 {envFile}\uFF08{varName}\uFF09",
2693
+ converted: 'config.json \u88E1\u7684\u660E\u6587\u5BC6\u78BC\u5DF2\u6539\u6210 {{ "$env": "{varName}" }} \u53C3\u7167\uFF0C\u4E4B\u5F8C\u8F2A\u66FF\u53EA\u6703\u52D5 env \u6A94\u3002',
2694
+ skipped_test: "\u5DF2\u8DF3\u904E\u9A57\u8B49\uFF08--skip-test\uFF09\uFF1A\u65B0\u5BC6\u78BC\u672A\u7D93\u8CC7\u6599\u5EAB\u78BA\u8A8D\u3002"
2695
+ }
2696
+ };
2697
+ });
2698
+
2699
+ // resources/lang/en/shell.json
2700
+ var shell_default;
2701
+ var init_shell = __esm(() => {
2702
+ shell_default = {
2703
+ welcome: "Connected to {system} ({database}@{host}:{port})",
2704
+ welcome_permission: "Permission: {permission}",
2705
+ prompt: "dbcli",
2706
+ continuation_prompt: " ...",
2707
+ goodbye: "Goodbye.",
2708
+ error_no_config: "No .dbcli config found. Run 'dbcli init' first.",
2709
+ error_connection_failed: "Connection failed: {message}",
2710
+ error_reconnecting: "Connection lost. Attempting to reconnect...",
2711
+ error_reconnect_failed: "Reconnection failed: {message}",
2712
+ error_reconnect_success: "Reconnected successfully.",
2713
+ error_command_failed: "Command error: {message}",
2714
+ error_sql_failed: "SQL error: {message}",
2715
+ error_permission: "Permission denied. Required: {required} (current: {current})",
2716
+ error_blacklisted: "Table '{table}' is blacklisted.",
2717
+ sql_mode_hint: "SQL-only mode. Only SQL statements are accepted.",
2718
+ multiline_cancelled: "Multiline input cancelled.",
2719
+ timing_display: "Time: {ms}ms",
2720
+ rows_display: "{count} row(s)",
2721
+ unknown_command: "Unknown command: '{command}'. Type '.help' for available commands.",
2722
+ subcommand_prefix_hint: "This line is being read as SQL and is waiting for its `;`. If you meant the dbcli subcommand, type {command} instead. Ctrl-C cancels.",
2723
+ es: {
2724
+ parse_empty: "Empty request",
2725
+ parse_needs_method_path: 'Request requires a method and a path, e.g. "GET /index/_search"',
2726
+ parse_needs_path: "Request requires a path",
2727
+ refuse_dot_segment: "Refused: '{path}' contains a '..' segment once decoded, and dbcli cannot tell which path Elasticsearch would route it to. Write the path out without '..'.",
2728
+ refuse_unparseable: "Refused: '{path}' is not the request the server would receive. It cannot be parsed as a path.",
2729
+ refuse_not_canonical: "Refused: '{path}' is not the request the server would receive. Write it as '{canonical}'.",
2730
+ refuse_source_param: "Refused: `source` in the query string is a request body by another route. Write the body as JSON.",
2731
+ refuse_search_template: "Refused: '{path}' is a search template, whose body cannot be inspected \u2014 it is a string rendered on the cluster, or stored there. Write the query out in full.",
2732
+ refuse_string_body: "Refused: a quoted string request body is not supported. Write the body as JSON.",
2733
+ refuse_audit_strict: "Refused: the audit entry for this request could not be written, and audit.strict is on. Fix the audit sink (disk space, directory permissions, a stale lockfile in .dbcli/audit) or turn audit.strict off to accept unrecorded requests.",
2734
+ blacklist_index: "index '{index}' is blacklist-protected",
2735
+ blacklist_unscoped: "'{path}' names no index, so it cannot be checked against the blacklist. Scope the request to an index, e.g. GET /<index>/_search.",
2736
+ blacklist_field: "field '{field}' is blacklist-protected and cannot be named in a request \u2014 sorting, aggregating or scripting on it would return its values.",
2737
+ banner_title: "Elasticsearch shell \u2014 Kibana Dev Tools syntax",
2738
+ banner_hint: 'Enter "<METHOD> /<path>" then an optional JSON body; submit with a blank line. Try: GET /_cat/indices',
2739
+ banner_keys: 'Ctrl+C cancels the current block; Ctrl+D or "exit" quits.',
2740
+ block_cancelled: "(block cancelled)",
2741
+ goodbye: "Goodbye"
2742
+ }
2743
+ };
2744
+ });
2745
+
2746
+ // resources/lang/zh-TW/shell.json
2747
+ var shell_default2;
2748
+ var init_shell2 = __esm(() => {
2749
+ shell_default2 = {
2750
+ welcome: "\u5DF2\u9023\u7DDA\u81F3 {system}\uFF08{database}@{host}:{port}\uFF09",
2751
+ welcome_permission: "\u6B0A\u9650\u7B49\u7D1A\uFF1A{permission}",
2752
+ prompt: "dbcli",
2753
+ continuation_prompt: " ...",
2754
+ goodbye: "\u518D\u898B\u3002",
2755
+ error_no_config: "\u627E\u4E0D\u5230 .dbcli \u8A2D\u5B9A\u6A94\u3002\u8ACB\u5148\u57F7\u884C 'dbcli init'\u3002",
2756
+ error_connection_failed: "\u9023\u7DDA\u5931\u6557\uFF1A{message}",
2757
+ error_reconnecting: "\u9023\u7DDA\u5DF2\u4E2D\u65B7\uFF0C\u5617\u8A66\u91CD\u65B0\u9023\u7DDA...",
2758
+ error_reconnect_failed: "\u91CD\u65B0\u9023\u7DDA\u5931\u6557\uFF1A{message}",
2759
+ error_reconnect_success: "\u91CD\u65B0\u9023\u7DDA\u6210\u529F\u3002",
2760
+ error_command_failed: "\u6307\u4EE4\u932F\u8AA4\uFF1A{message}",
2761
+ error_sql_failed: "SQL \u932F\u8AA4\uFF1A{message}",
2762
+ error_permission: "\u6B0A\u9650\u4E0D\u8DB3\u3002\u9700\u8981\uFF1A{required}\uFF08\u76EE\u524D\uFF1A{current}\uFF09",
2763
+ error_blacklisted: "\u8868\u683C '{table}' \u5DF2\u88AB\u5217\u5165\u9ED1\u540D\u55AE\u3002",
2764
+ sql_mode_hint: "\u7D14 SQL \u6A21\u5F0F\u3002\u50C5\u63A5\u53D7 SQL \u8A9E\u53E5\u3002",
2765
+ multiline_cancelled: "\u5DF2\u53D6\u6D88\u591A\u884C\u8F38\u5165\u3002",
2766
+ timing_display: "\u8017\u6642\uFF1A{ms}ms",
2767
+ rows_display: "{count} \u7B46\u8CC7\u6599",
2768
+ unknown_command: "\u672A\u77E5\u6307\u4EE4\uFF1A'{command}'\u3002\u8F38\u5165 '.help' \u67E5\u770B\u53EF\u7528\u6307\u4EE4\u3002",
2769
+ subcommand_prefix_hint: "\u9019\u4E00\u884C\u88AB\u7576\u6210 SQL\uFF0C\u6B63\u5728\u7B49\u5206\u865F\u3002\u5982\u679C\u4F60\u8981\u7684\u662F dbcli \u5B50\u6307\u4EE4\uFF0C\u8ACB\u6539\u6253 {command}\u3002Ctrl-C \u53EF\u53D6\u6D88\u3002",
2770
+ es: {
2771
+ parse_empty: "\u7A7A\u7684\u8ACB\u6C42",
2772
+ parse_needs_method_path: '\u8ACB\u6C42\u9700\u8981\u4E00\u500B method \u8207\u4E00\u500B path\uFF0C\u4F8B\u5982 "GET /index/_search"',
2773
+ parse_needs_path: "\u8ACB\u6C42\u9700\u8981\u4E00\u500B path",
2774
+ refuse_dot_segment: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u89E3\u78BC\u5F8C\u542B\u6709 '..' \u5340\u6BB5\uFF0Cdbcli \u7121\u6CD5\u5224\u65B7 Elasticsearch \u6703\u628A\u5B83\u8DEF\u7531\u5230\u54EA\u500B\u8DEF\u5F91\u3002\u8ACB\u628A\u8DEF\u5F91\u5BEB\u6210\u4E0D\u542B '..' \u7684\u5F62\u5F0F\u3002",
2775
+ refuse_unparseable: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u4E0D\u662F\u4F3A\u670D\u5668\u6703\u6536\u5230\u7684\u90A3\u500B\u8ACB\u6C42\u3002\u5B83\u7121\u6CD5\u88AB\u89E3\u6790\u6210\u4E00\u500B\u8DEF\u5F91\u3002",
2776
+ refuse_not_canonical: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u4E0D\u662F\u4F3A\u670D\u5668\u6703\u6536\u5230\u7684\u90A3\u500B\u8ACB\u6C42\u3002\u8ACB\u5BEB\u6210 '{canonical}'\u3002",
2777
+ refuse_source_param: "\u5DF2\u62D2\u7D55\uFF1Aquery string \u88E1\u7684 `source` \u662F\u53E6\u4E00\u689D\u8DEF\u5F91\u4E0A\u7684 request body\u3002\u8ACB\u628A body \u5BEB\u6210 JSON\u3002",
2778
+ refuse_search_template: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u662F search template\uFF0C\u5B83\u7684 body \u7121\u6CD5\u6AA2\u67E5\u2014\u2014\u90A3\u662F\u4E00\u500B\u5728\u53E2\u96C6\u4E0A\u7B97\u51FA\u4F86\u7684\u5B57\u4E32\uFF0C\u6216\u8005\u6839\u672C\u5B58\u5728\u53E2\u96C6\u88E1\u3002\u8ACB\u628A\u67E5\u8A62\u5B8C\u6574\u5BEB\u51FA\u4F86\u3002",
2779
+ refuse_string_body: "\u5DF2\u62D2\u7D55\uFF1A\u4E0D\u652F\u63F4\u4EE5\u5F15\u865F\u5B57\u4E32\u5F62\u5F0F\u5BEB\u7684 request body\u3002\u8ACB\u628A body \u5BEB\u6210 JSON\u3002",
2780
+ refuse_audit_strict: "\u5DF2\u62D2\u7D55\uFF1A\u9019\u500B\u8ACB\u6C42\u7684\u7A3D\u6838\u7D00\u9304\u5BEB\u4E0D\u51FA\u53BB\uFF0C\u800C audit.strict \u662F\u958B\u7684\u3002\u8ACB\u4FEE\u597D\u7A3D\u6838\u8F38\u51FA\u7AEF\uFF08\u78C1\u789F\u7A7A\u9593\u3001\u76EE\u9304\u6B0A\u9650\u3001.dbcli/audit \u88E1\u6B98\u7559\u7684 lockfile\uFF09\uFF0C\u6216\u95DC\u6389 audit.strict \u4EE5\u63A5\u53D7\u6C92\u6709\u7D00\u9304\u7684\u8ACB\u6C42\u3002",
2781
+ blacklist_index: "\u7D22\u5F15 '{index}' \u53D7\u9ED1\u540D\u55AE\u4FDD\u8B77",
2782
+ blacklist_unscoped: "'{path}' \u6307\u4E0D\u51FA\u4EFB\u4F55\u7D22\u5F15\uFF0C\u56E0\u6B64\u7121\u6CD5\u5C0D\u7167\u9ED1\u540D\u55AE\u6AA2\u67E5\u3002\u8ACB\u628A\u8ACB\u6C42\u9650\u5B9A\u5230\u67D0\u500B\u7D22\u5F15\uFF0C\u4F8B\u5982 GET /<index>/_search\u3002",
2783
+ blacklist_field: "\u6B04\u4F4D '{field}' \u53D7\u9ED1\u540D\u55AE\u4FDD\u8B77\uFF0C\u4E0D\u80FD\u51FA\u73FE\u5728\u8ACB\u6C42\u88E1\u2014\u2014\u5C0D\u5B83\u6392\u5E8F\u3001\u805A\u5408\u6216\u8DD1 script \u90FD\u6703\u628A\u5B83\u7684\u503C\u53D6\u56DE\u4F86\u3002",
2784
+ banner_title: "Elasticsearch shell \u2014\u2014 Kibana Dev Tools \u8A9E\u6CD5",
2785
+ banner_hint: '\u8F38\u5165 "<METHOD> /<path>"\uFF0C\u63A5\u8457\u5BEB\u53EF\u9078\u7684 JSON body\uFF1B\u7A7A\u4E00\u884C\u9001\u51FA\u3002\u53EF\u4EE5\u5148\u8A66\uFF1AGET /_cat/indices',
2786
+ banner_keys: 'Ctrl+C \u53D6\u6D88\u76EE\u524D\u9019\u500B block\uFF1BCtrl+D \u6216 "exit" \u96E2\u958B\u3002',
2787
+ block_cancelled: "\uFF08block \u5DF2\u53D6\u6D88\uFF09",
2788
+ goodbye: "\u518D\u898B"
2789
+ }
2790
+ };
2791
+ });
2792
+
2793
+ // resources/lang/en/ceremony.json
2794
+ var ceremony_default;
2795
+ var init_ceremony = __esm(() => {
2796
+ ceremony_default = {
2797
+ updated: "Updated {count} row(s) in {table}",
2798
+ deleted: "Deleted {count} row(s) from {table}",
2799
+ inserted: "Inserted {count} row(s) into {table}",
2800
+ matched_nothing: "No rows matched in {table} \u2014 nothing changed",
2801
+ cancelled: "Cancelled. {table} was not changed",
2802
+ dry_run: "Preview only. {table} was not changed",
2803
+ elapsed: "Took {seconds}s",
2804
+ failed: "Failed: {reason}",
2805
+ unknown_error: "unknown error",
2806
+ confirm_sql: "Generated SQL:",
2807
+ confirm_command: "Command to run:",
2808
+ confirm_params: "Parameters:",
2809
+ confirm_destructive_warning: "\u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!",
2810
+ confirm_destructive_prompt: "Are you sure you want to execute this DELETE operation? This cannot be undone.",
2811
+ confirm_destructive_warning_ddl: "\u26A0\uFE0F Warning: this schema change is destructive and cannot be undone!",
2812
+ confirm_ddl_prompt: "Are you sure you want to run this {operation}? This cannot be undone.",
2813
+ confirm_prompt: "Proceed with this operation?",
2814
+ gate_summary: "This will {operation} rows in {table}.",
2815
+ gate_summary_untargeted: "This will run a {operation} statement.",
2816
+ gate_statement: "Statement:",
2817
+ gate_cancelled: "Cancelled. Nothing was run.",
2818
+ gate_full_table_warning: "\u26A0\uFE0F This affects EVERY row in {table} and cannot be undone.",
2819
+ gate_ddl_warning: "\u26A0\uFE0F This destroys {table} and cannot be undone.",
2820
+ gate_ddl_warning_untargeted: "\u26A0\uFE0F This is a destructive schema change and cannot be undone.",
2821
+ gate_unparseable_warning: "\u26A0\uFE0F dbcli could not parse this statement, so it is treated as affecting everything.",
2822
+ gate_multiple_statements_warning: "\u26A0\uFE0F This is more than one statement, and dbcli will not judge them one at a time.",
2823
+ gate_multi_table_warning: "This write joins another table. How many rows of the target it touches depends on the data, not on the statement, so it cannot be checked before it runs.",
2824
+ gate_nested_write_warning: "\u26A0\uFE0F This statement carries a second write inside it, and dbcli will not read the tier of that write off this one.",
2825
+ gate_typed_prompt: "Type {phrase} to run it",
2826
+ gate_typed_mismatch: "That did not match {phrase}. Nothing was run.",
2827
+ gate_refused: "Refused: this statement is not limited to specific rows and nobody is here to confirm it (reason={reason}).",
2828
+ gate_refused_in_shell: "Refused: a dbcli subcommand started from the shell has no way to ask you, even though you are right here (reason={reason}).",
2829
+ gate_remedy_no_where: "Add a WHERE clause \u2014 WHERE 1=1 if you really do mean every row \u2014 or a LIMIT, then run it again.",
2830
+ gate_remedy_ddl_destruction: "No flag skips this. Run it from an interactive terminal, or apply the schema change through a reviewed migration.",
2831
+ gate_remedy_unparseable: "dbcli could not parse this statement and will not guess. Simplify it, or run it from an interactive terminal.",
2832
+ gate_remedy_multiple_statements: "Run one statement per command.",
2833
+ gate_remedy_multi_table: "Rewrite it as a single-table statement \u2014 move the other table into a subquery in the WHERE \u2014 or run it where someone can confirm it.",
2834
+ gate_remedy_nested_write: "Run the nested write as its own statement, or run this one where someone can confirm it.",
2835
+ gate_remedy_non_unique_where: "Match on a primary key or a uniquely indexed column, or run it from an interactive terminal.",
2836
+ gate_remedy_in_shell: "Type the statement at the dbcli> prompt instead \u2014 there the shell asks you to type the target table name.",
2837
+ recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
2838
+ recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
2839
+ blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
2840
+ };
2841
+ });
2842
+
2843
+ // resources/lang/zh-TW/ceremony.json
2844
+ var ceremony_default2;
2845
+ var init_ceremony2 = __esm(() => {
2846
+ ceremony_default2 = {
2847
+ updated: "\u5DF2\u66F4\u65B0 {table} \u7684 {count} \u5217",
2848
+ deleted: "\u5DF2\u5F9E {table} \u522A\u9664 {count} \u5217",
2849
+ inserted: "\u5DF2\u5BEB\u5165 {count} \u5217\u5230 {table}",
2850
+ matched_nothing: "{table} \u6C92\u6709\u4EFB\u4F55\u5217\u7B26\u5408\u689D\u4EF6\uFF0C\u8CC7\u6599\u672A\u8B8A\u52D5",
2851
+ cancelled: "\u5DF2\u53D6\u6D88\uFF0C{table} \u672A\u8B8A\u52D5",
2852
+ dry_run: "\u50C5\u9810\u89BD\uFF0C{table} \u672A\u8B8A\u52D5",
2853
+ elapsed: "\u8017\u6642 {seconds} \u79D2",
2854
+ failed: "\u5931\u6557\uFF1A{reason}",
2855
+ unknown_error: "\u672A\u77E5\u7684\u932F\u8AA4",
2856
+ confirm_sql: "\u7522\u751F\u7684 SQL\uFF1A",
2857
+ confirm_command: "\u5373\u5C07\u57F7\u884C\u7684\u6307\u4EE4\uFF1A",
2858
+ confirm_params: "\u53C3\u6578\uFF1A",
2859
+ confirm_destructive_warning: "\u26A0\uFE0F \u8B66\u544A\uFF1ADELETE \u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
2860
+ confirm_destructive_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B DELETE \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
2861
+ confirm_destructive_warning_ddl: "\u26A0\uFE0F \u8B66\u544A\uFF1A\u9019\u500B schema \u8B8A\u66F4\u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
2862
+ confirm_ddl_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B {operation} \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
2863
+ confirm_prompt: "\u8981\u7E7C\u7E8C\u57F7\u884C\u9019\u500B\u64CD\u4F5C\u55CE\uFF1F",
2864
+ gate_summary: "\u9019\u53E5\u6703\u5C0D {table} \u57F7\u884C {operation}\u3002",
2865
+ gate_summary_untargeted: "\u9019\u53E5\u6703\u57F7\u884C {operation}\u3002",
2866
+ gate_statement: "\u8A9E\u53E5\uFF1A",
2867
+ gate_cancelled: "\u5DF2\u53D6\u6D88\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
2868
+ gate_full_table_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u5F71\u97FF {table} \u7684\u6BCF\u4E00\u5217\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
2869
+ gate_ddl_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u6467\u6BC0 {table}\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
2870
+ gate_ddl_warning_untargeted: "\u26A0\uFE0F \u9019\u662F\u7834\u58DE\u6027\u7684 schema \u8B8A\u66F4\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
2871
+ gate_unparseable_warning: "\u26A0\uFE0F dbcli \u7121\u6CD5\u89E3\u6790\u9019\u53E5\uFF0C\u56E0\u6B64\u4E00\u5F8B\u8996\u70BA\u6703\u5F71\u97FF\u5168\u90E8\u8CC7\u6599\u3002",
2872
+ gate_multiple_statements_warning: "\u26A0\uFE0F \u9019\u88E1\u4E0D\u53EA\u4E00\u53E5\u8A9E\u53E5\uFF0Cdbcli \u4E0D\u6703\u9010\u53E5\u5224\u65B7\u3002",
2873
+ gate_multi_table_warning: "\u9019\u500B\u5BEB\u5165\u63A5\u4E86\u53E6\u4E00\u5F35\u8868\u3002\u5B83\u6703\u52D5\u5230\u76EE\u6A19\u8868\u5E7E\u5217\u53D6\u6C7A\u65BC\u8CC7\u6599\u800C\u4E0D\u662F\u8A9E\u53E5\u672C\u8EAB\uFF0C\u57F7\u884C\u524D\u7121\u6CD5\u6AA2\u67E5\u3002",
2874
+ gate_nested_write_warning: "\u26A0\uFE0F \u9019\u53E5\u88E1\u9762\u9084\u593E\u8457\u53E6\u4E00\u500B\u5BEB\u5165\uFF0Cdbcli \u4E0D\u6703\u62FF\u9019\u53E5\u7684\u5C64\u7D1A\u4EE3\u66FF\u90A3\u500B\u5BEB\u5165\u7684\u5C64\u7D1A\u3002",
2875
+ gate_typed_prompt: "\u8F38\u5165 {phrase} \u624D\u6703\u57F7\u884C",
2876
+ gate_typed_mismatch: "\u8F38\u5165\u7684\u5167\u5BB9\u8207 {phrase} \u4E0D\u7B26\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
2877
+ gate_refused: "\u5DF2\u62D2\u7D55\uFF1A\u9019\u53E5\u6C92\u6709\u9650\u5B9A\u8981\u52D5\u54EA\u4E9B\u5217\uFF0C\u800C\u73FE\u5728\u6C92\u6709\u4EBA\u80FD\u78BA\u8A8D\uFF08reason={reason}\uFF09\u3002",
2878
+ gate_refused_in_shell: "\u5DF2\u62D2\u7D55\uFF1A\u5F9E shell \u555F\u52D5\u7684 dbcli \u5B50\u6307\u4EE4\u6C92\u6709\u8FA6\u6CD5\u5411\u4F60\u63D0\u554F\uFF0C\u5373\u4F7F\u4F60\u4EBA\u5C31\u5728\u9019\u88E1\uFF08reason={reason}\uFF09\u3002",
2879
+ gate_remedy_no_where: "\u88DC\u4E0A WHERE\uFF08\u771F\u7684\u8981\u5168\u8868\u5C31\u5BEB WHERE 1=1\uFF09\u6216 LIMIT\uFF0C\u7136\u5F8C\u91CD\u8DD1\u3002",
2880
+ gate_remedy_ddl_destruction: "\u6C92\u6709\u4EFB\u4F55\u65D7\u6A19\u53EF\u4EE5\u8DF3\u904E\u9019\u4E00\u95DC\u3002\u8ACB\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\uFF0C\u6216\u6539\u8D70\u7D93\u904E\u5BE9\u67E5\u7684 migration\u3002",
2881
+ gate_remedy_unparseable: "dbcli \u7121\u6CD5\u89E3\u6790\u9019\u53E5\uFF0C\u4E5F\u4E0D\u6703\u7528\u731C\u7684\u3002\u8ACB\u7C21\u5316\u8A9E\u53E5\uFF0C\u6216\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\u3002",
2882
+ gate_remedy_multiple_statements: "\u4E00\u6B21\u53EA\u57F7\u884C\u4E00\u53E5\u8A9E\u53E5\u3002",
2883
+ gate_remedy_multi_table: "\u6539\u5BEB\u6210\u55AE\u8868\u8A9E\u53E5\u2014\u2014\u628A\u53E6\u4E00\u5F35\u8868\u79FB\u9032 WHERE \u7684\u5B50\u67E5\u8A62\u88E1\u2014\u2014\u6216\u6539\u5728\u6709\u4EBA\u53EF\u4EE5\u78BA\u8A8D\u7684\u5730\u65B9\u57F7\u884C\u3002",
2884
+ gate_remedy_nested_write: "\u628A\u88E1\u9762\u90A3\u500B\u5BEB\u5165\u62C6\u6210\u7368\u7ACB\u7684\u4E00\u53E5\u4F86\u57F7\u884C\uFF0C\u6216\u6539\u5728\u6709\u4EBA\u53EF\u4EE5\u78BA\u8A8D\u7684\u5730\u65B9\u57F7\u884C\u3002",
2885
+ gate_remedy_non_unique_where: "\u6539\u7528\u4E3B\u9375\u6216\u552F\u4E00\u7D22\u5F15\u6B04\u4F4D\u7576\u689D\u4EF6\uFF0C\u6216\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\u3002",
2886
+ gate_remedy_in_shell: "\u8ACB\u76F4\u63A5\u5728 dbcli> \u63D0\u793A\u7B26\u6253\u9019\u53E5\u8A9E\u53E5\u2014\u2014\u5728\u90A3\u88E1 dbcli \u6703\u8ACB\u4F60\u6253\u51FA\u76EE\u6A19\u8CC7\u6599\u8868\u7684\u540D\u7A31\u4F86\u78BA\u8A8D\u3002",
2887
+ recovery_none: "\u6B64\u64CD\u4F5C\u7121\u6CD5\u81EA\u52D5\u9084\u539F\uFF0C\u8981\u53D6\u56DE\u5148\u524D\u7684\u8CC7\u6599\u8ACB\u5F9E\u5099\u4EFD\u56DE\u5FA9\u3002",
2888
+ recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
2889
+ blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
2890
+ };
2891
+ });
2892
+
2893
+ // src/i18n/message-loader.ts
2894
+ class MessageLoader {
2895
+ static instance = null;
2896
+ messages = {};
2897
+ fallbackMessages = {};
2898
+ currentLang;
2899
+ constructor() {
2900
+ this.currentLang = Bun.env.DBCLI_LANG || "en", this.loadMessages();
2901
+ }
2902
+ static getInstance() {
2903
+ if (!MessageLoader.instance)
2904
+ MessageLoader.instance = new MessageLoader;
2905
+ return MessageLoader.instance;
2906
+ }
2907
+ loadMessages() {
2908
+ if (this.currentLang !== "en")
2909
+ this.messages = BUNDLED_MESSAGES[this.currentLang] || {};
2910
+ this.fallbackMessages = BUNDLED_MESSAGES.en || {};
2911
+ }
2912
+ t(key) {
2913
+ let parts = key.split("."), value = this.messages;
2914
+ for (let part of parts)
2915
+ if (value && typeof value === "object")
2916
+ value = value[part];
2917
+ else {
2918
+ value = void 0;
2919
+ break;
2920
+ }
2921
+ if (typeof value === "string")
2922
+ return value;
2923
+ value = this.fallbackMessages;
2924
+ for (let part of parts)
2925
+ if (value && typeof value === "object")
2926
+ value = value[part];
2927
+ else {
2928
+ value = void 0;
2929
+ break;
2930
+ }
2931
+ if (typeof value === "string")
2932
+ return value;
2933
+ return key;
2934
+ }
2935
+ setLanguage(lang) {
2936
+ this.currentLang = lang, this.messages = {}, this.loadMessages();
2937
+ }
2938
+ interpolate(key, vars) {
2939
+ return this.t(key).replace(/\{([^{}]+)\}/g, (match, name) => Object.prototype.hasOwnProperty.call(vars, name) ? String(vars[name]) : match);
2940
+ }
2941
+ }
2942
+ var BUNDLED_MESSAGES, messageLoader, t = (key) => messageLoader.t(key), t_vars = (key, vars) => messageLoader.interpolate(key, vars);
2943
+ var init_message_loader = __esm(() => {
2944
+ init_messages();
2945
+ init_messages2();
2946
+ init_shell();
2947
+ init_shell2();
2948
+ init_ceremony();
2949
+ init_ceremony2();
2950
+ BUNDLED_MESSAGES = {
2951
+ en: { ...messages_default, shell: shell_default, ceremony: ceremony_default },
2952
+ "zh-TW": {
2953
+ ...messages_default2,
2954
+ shell: shell_default2,
2955
+ ceremony: ceremony_default2
2956
+ }
2957
+ };
2958
+ messageLoader = MessageLoader.getInstance();
2959
+ });
2960
+
1938
2961
  // node_modules/zod/v3/helpers/util.js
1939
2962
  var util, objectUtil, ZodParsedType, getParsedType = (data) => {
1940
2963
  switch (typeof data) {
@@ -5119,7 +6142,261 @@ var init_zod = __esm(() => {
5119
6142
  init_external();
5120
6143
  });
5121
6144
 
6145
+ // src/adapters/capabilities.ts
6146
+ function cap(status, tier, note) {
6147
+ return Object.freeze({ status, tier, note });
6148
+ }
6149
+ function getEngineCapabilities(system) {
6150
+ return Object.freeze({ ...ENGINE_CAPABILITIES[system] });
6151
+ }
6152
+ function getEngineCapability(system, key) {
6153
+ return ENGINE_CAPABILITIES[system][key];
6154
+ }
6155
+ function supportsCapability(system, key) {
6156
+ return getEngineCapability(system, key).status === "supported";
6157
+ }
6158
+ function supportsSqlTls(system) {
6159
+ return typeof system === "string" && Object.hasOwn(ENGINE_CAPABILITIES, system) && supportsCapability(system, "connectionTls");
6160
+ }
6161
+ var COMMAND_CAPABILITY_KEYS, ENGINE_INDEPENDENT, SQL_BASE, SQL_ONLY_UNSUPPORTED, SQL_MODE_LIMITED, VERIFIED_SQL_TLS, ENGINE_CAPABILITIES;
6162
+ var init_capabilities = __esm(() => {
6163
+ COMMAND_CAPABILITY_KEYS = Object.freeze([
6164
+ "connectionTls",
6165
+ "init",
6166
+ "use",
6167
+ "list",
6168
+ "schema",
6169
+ "schemaSingle",
6170
+ "schemaFullScan",
6171
+ "query",
6172
+ "lint",
6173
+ "queryOutput",
6174
+ "queryLimitGuard",
6175
+ "q",
6176
+ "queries",
6177
+ "insert",
6178
+ "update",
6179
+ "delete",
6180
+ "export",
6181
+ "blacklistRead",
6182
+ "blacklist",
6183
+ "check",
6184
+ "diff",
6185
+ "migrate",
6186
+ "shell",
6187
+ "status",
6188
+ "doctor",
6189
+ "inspect",
6190
+ "report",
6191
+ "guide",
6192
+ "completion",
6193
+ "upgrade",
6194
+ "recover",
6195
+ "skill",
6196
+ "auditTail",
6197
+ "auditShow",
6198
+ "auditClear",
6199
+ "auditHealth",
6200
+ "explain",
6201
+ "plan",
6202
+ "impactAssess",
6203
+ "assert",
6204
+ "snapshot",
6205
+ "verify",
6206
+ "semantic",
6207
+ "design",
6208
+ "proxy",
6209
+ "proxyAnalyze",
6210
+ "verification",
6211
+ "verificationPrune",
6212
+ "evidence",
6213
+ "contract",
6214
+ "recovery",
6215
+ "backfillArtifact",
6216
+ "password",
6217
+ "capabilityDiscover",
6218
+ "capabilityCheck"
6219
+ ]);
6220
+ ENGINE_INDEPENDENT = {
6221
+ completion: cap("not-applicable", "none", "Shell completion is engine-independent."),
6222
+ upgrade: cap("not-applicable", "local-write", "Package update checks are engine-independent."),
6223
+ recover: cap("not-applicable", "dry-run", "Recovery operates on saved envelopes and gated command steps."),
6224
+ skill: cap("not-applicable", "local-write", "Skill and task-pack generation are engine-independent."),
6225
+ auditTail: cap("supported", "readonly", "Reads JSONL audit entries; never writes to engines."),
6226
+ auditShow: cap("supported", "readonly", "Looks up a single audit entry by id prefix or recovery_ref."),
6227
+ auditHealth: cap("supported", "readonly", "Renders AuditLogger.getHealth() snapshot."),
6228
+ auditClear: cap("supported", "local-write", "Removes <conn>.jsonl + .jsonl.1 from local disk; never touches DB.")
6229
+ }, SQL_BASE = {
6230
+ connectionTls: cap("unsupported", "none", "Explicit verified SQL TLS is not supported by this engine."),
6231
+ init: cap("supported", "interactive", "SQL connection initialization is supported."),
6232
+ use: cap("supported", "local-write", "V2 config supports named SQL connections."),
6233
+ list: cap("supported", "readonly", "Lists relational tables."),
6234
+ schema: cap("supported", "readonly", "Unified schema command (single-table or full scan)."),
6235
+ schemaSingle: cap("supported", "readonly", "Reads a single table schema."),
6236
+ schemaFullScan: cap("supported", "readonly", "Full scan, refresh, and reset are supported."),
6237
+ query: cap("supported", "readonly", "Runs SQL through permission and blacklist guards."),
6238
+ lint: cap("supported", "readonly", "Statically analyzes SQL without connecting to the database."),
6239
+ queryOutput: cap("supported", "readonly", "table/json/csv output is supported."),
6240
+ queryLimitGuard: cap("supported", "readonly", "Query-only auto-limit and size guard are supported."),
6241
+ q: cap("supported", "readonly", "Saved SQL snippets allow SELECT/WITH only."),
6242
+ queries: cap("supported", "local-write", "Snippet management is available regardless of active connection."),
6243
+ insert: cap("supported", "db-write", "Dedicated INSERT command is supported."),
6244
+ update: cap("supported", "db-write", "Dedicated UPDATE command is supported."),
6245
+ delete: cap("supported", "db-write", "Dedicated DELETE command is supported."),
6246
+ export: cap("supported", "readonly", "SQL export is supported."),
6247
+ blacklistRead: cap("supported", "readonly", "Reads the configured blacklist rules."),
6248
+ blacklist: cap("supported", "local-write", "Blacklist management and enforcement are supported."),
6249
+ check: cap("supported", "readonly", "Data health checks are SQL-only."),
6250
+ diff: cap("supported", "readonly", "Relational schema snapshots are supported."),
6251
+ migrate: cap("supported", "db-write", "DDL migrations are supported for SQL engines."),
6252
+ shell: cap("supported", "interactive", "Interactive SQL shell is supported."),
6253
+ status: cap("supported", "readonly", "Safe config summary is supported."),
6254
+ doctor: cap("supported", "readonly", "Engine-specific diagnostics are supported."),
6255
+ inspect: cap("supported", "readonly", "Agent context snapshot is supported."),
6256
+ report: cap("supported", "readonly", "Diagnostic report is supported."),
6257
+ guide: cap("supported", "readonly", "Deterministic next-command planner is supported."),
6258
+ explain: cap("supported", "readonly", "EXPLAIN plans are read from SQL engines."),
6259
+ plan: cap("supported", "readonly", "Offline write-risk analysis of SQL against the schema cache."),
6260
+ impactAssess: cap("supported", "local-write", "Offline impact assessment of a SQL schema change; writes its report locally."),
6261
+ assert: cap("supported", "local-write", "Data assertions over SQL results; optionally writes a verification artifact."),
6262
+ snapshot: cap("supported", "local-write", "Captures a SQL result fingerprint under .dbcli/snapshots/."),
6263
+ verify: cap("supported", "local-write", "Scenario verification over SQL; writes a verification artifact."),
6264
+ semantic: cap("supported", "readonly", "Governed semantic context and drift over a SQL schema cache."),
6265
+ design: cap("supported", "local-write", "Schema design authoring and comparison for SQL dialects; init writes a template."),
6266
+ proxy: cap("supported", "interactive", "Local observing proxy for the MySQL, MariaDB and PostgreSQL wire protocols."),
6267
+ password: cap("supported", "interactive", "Rotates the stored connection credential; refused under agent mode."),
6268
+ proxyAnalyze: cap("not-applicable", "readonly", "Reads locally recorded proxy events; never contacts an engine."),
6269
+ verification: cap("not-applicable", "readonly", "Inspects verification artifacts under .dbcli/verification/."),
6270
+ verificationPrune: cap("not-applicable", "local-write", "Deletes local verification artifacts; dry-run unless --execute --force."),
6271
+ evidence: cap("not-applicable", "local-write", "Composes, validates and renders evidence packs from local artifacts."),
6272
+ contract: cap("not-applicable", "readonly", "Validates and inspects governed semantic contracts offline."),
6273
+ recovery: cap("not-applicable", "readonly", "Renders recovery codes and their remediation steps from a static table."),
6274
+ backfillArtifact: cap("not-applicable", "local-write", "Builds a backfill artifact from a source manifest and v2 connection identity."),
6275
+ capabilityDiscover: cap("not-applicable", "readonly", "Emits the static capability catalog; opens no connection."),
6276
+ capabilityCheck: cap("not-applicable", "readonly", "Evaluates required capability ids against the local configuration only."),
6277
+ ...ENGINE_INDEPENDENT
6278
+ }, SQL_ONLY_UNSUPPORTED = {
6279
+ explain: cap("unsupported", "none", "src/commands/explain.ts refuses a non-SQL connection."),
6280
+ plan: cap("unsupported", "none", "Risk analysis is SQL-dialect based; toSqlDialect resolves no dialect here."),
6281
+ assert: cap("unsupported", "none", "src/commands/assert.ts refuses a non-SQL connection."),
6282
+ snapshot: cap("unsupported", "none", "src/commands/snapshot.ts refuses a non-SQL connection."),
6283
+ verify: cap("unsupported", "none", "src/commands/verify.ts refuses a non-SQL connection."),
6284
+ proxy: cap("unsupported", "none", "The proxy speaks the MySQL and PostgreSQL wire protocols only.")
6285
+ }, SQL_MODE_LIMITED = {
6286
+ impactAssess: cap("limited", "local-write", "Runs against an ORM artifact; --against-cache needs a SQL connection."),
6287
+ semantic: cap("limited", "readonly", "Governed context, search and drift work; draft validate needs a SQL connection."),
6288
+ design: cap("limited", "local-write", "Authoring and ORM comparison work; --against-cache needs a SQL connection.")
6289
+ }, VERIFIED_SQL_TLS = cap("supported", "readonly", "Uses profile-scoped verified SQL TLS; availability does not validate CA or handshake."), ENGINE_CAPABILITIES = Object.freeze({
6290
+ postgresql: Object.freeze({
6291
+ ...SQL_BASE,
6292
+ connectionTls: VERIFIED_SQL_TLS,
6293
+ check: cap("limited", "readonly", "SQL-only and strongest on MySQL/MariaDB.")
6294
+ }),
6295
+ sqlite: Object.freeze({
6296
+ ...SQL_BASE,
6297
+ init: cap("supported", "local-write", "Asks for a file path and a permission; the file must already exist."),
6298
+ use: cap("supported", "local-write", "Switches connections and displays the file path."),
6299
+ schemaFullScan: cap("unsupported", "none", "Full schema cache scan is not implemented for SQLite."),
6300
+ lint: cap("unsupported", "none", "Static lint is not enabled for the SQLite dialect yet."),
6301
+ queries: cap("supported", "local-write", "Snippets may declare `engine: sqlite` and bind with `?` placeholders."),
6302
+ insert: cap("supported", "db-write", "Row insert through the shared data-executor path."),
6303
+ update: cap("supported", "db-write", "Row update with a mandatory --where clause."),
6304
+ delete: cap("supported", "db-write", "Row delete with a mandatory --where clause."),
6305
+ export: cap("supported", "local-write", "Exports SELECT results to json, csv or html through the shared query path."),
6306
+ check: cap("unsupported", "none", "Data health check is not enabled for SQLite."),
6307
+ diff: cap("unsupported", "none", "Schema snapshots are not enabled for SQLite."),
6308
+ migrate: cap("unsupported", "none", "SQLite ALTER TABLE covers few operations and a column change is a table rebuild; no DDL generator exists."),
6309
+ shell: cap("unsupported", "none", "The REPL is not enabled for SQLite."),
6310
+ doctor: cap("supported", "readonly", "Checks the file exists, is readable, and is writable when the permission allows writes."),
6311
+ inspect: cap("unsupported", "none", "Inspection collectors are not enabled for SQLite."),
6312
+ report: cap("unsupported", "none", "Report diagnostics query engine-specific statistics views."),
6313
+ guide: cap("unsupported", "none", "Guides depend on engine-specific EXPLAIN output."),
6314
+ explain: cap("unsupported", "none", "EXPLAIN handling is not enabled for SQLite."),
6315
+ plan: cap("unsupported", "none", "Plan capture is not enabled for SQLite."),
6316
+ impactAssess: cap("unsupported", "none", "Impact assessment is not enabled for SQLite."),
6317
+ assert: cap("unsupported", "none", "Assertions are not enabled for SQLite."),
6318
+ snapshot: cap("unsupported", "none", "Result snapshots are not enabled for SQLite."),
6319
+ verify: cap("unsupported", "none", "Write verification is not enabled for SQLite."),
6320
+ semantic: cap("unsupported", "none", "Semantic context work is not enabled for SQLite."),
6321
+ design: cap("unsupported", "none", "Schema design work is not enabled for SQLite."),
6322
+ proxy: cap("not-applicable", "none", "SQLite has no network protocol to proxy."),
6323
+ proxyAnalyze: cap("not-applicable", "none", "There are no SQLite proxy events to analyse."),
6324
+ password: cap("not-applicable", "none", "A SQLite connection has no credential to store or rotate."),
6325
+ 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.")
6326
+ }),
6327
+ mysql: Object.freeze({ ...SQL_BASE, connectionTls: VERIFIED_SQL_TLS }),
6328
+ mariadb: Object.freeze({ ...SQL_BASE, connectionTls: VERIFIED_SQL_TLS }),
6329
+ mongodb: Object.freeze({
6330
+ ...SQL_BASE,
6331
+ ...SQL_ONLY_UNSUPPORTED,
6332
+ ...SQL_MODE_LIMITED,
6333
+ ...SQL_MODE_LIMITED,
6334
+ ...SQL_MODE_LIMITED,
6335
+ lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
6336
+ schemaSingle: cap("limited", "readonly", "MongoDB schema is sampled from documents."),
6337
+ schemaFullScan: cap("limited", "readonly", "Full scan is sampled and document-oriented."),
6338
+ query: cap("limited", "readonly", "Uses JSON filter or aggregation syntax, not SQL."),
6339
+ queryLimitGuard: cap("limited", "readonly", "Applies result limits and collection size guard."),
6340
+ queries: cap("limited", "local-write", "Snippet management works with MongoDB-specific saved-query limitations."),
6341
+ q: cap("limited", "readonly", "Saved MongoDB snippets require collection frontmatter, accept JSON find/aggregate bodies, and JSON-encode parameter substitutions."),
6342
+ insert: cap("limited", "db-write", "Document insert is supported with Mongo-specific behavior."),
6343
+ update: cap("limited", "db-write", "Document update is supported with Mongo-specific behavior."),
6344
+ delete: cap("limited", "db-write", "Document delete is supported with Mongo-specific behavior."),
6345
+ export: cap("limited", "readonly", "MongoDB export is supported with document-query syntax."),
6346
+ blacklistRead: cap("limited", "readonly", "Reads the engine-specific blacklist rules."),
6347
+ blacklist: cap("limited", "local-write", "Rule management works; enforcement differs from SQL column enforcement."),
6348
+ check: cap("unsupported", "none", "Data health check is SQL-only."),
6349
+ diff: cap("unsupported", "none", "Schema snapshots are relational only."),
6350
+ migrate: cap("unsupported", "none", "DDL migrations are SQL-only."),
6351
+ shell: cap("limited", "interactive", "MongoDB shell support is narrower than SQL shell support.")
6352
+ }),
6353
+ redis: Object.freeze({
6354
+ ...SQL_BASE,
6355
+ ...SQL_ONLY_UNSUPPORTED,
6356
+ lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
6357
+ schemaSingle: cap("limited", "readonly", "Per-key synthetic schema only."),
6358
+ schemaFullScan: cap("unsupported", "none", "Redis has no full schema cache scan."),
6359
+ query: cap("limited", "readonly", "Runs allow-listed Redis commands."),
6360
+ queryLimitGuard: cap("limited", "readonly", "SCAN/LRANGE/ZRANGE COUNT/stop rewrite; HGETALL/SMEMBERS/KEYS truncate at 1000; --no-limit bypasses."),
6361
+ q: cap("limited", "readonly", "Saved Redis snippets use a read-only allowlist and range guards."),
6362
+ queries: cap("limited", "local-write", "Snippet management works with Redis-specific saved-query limitations."),
6363
+ insert: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
6364
+ update: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
6365
+ delete: cap("limited", "db-write", "Basic delete via DEL/HDEL/LREM/SREM/ZREM (needs data-admin); supports --dry-run."),
6366
+ export: cap("unsupported", "none", "Redis export is not supported."),
6367
+ blacklistRead: cap("limited", "readonly", "Reads the engine-specific blacklist rules."),
6368
+ blacklist: cap("limited", "local-write", "Key-glob rejection (Redis-native pattern) plus value/hash-field masking ([REDACTED])."),
6369
+ check: cap("unsupported", "none", "Data health check is SQL-only."),
6370
+ diff: cap("unsupported", "none", "Schema snapshots are relational only."),
6371
+ migrate: cap("unsupported", "none", "DDL migrations are SQL-only."),
6372
+ shell: cap("limited", "interactive", "Single-line Redis REPL (no trailing semicolon needed) with auto-limit and key-glob blacklist; narrower than SQL shell.")
6373
+ }),
6374
+ elasticsearch: Object.freeze({
6375
+ ...SQL_BASE,
6376
+ ...SQL_ONLY_UNSUPPORTED,
6377
+ lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
6378
+ schemaSingle: cap("limited", "readonly", "Schema flattens index mappings."),
6379
+ schemaFullScan: cap("supported", "readonly", "Full scan iterates non-system indices."),
6380
+ query: cap("limited", "readonly", "Uses JSON DSL or Lucene query strings with an index."),
6381
+ queryLimitGuard: cap("limited", "readonly", "Applies Elasticsearch size guard."),
6382
+ q: cap("limited", "readonly", "Saved ES snippets require index frontmatter and reject scripts."),
6383
+ queries: cap("limited", "local-write", "Snippet management works with Elasticsearch-specific saved-query limitations."),
6384
+ insert: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
6385
+ update: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
6386
+ delete: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
6387
+ export: cap("limited", "readonly", "Exports DSL hits or a full index via scroll, capped at 1000 rows (use --no-limit)."),
6388
+ blacklistRead: cap("limited", "readonly", "Reads the engine-specific blacklist rules."),
6389
+ blacklist: cap("limited", "local-write", "Index-level and flattened-column enforcement differ from SQL."),
6390
+ check: cap("unsupported", "none", "Data health check is SQL-only."),
6391
+ diff: cap("unsupported", "none", "Schema snapshots are relational only."),
6392
+ migrate: cap("unsupported", "none", "DDL migrations are SQL-only."),
6393
+ shell: cap("limited", "interactive", "Dedicated Kibana Dev Tools-style REPL (method/path + JSON body, blank-line submit); read-focused.")
6394
+ })
6395
+ });
6396
+ });
6397
+
5122
6398
  // src/utils/validation.ts
6399
+ import { isAbsolute } from "path";
5123
6400
  function parseTimeoutOption(value) {
5124
6401
  let parsed = Number(value);
5125
6402
  if (!Number.isInteger(parsed) || parsed < MIN_CONNECTION_TIMEOUT_MS || parsed > MAX_CONNECTION_TIMEOUT_MS)
@@ -5144,16 +6421,30 @@ function isSqliteMemoryTarget(value) {
5144
6421
  let query = queryStart === -1 ? "" : withoutScheme.slice(queryStart + 1);
5145
6422
  return new URLSearchParams(query).get("mode")?.toLowerCase() === "memory";
5146
6423
  }
6424
+ function rejectUnsupportedSqlTls(raw, ctx) {
6425
+ if (raw !== null && typeof raw === "object" && "sqlTls" in raw && raw.sqlTls !== void 0 && (!("system" in raw) || !supportsSqlTls(raw.system)))
6426
+ ctx.addIssue({
6427
+ code: ZodIssueCode.custom,
6428
+ path: ["sqlTls"],
6429
+ message: t("errors.sql_tls_unsupported")
6430
+ });
6431
+ return raw;
6432
+ }
5147
6433
  function validateFormat(value, allowedFormats, commandName) {
5148
6434
  if (!allowedFormats.includes(value)) {
5149
6435
  let allowed = allowedFormats.join(", ");
5150
6436
  throw Error(`Invalid format "${value}" for ${commandName}. Allowed: ${allowed}`);
5151
6437
  }
5152
6438
  }
5153
- 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, RESERVED_MONGODB_OPTION_KEYS, MongoDBOptionsSchema, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, 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.", 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;
6439
+ var SqlTlsConfigSchema, 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, RESERVED_MONGODB_OPTION_KEYS, MongoDBOptionsSchema, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, 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.", 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;
5154
6440
  var init_validation = __esm(() => {
6441
+ init_message_loader();
5155
6442
  init_zod();
5156
- EnvRefSchema = objectType({
6443
+ init_capabilities();
6444
+ SqlTlsConfigSchema = objectType({
6445
+ mode: literalType("verify-full"),
6446
+ caPath: stringType().min(1).refine(isAbsolute, () => ({ message: t("errors.sql_tls_ca_absolute") }))
6447
+ }).strict(), EnvRefSchema = objectType({
5157
6448
  $env: stringType()
5158
6449
  }).strict(), StringOrEnvRef = unionType([stringType().min(1), EnvRefSchema]), NumberOrEnvRef = unionType([numberType().int().min(1).max(65535), EnvRefSchema]), OptStringOrEnvRef = unionType([stringType(), EnvRefSchema]).optional().default(""), OptNumberOrEnvRef = unionType([numberType().int(), EnvRefSchema]).optional().default(27017), TimeoutField = {
5159
6450
  timeout: numberType().int().min(MIN_CONNECTION_TIMEOUT_MS).max(MAX_CONNECTION_TIMEOUT_MS).optional(),
@@ -5189,6 +6480,7 @@ var init_validation = __esm(() => {
5189
6480
  srv: booleanType().optional().default(!1),
5190
6481
  ...TimeoutField
5191
6482
  }), SqlConnectionConfigSchema = objectType({
6483
+ sqlTls: SqlTlsConfigSchema.optional(),
5192
6484
  system: enumType(["postgresql", "mysql", "mariadb"]),
5193
6485
  host: StringOrEnvRef,
5194
6486
  port: NumberOrEnvRef,
@@ -5231,13 +6523,14 @@ var init_validation = __esm(() => {
5231
6523
  caPath: stringType().optional(),
5232
6524
  rejectUnauthorized: booleanType().optional().default(!0),
5233
6525
  ...TimeoutField
5234
- }), ConnectionConfigSchema = unionType([
6526
+ });
6527
+ ConnectionConfigSchema = preprocessType(rejectUnsupportedSqlTls, unionType([
5235
6528
  SqlConnectionConfigSchema,
5236
6529
  SqliteConnectionConfigSchema,
5237
6530
  MongoDBConnectionConfigSchema,
5238
6531
  RedisConnectionConfigSchema,
5239
6532
  ElasticsearchConnectionConfigSchema
5240
- ]), PermissionSchema = enumType(["query-only", "read-write", "data-admin", "admin"]).default("query-only"), EnvironmentLabelSchema = stringType().trim().transform((value) => value || void 0).optional(), MetadataSchema = objectType({
6533
+ ])), PermissionSchema = enumType(["query-only", "read-write", "data-admin", "admin"]).default("query-only"), EnvironmentLabelSchema = stringType().trim().transform((value) => value || void 0).optional(), MetadataSchema = objectType({
5241
6534
  createdAt: stringType().datetime().optional(),
5242
6535
  version: stringType().default("1.0"),
5243
6536
  schemaLastUpdated: stringType().datetime().optional(),
@@ -5312,7 +6605,7 @@ var init_validation = __esm(() => {
5312
6605
  RedisNamedConnectionSchema,
5313
6606
  ElasticsearchNamedConnectionSchema
5314
6607
  ]), NamedConnectionSchema = preprocessType((raw, ctx) => {
5315
- if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
6608
+ if (rejectUnsupportedSqlTls(raw, ctx), raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
5316
6609
  if ("blacklist" in raw)
5317
6610
  ctx.addIssue({
5318
6611
  code: ZodIssueCode.custom,
@@ -6755,1532 +8048,556 @@ class SchemaIndexBuilder {
6755
8048
  }
6756
8049
  static getFileForTable(tableName) {
6757
8050
  if (/^(old_|legacy_|archive_)/.test(tableName))
6758
- return "legacy.json";
6759
- return "infrequent.json";
6760
- }
6761
- }
6762
- var init_schema_index = __esm(() => {
6763
- init_schema_path();
6764
- });
6765
-
6766
- // src/core/schema-loader.ts
6767
- import { join as join7 } from "path";
6768
-
6769
- class SchemaLayeredLoader {
6770
- dbcliPath;
6771
- connectionName;
6772
- options;
6773
- cache = null;
6774
- index = null;
6775
- loadTime = 0;
6776
- constructor(dbcliPath, options) {
6777
- this.dbcliPath = dbcliPath, this.connectionName = options?.connectionName, this.options = {
6778
- maxCacheItems: options?.maxCacheItems || 100,
6779
- maxCacheSize: options?.maxCacheSize || 52428800,
6780
- hotTableThreshold: options?.hotTableThreshold || 20,
6781
- enableStreaming: options?.enableStreaming || !1,
6782
- streamingTimeout: options?.streamingTimeout || 30000
6783
- };
6784
- }
6785
- async initialize() {
6786
- let startTime = performance.now();
6787
- try {
6788
- if (await this.ensureDirectories(), this.index = await SchemaIndexBuilder.loadIndex(this.dbcliPath, this.connectionName), this.cache = new SchemaCacheManager(this.dbcliPath, {
6789
- maxCacheItems: this.options.maxCacheItems,
6790
- maxCacheSize: this.options.maxCacheSize,
6791
- connectionName: this.connectionName
6792
- }), await this.cache.initialize(), this.loadTime = performance.now() - startTime, this.loadTime > 50)
6793
- console.warn(`Schema initialization took ${this.loadTime.toFixed(2)}ms (target: < 100ms)`);
6794
- return {
6795
- cache: this.cache,
6796
- index: this.index,
6797
- loadTime: this.loadTime
6798
- };
6799
- } catch (error) {
6800
- if (console.error("Failed to initialize schema loader:", error), this.loadTime = performance.now() - startTime, !this.cache)
6801
- this.cache = new SchemaCacheManager(this.dbcliPath, {
6802
- maxCacheItems: this.options.maxCacheItems,
6803
- maxCacheSize: this.options.maxCacheSize,
6804
- connectionName: this.connectionName
6805
- });
6806
- return {
6807
- cache: this.cache,
6808
- index: this.index,
6809
- loadTime: this.loadTime
6810
- };
6811
- }
6812
- }
6813
- async loadColdTable(tableName, cache) {
6814
- try {
6815
- return await cache.getTableSchema(tableName);
6816
- } catch (error) {
6817
- return console.error(`Failed to load cold table ${tableName}:`, error), null;
6818
- }
6819
- }
6820
- async ensureDirectories() {
6821
- let base = resolveSchemaPath(this.dbcliPath, this.connectionName), dirs = [base, join7(base, "cold")];
6822
- for (let dir of dirs)
6823
- try {
6824
- if (!await Bun.file(dir).exists()) {
6825
- if (await Bun.spawn(["mkdir", "-p", dir]).exited !== 0)
6826
- console.error(`Failed to create directory: ${dir}`);
6827
- }
6828
- } catch (error) {
6829
- console.error(`Error ensuring directory ${dir}:`, error);
6830
- }
6831
- }
6832
- getBenchmark() {
6833
- if (!this.cache || !this.index)
6834
- return {
6835
- initTime: this.loadTime,
6836
- hotTables: 0,
6837
- totalTables: 0,
6838
- estimatedSize: 0
6839
- };
6840
- let estimatedSize = Object.values(this.index.tables).reduce((sum, table) => sum + (table.estimatedSize || 0), 0);
6841
- return {
6842
- initTime: this.loadTime,
6843
- hotTables: this.index.hotTables.length,
6844
- totalTables: this.index.metadata.totalTables,
6845
- estimatedSize
6846
- };
6847
- }
6848
- }
6849
- var init_schema_loader = __esm(() => {
6850
- init_schema_cache();
6851
- init_schema_index();
6852
- init_schema_path();
6853
- });
6854
-
6855
- // src/core/config.ts
6856
- import { join as join8 } from "path";
6857
- import { mkdir as mkdir3 } from "fs/promises";
6858
- function assertNoConnectionSelectorOnV1(connectionName) {
6859
- if (connectionName === void 0)
6860
- return;
6861
- throw new ConfigError(`\u9023\u7DDA '${connectionName}' \u4E0D\u5B58\u5728\uFF1A\u6B64\u5C08\u6848\u4F7F\u7528\u55AE\u4E00\u9023\u7DDA (v1) \u8A2D\u5B9A\uFF0C\u6C92\u6709\u5177\u540D\u9023\u7DDA\u3002` + " \u79FB\u9664 --use / DBCLI_CONNECTION\uFF0C\u6216\u6539\u7528 'dbcli init --conn-name <name>' \u5347\u7D1A\u70BA\u591A\u9023\u7DDA (v2) \u8A2D\u5B9A\u3002");
6862
- }
6863
- function setGlobalConnectionName(name) {
6864
- _globalConnectionName = name;
6865
- }
6866
- function getGlobalConnectionName() {
6867
- return _globalConnectionName;
6868
- }
6869
- async function getSchemaIsolationConnectionName(dbcliPath) {
6870
- let effectiveName = getGlobalConnectionName();
6871
- try {
6872
- let storagePath = await resolveConfigStoragePath(dbcliPath);
6873
- if (!((await Bun.file(storagePath).stat())?.isDirectory() ?? !1))
6874
- return;
6875
- let configJsonPath = join8(storagePath, "config.json"), configFile = Bun.file(configJsonPath);
6876
- if (!await configFile.exists())
6877
- return;
6878
- let raw = JSON.parse(await configFile.text());
6879
- if (detectConfigVersion(raw) !== 2)
6880
- return;
6881
- let v2Config = DbcliConfigV2Schema.parse(raw);
6882
- return resolveConnection(v2Config, effectiveName).name;
6883
- } catch {
6884
- return;
6885
- }
6886
- }
6887
- function isEnvReference(value) {
6888
- return typeof value === "object" && value !== null && "$env" in value && typeof value.$env === "string";
6889
- }
6890
- function resolveEnvReferences(config, env, parentKey) {
6891
- if (isEnvReference(config)) {
6892
- let envKey = config.$env, value = resolveEnvRef(config, parentKey ?? "<root>", env);
6893
- if (parentKey === "port") {
6894
- let portNum = parseInt(value, 10);
6895
- if (isNaN(portNum) || portNum < 1 || portNum > 65535)
6896
- throw new ConfigError(`${envKey} must be a valid port number (1-65535), got: ${value}`);
6897
- return portNum;
6898
- }
6899
- return value;
6900
- }
6901
- if (Array.isArray(config))
6902
- return config.map((item) => resolveEnvReferences(item, env, parentKey));
6903
- if (typeof config === "object" && config !== null) {
6904
- let resolved = {};
6905
- for (let [key, value] of Object.entries(config))
6906
- resolved[key] = resolveEnvReferences(value, env, key);
6907
- return resolved;
6908
- }
6909
- return config;
6910
- }
6911
- function parseEnvPassword(content) {
6912
- let match = content.match(/^DBCLI_PASSWORD=(.+)$/m);
6913
- if (match?.[1] == null)
6914
- return null;
6915
- let raw = match[1].trim();
6916
- return (raw.startsWith('"') && raw.endsWith('"') || raw.startsWith("'") && raw.endsWith("'")) && raw.length >= 2 ? raw.slice(1, -1) : raw;
6917
- }
6918
- var _globalConnectionName, DEFAULT_CONFIG, configModule;
6919
- var init_config = __esm(() => {
6920
- init_validation();
6921
- init_errors3();
6922
- init_config_error_format();
6923
- init_config_v2();
6924
- init_config_binding();
6925
- init_public();
6926
- init_config_mutation_guard();
6927
- init_config_integrity();
6928
- DEFAULT_CONFIG = {
6929
- connection: {
6930
- system: "postgresql",
6931
- host: "localhost",
6932
- port: 5432,
6933
- user: "",
6934
- password: "",
6935
- database: ""
6936
- },
6937
- permission: "query-only",
6938
- schema: {},
6939
- metadata: {
6940
- version: "1.0"
6941
- },
6942
- blacklist: { tables: [], columns: {} },
6943
- audit: {
6944
- enabled: !0,
6945
- strict: !1,
6946
- rotation: { ...DEFAULT_AUDIT_ROTATION }
6947
- }
6948
- };
6949
- configModule = {
6950
- async read(path, connectionName, options = {}) {
6951
- let effectiveConnectionName = connectionName ?? _globalConnectionName, rawForDiagnostics;
6952
- try {
6953
- let binding = await readProjectBinding(path), storagePath = await resolveConfigStoragePath(path);
6954
- if (binding) {
6955
- if (!await Bun.file(join8(storagePath, "config.json")).exists())
6956
- throw new ConfigError(`Bound dbcli config not found: ${join8(storagePath, "config.json")}`);
6957
- }
6958
- let isDirectory = !1;
6959
- try {
6960
- isDirectory = (await Bun.file(storagePath).stat())?.isDirectory() ?? !1;
6961
- } catch {
6962
- isDirectory = !1;
6963
- }
6964
- if (isDirectory) {
6965
- let configPath = join8(storagePath, "config.json"), configFile = Bun.file(configPath);
6966
- if (await configFile.exists()) {
6967
- await assertAgentReadableFile(configPath);
6968
- let content = await configFile.text();
6969
- await assertConfigIntegrity(storagePath, content, { requireRecord: !0 });
6970
- let config = JSON.parse(content);
6971
- if (rawForDiagnostics = config, detectConfigVersion(config) === 2) {
6972
- let v2Config = DbcliConfigV2Schema.parse(config), resolved = resolveConnection(v2Config, effectiveConnectionName);
6973
- assertExplicitProductionSelection(resolved, effectiveConnectionName), await loadConnectionEnv(resolved, storagePath);
6974
- let envLocalPath = join8(storagePath, ".env.local"), envLocalFile = Bun.file(envLocalPath), legacyPassword = null;
6975
- if (await envLocalFile.exists()) {
6976
- let envContent = await envLocalFile.text();
6977
- if (legacyPassword = parseEnvPassword(envContent), legacyPassword && !process.env.DBCLI_PASSWORD)
6978
- process.env.DBCLI_PASSWORD = legacyPassword;
6979
- }
6980
- let resolvedConnection = resolveEnvReferences(resolved.connection, process.env);
6981
- if (!resolvedConnection.password && legacyPassword)
6982
- resolvedConnection.password = legacyPassword;
6983
- let schema = (v2Config.schemas ?? {})[resolved.name] ?? v2Config.schema;
6984
- if (options.loadLayeredSchema !== !1)
6985
- try {
6986
- await Promise.resolve().then(() => init_schema_loader());
6987
- let loader = new SchemaLayeredLoader(storagePath, {
6988
- connectionName: resolved.name
6989
- }), { cache, index } = await loader.initialize();
6990
- if (index && Object.keys(index.tables).length > 0) {
6991
- let layeredSchema = {};
6992
- for (let tableName of Object.keys(index.tables)) {
6993
- let s = await cache.getTableSchema(tableName);
6994
- if (s)
6995
- layeredSchema[tableName] = s;
6996
- }
6997
- if (Object.keys(layeredSchema).length > 0)
6998
- schema = layeredSchema;
6999
- }
7000
- } catch {
7001
- console.warn("Warning: Failed to load layered schema cache, falling back to config.json");
7002
- }
7003
- return {
7004
- ...DbcliConfigSchema.parse({
7005
- connection: resolvedConnection,
7006
- permission: resolved.permission,
7007
- schema,
7008
- metadata: v2Config.metadata,
7009
- blacklist: v2Config.blacklist,
7010
- audit: v2Config.audit,
7011
- redis: v2Config.redis
7012
- }),
7013
- effectiveConnectionName: resolved.name,
7014
- ...resolved.environment && { effectiveEnvironment: resolved.environment }
7015
- };
7016
- }
7017
- assertNoConnectionSelectorOnV1(effectiveConnectionName);
7018
- let resolvedConfig = resolveEnvReferences(config, process.env), envPath = join8(storagePath, ".env.local"), envFile = Bun.file(envPath);
7019
- if (await envFile.exists()) {
7020
- let envContent = await envFile.text(), password = parseEnvPassword(envContent);
7021
- if (password && !resolvedConfig.connection.password)
7022
- resolvedConfig.connection.password = password;
7023
- }
7024
- return DbcliConfigSchema.parse(resolvedConfig);
7025
- }
7026
- }
7027
- let file = Bun.file(path);
7028
- if (await file.exists()) {
7029
- if (process.env.DBCLI_AGENT_MODE === "1")
7030
- throw new ConfigError(`Agent mode refuses legacy single-file config: ${path}. Run the human/admin migration workflow to move it to V2 home storage first.`);
7031
- assertNoConnectionSelectorOnV1(effectiveConnectionName);
7032
- let content = await file.text(), raw = JSON.parse(content);
7033
- rawForDiagnostics = raw;
7034
- let resolved = resolveEnvReferences(raw, process.env);
7035
- return DbcliConfigSchema.parse(resolved);
7036
- }
7037
- if (process.env.DBCLI_AGENT_MODE === "1")
7038
- throw new ConfigError(`Agent mode refuses a missing config: ${path}. Run the human/admin setup workflow to provision it.`);
7039
- return { ...DEFAULT_CONFIG };
7040
- } catch (error) {
7041
- if (error instanceof ConfigError)
7042
- throw error;
7043
- if (isZodError(error))
7044
- throw new ConfigError(`Failed to read .dbcli config: \u8A2D\u5B9A\u5167\u5BB9\u4E0D\u7B26\u5408\u7D50\u69CB
7045
- ${formatConfigValidationError(error, rawForDiagnostics)}`);
7046
- if (error instanceof Error && error.message.includes("JSON"))
7047
- throw new ConfigError(`Failed to parse .dbcli file: ${error.message}`);
7048
- throw new ConfigError(`Failed to read .dbcli config: ${error instanceof Error ? error.message : String(error)}`);
7049
- }
7050
- },
7051
- validate(raw) {
7052
- try {
7053
- return DbcliConfigSchema.parse(raw);
7054
- } catch (error) {
7055
- if (isZodError(error))
7056
- throw new ConfigError(`Invalid .dbcli config structure:
7057
- ${formatConfigValidationError(error, raw)}`);
7058
- let errorMessage = error instanceof Error ? error.message : String(error);
7059
- throw new ConfigError(`Invalid .dbcli config structure: ${errorMessage}`);
7060
- }
7061
- },
7062
- merge(existing, updates) {
7063
- return {
7064
- ...existing,
7065
- ...updates,
7066
- connection: {
7067
- ...existing.connection,
7068
- ...updates.connection || {}
7069
- },
7070
- schema: {
7071
- ...existing.schema,
7072
- ...updates.schema || {}
7073
- },
7074
- metadata: {
7075
- ...existing.metadata,
7076
- ...updates.metadata || {},
7077
- createdAt: existing.metadata?.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
7078
- version: existing.metadata?.version || "1.0"
7079
- }
7080
- };
7081
- },
7082
- async write(path, config) {
7083
- try {
7084
- assertConfigMutationApproved(), this.validate(config);
7085
- let storagePath = await resolveConfigStoragePath(path), isDirectory = !1;
7086
- try {
7087
- isDirectory = (await Bun.file(storagePath).stat())?.isDirectory() ?? !1;
7088
- } catch {
7089
- isDirectory = !1;
7090
- }
7091
- if (isDirectory || path.endsWith(".dbcli") || path === storagePath && isDirectory)
7092
- if (await mkdir3(storagePath, { recursive: !0 }), isEnvReference(config.connection.password)) {
7093
- let configJson = JSON.stringify(config, null, 2);
7094
- await writeConfigWithIntegrity(storagePath, configJson);
7095
- } else {
7096
- let password = config.connection.password, configWithoutPassword = {
7097
- ...config,
7098
- connection: {
7099
- ...config.connection,
7100
- password: void 0
7101
- }
7102
- };
7103
- delete configWithoutPassword.connection.password;
7104
- let configJson = JSON.stringify(configWithoutPassword, null, 2);
7105
- if (await writeConfigWithIntegrity(storagePath, configJson), password) {
7106
- let envPath = join8(storagePath, ".env.local"), envContent = `# Database Credentials - DO NOT commit to git
7107
-
7108
- DBCLI_PASSWORD=${password}
7109
- `;
7110
- await Bun.file(envPath).write(envContent);
7111
- }
7112
- }
7113
- else {
7114
- let json = JSON.stringify(config, null, 2);
7115
- await Bun.file(path).write(json);
7116
- }
7117
- } catch (error) {
7118
- if (error instanceof ConfigError)
7119
- throw error;
7120
- throw new ConfigError(`Failed to write .dbcli config: ${error instanceof Error ? error.message : String(error)}`);
7121
- }
7122
- }
7123
- };
7124
- });
7125
-
7126
- // src/core/correlation-id.ts
7127
- function isCorrelationId(value) {
7128
- return typeof value === "string" && CORRELATION_ID_PATTERN.test(value);
7129
- }
7130
- function setGlobalCorrelationId(value) {
7131
- if (value !== void 0 && !isCorrelationId(value))
7132
- throw Error("Invalid correlation ID");
7133
- globalCorrelationId = value;
7134
- }
7135
- function getGlobalCorrelationId() {
7136
- return globalCorrelationId;
7137
- }
7138
- var CORRELATION_ID_PATTERN, globalCorrelationId;
7139
- var init_correlation_id = __esm(() => {
7140
- CORRELATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/;
7141
- });
7142
-
7143
- // src/utils/connection-timeout.ts
7144
- function setGlobalConnectionTimeout(timeout) {
7145
- _globalConnectionTimeout = timeout;
7146
- }
7147
- function setGlobalStatementTimeout(timeout) {
7148
- _globalStatementTimeout = timeout;
7149
- }
7150
- function resolveConnectionTimeout(configured) {
7151
- return _globalConnectionTimeout ?? configured;
7152
- }
7153
- function resolveStatementTimeout(configured) {
7154
- return _globalStatementTimeout ?? configured;
7155
- }
7156
- function withResolvedTimeout(options) {
7157
- let timeout = resolveConnectionTimeout(options.timeout), statementTimeout = resolveStatementTimeout(options.statementTimeout);
7158
- if (timeout === options.timeout && statementTimeout === options.statementTimeout)
7159
- return options;
7160
- return { ...options, timeout, statementTimeout };
7161
- }
7162
- var _globalConnectionTimeout, _globalStatementTimeout;
7163
-
7164
- // src/core/connection-selector.ts
7165
- function createConnectionSelectorOption() {
7166
- return new Option2("--use <connection>", CONNECTION_SELECTOR_DESCRIPTION);
7167
- }
7168
- var CONNECTION_SELECTOR_DESCRIPTION = "Use a specific named connection for this invocation (v2 config)";
7169
- var init_connection_selector = __esm(() => {
7170
- init_esm();
7171
- });
7172
-
7173
- // src/utils/config-path.ts
7174
- function resolveConfigPath(command, options, fallback = ".dbcli") {
7175
- for (let current = command;current; current = current.parent ?? void 0) {
7176
- let source = current.getOptionValueSource("config");
7177
- if (source && source !== "default") {
7178
- let currentOptions = current.opts();
7179
- if (typeof currentOptions.config === "string" && currentOptions.config.length > 0)
7180
- return currentOptions.config;
7181
- }
7182
- }
7183
- if (typeof options?.config === "string" && options.config.length > 0 && options.config !== fallback)
7184
- return options.config;
7185
- for (let current = command;current; current = current.parent ?? void 0) {
7186
- let source = current.getOptionValueSource("global");
7187
- if (source && source !== "default" && current.opts().global === !0)
7188
- return getGlobalConfigPath();
7189
- }
7190
- if (options?.global === !0)
7191
- return getGlobalConfigPath();
7192
- if (typeof options?.config === "string" && options.config.length > 0)
7193
- return options.config;
7194
- return fallback;
7195
- }
7196
- var init_config_path = __esm(() => {
7197
- init_config_binding();
7198
- });
7199
-
7200
- // src/utils/config-sidecar-path.ts
7201
- import { dirname as dirname2, extname, join as join9 } from "path";
7202
- function configSidecarPath(configPath, fileName) {
7203
- let directory = extname(configPath) !== "" && !configPath.endsWith(".dbcli") ? dirname2(configPath) : configPath;
7204
- return join9(directory, fileName);
7205
- }
7206
- var init_config_sidecar_path = () => {};
7207
-
7208
- // src/utils/skill-check-cache.ts
7209
- import { mkdir as mkdir5, rename as rename3, unlink as unlink4, writeFile as writeFile2 } from "fs/promises";
7210
- import { dirname as dirname4 } from "path";
7211
- function cachePath(configPath) {
7212
- return configSidecarPath(configPath, CACHE_FILE_NAME);
7213
- }
7214
- async function readSkillCheckCache(configPath, dbcliVersion, options = {}) {
7215
- try {
7216
- let file = Bun.file(cachePath(configPath));
7217
- if (!await file.exists())
7218
- return null;
7219
- let raw = await file.json();
7220
- if (raw.version !== 1 || !Array.isArray(raw.outdated))
7221
- return null;
7222
- if (raw.dbcliVersion !== dbcliVersion)
7223
- return null;
7224
- if (typeof raw.checkedAt !== "number")
7225
- return null;
7226
- let age = (options.now ?? Date.now()) - raw.checkedAt;
7227
- if (age < 0 || age > SKILL_CHECK_TTL_MS)
7228
- return null;
7229
- return raw.outdated;
7230
- } catch {
7231
- return null;
8051
+ return "legacy.json";
8052
+ return "infrequent.json";
7232
8053
  }
7233
8054
  }
7234
- async function writeSkillCheckCache(configPath, dbcliVersion, outdated, options = {}) {
7235
- let path = cachePath(configPath), payload = {
7236
- version: 1,
7237
- dbcliVersion,
7238
- checkedAt: options.now ?? Date.now(),
7239
- outdated
7240
- };
7241
- try {
7242
- await mkdir5(dirname4(path), { recursive: !0 });
7243
- let temporary = `${path}.tmp-${process.pid}`;
7244
- await writeFile2(temporary, JSON.stringify(payload), "utf8"), await rename3(temporary, path);
7245
- } catch {}
7246
- }
7247
- async function invalidateSkillCheckCache(configPath) {
7248
- await unlink4(cachePath(configPath)).catch(() => {
7249
- return;
7250
- });
7251
- }
7252
- var SKILL_CHECK_TTL_MS = 86400000, CACHE_FILE_NAME = "skill-check.json";
7253
- var init_skill_check_cache = __esm(() => {
7254
- init_config_sidecar_path();
8055
+ var init_schema_index = __esm(() => {
8056
+ init_schema_path();
7255
8057
  });
7256
8058
 
7257
- // src/core/slow-query-advisory.ts
7258
- function slowQueryThresholdFor(options) {
7259
- if (options.recovery === !0)
7260
- return 0;
7261
- let threshold = options.slowMs ?? 1000;
7262
- return assertValidSlowQueryThreshold(threshold), threshold;
7263
- }
7264
- function assertValidSlowQueryThreshold(value) {
7265
- if (value === void 0)
7266
- return;
7267
- if (!Number.isSafeInteger(value) || value < 0)
7268
- throw Error("slow-ms must be a non-negative integer");
7269
- }
7270
- function parseSlowQueryThreshold(value) {
7271
- if (!/^\d+$/.test(value))
7272
- throw Error("slow-ms must be a non-negative integer");
7273
- let parsed = Number(value);
7274
- return assertValidSlowQueryThreshold(parsed), parsed;
7275
- }
7276
- function recommendationFor(system) {
7277
- return system === void 0 || GUIDE_SLOW_QUERY_SYSTEMS.includes(system) ? "Review safely with: dbcli guide slow-query --format markdown. This hint runs no additional database diagnostics." : `Re-check the query shape and the filters it scans; dbcli ships no slow-query diagnostics for ${system}. This hint runs no additional database diagnostics.`;
7278
- }
7279
- function buildSlowQueryAdvisory(executionTimeMs, options) {
7280
- let thresholdMs = slowQueryThresholdFor(options);
7281
- if (thresholdMs === 0 || executionTimeMs === void 0 || executionTimeMs < thresholdMs)
7282
- return;
7283
- return {
7284
- code: "SLOW_QUERY",
7285
- executionTimeMs,
7286
- thresholdMs,
7287
- recommendation: recommendationFor(options.system)
7288
- };
7289
- }
7290
- function attachSlowQueryAdvisory(result, options) {
7291
- let advisory = buildSlowQueryAdvisory(result.executionTimeMs, options);
7292
- if (!advisory)
7293
- return result;
7294
- return {
7295
- ...result,
7296
- metadata: {
7297
- statement: result.metadata?.statement ?? "UNKNOWN",
7298
- ...result.metadata,
7299
- performanceAdvisory: advisory
7300
- }
7301
- };
7302
- }
7303
- var GUIDE_SLOW_QUERY_SYSTEMS;
7304
- var init_slow_query_advisory = __esm(() => {
7305
- GUIDE_SLOW_QUERY_SYSTEMS = ["mariadb", "mysql", "postgresql", "redis"];
7306
- });
8059
+ // src/core/schema-loader.ts
8060
+ import { join as join7 } from "path";
7307
8061
 
7308
- // resources/lang/en/messages.json
7309
- var messages_default;
7310
- var init_messages = __esm(() => {
7311
- messages_default = {
7312
- cli: {
7313
- agent_output_option: "Emit one Operation Envelope v1 (capabilities and capabilities check only)"
7314
- },
7315
- init: {
7316
- description: "Initialize dbcli configuration with .env parsing and interactive prompts",
7317
- welcome: "Welcome to dbcli",
7318
- prompt_connection_name: "Connection name",
7319
- connection_name_required: "Connection name is required. Provide --conn-name <name> (this is separate from the database --name).",
7320
- select_system: "Select database system:",
7321
- prompt_host: "Database host: ",
7322
- prompt_port: "Database port: ",
7323
- prompt_user: "Database user: ",
7324
- prompt_password: "Database password: ",
7325
- secret_alternatives_sql: "pass --password, supply the credential through .env or the process environment, or store a reference with --use-env-refs",
7326
- secret_alternatives_mongo_password: "pass --password, supply the credential through .env or the process environment, or paste the connection string with --uri",
7327
- secret_alternatives_mongo_uri: "pass the connection string with --uri",
7328
- mongo_uri_conflicts_fields: "--uri cannot be combined with {flags}: the connection string already carries those settings. Use one or the other (--name may be combined to override the database name).",
7329
- mongo_uri_with_env_refs: "--uri cannot be combined with --use-env-refs: the connection string is decomposed into config fields, and its credentials are not moved into environment variables. Either remove --use-env-refs so dbcli decomposes the string into the field config, or use the --env-* flags (--env-host, --env-user, --env-password, ...) to name one variable per field.",
7330
- mongo_uri_fallback_warning: "\u26A0\uFE0F This connection string {reasons}, so it cannot become fields. It is stored as-is in uri, and its credentials will sit in plaintext in the config file.",
7331
- mongo_uri_reason_unix_socket: "uses a Unix domain socket",
7332
- mongo_uri_reason_multi_host: "contains more than one host (a field config holds only one)",
7333
- mongo_uri_reason_secret_option: "contains secret option(s) ({keys}) that would land in plaintext options",
7334
- mongo_uri_reason_duplicate_key: "repeats the query key(s) {keys} (a field config can hold only one value per key)",
7335
- mongo_uri_reason_non_scram: "uses the non-SCRAM auth mechanism {mechanism} (field config cannot express it)",
7336
- mongo_decomposed_header: "Connection string decomposed into these fields (password hidden):",
7337
- mongo_paste_env_refs_warning: "\u26A0\uFE0F --use-env-refs does not apply to a pasted connection string: its credentials are written to the config file as they are, not as environment-variable references.",
7338
- mongo_setup_mode_paste: "Paste a connection string (copied from Atlas / Compass)",
7339
- mongo_string_invalid: "Not a valid MongoDB connection string (it is not echoed because it may carry credentials).",
7340
- mongo_string_bad_host: "The connection string has an invalid host or port (the port must be an integer from 1 to 65535).",
7341
- mongo_string_bad_credentials: "The connection string's credentials contain an unescaped reserved character such as @, : or /. Percent-encode it (for example @ as %40).",
7342
- mongo_string_tls_conflict: "The connection string sets tls and ssl to different values.",
7343
- mongo_string_bad_tls: "The connection string's {key} must be true or false.",
7344
- mongo_string_empty_option: "The connection string sets {key} but leaves it empty. Give it a value or remove it.",
7345
- prompt_name: "Database name: ",
7346
- prompt_permission: "Permission level (query-only, read-write, data-admin, admin): ",
7347
- env_parse_note: "Note: Unable to parse .env configuration, using interactive prompts",
7348
- connection_testing: "Testing database connection...",
7349
- connection_success: "\u2713 Database connection successful",
7350
- connection_failed: "\u2717 Database connection failed",
7351
- config_saved: "Configuration saved to .dbcli",
7352
- config_saved_global: "Global configuration saved to {path}",
7353
- config_exists_overwrite: "Configuration file .dbcli already exists. Overwrite? (y/n): ",
7354
- cancelled: "Cancelled. Configuration not changed.",
7355
- skip_test_env_ref: "Skipping connection test in env-ref mode",
7356
- skip_test: "Skipping connection test (--skip-test)",
7357
- connection_hints: "Hints:",
7358
- config_exists_use_force: ".dbcli exists. Use --force option to overwrite.",
7359
- connection_added: "Connection '{name}' added",
7360
- connection_removed: "Connection '{name}' removed",
7361
- connection_removed_default_cleared: "Connection '{name}' removed. Default connection cleared. Specify --use <name> or DBCLI_CONNECTION for database commands.",
7362
- connection_renamed: "Connection '{oldName}' renamed to '{newName}'",
7363
- v1_migration_prompt: "Detected old config format. Create new format and import existing connection as 'default'?",
7364
- config_saved_v2: "V2 configuration saved to .dbcli",
7365
- config_not_found: "Configuration file not found",
7366
- requires_v2_remove: "Removing a connection requires V2 config format",
7367
- requires_v2_rename: "Renaming a connection requires V2 config format",
7368
- connection_not_found: "Connection '{name}' does not exist",
7369
- cannot_remove_last: "Cannot remove the last connection",
7370
- connection_already_exists: "Connection '{name}' already exists",
7371
- rename_invalid_format: "Usage: --rename <old-name>:<new-name>",
7372
- prompt_sqlite_file: "SQLite database file path: "
7373
- },
7374
- inspect: {
7375
- description: "Read-only snapshot of connection, permission, blacklist, objects, snippets, and suggested next commands.",
7376
- no_config: "No dbcli configuration found. Run `dbcli init` to start."
7377
- },
7378
- report: {
7379
- description: "Diagnostic report (health/capacity/perf) built from built-in snippets. Read-only, bounded.",
7380
- no_config: "No dbcli configuration found. Run `dbcli init` to start.",
7381
- section_invalid: "Unknown report section '{name}'. Allowed: health, capacity, perf"
7382
- },
7383
- guide: {
7384
- description: "Deterministic next-command planner for a fixed set of database goals.",
7385
- no_config: "No dbcli configuration found. Run `dbcli init` to start.",
7386
- missing_goal: "Missing goal. Try `dbcli guide --list` to see available goals.",
7387
- goal_invalid: "Unknown guide goal '{name}'. Run `dbcli guide --list` for valid IDs."
7388
- },
7389
- recovery: {
7390
- description: "Look up the structured recovery envelope for a known dbcli error code, or list all codes.",
7391
- missing_code: "Missing --code. Try `dbcli recovery --list` to see available codes.",
7392
- code_invalid: "Unknown recovery code '{name}'. Run `dbcli recovery --list` for valid codes.",
7393
- no_config: "No dbcli configuration found. Run `dbcli init` to start."
7394
- },
7395
- use: {
7396
- description: "Switch or display the default database connection",
7397
- switched: "Switched default connection to {name}",
7398
- current: "Current default connection: {name}",
7399
- current_none: "No default connection. Specify --use <name> or DBCLI_CONNECTION.",
7400
- unset: "Default connection cleared. Specify --use <name> or DBCLI_CONNECTION for database commands.",
7401
- unset_conflict: "--unset cannot be combined with a connection name, --list, --confirm-production, or --format json",
7402
- requires_v2: "This feature requires v2 config format. Use 'dbcli init --conn-name <name>' to create one",
7403
- available: "Available connections"
7404
- },
7405
- schema: {
7406
- description: "Retrieve table structure or list all tables",
7407
- fetching: "Fetching schema...",
7408
- success: "Schema updated",
7409
- not_found: "Table not found in database",
7410
- refresh_prompt: "Schema has changes. Apply? (y/n): ",
7411
- columns_header: "Columns",
7412
- scanning_database: "\uD83D\uDD0D Scanning database schema...",
7413
- tables_found: "\uD83D\uDCCD Found {count} table(s). Fetching schema details...",
7414
- processing_tables: " Processed {processed}/{total} table(s)",
7415
- schema_exists_warning: "\u26A0\uFE0F Database schema already exists in .dbcli",
7416
- use_force_to_override: " Use --force to override without confirmation"
7417
- },
7418
- list: {
7419
- description: "List all tables in the database",
7420
- no_tables: "No tables found in database"
7421
- },
7422
- query: {
7423
- description: "Execute SQL query",
7424
- executing: "Executing query...",
7425
- no_results: "No results returned",
7426
- result_count: "Results: {count} row(s)"
7427
- },
7428
- q: {
7429
- description: "Run a saved query snippet by @name",
7430
- missing_param: "Missing required parameters for snippet '{name}'",
7431
- engine_mismatch: "Engine mismatch for snippet '{name}'",
7432
- snippet_not_found: "Snippet not found: {name}",
7433
- dry_run_header: "Dry-run preview (no execution):"
7434
- },
7435
- audit: {
7436
- description: "Inspect, look up, clear, or check the health of the audit log",
7437
- tail: {
7438
- description: "Show recent audit entries (default: last 10 from current connection)"
7439
- },
7440
- show: {
7441
- description: "Look up a single audit entry by id (UUID prefix >=4) or --recovery-ref"
7442
- },
7443
- clear: {
7444
- description: "Delete audit log files (.jsonl + .jsonl.1) for the current connection",
7445
- prompt_header: "About to clear audit log for connection '{conn}':",
7446
- prompt_file_line: " {file} \u2014 {entries} entries, {size}",
7447
- prompt_continue: "Continue? [y/N] ",
7448
- requires_tty_or_yes: "Cannot prompt for confirmation in non-interactive session. Use --yes to clear without prompt.",
7449
- summary_cleared: "Cleared {count} entries from '{conn}'.",
7450
- summary_nothing: "Nothing to clear.",
7451
- summary_failed: "Failed to clear: {message}."
7452
- },
7453
- health: {
7454
- description: "Show AuditLogger health snapshot (size, entries, lock state, last write)"
7455
- },
7456
- write_gate: {
7457
- description: "Summarize tier-two write-gate decisions (ADR 0010's measurement)",
7458
- header: "Write-gate summary \u2014 connection: {conn}",
7459
- window: "Audit entries scanned: {scanned}, {from} \u2192 {to}",
7460
- decisions: "Tier-two evaluations: {count}, {from} \u2192 {to}",
7461
- tier_one: "Tier one, counted apart: {count} ({outcomes})",
7462
- no_decisions: "Tier two was never reached in this window. Per ADR 0010 that falsifies the criterion, not the gate: the shapes it looks for are not the shapes people write.",
7463
- no_entries: "The audit log is empty, so this says nothing about the gate either way. Run some writes, or check `dbcli audit health`."
7464
- },
7465
- disabled_hint: "Audit is disabled (audit.enabled = false in .dbcli). Use 'dbcli audit health' for details.",
7466
- no_entries: "No audit entries.",
7467
- show_no_match: "No audit entry matches '{prefix}'.",
7468
- show_ambiguous: "Ambiguous prefix '{prefix}': matches {count} entries. Please use a longer prefix.",
7469
- show_prefix_too_short: "Prefix must be at least 4 characters.",
7470
- show_recovery_no_match: "No audit entry has recovery_ref '{ref}'.",
7471
- show_recovery_ambiguous: "Multiple entries reference recovery_ref '{ref}': matches {count}. This should be rare; inspect file directly.",
7472
- show_mutex_violation: "Provide either <id> argument or --recovery-ref, not both.",
7473
- n_capped_warning: "--n value {requested} exceeds max {max}; capped to {max}.",
7474
- n_must_be_positive: "--n must be a positive integer."
7475
- },
7476
- queries: {
7477
- description: "Manage saved query snippets",
7478
- list_description: "List saved snippets",
7479
- show_description: "Show a snippet (frontmatter + SQL)",
7480
- new_description: "Create a new snippet file",
7481
- edit_description: "Open a snippet in $EDITOR",
7482
- check_description: "Validate all snippets",
7483
- delete_description: "Delete a local snippet file",
7484
- rename_description: "Rename a local snippet",
7485
- copy_description: "Copy a snippet from any tier into local",
7486
- import_description: "Import an external .sql file into local",
7487
- export_description: "Export a snippet's raw SQL to stdout or file",
7488
- created: "Created {file}",
7489
- first_run_hint: "Tip: commit .dbcli-shared/ to share snippets with your team.",
7490
- no_snippets: "No snippets found. Create one with: dbcli queries new @<name>",
7491
- check_ok: "\u2713 {count} snippets parsed successfully",
7492
- check_failed: "\u2717 {count} snippet(s) have errors",
7493
- search_description: "Search snippets by keywords (fuzzy ranking)",
7494
- suggest_description: "Suggest snippets by intent prefix",
7495
- search_no_keywords: "Provide at least one keyword, or try 'queries list' / 'queries suggest'.",
7496
- search_no_results: "No snippets matched. Try 'queries list' or 'queries suggest <intent>'.",
7497
- suggest_missing_intent: "Provide an intent (or prefix). Available: {available}",
7498
- suggest_invalid_intent: "Invalid intent '{value}'. Must match ^[a-z][a-z0-9.-]*$",
7499
- suggest_no_results: "No snippets with intent prefix '{intent}'. Available: {available}",
7500
- no_active_connection_hint: "No active connection; showing snippets across all engines."
7501
- },
7502
- errors: {
7503
- message: "Error: {message}",
7504
- query_only_boundary_failed: "Could not establish the query-only database boundary for {system}: {detail}",
7505
- query_only_boundary_verify: "Verify that {system} supports read-only transactions and that this account may start one",
7506
- query_only_boundary_not_executed: "The target statement was not executed; fix the database setting or connection before retrying",
7507
- query_only_cleanup_completed: "The query-only target completed, but transaction cleanup failed for {system}: {detail}",
7508
- query_only_cleanup_uncertain: "The query-only target failed and its outcome is uncertain because transaction cleanup also failed for {system}: {detail}",
7509
- query_only_cleanup_completed_retry: "The target statement ran; do not retry it unless repeating its external effects is safe",
7510
- query_only_cleanup_uncertain_retry: "The target may have produced effects before failing; do not retry it unless repeating them is safe",
7511
- query_only_cleanup_reconnect: "Reconnect before running another statement because this connection was discarded",
7512
- invalid_config: "Invalid configuration: {field}",
7513
- connection_failed: "Failed to connect to database: {message}",
7514
- permission_denied: "Permission denied (required: {required})",
7515
- permission_requires_level: "{type} operation requires {minimum} permission or higher (current level: {permission})",
7516
- elasticsearch_requires_level: "Elasticsearch {type} operation requires {minimum} permission or higher (current level: {permission})",
7517
- 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}).",
7518
- 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.",
7519
- 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.",
7520
- permission_denied_reason: "Permission denied: {reason}",
7521
- table_not_found: "Table not found: {table}",
7522
- unsupported_lang: "Language '{lang}' not supported, using English",
7523
- invalid_json: "Invalid JSON: {message}",
7524
- table_blacklisted: "Error: Table '{table}' is blacklisted for {operation} operations",
7525
- redis_target_blacklisted: "Redis command refused by blacklist",
7526
- column_blacklisted_write: "Error: {operation} on table '{table}' touches blacklisted columns: {columns}",
7527
- table_already_blacklisted: "Error: Table '{table}' is already blacklisted",
7528
- table_not_in_blacklist: "Error: Table '{table}' is not in the blacklist",
7529
- column_already_blacklisted: "Error: Column '{table}.{column}' is already blacklisted",
7530
- column_not_in_blacklist: "Error: Column '{table}.{column}' is not in the blacklist",
7531
- invalid_table_name: "Error: Invalid table name: {table}",
7532
- invalid_column_format: "Error: Invalid column format. Use 'table.column'",
7533
- invalid_system: "Invalid database system: {system}",
7534
- invalid_permission: "Invalid permission level: {permission}",
7535
- invalid_port: "Invalid port: {port}",
7536
- require_user: "Non-interactive mode requires --user option",
7537
- require_name: "Non-interactive mode requires --name option",
7538
- env_refs_missing_options: `When using --use-env-refs, environment variable names must be specified.
7539
- Provide options: --env-host, --env-port, --env-user, --env-password, --env-database
7540
- Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactive`,
7541
- env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
7542
- Set {envKey} in .env or environment variables.
7543
- Hint: run 'export {envKey}=<value>' and retry`,
7544
- invalid_output_format: "--format {format} is not supported here \u2014 use text or json",
7545
- sqlite_file_required: "A SQLite connection needs a file path. Pass --file <path> or run init interactively."
7546
- },
7547
- success: {
7548
- inserted: "Successfully inserted {count} row(s)",
7549
- updated: "Successfully updated {count} row(s)",
7550
- deleted: "Successfully deleted {count} row(s)",
7551
- no_changes: "No changes to apply"
7552
- },
7553
- insert: {
7554
- description: "Insert data into a table",
7555
- prompt_data: "Enter JSON data to insert: ",
7556
- confirm: "Insert {count} row(s) into {table}? (y/n): ",
7557
- elasticsearch_unsupported: "Elasticsearch does not support the insert command; for now, use an external tool (e.g. curl or Kibana DevTools) to write documents"
7558
- },
7559
- update: {
7560
- description: "Update data in a table",
7561
- prompt_where: "Enter WHERE clause (e.g., id=1): ",
7562
- prompt_set: 'Enter JSON updates (e.g., {"status": "active"}): ',
7563
- confirm: "Update rows in {table}? (y/n): ",
7564
- elasticsearch_unsupported: "Elasticsearch does not support the update command; for now, use an external tool (e.g. curl or Kibana DevTools) to update documents"
7565
- },
7566
- delete: {
7567
- description: "Delete data from a table",
7568
- prompt_where: "Enter WHERE clause (e.g., id=1): ",
7569
- confirm: "Delete rows from {table}? (y/n): ",
7570
- admin_only: "Delete command requires data-admin or admin permission",
7571
- elasticsearch_unsupported: "Elasticsearch does not support the delete command; for now, use an external tool (e.g. curl or Kibana DevTools) to delete documents"
7572
- },
7573
- export: {
7574
- description: "Export query results",
7575
- formats: "Supported formats: json, csv",
7576
- exported: "Exported {count} rows to {file}"
7577
- },
7578
- upgrade: {
7579
- description: "Check for updates and upgrade dbcli to the latest version",
7580
- checking: "Checking for updates...",
7581
- already_up_to_date: "\u2713 Already up to date (v{version})",
7582
- new_version_available: "New version available: v{version}",
7583
- current_version: "Current version : v{version}",
7584
- latest_version: "Latest version : v{version}",
7585
- update_hint: '[INFO] dbcli v{version} available. Run "dbcli upgrade" to upgrade.',
7586
- install_hint: 'Run "dbcli upgrade" to install the update.',
7587
- upgrading: "Upgrading...",
7588
- success: "\u2713 Successfully upgraded to v{version}",
7589
- skill_recheck_hint: 'Hint: If you have installed AI skills, run "dbcli skill --install <platform>" to ensure they are up to date.',
7590
- failed: "\u2717 Upgrade failed. Try running manually:",
7591
- manual_hint: "bun add -g @carllee1983/dbcli@latest",
7592
- network_error: "\u26A0 Could not check for updates (network error or registry unavailable)"
7593
- },
7594
- skill: {
7595
- description: "Generate AI skill documentation",
7596
- context_description: "Generate AI-friendly database schema and query snippet context",
7597
- installed: `Skill: {path}
7598
- Reference: {referencePath}`,
7599
- update_available: "Skill updates available for the following platforms:",
7600
- update_hint: 'Run "dbcli skill --install <platform>" to update.'
7601
- },
7602
- blacklist: {
7603
- description: "Manage sensitive data blacklist to prevent AI access",
7604
- list_title: "Current Blacklist Configuration",
7605
- tables_label: "Blacklisted tables",
7606
- columns_label: "Blacklisted columns",
7607
- none: "No tables or columns are currently blacklisted",
7608
- table_added: "\u2713 Table '{table}' added to blacklist",
7609
- table_removed: "\u2713 Table '{table}' removed from blacklist",
7610
- column_added: "\u2713 Column '{table}.{column}' added to blacklist",
7611
- column_removed: "\u2713 Column '{table}.{column}' removed from blacklist",
7612
- refuse_wildcard: "Refused: '{table}' contains wildcard characters, which are matched literally on {system} \u2014 the entry would never match anything. Write the exact name."
7613
- },
7614
- security: {
7615
- columns_omitted: "Security: {count} column(s) were omitted based on your blacklist"
7616
- },
7617
- warnings: {
7618
- blacklist_override_used: "Warning: Blacklist override enabled (DBCLI_OVERRIDE_BLACKLIST=true). Executing {operation} on blacklisted table '{table}'",
7619
- redis_blacklist_override_active: "Warning: Redis blacklist enforcement disabled by DBCLI_OVERRIDE_BLACKLIST=true"
7620
- },
7621
- version: {
7622
- unsupported_warning: "{system} {version} is below minimum supported version {minVersion}. Some features may not work correctly.",
7623
- doctor_pass: "{system} {version} (meets >= {minVersion})",
7624
- doctor_warn: "{system} {version} is below supported >= {minVersion}"
7625
- },
7626
- migrate: {
7627
- description: "Schema DDL operations (CREATE/DROP/ALTER TABLE, INDEX, CONSTRAINT, ENUM)",
7628
- dry_run_preview: "[DRY RUN] Generated SQL (not executed):",
7629
- executing: "Executing DDL...",
7630
- success: "DDL executed successfully",
7631
- confirm_drop: "This is a destructive operation ({operation}). Proceed?",
7632
- permission_required: "DDL operations require admin permission (current: {permission})",
7633
- blacklist_blocked: 'Table "{table}" is blacklisted \u2014 DDL operations are blocked',
7634
- schema_refreshed: 'Schema cache updated for table "{table}"',
7635
- no_sql_generated: "No SQL generated (operation not applicable for this database)",
7636
- create_description: "Create a new table",
7637
- drop_description: "Drop a table",
7638
- add_column_description: "Add a column to a table",
7639
- drop_column_description: "Drop a column from a table",
7640
- alter_column_description: "Alter a column in a table",
7641
- add_index_description: "Add an index to a table",
7642
- drop_index_description: "Drop an index",
7643
- add_constraint_description: "Add a constraint to a table",
7644
- drop_constraint_description: "Drop a constraint from a table",
7645
- add_enum_description: "Create an enum type (PostgreSQL only)",
7646
- alter_enum_description: "Add a value to an enum type",
7647
- drop_enum_description: "Drop an enum type"
7648
- },
7649
- password: {
7650
- description: "Change the password of a single connection, leaving every other setting untouched",
7651
- arg_connection: "Connection name (defaults to the current default connection)",
7652
- opt_stdin: "Read the new password from stdin (for rotation scripts)",
7653
- opt_password: "New password given inline (visible in shell history and process list)",
7654
- opt_skip_test: "Skip verifying the new password against the database before saving",
7655
- prompt: "New password for connection '{name}'",
7656
- source_conflict: "--password and --stdin cannot be used together; pick one source.",
7657
- needs_source: "No terminal available for a masked prompt. Pass the value with --stdin or --password.",
7658
- empty: "The new password is empty; nothing was changed.",
7659
- updated: "Password for connection '{name}' updated \u2192 {envFile} ({varName})",
7660
- converted: 'The literal password in config.json was replaced by a {{ "$env": "{varName}" }} reference; later rotations only touch the env file.',
7661
- skipped_test: "Verification skipped (--skip-test): the new password was not checked against the database."
8062
+ class SchemaLayeredLoader {
8063
+ dbcliPath;
8064
+ connectionName;
8065
+ options;
8066
+ cache = null;
8067
+ index = null;
8068
+ loadTime = 0;
8069
+ constructor(dbcliPath, options) {
8070
+ this.dbcliPath = dbcliPath, this.connectionName = options?.connectionName, this.options = {
8071
+ maxCacheItems: options?.maxCacheItems || 100,
8072
+ maxCacheSize: options?.maxCacheSize || 52428800,
8073
+ hotTableThreshold: options?.hotTableThreshold || 20,
8074
+ enableStreaming: options?.enableStreaming || !1,
8075
+ streamingTimeout: options?.streamingTimeout || 30000
8076
+ };
8077
+ }
8078
+ async initialize() {
8079
+ let startTime = performance.now();
8080
+ try {
8081
+ if (await this.ensureDirectories(), this.index = await SchemaIndexBuilder.loadIndex(this.dbcliPath, this.connectionName), this.cache = new SchemaCacheManager(this.dbcliPath, {
8082
+ maxCacheItems: this.options.maxCacheItems,
8083
+ maxCacheSize: this.options.maxCacheSize,
8084
+ connectionName: this.connectionName
8085
+ }), await this.cache.initialize(), this.loadTime = performance.now() - startTime, this.loadTime > 50)
8086
+ console.warn(`Schema initialization took ${this.loadTime.toFixed(2)}ms (target: < 100ms)`);
8087
+ return {
8088
+ cache: this.cache,
8089
+ index: this.index,
8090
+ loadTime: this.loadTime
8091
+ };
8092
+ } catch (error) {
8093
+ if (console.error("Failed to initialize schema loader:", error), this.loadTime = performance.now() - startTime, !this.cache)
8094
+ this.cache = new SchemaCacheManager(this.dbcliPath, {
8095
+ maxCacheItems: this.options.maxCacheItems,
8096
+ maxCacheSize: this.options.maxCacheSize,
8097
+ connectionName: this.connectionName
8098
+ });
8099
+ return {
8100
+ cache: this.cache,
8101
+ index: this.index,
8102
+ loadTime: this.loadTime
8103
+ };
7662
8104
  }
7663
- };
8105
+ }
8106
+ async loadColdTable(tableName, cache) {
8107
+ try {
8108
+ return await cache.getTableSchema(tableName);
8109
+ } catch (error) {
8110
+ return console.error(`Failed to load cold table ${tableName}:`, error), null;
8111
+ }
8112
+ }
8113
+ async ensureDirectories() {
8114
+ let base = resolveSchemaPath(this.dbcliPath, this.connectionName), dirs = [base, join7(base, "cold")];
8115
+ for (let dir of dirs)
8116
+ try {
8117
+ if (!await Bun.file(dir).exists()) {
8118
+ if (await Bun.spawn(["mkdir", "-p", dir]).exited !== 0)
8119
+ console.error(`Failed to create directory: ${dir}`);
8120
+ }
8121
+ } catch (error) {
8122
+ console.error(`Error ensuring directory ${dir}:`, error);
8123
+ }
8124
+ }
8125
+ getBenchmark() {
8126
+ if (!this.cache || !this.index)
8127
+ return {
8128
+ initTime: this.loadTime,
8129
+ hotTables: 0,
8130
+ totalTables: 0,
8131
+ estimatedSize: 0
8132
+ };
8133
+ let estimatedSize = Object.values(this.index.tables).reduce((sum, table) => sum + (table.estimatedSize || 0), 0);
8134
+ return {
8135
+ initTime: this.loadTime,
8136
+ hotTables: this.index.hotTables.length,
8137
+ totalTables: this.index.metadata.totalTables,
8138
+ estimatedSize
8139
+ };
8140
+ }
8141
+ }
8142
+ var init_schema_loader = __esm(() => {
8143
+ init_schema_cache();
8144
+ init_schema_index();
8145
+ init_schema_path();
7664
8146
  });
7665
8147
 
7666
- // resources/lang/zh-TW/messages.json
7667
- var messages_default2;
7668
- var init_messages2 = __esm(() => {
7669
- messages_default2 = {
7670
- cli: {
7671
- agent_output_option: "\u8F38\u51FA\u4E00\u4EFD Operation Envelope v1\uFF08\u50C5\u652F\u63F4 capabilities \u8207 capabilities check\uFF09"
7672
- },
7673
- init: {
7674
- description: "\u521D\u59CB\u5316 dbcli \u914D\u7F6E\uFF0C\u89E3\u6790 .env \u4E26\u9032\u884C\u4E92\u52D5\u5F0F\u63D0\u793A",
7675
- welcome: "\u6B61\u8FCE\u4F7F\u7528 dbcli",
7676
- prompt_connection_name: "\u9023\u7DDA\u540D\u7A31",
7677
- connection_name_required: "\u9023\u7DDA\u540D\u7A31\u4E0D\u53EF\u7A7A\u767D\uFF0C\u8ACB\u63D0\u4F9B --conn-name <name>\uFF08\u8207\u8CC7\u6599\u5EAB\u540D\u7A31 --name \u4E0D\u540C\uFF09\u3002",
7678
- select_system: "\u9078\u64C7\u8CC7\u6599\u5EAB\u7CFB\u7D71\uFF1A",
7679
- prompt_host: "\u8CC7\u6599\u5EAB\u4E3B\u6A5F\uFF1A",
7680
- prompt_port: "\u8CC7\u6599\u5EAB\u57E0\u865F\uFF1A",
7681
- prompt_user: "\u8CC7\u6599\u5EAB\u4F7F\u7528\u8005\uFF1A",
7682
- prompt_password: "\u8CC7\u6599\u5EAB\u5BC6\u78BC\uFF1A",
7683
- secret_alternatives_sql: "\u6539\u7528 --password\u3001\u628A\u6191\u8B49\u653E\u9032 .env \u6216\u884C\u7A0B\u74B0\u5883\u8B8A\u6578\uFF0C\u6216\u4EE5 --use-env-refs \u5B58\u6210\u53C3\u7167",
7684
- secret_alternatives_mongo_password: "\u6539\u7528 --password\u3001\u628A\u6191\u8B49\u653E\u9032 .env \u6216\u884C\u7A0B\u74B0\u5883\u8B8A\u6578\uFF0C\u6216\u4EE5 --uri \u8CBC\u4E0A\u9023\u7DDA\u5B57\u4E32",
7685
- secret_alternatives_mongo_uri: "\u4EE5 --uri \u50B3\u5165\u9023\u7DDA\u5B57\u4E32",
7686
- mongo_uri_conflicts_fields: "--uri \u4E0D\u80FD\u8207 {flags} \u4E26\u7528\uFF1A\u9023\u7DDA\u5B57\u4E32\u5DF2\u5305\u542B\u9019\u4E9B\u8A2D\u5B9A\u3002\u8ACB\u64C7\u4E00\u4F7F\u7528\uFF08--name \u53EF\u4E26\u7528\uFF0C\u7528\u4F86\u8986\u84CB\u8CC7\u6599\u5EAB\u540D\u7A31\uFF09\u3002",
7687
- mongo_uri_with_env_refs: "--uri \u4E0D\u80FD\u8207 --use-env-refs \u4E26\u7528\uFF1A\u9023\u7DDA\u5B57\u4E32\u6703\u88AB\u62C6\u89E3\u6210\u6B04\u4F4D\u8A2D\u5B9A\u5BEB\u5165\uFF0C\u5E33\u5BC6\u4E0D\u6703\u88AB\u62BD\u6210\u74B0\u5883\u8B8A\u6578\u53C3\u7167\u3002\u8ACB\u6539\u8D70\u4E0B\u5217\u4EFB\u4E00\u505A\u6CD5\uFF1A\u62FF\u6389 --use-env-refs\uFF0C\u8B93 dbcli \u62C6\u89E3\u5B57\u4E32\u5BEB\u5165\u6B04\u4F4D\u8A2D\u5B9A\uFF1B\u6216\u6539\u7528 --env-* flag\uFF08--env-host\u3001--env-user\u3001--env-password \u7B49\uFF09\u9010\u6B04\u7D66\u8B8A\u6578\u540D\u7A31\u3002",
7688
- mongo_uri_fallback_warning: "\u26A0\uFE0F \u6B64\u9023\u7DDA\u5B57\u4E32{reasons}\uFF0C\u7121\u6CD5\u62C6\u6210\u6B04\u4F4D\u8A2D\u5B9A\uFF0C\u5DF2\u539F\u6A23\u5B58\u5165 uri\uFF1B\u5E33\u5BC6\u5C07\u660E\u6587\u5B58\u653E\u65BC\u8A2D\u5B9A\u6A94\u3002",
7689
- mongo_uri_reason_unix_socket: "\u4F7F\u7528 Unix domain socket\uFF08\u672C\u6A5F\u901A\u8A0A\u7AEF\uFF09",
7690
- mongo_uri_reason_multi_host: "\u542B\u591A\u7D44 host\uFF08\u6B04\u4F4D\u8A2D\u5B9A\u53EA\u80FD\u8868\u9054\u4E00\u7D44\uFF09",
7691
- mongo_uri_reason_secret_option: "\u542B\u6A5F\u5BC6 option\uFF08{keys}\uFF09\uFF0C\u62C6\u9032 options \u6703\u660E\u6587\u843D\u76E4",
7692
- mongo_uri_reason_duplicate_key: "\u542B\u91CD\u8907\u7684 query \u9375\uFF08{keys}\uFF09\uFF08\u6B04\u4F4D\u8A2D\u5B9A\u6BCF\u500B\u9375\u53EA\u80FD\u5B58\u4E00\u500B\u503C\uFF09",
7693
- mongo_uri_reason_non_scram: "\u4F7F\u7528\u975E SCRAM \u7684\u8A8D\u8B49\u6A5F\u5236 {mechanism}\uFF08\u6B04\u4F4D\u8A2D\u5B9A\u7121\u6CD5\u8868\u9054\uFF09",
7694
- mongo_decomposed_header: "\u9023\u7DDA\u5B57\u4E32\u5DF2\u62C6\u89E3\u6210\u4E0B\u5217\u6B04\u4F4D\uFF08\u5BC6\u78BC\u5DF2\u906E\u853D\uFF09\uFF1A",
7695
- mongo_paste_env_refs_warning: "\u26A0\uFE0F --use-env-refs \u4E0D\u9069\u7528\u65BC\u8CBC\u4E0A\u7684\u9023\u7DDA\u5B57\u4E32\uFF1A\u5E33\u5BC6\u6703\u4EE5\u5B57\u9762\u503C\u5BEB\u5165\u8A2D\u5B9A\u6A94\uFF0C\u4E0D\u6703\u8B8A\u6210\u74B0\u5883\u8B8A\u6578\u53C3\u7167\u3002",
7696
- mongo_setup_mode_paste: "\u8CBC\u4E0A\u9023\u7DDA\u5B57\u4E32\uFF08Atlas / Compass \u8907\u88FD\u7684\uFF09",
7697
- mongo_string_invalid: "\u4E0D\u662F\u5408\u6CD5\u7684 MongoDB \u9023\u7DDA\u5B57\u4E32\uFF08\u5B57\u4E32\u53EF\u80FD\u5E36\u6709\u5E33\u5BC6\uFF0C\u6240\u4EE5\u4E0D\u5370\u51FA\uFF09\u3002",
7698
- mongo_string_bad_host: "\u9023\u7DDA\u5B57\u4E32\u7684 host \u6216\u57E0\u865F\u4E0D\u5408\u6CD5\uFF08\u57E0\u865F\u9808\u70BA 1 \u81F3 65535 \u7684\u6574\u6578\uFF09\u3002",
7699
- mongo_string_bad_credentials: "\u9023\u7DDA\u5B57\u4E32\u7684\u5E33\u865F\u6216\u5BC6\u78BC\u542B\u6709\u672A\u8DF3\u812B\u7684\u4FDD\u7559\u5B57\u5143\uFF08\u5982 @\u3001:\u3001/\uFF09\u3002\u8ACB\u505A\u767E\u5206\u6BD4\u7DE8\u78BC\uFF08\u4F8B\u5982 @ \u5BEB\u6210 %40\uFF09\u3002",
7700
- mongo_string_tls_conflict: "\u9023\u7DDA\u5B57\u4E32\u540C\u6642\u6307\u5B9A\u4E86 tls \u8207 ssl \u4E14\u503C\u4E0D\u540C\u3002",
7701
- mongo_string_bad_tls: "\u9023\u7DDA\u5B57\u4E32\u7684 {key} \u53EA\u63A5\u53D7 true \u6216 false\u3002",
7702
- mongo_string_empty_option: "\u9023\u7DDA\u5B57\u4E32\u6307\u5B9A\u4E86 {key} \u537B\u6C92\u6709\u503C\u3002\u8ACB\u7D66\u503C\u6216\u79FB\u9664\u3002",
7703
- prompt_name: "\u8CC7\u6599\u5EAB\u540D\u7A31\uFF1A",
7704
- prompt_permission: "\u6B0A\u9650\u7B49\u7D1A\uFF08query-only, read-write, data-admin, admin\uFF09\uFF1A",
7705
- env_parse_note: "\u63D0\u793A\uFF1A\u7121\u6CD5\u89E3\u6790 .env \u914D\u7F6E\uFF0C\u4F7F\u7528\u4E92\u52D5\u5F0F\u63D0\u793A",
7706
- connection_testing: "\u6B63\u5728\u6E2C\u8A66\u8CC7\u6599\u5EAB\u9023\u63A5...",
7707
- connection_success: "\u2713 \u8CC7\u6599\u5EAB\u9023\u63A5\u6210\u529F",
7708
- connection_failed: "\u2717 \u8CC7\u6599\u5EAB\u9023\u63A5\u5931\u6557",
7709
- config_saved: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u81F3 .dbcli",
7710
- config_saved_global: "\u5168\u57DF\u8A2D\u5B9A\u5DF2\u5132\u5B58\u81F3 {path}",
7711
- config_exists_overwrite: "\u914D\u7F6E\u6A94\u6848 .dbcli \u5DF2\u5B58\u5728\u3002\u662F\u5426\u8986\u84CB\uFF1F (y/n)\uFF1A",
7712
- cancelled: "\u5DF2\u53D6\u6D88\u3002\u914D\u7F6E\u672A\u66F4\u6539\u3002",
7713
- skip_test_env_ref: "\u8DF3\u904E\u9023\u7DDA\u6E2C\u8A66\uFF08\u74B0\u5883\u8B8A\u6578\u53C3\u7167\u6A21\u5F0F\uFF09",
7714
- skip_test: "\u8DF3\u904E\u9023\u7DDA\u6E2C\u8A66\uFF08--skip-test\uFF09",
7715
- connection_hints: "\u63D0\u793A\uFF1A",
7716
- config_exists_use_force: ".dbcli \u5DF2\u5B58\u5728\u3002\u4F7F\u7528 --force \u9078\u9805\u8986\u84CB\u3002",
7717
- connection_added: "\u5DF2\u65B0\u589E\u9023\u7DDA '{name}'",
7718
- connection_removed: "\u5DF2\u79FB\u9664\u9023\u7DDA '{name}'",
7719
- connection_removed_default_cleared: "\u5DF2\u79FB\u9664\u9023\u7DDA '{name}'\uFF0C\u9810\u8A2D\u9023\u7DDA\u5DF2\u6E05\u7A7A\u3002\u57F7\u884C\u8CC7\u6599\u5EAB\u6307\u4EE4\u6642\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION\u3002",
7720
- connection_renamed: "\u5DF2\u5C07\u9023\u7DDA '{oldName}' \u91CD\u65B0\u547D\u540D\u70BA '{newName}'",
7721
- v1_migration_prompt: "\u5075\u6E2C\u5230\u820A\u683C\u5F0F\u8A2D\u5B9A\uFF0C\u5C07\u5EFA\u7ACB\u65B0\u683C\u5F0F\u4E26\u5C07\u73FE\u6709\u9023\u7DDA\u532F\u5165\u70BA 'default'\uFF0C\u662F\u5426\u7E7C\u7E8C\uFF1F",
7722
- config_saved_v2: "V2 \u8A2D\u5B9A\u5DF2\u5132\u5B58\u81F3 .dbcli",
7723
- config_not_found: "\u627E\u4E0D\u5230\u8A2D\u5B9A\u6A94",
7724
- requires_v2_remove: "\u79FB\u9664\u9023\u7DDA\u9700\u8981 V2 \u683C\u5F0F\u8A2D\u5B9A",
7725
- requires_v2_rename: "\u91CD\u65B0\u547D\u540D\u9023\u7DDA\u9700\u8981 V2 \u683C\u5F0F\u8A2D\u5B9A",
7726
- connection_not_found: "\u9023\u7DDA '{name}' \u4E0D\u5B58\u5728",
7727
- cannot_remove_last: "\u7121\u6CD5\u79FB\u9664\u6700\u5F8C\u4E00\u500B\u9023\u7DDA",
7728
- connection_already_exists: "\u9023\u7DDA '{name}' \u5DF2\u5B58\u5728",
7729
- rename_invalid_format: "\u7528\u6CD5\uFF1A--rename <\u820A\u540D\u7A31>:<\u65B0\u540D\u7A31>",
7730
- prompt_sqlite_file: "SQLite \u8CC7\u6599\u5EAB\u6A94\u6848\u8DEF\u5F91\uFF1A"
7731
- },
7732
- inspect: {
7733
- 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",
7734
- no_config: "\u627E\u4E0D\u5230 dbcli \u8A2D\u5B9A\u6A94\u3002\u8ACB\u5148\u57F7\u884C `dbcli init`\u3002"
7735
- },
7736
- report: {
7737
- description: "\u8A3A\u65B7\u5831\u544A\uFF08health/capacity/perf\uFF09\uFF1A\u4EE5\u5167\u5EFA\u7247\u6BB5\u7522\u751F\u552F\u8B80\u3001\u6709\u754C\u7684\u8A3A\u65B7\u5FEB\u7167\u3002",
7738
- no_config: "\u627E\u4E0D\u5230 dbcli \u8A2D\u5B9A\u6A94\u3002\u8ACB\u5148\u57F7\u884C `dbcli init`\u3002",
7739
- section_invalid: "\u672A\u77E5\u7684 report section '{name}'\u3002\u5141\u8A31\u503C\uFF1Ahealth\u3001capacity\u3001perf"
7740
- },
7741
- guide: {
7742
- description: "\u91DD\u5C0D\u56FA\u5B9A\u8CC7\u6599\u5EAB\u76EE\u6A19\u6E05\u55AE\uFF0C\u8F38\u51FA\u53EF\u9810\u6E2C\u7684\u4E0B\u4E00\u6B65\u6307\u4EE4\u8A08\u756B\u3002",
7743
- no_config: "\u627E\u4E0D\u5230 dbcli \u8A2D\u5B9A\u6A94\u3002\u8ACB\u5148\u57F7\u884C `dbcli init`\u3002",
7744
- missing_goal: "\u7F3A\u5C11 goal \u53C3\u6578\u3002\u8ACB\u57F7\u884C `dbcli guide --list` \u67E5\u770B\u53EF\u7528\u76EE\u6A19\u3002",
7745
- goal_invalid: "\u672A\u77E5\u7684 guide goal '{name}'\u3002\u8ACB\u57F7\u884C `dbcli guide --list` \u53D6\u5F97\u6709\u6548\u76EE\u6A19 ID\u3002"
7746
- },
7747
- recovery: {
7748
- description: "\u67E5\u8A62\u5DF2\u77E5 dbcli \u932F\u8AA4\u78BC\u5C0D\u61C9\u7684\u7D50\u69CB\u5316\u6062\u5FA9\u65B9\u6848\uFF0C\u6216\u5217\u51FA\u6240\u6709\u652F\u63F4\u7684\u932F\u8AA4\u78BC\u3002",
7749
- missing_code: "\u7F3A\u5C11 --code \u53C3\u6578\u3002\u8ACB\u57F7\u884C `dbcli recovery --list` \u67E5\u770B\u53EF\u7528\u4EE3\u78BC\u3002",
7750
- code_invalid: "\u672A\u77E5\u7684 recovery code '{name}'\u3002\u8ACB\u57F7\u884C `dbcli recovery --list` \u53D6\u5F97\u6709\u6548\u4EE3\u78BC\u3002",
7751
- no_config: "\u627E\u4E0D\u5230 dbcli \u8A2D\u5B9A\u6A94\u3002\u8ACB\u5148\u57F7\u884C `dbcli init`\u3002"
7752
- },
7753
- use: {
7754
- description: "\u5207\u63DB\u6216\u986F\u793A\u9810\u8A2D\u8CC7\u6599\u5EAB\u9023\u7DDA",
7755
- switched: "\u5DF2\u5207\u63DB\u9810\u8A2D\u9023\u7DDA\u70BA {name}",
7756
- current: "\u76EE\u524D\u7684\u9810\u8A2D\u9023\u7DDA\uFF1A{name}",
7757
- current_none: "\u76EE\u524D\u6C92\u6709\u9810\u8A2D\u9023\u7DDA\u3002\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION\u3002",
7758
- unset: "\u9810\u8A2D\u9023\u7DDA\u5DF2\u6E05\u7A7A\u3002\u57F7\u884C\u8CC7\u6599\u5EAB\u6307\u4EE4\u6642\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION\u3002",
7759
- unset_conflict: "--unset \u4E0D\u53EF\u8207\u9023\u7DDA\u540D\u7A31\u3001--list\u3001--confirm-production \u6216 --format json \u540C\u6642\u4F7F\u7528",
7760
- requires_v2: "\u6B64\u529F\u80FD\u9700\u8981\u65B0\u683C\u5F0F\u8A2D\u5B9A\u3002\u8ACB\u4F7F\u7528 dbcli init --conn-name <\u540D\u7A31> \u5EFA\u7ACB\u591A\u9023\u7DDA\u8A2D\u5B9A",
7761
- available: "\u53EF\u7528\u9023\u7DDA"
7762
- },
7763
- schema: {
7764
- description: "\u6AA2\u7D22\u8868\u683C\u7D50\u69CB\u6216\u5217\u51FA\u6240\u6709\u8868\u683C",
7765
- fetching: "\u6B63\u5728\u6293\u53D6\u6A21\u5F0F...",
7766
- success: "\u6A21\u5F0F\u5DF2\u66F4\u65B0",
7767
- not_found: "\u8CC7\u6599\u5EAB\u4E2D\u627E\u4E0D\u5230\u8868\u683C",
7768
- refresh_prompt: "\u6A21\u5F0F\u6709\u8B8A\u66F4\u3002\u662F\u5426\u5957\u7528\uFF1F (y/n)\uFF1A",
7769
- columns_header: "\u6B04\u4F4D",
7770
- scanning_database: "\uD83D\uDD0D \u6B63\u5728\u6383\u63CF\u8CC7\u6599\u5EAB\u67B6\u69CB...",
7771
- tables_found: "\uD83D\uDCCD \u627E\u5230 {count} \u500B\u8868\u683C\u3002\u6B63\u5728\u7372\u53D6\u67B6\u69CB\u8A73\u60C5...",
7772
- processing_tables: " \u8655\u7406\u4E86 {processed}/{total} \u500B\u8868\u683C",
7773
- schema_exists_warning: "\u26A0\uFE0F \u8CC7\u6599\u5EAB\u67B6\u69CB\u5DF2\u5B58\u5728\u65BC .dbcli",
7774
- use_force_to_override: " \u4F7F\u7528 --force \u9032\u884C\u8986\u84CB\u800C\u7121\u9700\u78BA\u8A8D"
7775
- },
7776
- list: {
7777
- description: "\u5217\u51FA\u8CC7\u6599\u5EAB\u4E2D\u7684\u6240\u6709\u8868\u683C",
7778
- no_tables: "\u8CC7\u6599\u5EAB\u4E2D\u672A\u627E\u5230\u8868\u683C"
7779
- },
7780
- query: {
7781
- description: "\u57F7\u884C SQL \u67E5\u8A62",
7782
- executing: "\u6B63\u5728\u57F7\u884C\u67E5\u8A62...",
7783
- no_results: "\u672A\u8FD4\u56DE\u4EFB\u4F55\u7D50\u679C",
7784
- result_count: "\u7D50\u679C\uFF1A{count} \u5217"
8148
+ // src/core/config.ts
8149
+ import { join as join8 } from "path";
8150
+ import { mkdir as mkdir3 } from "fs/promises";
8151
+ function assertNoConnectionSelectorOnV1(connectionName) {
8152
+ if (connectionName === void 0)
8153
+ return;
8154
+ throw new ConfigError(`\u9023\u7DDA '${connectionName}' \u4E0D\u5B58\u5728\uFF1A\u6B64\u5C08\u6848\u4F7F\u7528\u55AE\u4E00\u9023\u7DDA (v1) \u8A2D\u5B9A\uFF0C\u6C92\u6709\u5177\u540D\u9023\u7DDA\u3002` + " \u79FB\u9664 --use / DBCLI_CONNECTION\uFF0C\u6216\u6539\u7528 'dbcli init --conn-name <name>' \u5347\u7D1A\u70BA\u591A\u9023\u7DDA (v2) \u8A2D\u5B9A\u3002");
8155
+ }
8156
+ function setGlobalConnectionName(name) {
8157
+ _globalConnectionName = name;
8158
+ }
8159
+ function getGlobalConnectionName() {
8160
+ return _globalConnectionName;
8161
+ }
8162
+ async function getSchemaIsolationConnectionName(dbcliPath) {
8163
+ let effectiveName = getGlobalConnectionName();
8164
+ try {
8165
+ let storagePath = await resolveConfigStoragePath(dbcliPath);
8166
+ if (!((await Bun.file(storagePath).stat())?.isDirectory() ?? !1))
8167
+ return;
8168
+ let configJsonPath = join8(storagePath, "config.json"), configFile = Bun.file(configJsonPath);
8169
+ if (!await configFile.exists())
8170
+ return;
8171
+ let raw = JSON.parse(await configFile.text());
8172
+ if (detectConfigVersion(raw) !== 2)
8173
+ return;
8174
+ let v2Config = DbcliConfigV2Schema.parse(raw);
8175
+ return resolveConnection(v2Config, effectiveName).name;
8176
+ } catch {
8177
+ return;
8178
+ }
8179
+ }
8180
+ function isEnvReference(value) {
8181
+ return typeof value === "object" && value !== null && "$env" in value && typeof value.$env === "string";
8182
+ }
8183
+ function resolveEnvReferences(config, env, parentKey) {
8184
+ if (isEnvReference(config)) {
8185
+ let envKey = config.$env, value = resolveEnvRef(config, parentKey ?? "<root>", env);
8186
+ if (parentKey === "port") {
8187
+ let portNum = parseInt(value, 10);
8188
+ if (isNaN(portNum) || portNum < 1 || portNum > 65535)
8189
+ throw new ConfigError(`${envKey} must be a valid port number (1-65535), got: ${value}`);
8190
+ return portNum;
8191
+ }
8192
+ return value;
8193
+ }
8194
+ if (Array.isArray(config))
8195
+ return config.map((item) => resolveEnvReferences(item, env, parentKey));
8196
+ if (typeof config === "object" && config !== null) {
8197
+ let resolved = {};
8198
+ for (let [key, value] of Object.entries(config))
8199
+ resolved[key] = resolveEnvReferences(value, env, key);
8200
+ return resolved;
8201
+ }
8202
+ return config;
8203
+ }
8204
+ function parseEnvPassword(content) {
8205
+ let match = content.match(/^DBCLI_PASSWORD=(.+)$/m);
8206
+ if (match?.[1] == null)
8207
+ return null;
8208
+ let raw = match[1].trim();
8209
+ return (raw.startsWith('"') && raw.endsWith('"') || raw.startsWith("'") && raw.endsWith("'")) && raw.length >= 2 ? raw.slice(1, -1) : raw;
8210
+ }
8211
+ var _globalConnectionName, DEFAULT_CONFIG, configModule;
8212
+ var init_config = __esm(() => {
8213
+ init_validation();
8214
+ init_errors3();
8215
+ init_config_error_format();
8216
+ init_config_v2();
8217
+ init_config_binding();
8218
+ init_public();
8219
+ init_config_mutation_guard();
8220
+ init_config_integrity();
8221
+ DEFAULT_CONFIG = {
8222
+ connection: {
8223
+ system: "postgresql",
8224
+ host: "localhost",
8225
+ port: 5432,
8226
+ user: "",
8227
+ password: "",
8228
+ database: ""
7785
8229
  },
7786
- q: {
7787
- description: "\u4F9D @name \u57F7\u884C\u5DF2\u4FDD\u5B58\u7684\u67E5\u8A62\u7247\u6BB5",
7788
- missing_param: "\u7247\u6BB5 '{name}' \u7F3A\u5C11\u5FC5\u8981\u53C3\u6578",
7789
- engine_mismatch: "\u7247\u6BB5 '{name}' \u5F15\u64CE\u4E0D\u7B26",
7790
- snippet_not_found: "\u627E\u4E0D\u5230\u7247\u6BB5\uFF1A{name}",
7791
- dry_run_header: "Dry-run \u9810\u89BD\uFF08\u4E0D\u6703\u57F7\u884C\uFF09\uFF1A"
8230
+ permission: "query-only",
8231
+ schema: {},
8232
+ metadata: {
8233
+ version: "1.0"
7792
8234
  },
8235
+ blacklist: { tables: [], columns: {} },
7793
8236
  audit: {
7794
- description: "\u6AA2\u8996\u3001\u67E5\u8A62\u3001\u6E05\u7A7A\u6216\u6AA2\u67E5 audit log \u5065\u5EB7\u72C0\u614B",
7795
- tail: {
7796
- description: "\u986F\u793A\u6700\u8FD1\u7684 audit \u7D00\u9304\uFF08\u9810\u8A2D\uFF1A\u7576\u524D\u9023\u7DDA\u6700\u5F8C 10 \u7B46\uFF09"
7797
- },
7798
- show: {
7799
- description: "\u4F9D id\uFF08UUID \u524D\u7DB4 \u22654 \u78BC\uFF09\u6216 --recovery-ref \u67E5\u8A62\u55AE\u7B46 audit \u7D00\u9304"
7800
- },
7801
- clear: {
7802
- description: "\u522A\u9664\u7576\u524D\u9023\u7DDA\u7684 audit log \u6A94\u6848\uFF08.jsonl \u8207 .jsonl.1\uFF09",
7803
- prompt_header: "\u5373\u5C07\u6E05\u7A7A\u9023\u7DDA '{conn}' \u7684 audit log\uFF1A",
7804
- prompt_file_line: " {file} \u2014 {entries} \u7B46\uFF0C{size}",
7805
- prompt_continue: "\u78BA\u8A8D\u57F7\u884C\uFF1F[y/N] ",
7806
- requires_tty_or_yes: "\u975E\u4E92\u52D5 session \u7121\u6CD5\u986F\u793A\u78BA\u8A8D\u63D0\u793A\u3002\u8ACB\u4F7F\u7528 --yes \u76F4\u63A5\u6E05\u7A7A\u3002",
7807
- summary_cleared: "\u5DF2\u5F9E '{conn}' \u6E05\u9664 {count} \u7B46 audit \u7D00\u9304\u3002",
7808
- summary_nothing: "\u6C92\u6709\u53EF\u6E05\u9664\u7684\u5167\u5BB9\u3002",
7809
- summary_failed: "\u6E05\u9664\u5931\u6557\uFF1A{message}\u3002"
7810
- },
7811
- health: {
7812
- description: "\u986F\u793A AuditLogger \u5065\u5EB7\u72C0\u614B\uFF08\u6A94\u6848\u5927\u5C0F\u3001\u7B46\u6578\u3001lock \u72C0\u614B\u3001\u6700\u5F8C\u5BEB\u5165\u7D50\u679C\uFF09"
7813
- },
7814
- write_gate: {
7815
- description: "\u7D71\u8A08\u7B2C\u4E8C\u7D1A\u5BEB\u5165\u9598\u9580\u7684\u5224\u5B9A\uFF08ADR 0010 \u62BC\u6CE8\u7684\u90A3\u500B\u91CF\u6E2C\uFF09",
7816
- header: "\u5BEB\u5165\u9598\u9580\u6458\u8981 \u2014 \u9023\u7DDA\uFF1A{conn}",
7817
- window: "\u6383\u904E\u7684\u7A3D\u6838\u7D00\u9304\uFF1A{scanned} \u7B46\uFF0C{from} \u2192 {to}",
7818
- decisions: "\u7B2C\u4E8C\u7D1A\u5224\u5B9A\uFF1A{count} \u6B21\uFF0C{from} \u2192 {to}",
7819
- tier_one: "\u7B2C\u4E00\u7D1A\uFF0C\u5206\u958B\u8A08\u7B97\uFF1A{count} \u6B21\uFF08{outcomes}\uFF09",
7820
- no_decisions: "\u9019\u6BB5\u5340\u9593\u5167\u7B2C\u4E8C\u7D1A\u4E00\u6B21\u90FD\u6C92\u88AB\u89F8\u53CA\u3002\u4F9D ADR 0010\uFF0C\u9019\u63A8\u7FFB\u7684\u662F\u5224\u6E96\u800C\u4E0D\u662F\u9598\u9580\uFF1A\u5B83\u5728\u627E\u7684\u5F62\u72C0\u4E0D\u662F\u5927\u5BB6\u5BE6\u969B\u6703\u5BEB\u7684\u5F62\u72C0\u3002",
7821
- no_entries: "\u7A3D\u6838\u7D00\u9304\u662F\u7A7A\u7684\uFF0C\u6240\u4EE5\u9019\u5C0D\u9598\u9580\u6C92\u6709\u4EFB\u4F55\u7D50\u8AD6\u3002\u5148\u8DD1\u4E00\u4E9B\u5BEB\u5165\uFF0C\u6216\u7528 `dbcli audit health` \u6AA2\u67E5\u3002"
7822
- },
7823
- disabled_hint: "Audit \u5DF2\u505C\u7528\uFF08.dbcli \u4E2D audit.enabled = false\uFF09\u3002\u57F7\u884C `dbcli audit health` \u67E5\u770B\u8A73\u60C5\u3002",
7824
- no_entries: "\u6C92\u6709 audit \u7D00\u9304\u3002",
7825
- show_no_match: "\u627E\u4E0D\u5230\u7B26\u5408 '{prefix}' \u7684 audit \u7D00\u9304\u3002",
7826
- show_ambiguous: "\u524D\u7DB4 '{prefix}' \u4E0D\u5920\u7CBE\u78BA\uFF1A\u5339\u914D\u5230 {count} \u7B46\u7D00\u9304\uFF0C\u8ACB\u6539\u7528\u66F4\u9577\u7684\u524D\u7DB4\u3002",
7827
- show_prefix_too_short: "\u524D\u7DB4\u81F3\u5C11\u9700 4 \u500B\u5B57\u5143\u3002",
7828
- show_recovery_no_match: "\u6C92\u6709\u4EFB\u4F55 audit \u7D00\u9304\u7684 recovery_ref \u70BA '{ref}'\u3002",
7829
- show_recovery_ambiguous: "\u6709 {count} \u7B46\u7D00\u9304\u7684 recovery_ref \u70BA '{ref}'\uFF0C\u9019\u61C9\u5C6C\u7F55\u898B\u60C5\u6CC1\uFF1B\u8ACB\u76F4\u63A5\u6AA2\u8996\u6A94\u6848\u3002",
7830
- show_mutex_violation: "\u8ACB\u64C7\u4E00\u63D0\u4F9B <id> \u53C3\u6578\u6216 --recovery-ref\uFF0C\u5169\u8005\u4E0D\u53EF\u540C\u6642\u4F7F\u7528\u3002",
7831
- n_capped_warning: "--n \u503C {requested} \u8D85\u904E\u4E0A\u9650 {max}\uFF0C\u5DF2\u81EA\u52D5\u7E2E\u6E1B\u70BA {max}\u3002",
7832
- n_must_be_positive: "--n \u5FC5\u9808\u70BA\u6B63\u6574\u6578\u3002"
7833
- },
7834
- queries: {
7835
- description: "\u7BA1\u7406\u5DF2\u4FDD\u5B58\u7684\u67E5\u8A62\u7247\u6BB5",
7836
- list_description: "\u5217\u51FA\u6240\u6709\u7247\u6BB5",
7837
- show_description: "\u986F\u793A\u7247\u6BB5\u5167\u5BB9\uFF08frontmatter + SQL\uFF09",
7838
- new_description: "\u5EFA\u7ACB\u65B0\u7684\u7247\u6BB5\u6A94",
7839
- edit_description: "\u4EE5 $EDITOR \u958B\u555F\u7247\u6BB5",
7840
- check_description: "\u9A57\u8B49\u6240\u6709\u7247\u6BB5",
7841
- delete_description: "\u522A\u9664 local snippet \u6A94\u6848",
7842
- rename_description: "\u91CD\u65B0\u547D\u540D local snippet",
7843
- copy_description: "\u5C07\u4EFB\u4E00\u5C64\u7D1A\u7684 snippet \u8907\u88FD\u5230 local",
7844
- import_description: "\u532F\u5165\u5916\u90E8 .sql \u6A94\u6848\u5230 local",
7845
- export_description: "\u5C07 snippet \u539F\u59CB SQL \u8F38\u51FA\u5230 stdout \u6216\u6A94\u6848",
7846
- created: "\u5DF2\u5EFA\u7ACB {file}",
7847
- first_run_hint: "\u63D0\u793A\uFF1A\u5C07 .dbcli-shared/ commit \u9032\u7248\u63A7\u53EF\u8207\u5718\u968A\u5171\u4EAB\u7247\u6BB5\u3002",
7848
- no_snippets: "\u5C1A\u7121\u4EFB\u4F55\u7247\u6BB5\u3002\u57F7\u884C dbcli queries new @<name> \u5EFA\u7ACB\u3002",
7849
- check_ok: "\u2713 \u6210\u529F\u89E3\u6790 {count} \u500B\u7247\u6BB5",
7850
- check_failed: "\u2717 \u6709 {count} \u500B\u7247\u6BB5\u89E3\u6790\u5931\u6557",
7851
- search_description: "\u4F9D\u95DC\u9375\u5B57\u641C\u5C0B snippet\uFF08fuzzy \u6392\u5E8F\uFF09",
7852
- suggest_description: "\u4F9D intent prefix \u63A8\u85A6 snippet",
7853
- search_no_keywords: "\u81F3\u5C11\u63D0\u4F9B\u4E00\u500B\u95DC\u9375\u5B57\uFF0C\u6216\u8A66\u8A66 'queries list' / 'queries suggest'\u3002",
7854
- search_no_results: "\u6C92\u6709\u547D\u4E2D\u7684 snippet\u3002\u8A66\u8A66 'queries list' \u6216 'queries suggest <intent>'\u3002",
7855
- suggest_missing_intent: "\u8ACB\u63D0\u4F9B intent\uFF08\u6216 prefix\uFF09\u3002\u53EF\u7528\uFF1A{available}",
7856
- suggest_invalid_intent: "Intent '{value}' \u683C\u5F0F\u932F\u8AA4\uFF0C\u5FC5\u9808\u7B26\u5408 ^[a-z][a-z0-9.-]*$",
7857
- suggest_no_results: "\u6C92\u6709 intent prefix \u70BA '{intent}' \u7684 snippet\u3002\u53EF\u7528\uFF1A{available}",
7858
- no_active_connection_hint: "\u5C1A\u672A\u8A2D\u5B9A connection\uFF1B\u5217\u51FA\u6240\u6709\u5F15\u64CE\u7684 snippet\u3002"
7859
- },
7860
- errors: {
7861
- message: "\u932F\u8AA4\uFF1A{message}",
7862
- query_only_boundary_failed: "\u7121\u6CD5\u70BA {system} \u5EFA\u7ACB query-only \u8CC7\u6599\u5EAB\u908A\u754C\uFF1A{detail}",
7863
- query_only_boundary_verify: "\u8ACB\u78BA\u8A8D {system} \u652F\u63F4\u552F\u8B80\u4EA4\u6613\uFF0C\u4E14\u6B64\u5E33\u865F\u53EF\u4EE5\u555F\u52D5\u4EA4\u6613",
7864
- query_only_boundary_not_executed: "\u76EE\u6A19\u8A9E\u53E5\u5C1A\u672A\u57F7\u884C\uFF1B\u8ACB\u4FEE\u6B63\u8CC7\u6599\u5EAB\u8A2D\u5B9A\u6216\u9023\u7DDA\u5F8C\u518D\u8A66",
7865
- query_only_cleanup_completed: "query-only \u76EE\u6A19\u5DF2\u5B8C\u6210\uFF0C\u4F46 {system} \u7684\u4EA4\u6613\u6E05\u7406\u5931\u6557\uFF1A{detail}",
7866
- query_only_cleanup_uncertain: "query-only \u76EE\u6A19\u5931\u6557\uFF0C\u4E14 {system} \u7684\u4EA4\u6613\u6E05\u7406\u4E5F\u5931\u6557\uFF0C\u56E0\u6B64\u7D50\u679C\u4E0D\u78BA\u5B9A\uFF1A{detail}",
7867
- query_only_cleanup_completed_retry: "\u76EE\u6A19\u8A9E\u53E5\u5DF2\u57F7\u884C\uFF1B\u9664\u975E\u53EF\u5B89\u5168\u91CD\u8907\u5176\u5916\u90E8\u526F\u4F5C\u7528\uFF0C\u5426\u5247\u4E0D\u8981\u91CD\u8A66",
7868
- query_only_cleanup_uncertain_retry: "\u76EE\u6A19\u8A9E\u53E5\u53EF\u80FD\u5728\u5931\u6557\u524D\u7522\u751F\u526F\u4F5C\u7528\uFF1B\u9664\u975E\u53EF\u5B89\u5168\u91CD\u8907\u9019\u4E9B\u526F\u4F5C\u7528\uFF0C\u5426\u5247\u4E0D\u8981\u91CD\u8A66",
7869
- query_only_cleanup_reconnect: "\u6B64\u9023\u7DDA\u5DF2\u4E1F\u68C4\uFF1B\u57F7\u884C\u4E0B\u4E00\u53E5\u524D\u8ACB\u5148\u91CD\u65B0\u9023\u7DDA",
7870
- invalid_config: "\u914D\u7F6E\u7121\u6548\uFF1A{field}",
7871
- connection_failed: "\u7121\u6CD5\u9023\u63A5\u5230\u8CC7\u6599\u5EAB\uFF1A{message}",
7872
- permission_denied: "\u6B0A\u9650\u88AB\u62D2\uFF08\u9700\u8981\uFF1A{required}\uFF09",
7873
- permission_requires_level: "{type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
7874
- elasticsearch_requires_level: "Elasticsearch {type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
7875
- 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",
7876
- 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",
7877
- 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",
7878
- permission_denied_reason: "\u6B0A\u9650\u88AB\u62D2\uFF1A{reason}",
7879
- table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
7880
- unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
7881
- invalid_json: "\u7121\u6548\u7684 JSON\uFF1A{message}",
7882
- table_blacklisted: "\u932F\u8AA4: \u8868\u683C '{table}' \u7684 {operation} \u64CD\u4F5C\u5DF2\u88AB\u9ED1\u540D\u55AE",
7883
- redis_target_blacklisted: "Redis \u6307\u4EE4\u5DF2\u88AB\u9ED1\u540D\u55AE\u62D2\u7D55",
7884
- column_blacklisted_write: "\u932F\u8AA4: \u8868\u683C '{table}' \u7684 {operation} \u64CD\u4F5C\u5305\u542B\u9ED1\u540D\u55AE\u6B04\u4F4D: {columns}",
7885
- table_already_blacklisted: "\u932F\u8AA4: \u8868\u683C '{table}' \u5DF2\u5728\u9ED1\u540D\u55AE\u4E2D",
7886
- table_not_in_blacklist: "\u932F\u8AA4: \u8868\u683C '{table}' \u4E0D\u5728\u9ED1\u540D\u55AE\u4E2D",
7887
- column_already_blacklisted: "\u932F\u8AA4: \u6B04\u4F4D '{table}.{column}' \u5DF2\u5728\u9ED1\u540D\u55AE\u4E2D",
7888
- column_not_in_blacklist: "\u932F\u8AA4: \u6B04\u4F4D '{table}.{column}' \u4E0D\u5728\u9ED1\u540D\u55AE\u4E2D",
7889
- invalid_table_name: "\u932F\u8AA4: \u7121\u6548\u7684\u8868\u683C\u540D\u7A31: {table}",
7890
- invalid_column_format: "\u932F\u8AA4: \u7121\u6548\u7684\u6B04\u4F4D\u683C\u5F0F\u3002\u4F7F\u7528 'table.column'",
7891
- invalid_system: "\u7121\u6548\u7684\u8CC7\u6599\u5EAB\u7CFB\u7D71\uFF1A{system}",
7892
- invalid_permission: "\u7121\u6548\u7684\u6B0A\u9650\u7B49\u7D1A\uFF1A{permission}",
7893
- invalid_port: "\u7121\u6548\u7684\u57E0\u865F\uFF1A{port}",
7894
- require_user: "\u975E\u4E92\u52D5\u6A21\u5F0F\u9700\u8981 --user \u9078\u9805",
7895
- require_name: "\u975E\u4E92\u52D5\u6A21\u5F0F\u9700\u8981 --name \u9078\u9805",
7896
- env_refs_missing_options: `\u4F7F\u7528 --use-env-refs \u6642\uFF0C\u5FC5\u9808\u6307\u5B9A\u74B0\u5883\u8B8A\u6578\u540D\u7A31\u3002
7897
- \u8ACB\u63D0\u4F9B\u9078\u9805\uFF1A--env-host\u3001--env-port\u3001--env-user\u3001--env-password\u3001--env-database
7898
- \u6216\u4F7F\u7528\u4E92\u52D5\u6A21\u5F0F\uFF1A\u57F7\u884C "dbcli init --use-env-refs"\uFF08\u4E0D\u52A0 --no-interactive\uFF09`,
7899
- env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
7900
- \u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
7901
- \u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`,
7902
- invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json",
7903
- 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"
7904
- },
7905
- success: {
7906
- inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
7907
- updated: "\u6210\u529F\u66F4\u65B0 {count} \u5217",
7908
- deleted: "\u6210\u529F\u522A\u9664 {count} \u5217",
7909
- no_changes: "\u6C92\u6709\u8B8A\u66F4\u8981\u5957\u7528"
7910
- },
7911
- insert: {
7912
- description: "\u63D2\u5165\u8CC7\u6599\u5230\u8868\u683C",
7913
- prompt_data: "\u8F38\u5165\u8981\u63D2\u5165\u7684 JSON \u8CC7\u6599\uFF1A",
7914
- confirm: "\u5C07 {count} \u5217\u63D2\u5165\u5230 {table}\uFF1F (y/n)\uFF1A",
7915
- elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 insert \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u5BEB\u5165"
7916
- },
7917
- update: {
7918
- description: "\u66F4\u65B0\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
7919
- prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
7920
- prompt_set: '\u8F38\u5165 JSON \u66F4\u65B0 (\u4F8B\u5982\uFF1A{"status": "active"})\uFF1A',
7921
- confirm: "\u66F4\u65B0 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
7922
- elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 update \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u66F4\u65B0"
7923
- },
7924
- delete: {
7925
- description: "\u522A\u9664\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
7926
- prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
7927
- confirm: "\u522A\u9664 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
7928
- admin_only: "\u522A\u9664\u547D\u4EE4\u9700\u8981 data-admin \u6216 admin \u6B0A\u9650",
7929
- elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 delete \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u522A\u9664"
7930
- },
7931
- export: {
7932
- description: "\u532F\u51FA\u67E5\u8A62\u7D50\u679C",
7933
- formats: "\u652F\u63F4\u7684\u683C\u5F0F\uFF1Ajson\u3001csv",
7934
- exported: "\u5DF2\u5C07 {count} \u5217\u532F\u51FA\u81F3 {file}"
7935
- },
7936
- upgrade: {
7937
- description: "\u6AA2\u67E5\u66F4\u65B0\u4E26\u5C07 dbcli \u5347\u7D1A\u81F3\u6700\u65B0\u7248\u672C",
7938
- checking: "\u6B63\u5728\u6AA2\u67E5\u66F4\u65B0...",
7939
- already_up_to_date: "\u2713 \u5DF2\u7D93\u662F\u6700\u65B0\u7248\u672C (v{version})",
7940
- new_version_available: "\u767C\u73FE\u65B0\u7248\u672C\uFF1Av{version}",
7941
- current_version: "\u76EE\u524D\u7248\u672C\uFF1Av{version}",
7942
- latest_version: "\u6700\u65B0\u7248\u672C\uFF1Av{version}",
7943
- update_hint: '[\u63D0\u793A] dbcli v{version} \u5DF2\u767C\u5E03\u3002\u57F7\u884C "dbcli upgrade" \u9032\u884C\u5347\u7D1A\u3002',
7944
- install_hint: '\u57F7\u884C "dbcli upgrade" \u5B89\u88DD\u66F4\u65B0\u3002',
7945
- upgrading: "\u6B63\u5728\u5347\u7D1A...",
7946
- success: "\u2713 \u6210\u529F\u5347\u7D1A\u81F3 v{version}",
7947
- skill_recheck_hint: '\u63D0\u793A\uFF1A\u82E5\u5DF2\u5B89\u88DD AI \u6280\u80FD\uFF0C\u8ACB\u57F7\u884C "dbcli skill --install <platform>" \u78BA\u8A8D\u6280\u80FD\u70BA\u6700\u65B0\u7248\u672C\u3002',
7948
- failed: "\u2717 \u5347\u7D1A\u5931\u6557\u3002\u8ACB\u5617\u8A66\u624B\u52D5\u57F7\u884C\uFF1A",
7949
- manual_hint: "bun add -g @carllee1983/dbcli@latest",
7950
- network_error: "\u26A0 \u7121\u6CD5\u6AA2\u67E5\u66F4\u65B0\uFF08\u7DB2\u8DEF\u932F\u8AA4\u6216\u8A3B\u518A\u8868\u4E0D\u53EF\u7528\uFF09"
7951
- },
7952
- skill: {
7953
- description: "\u751F\u6210 AI \u6280\u80FD\u6587\u6A94",
7954
- context_description: "\u751F\u6210 AI \u5354\u4F5C\u6240\u9700\u7684\u8CC7\u6599\u5EAB\u67B6\u69CB\u8207\u67E5\u8A62\u7247\u6BB5\u4E0A\u4E0B\u6587",
7955
- installed: `\u6280\u80FD: {path}
7956
- \u53C3\u8003: {referencePath}`,
7957
- update_available: "\u4EE5\u4E0B\u5E73\u53F0\u7684\u6280\u80FD\u9700\u8981\u66F4\u65B0\uFF1A",
7958
- update_hint: '\u57F7\u884C "dbcli skill --install <platform>" \u9032\u884C\u66F4\u65B0\u3002'
7959
- },
7960
- blacklist: {
7961
- description: "\u7BA1\u7406\u654F\u611F\u8CC7\u6599\u9ED1\u540D\u55AE\u4EE5\u9632\u6B62 AI \u5B58\u53D6",
7962
- list_title: "\u76EE\u524D\u9ED1\u540D\u55AE\u914D\u7F6E",
7963
- tables_label: "\u5DF2\u9ED1\u540D\u55AE\u7684\u8868\u683C",
7964
- columns_label: "\u5DF2\u9ED1\u540D\u55AE\u7684\u6B04\u4F4D",
7965
- none: "\u76EE\u524D\u6C92\u6709\u9ED1\u540D\u55AE\u8868\u683C\u6216\u6B04\u4F4D",
7966
- table_added: "\u2713 \u8868\u683C '{table}' \u5DF2\u65B0\u589E\u81F3\u9ED1\u540D\u55AE",
7967
- table_removed: "\u2713 \u8868\u683C '{table}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664",
7968
- column_added: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u65B0\u589E\u81F3\u9ED1\u540D\u55AE",
7969
- column_removed: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664",
7970
- refuse_wildcard: "\u5DF2\u62D2\u7D55\uFF1A'{table}' \u542B\u6709\u842C\u7528\u5B57\u5143\uFF0C\u800C {system} \u4E0A\u7684\u6BD4\u5C0D\u662F\u9010\u5B57\u9032\u884C\u7684\u2014\u2014\u9019\u7B46\u8A2D\u5B9A\u6C38\u9060\u4E0D\u6703\u547D\u4E2D\u4EFB\u4F55\u6771\u897F\u3002\u8ACB\u5BEB\u51FA\u78BA\u5207\u7684\u540D\u7A31\u3002"
7971
- },
7972
- security: {
7973
- columns_omitted: "\u5B89\u5168: {count} \u500B\u6B04\u4F4D\u6839\u64DA\u4F60\u7684\u9ED1\u540D\u55AE\u5DF2\u88AB\u96B1\u85CF"
7974
- },
7975
- warnings: {
7976
- blacklist_override_used: "\u8B66\u544A: \u5DF2\u555F\u7528\u9ED1\u540D\u55AE\u8986\u84CB (DBCLI_OVERRIDE_BLACKLIST=true)\u3002\u57F7\u884C {operation} \u5728\u5DF2\u9ED1\u540D\u55AE\u7684\u8868\u683C '{table}' \u4E0A",
7977
- redis_blacklist_override_active: "\u8B66\u544A: DBCLI_OVERRIDE_BLACKLIST=true \u5DF2\u505C\u7528 Redis \u9ED1\u540D\u55AE\u5F37\u5236\u57F7\u884C"
8237
+ enabled: !0,
8238
+ strict: !1,
8239
+ rotation: { ...DEFAULT_AUDIT_ROTATION }
8240
+ }
8241
+ };
8242
+ configModule = {
8243
+ async read(path, connectionName, options = {}) {
8244
+ let effectiveConnectionName = connectionName ?? _globalConnectionName, rawForDiagnostics;
8245
+ try {
8246
+ let binding = await readProjectBinding(path), storagePath = await resolveConfigStoragePath(path);
8247
+ if (binding) {
8248
+ if (!await Bun.file(join8(storagePath, "config.json")).exists())
8249
+ throw new ConfigError(`Bound dbcli config not found: ${join8(storagePath, "config.json")}`);
8250
+ }
8251
+ let isDirectory = !1;
8252
+ try {
8253
+ isDirectory = (await Bun.file(storagePath).stat())?.isDirectory() ?? !1;
8254
+ } catch {
8255
+ isDirectory = !1;
8256
+ }
8257
+ if (isDirectory) {
8258
+ let configPath = join8(storagePath, "config.json"), configFile = Bun.file(configPath);
8259
+ if (await configFile.exists()) {
8260
+ await assertAgentReadableFile(configPath);
8261
+ let content = await configFile.text();
8262
+ await assertConfigIntegrity(storagePath, content, { requireRecord: !0 });
8263
+ let config = JSON.parse(content);
8264
+ if (rawForDiagnostics = config, detectConfigVersion(config) === 2) {
8265
+ let v2Config = DbcliConfigV2Schema.parse(config), resolved = resolveConnection(v2Config, effectiveConnectionName);
8266
+ assertExplicitProductionSelection(resolved, effectiveConnectionName), await loadConnectionEnv(resolved, storagePath);
8267
+ let envLocalPath = join8(storagePath, ".env.local"), envLocalFile = Bun.file(envLocalPath), legacyPassword = null;
8268
+ if (await envLocalFile.exists()) {
8269
+ let envContent = await envLocalFile.text();
8270
+ if (legacyPassword = parseEnvPassword(envContent), legacyPassword && !process.env.DBCLI_PASSWORD)
8271
+ process.env.DBCLI_PASSWORD = legacyPassword;
8272
+ }
8273
+ let resolvedConnection = resolveEnvReferences(resolved.connection, process.env);
8274
+ if (!resolvedConnection.password && legacyPassword)
8275
+ resolvedConnection.password = legacyPassword;
8276
+ let schema = (v2Config.schemas ?? {})[resolved.name] ?? v2Config.schema;
8277
+ if (options.loadLayeredSchema !== !1)
8278
+ try {
8279
+ await Promise.resolve().then(() => init_schema_loader());
8280
+ let loader = new SchemaLayeredLoader(storagePath, {
8281
+ connectionName: resolved.name
8282
+ }), { cache, index } = await loader.initialize();
8283
+ if (index && Object.keys(index.tables).length > 0) {
8284
+ let layeredSchema = {};
8285
+ for (let tableName of Object.keys(index.tables)) {
8286
+ let s = await cache.getTableSchema(tableName);
8287
+ if (s)
8288
+ layeredSchema[tableName] = s;
8289
+ }
8290
+ if (Object.keys(layeredSchema).length > 0)
8291
+ schema = layeredSchema;
8292
+ }
8293
+ } catch {
8294
+ console.warn("Warning: Failed to load layered schema cache, falling back to config.json");
8295
+ }
8296
+ return {
8297
+ ...DbcliConfigSchema.parse({
8298
+ connection: resolvedConnection,
8299
+ permission: resolved.permission,
8300
+ schema,
8301
+ metadata: v2Config.metadata,
8302
+ blacklist: v2Config.blacklist,
8303
+ audit: v2Config.audit,
8304
+ redis: v2Config.redis
8305
+ }),
8306
+ effectiveConnectionName: resolved.name,
8307
+ ...resolved.environment && { effectiveEnvironment: resolved.environment }
8308
+ };
8309
+ }
8310
+ if (assertNoConnectionSelectorOnV1(effectiveConnectionName), config.connection?.sqlTls !== void 0)
8311
+ ConnectionConfigSchema.parse(config.connection);
8312
+ let resolvedConfig = resolveEnvReferences(config, process.env), envPath = join8(storagePath, ".env.local"), envFile = Bun.file(envPath);
8313
+ if (await envFile.exists()) {
8314
+ let envContent = await envFile.text(), password = parseEnvPassword(envContent);
8315
+ if (password && !resolvedConfig.connection.password)
8316
+ resolvedConfig.connection.password = password;
8317
+ }
8318
+ return DbcliConfigSchema.parse(resolvedConfig);
8319
+ }
8320
+ }
8321
+ let file = Bun.file(path);
8322
+ if (await file.exists()) {
8323
+ if (process.env.DBCLI_AGENT_MODE === "1")
8324
+ throw new ConfigError(`Agent mode refuses legacy single-file config: ${path}. Run the human/admin migration workflow to move it to V2 home storage first.`);
8325
+ assertNoConnectionSelectorOnV1(effectiveConnectionName);
8326
+ let content = await file.text(), raw = JSON.parse(content);
8327
+ if (rawForDiagnostics = raw, raw.connection?.sqlTls !== void 0)
8328
+ ConnectionConfigSchema.parse(raw.connection);
8329
+ let resolved = resolveEnvReferences(raw, process.env);
8330
+ return DbcliConfigSchema.parse(resolved);
8331
+ }
8332
+ if (process.env.DBCLI_AGENT_MODE === "1")
8333
+ throw new ConfigError(`Agent mode refuses a missing config: ${path}. Run the human/admin setup workflow to provision it.`);
8334
+ return { ...DEFAULT_CONFIG };
8335
+ } catch (error) {
8336
+ if (error instanceof ConfigError)
8337
+ throw error;
8338
+ if (isZodError(error))
8339
+ throw new ConfigError(`Failed to read .dbcli config: \u8A2D\u5B9A\u5167\u5BB9\u4E0D\u7B26\u5408\u7D50\u69CB
8340
+ ${formatConfigValidationError(error, rawForDiagnostics)}`);
8341
+ if (error instanceof Error && error.message.includes("JSON"))
8342
+ throw new ConfigError(`Failed to parse .dbcli file: ${error.message}`);
8343
+ throw new ConfigError(`Failed to read .dbcli config: ${error instanceof Error ? error.message : String(error)}`);
8344
+ }
7978
8345
  },
7979
- version: {
7980
- unsupported_warning: "{system} {version} \u4F4E\u65BC\u6700\u4F4E\u652F\u63F4\u7248\u672C {minVersion}\u3002\u90E8\u5206\u529F\u80FD\u53EF\u80FD\u7121\u6CD5\u6B63\u5E38\u904B\u4F5C\u3002",
7981
- doctor_pass: "{system} {version}\uFF08\u7B26\u5408 >= {minVersion}\uFF09",
7982
- doctor_warn: "{system} {version} \u4F4E\u65BC\u652F\u63F4\u7248\u672C >= {minVersion}"
8346
+ validate(raw) {
8347
+ try {
8348
+ return DbcliConfigSchema.parse(raw);
8349
+ } catch (error) {
8350
+ if (isZodError(error))
8351
+ throw new ConfigError(`Invalid .dbcli config structure:
8352
+ ${formatConfigValidationError(error, raw)}`);
8353
+ let errorMessage = error instanceof Error ? error.message : String(error);
8354
+ throw new ConfigError(`Invalid .dbcli config structure: ${errorMessage}`);
8355
+ }
7983
8356
  },
7984
- migrate: {
7985
- description: "Schema DDL \u64CD\u4F5C\uFF08CREATE/DROP/ALTER TABLE\u3001INDEX\u3001CONSTRAINT\u3001ENUM\uFF09",
7986
- dry_run_preview: "[\u9810\u89BD] \u7522\u751F\u7684 SQL\uFF08\u672A\u57F7\u884C\uFF09\uFF1A",
7987
- executing: "\u57F7\u884C DDL \u4E2D...",
7988
- success: "DDL \u57F7\u884C\u6210\u529F",
7989
- confirm_drop: "\u9019\u662F\u7834\u58DE\u6027\u64CD\u4F5C\uFF08{operation}\uFF09\uFF0C\u78BA\u5B9A\u7E7C\u7E8C\uFF1F",
7990
- permission_required: "DDL \u64CD\u4F5C\u9700\u8981 admin \u6B0A\u9650\uFF08\u76EE\u524D\uFF1A{permission}\uFF09",
7991
- blacklist_blocked: '\u8868\u683C "{table}" \u5DF2\u88AB\u5217\u5165\u9ED1\u540D\u55AE \u2014 DDL \u64CD\u4F5C\u88AB\u963B\u64CB',
7992
- schema_refreshed: '\u5DF2\u66F4\u65B0\u8868\u683C "{table}" \u7684 Schema \u5FEB\u53D6',
7993
- no_sql_generated: "\u672A\u7522\u751F SQL\uFF08\u6B64\u64CD\u4F5C\u4E0D\u9069\u7528\u65BC\u6B64\u8CC7\u6599\u5EAB\uFF09",
7994
- create_description: "\u5EFA\u7ACB\u65B0\u8868\u683C",
7995
- drop_description: "\u522A\u9664\u8868\u683C",
7996
- add_column_description: "\u65B0\u589E\u6B04\u4F4D\u81F3\u8868\u683C",
7997
- drop_column_description: "\u522A\u9664\u8868\u683C\u6B04\u4F4D",
7998
- alter_column_description: "\u4FEE\u6539\u8868\u683C\u6B04\u4F4D",
7999
- add_index_description: "\u65B0\u589E\u7D22\u5F15\u81F3\u8868\u683C",
8000
- drop_index_description: "\u522A\u9664\u7D22\u5F15",
8001
- add_constraint_description: "\u65B0\u589E\u7D04\u675F\u81F3\u8868\u683C",
8002
- drop_constraint_description: "\u522A\u9664\u8868\u683C\u7D04\u675F",
8003
- add_enum_description: "\u5EFA\u7ACB\u5217\u8209\u578B\u5225\uFF08\u50C5 PostgreSQL\uFF09",
8004
- alter_enum_description: "\u65B0\u589E\u5217\u8209\u503C",
8005
- drop_enum_description: "\u522A\u9664\u5217\u8209\u578B\u5225"
8357
+ merge(existing, updates) {
8358
+ return {
8359
+ ...existing,
8360
+ ...updates,
8361
+ connection: {
8362
+ ...existing.connection,
8363
+ ...updates.connection || {}
8364
+ },
8365
+ schema: {
8366
+ ...existing.schema,
8367
+ ...updates.schema || {}
8368
+ },
8369
+ metadata: {
8370
+ ...existing.metadata,
8371
+ ...updates.metadata || {},
8372
+ createdAt: existing.metadata?.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
8373
+ version: existing.metadata?.version || "1.0"
8374
+ }
8375
+ };
8006
8376
  },
8007
- password: {
8008
- description: "\u53EA\u8B8A\u66F4\u55AE\u4E00\u9023\u7DDA\u7684\u5BC6\u78BC\uFF0C\u5176\u9918\u8A2D\u5B9A\u539F\u5C01\u4E0D\u52D5",
8009
- arg_connection: "\u9023\u7DDA\u540D\u7A31\uFF08\u7701\u7565\u6642\u6539\u9810\u8A2D\u9023\u7DDA\uFF09",
8010
- opt_stdin: "\u5F9E stdin \u8B80\u53D6\u65B0\u5BC6\u78BC\uFF08\u4F9B\u8F2A\u66FF\u8173\u672C\u4F7F\u7528\uFF09",
8011
- opt_password: "\u76F4\u63A5\u5E36\u5165\u65B0\u5BC6\u78BC\uFF08\u6703\u7559\u5728 shell \u6B77\u53F2\u8207\u884C\u7A0B\u5217\u8868\uFF09",
8012
- opt_skip_test: "\u5B58\u6A94\u524D\u4E0D\u5148\u7528\u65B0\u5BC6\u78BC\u9023\u7DDA\u9A57\u8B49",
8013
- prompt: "\u9023\u7DDA '{name}' \u7684\u65B0\u5BC6\u78BC",
8014
- source_conflict: "--password \u8207 --stdin \u53EA\u80FD\u64C7\u4E00\u3002",
8015
- needs_source: "\u6C92\u6709\u7D42\u7AEF\u6A5F\u53EF\u505A\u906E\u853D\u8F38\u5165\uFF0C\u8ACB\u6539\u7528 --stdin \u6216 --password \u50B3\u5165\u3002",
8016
- empty: "\u65B0\u5BC6\u78BC\u662F\u7A7A\u7684\uFF0C\u672A\u505A\u4EFB\u4F55\u8B8A\u66F4\u3002",
8017
- updated: "\u5DF2\u66F4\u65B0\u9023\u7DDA '{name}' \u7684\u5BC6\u78BC \u2192 {envFile}\uFF08{varName}\uFF09",
8018
- converted: 'config.json \u88E1\u7684\u660E\u6587\u5BC6\u78BC\u5DF2\u6539\u6210 {{ "$env": "{varName}" }} \u53C3\u7167\uFF0C\u4E4B\u5F8C\u8F2A\u66FF\u53EA\u6703\u52D5 env \u6A94\u3002',
8019
- skipped_test: "\u5DF2\u8DF3\u904E\u9A57\u8B49\uFF08--skip-test\uFF09\uFF1A\u65B0\u5BC6\u78BC\u672A\u7D93\u8CC7\u6599\u5EAB\u78BA\u8A8D\u3002"
8020
- }
8021
- };
8022
- });
8377
+ async write(path, config) {
8378
+ try {
8379
+ assertConfigMutationApproved(), this.validate(config);
8380
+ let storagePath = await resolveConfigStoragePath(path), isDirectory = !1;
8381
+ try {
8382
+ isDirectory = (await Bun.file(storagePath).stat())?.isDirectory() ?? !1;
8383
+ } catch {
8384
+ isDirectory = !1;
8385
+ }
8386
+ if (isDirectory || path.endsWith(".dbcli") || path === storagePath && isDirectory)
8387
+ if (await mkdir3(storagePath, { recursive: !0 }), isEnvReference(config.connection.password)) {
8388
+ let configJson = JSON.stringify(config, null, 2);
8389
+ await writeConfigWithIntegrity(storagePath, configJson);
8390
+ } else {
8391
+ let password = config.connection.password, configWithoutPassword = {
8392
+ ...config,
8393
+ connection: {
8394
+ ...config.connection,
8395
+ password: void 0
8396
+ }
8397
+ };
8398
+ delete configWithoutPassword.connection.password;
8399
+ let configJson = JSON.stringify(configWithoutPassword, null, 2);
8400
+ if (await writeConfigWithIntegrity(storagePath, configJson), password) {
8401
+ let envPath = join8(storagePath, ".env.local"), envContent = `# Database Credentials - DO NOT commit to git
8023
8402
 
8024
- // resources/lang/en/shell.json
8025
- var shell_default;
8026
- var init_shell = __esm(() => {
8027
- shell_default = {
8028
- welcome: "Connected to {system} ({database}@{host}:{port})",
8029
- welcome_permission: "Permission: {permission}",
8030
- prompt: "dbcli",
8031
- continuation_prompt: " ...",
8032
- goodbye: "Goodbye.",
8033
- error_no_config: "No .dbcli config found. Run 'dbcli init' first.",
8034
- error_connection_failed: "Connection failed: {message}",
8035
- error_reconnecting: "Connection lost. Attempting to reconnect...",
8036
- error_reconnect_failed: "Reconnection failed: {message}",
8037
- error_reconnect_success: "Reconnected successfully.",
8038
- error_command_failed: "Command error: {message}",
8039
- error_sql_failed: "SQL error: {message}",
8040
- error_permission: "Permission denied. Required: {required} (current: {current})",
8041
- error_blacklisted: "Table '{table}' is blacklisted.",
8042
- sql_mode_hint: "SQL-only mode. Only SQL statements are accepted.",
8043
- multiline_cancelled: "Multiline input cancelled.",
8044
- timing_display: "Time: {ms}ms",
8045
- rows_display: "{count} row(s)",
8046
- unknown_command: "Unknown command: '{command}'. Type '.help' for available commands.",
8047
- subcommand_prefix_hint: "This line is being read as SQL and is waiting for its `;`. If you meant the dbcli subcommand, type {command} instead. Ctrl-C cancels.",
8048
- es: {
8049
- parse_empty: "Empty request",
8050
- parse_needs_method_path: 'Request requires a method and a path, e.g. "GET /index/_search"',
8051
- parse_needs_path: "Request requires a path",
8052
- refuse_dot_segment: "Refused: '{path}' contains a '..' segment once decoded, and dbcli cannot tell which path Elasticsearch would route it to. Write the path out without '..'.",
8053
- refuse_unparseable: "Refused: '{path}' is not the request the server would receive. It cannot be parsed as a path.",
8054
- refuse_not_canonical: "Refused: '{path}' is not the request the server would receive. Write it as '{canonical}'.",
8055
- refuse_source_param: "Refused: `source` in the query string is a request body by another route. Write the body as JSON.",
8056
- refuse_search_template: "Refused: '{path}' is a search template, whose body cannot be inspected \u2014 it is a string rendered on the cluster, or stored there. Write the query out in full.",
8057
- refuse_string_body: "Refused: a quoted string request body is not supported. Write the body as JSON.",
8058
- refuse_audit_strict: "Refused: the audit entry for this request could not be written, and audit.strict is on. Fix the audit sink (disk space, directory permissions, a stale lockfile in .dbcli/audit) or turn audit.strict off to accept unrecorded requests.",
8059
- blacklist_index: "index '{index}' is blacklist-protected",
8060
- blacklist_unscoped: "'{path}' names no index, so it cannot be checked against the blacklist. Scope the request to an index, e.g. GET /<index>/_search.",
8061
- blacklist_field: "field '{field}' is blacklist-protected and cannot be named in a request \u2014 sorting, aggregating or scripting on it would return its values.",
8062
- banner_title: "Elasticsearch shell \u2014 Kibana Dev Tools syntax",
8063
- banner_hint: 'Enter "<METHOD> /<path>" then an optional JSON body; submit with a blank line. Try: GET /_cat/indices',
8064
- banner_keys: 'Ctrl+C cancels the current block; Ctrl+D or "exit" quits.',
8065
- block_cancelled: "(block cancelled)",
8066
- goodbye: "Goodbye"
8403
+ DBCLI_PASSWORD=${password}
8404
+ `;
8405
+ await Bun.file(envPath).write(envContent);
8406
+ }
8407
+ }
8408
+ else {
8409
+ let json = JSON.stringify(config, null, 2);
8410
+ await Bun.file(path).write(json);
8411
+ }
8412
+ } catch (error) {
8413
+ if (error instanceof ConfigError)
8414
+ throw error;
8415
+ throw new ConfigError(`Failed to write .dbcli config: ${error instanceof Error ? error.message : String(error)}`);
8416
+ }
8067
8417
  }
8068
8418
  };
8069
8419
  });
8070
8420
 
8071
- // resources/lang/zh-TW/shell.json
8072
- var shell_default2;
8073
- var init_shell2 = __esm(() => {
8074
- shell_default2 = {
8075
- welcome: "\u5DF2\u9023\u7DDA\u81F3 {system}\uFF08{database}@{host}:{port}\uFF09",
8076
- welcome_permission: "\u6B0A\u9650\u7B49\u7D1A\uFF1A{permission}",
8077
- prompt: "dbcli",
8078
- continuation_prompt: " ...",
8079
- goodbye: "\u518D\u898B\u3002",
8080
- error_no_config: "\u627E\u4E0D\u5230 .dbcli \u8A2D\u5B9A\u6A94\u3002\u8ACB\u5148\u57F7\u884C 'dbcli init'\u3002",
8081
- error_connection_failed: "\u9023\u7DDA\u5931\u6557\uFF1A{message}",
8082
- error_reconnecting: "\u9023\u7DDA\u5DF2\u4E2D\u65B7\uFF0C\u5617\u8A66\u91CD\u65B0\u9023\u7DDA...",
8083
- error_reconnect_failed: "\u91CD\u65B0\u9023\u7DDA\u5931\u6557\uFF1A{message}",
8084
- error_reconnect_success: "\u91CD\u65B0\u9023\u7DDA\u6210\u529F\u3002",
8085
- error_command_failed: "\u6307\u4EE4\u932F\u8AA4\uFF1A{message}",
8086
- error_sql_failed: "SQL \u932F\u8AA4\uFF1A{message}",
8087
- error_permission: "\u6B0A\u9650\u4E0D\u8DB3\u3002\u9700\u8981\uFF1A{required}\uFF08\u76EE\u524D\uFF1A{current}\uFF09",
8088
- error_blacklisted: "\u8868\u683C '{table}' \u5DF2\u88AB\u5217\u5165\u9ED1\u540D\u55AE\u3002",
8089
- sql_mode_hint: "\u7D14 SQL \u6A21\u5F0F\u3002\u50C5\u63A5\u53D7 SQL \u8A9E\u53E5\u3002",
8090
- multiline_cancelled: "\u5DF2\u53D6\u6D88\u591A\u884C\u8F38\u5165\u3002",
8091
- timing_display: "\u8017\u6642\uFF1A{ms}ms",
8092
- rows_display: "{count} \u7B46\u8CC7\u6599",
8093
- unknown_command: "\u672A\u77E5\u6307\u4EE4\uFF1A'{command}'\u3002\u8F38\u5165 '.help' \u67E5\u770B\u53EF\u7528\u6307\u4EE4\u3002",
8094
- subcommand_prefix_hint: "\u9019\u4E00\u884C\u88AB\u7576\u6210 SQL\uFF0C\u6B63\u5728\u7B49\u5206\u865F\u3002\u5982\u679C\u4F60\u8981\u7684\u662F dbcli \u5B50\u6307\u4EE4\uFF0C\u8ACB\u6539\u6253 {command}\u3002Ctrl-C \u53EF\u53D6\u6D88\u3002",
8095
- es: {
8096
- parse_empty: "\u7A7A\u7684\u8ACB\u6C42",
8097
- parse_needs_method_path: '\u8ACB\u6C42\u9700\u8981\u4E00\u500B method \u8207\u4E00\u500B path\uFF0C\u4F8B\u5982 "GET /index/_search"',
8098
- parse_needs_path: "\u8ACB\u6C42\u9700\u8981\u4E00\u500B path",
8099
- refuse_dot_segment: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u89E3\u78BC\u5F8C\u542B\u6709 '..' \u5340\u6BB5\uFF0Cdbcli \u7121\u6CD5\u5224\u65B7 Elasticsearch \u6703\u628A\u5B83\u8DEF\u7531\u5230\u54EA\u500B\u8DEF\u5F91\u3002\u8ACB\u628A\u8DEF\u5F91\u5BEB\u6210\u4E0D\u542B '..' \u7684\u5F62\u5F0F\u3002",
8100
- refuse_unparseable: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u4E0D\u662F\u4F3A\u670D\u5668\u6703\u6536\u5230\u7684\u90A3\u500B\u8ACB\u6C42\u3002\u5B83\u7121\u6CD5\u88AB\u89E3\u6790\u6210\u4E00\u500B\u8DEF\u5F91\u3002",
8101
- refuse_not_canonical: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u4E0D\u662F\u4F3A\u670D\u5668\u6703\u6536\u5230\u7684\u90A3\u500B\u8ACB\u6C42\u3002\u8ACB\u5BEB\u6210 '{canonical}'\u3002",
8102
- refuse_source_param: "\u5DF2\u62D2\u7D55\uFF1Aquery string \u88E1\u7684 `source` \u662F\u53E6\u4E00\u689D\u8DEF\u5F91\u4E0A\u7684 request body\u3002\u8ACB\u628A body \u5BEB\u6210 JSON\u3002",
8103
- refuse_search_template: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u662F search template\uFF0C\u5B83\u7684 body \u7121\u6CD5\u6AA2\u67E5\u2014\u2014\u90A3\u662F\u4E00\u500B\u5728\u53E2\u96C6\u4E0A\u7B97\u51FA\u4F86\u7684\u5B57\u4E32\uFF0C\u6216\u8005\u6839\u672C\u5B58\u5728\u53E2\u96C6\u88E1\u3002\u8ACB\u628A\u67E5\u8A62\u5B8C\u6574\u5BEB\u51FA\u4F86\u3002",
8104
- refuse_string_body: "\u5DF2\u62D2\u7D55\uFF1A\u4E0D\u652F\u63F4\u4EE5\u5F15\u865F\u5B57\u4E32\u5F62\u5F0F\u5BEB\u7684 request body\u3002\u8ACB\u628A body \u5BEB\u6210 JSON\u3002",
8105
- refuse_audit_strict: "\u5DF2\u62D2\u7D55\uFF1A\u9019\u500B\u8ACB\u6C42\u7684\u7A3D\u6838\u7D00\u9304\u5BEB\u4E0D\u51FA\u53BB\uFF0C\u800C audit.strict \u662F\u958B\u7684\u3002\u8ACB\u4FEE\u597D\u7A3D\u6838\u8F38\u51FA\u7AEF\uFF08\u78C1\u789F\u7A7A\u9593\u3001\u76EE\u9304\u6B0A\u9650\u3001.dbcli/audit \u88E1\u6B98\u7559\u7684 lockfile\uFF09\uFF0C\u6216\u95DC\u6389 audit.strict \u4EE5\u63A5\u53D7\u6C92\u6709\u7D00\u9304\u7684\u8ACB\u6C42\u3002",
8106
- blacklist_index: "\u7D22\u5F15 '{index}' \u53D7\u9ED1\u540D\u55AE\u4FDD\u8B77",
8107
- blacklist_unscoped: "'{path}' \u6307\u4E0D\u51FA\u4EFB\u4F55\u7D22\u5F15\uFF0C\u56E0\u6B64\u7121\u6CD5\u5C0D\u7167\u9ED1\u540D\u55AE\u6AA2\u67E5\u3002\u8ACB\u628A\u8ACB\u6C42\u9650\u5B9A\u5230\u67D0\u500B\u7D22\u5F15\uFF0C\u4F8B\u5982 GET /<index>/_search\u3002",
8108
- blacklist_field: "\u6B04\u4F4D '{field}' \u53D7\u9ED1\u540D\u55AE\u4FDD\u8B77\uFF0C\u4E0D\u80FD\u51FA\u73FE\u5728\u8ACB\u6C42\u88E1\u2014\u2014\u5C0D\u5B83\u6392\u5E8F\u3001\u805A\u5408\u6216\u8DD1 script \u90FD\u6703\u628A\u5B83\u7684\u503C\u53D6\u56DE\u4F86\u3002",
8109
- banner_title: "Elasticsearch shell \u2014\u2014 Kibana Dev Tools \u8A9E\u6CD5",
8110
- banner_hint: '\u8F38\u5165 "<METHOD> /<path>"\uFF0C\u63A5\u8457\u5BEB\u53EF\u9078\u7684 JSON body\uFF1B\u7A7A\u4E00\u884C\u9001\u51FA\u3002\u53EF\u4EE5\u5148\u8A66\uFF1AGET /_cat/indices',
8111
- banner_keys: 'Ctrl+C \u53D6\u6D88\u76EE\u524D\u9019\u500B block\uFF1BCtrl+D \u6216 "exit" \u96E2\u958B\u3002',
8112
- block_cancelled: "\uFF08block \u5DF2\u53D6\u6D88\uFF09",
8113
- goodbye: "\u518D\u898B"
8114
- }
8115
- };
8421
+ // src/core/correlation-id.ts
8422
+ function isCorrelationId(value) {
8423
+ return typeof value === "string" && CORRELATION_ID_PATTERN.test(value);
8424
+ }
8425
+ function setGlobalCorrelationId(value) {
8426
+ if (value !== void 0 && !isCorrelationId(value))
8427
+ throw Error("Invalid correlation ID");
8428
+ globalCorrelationId = value;
8429
+ }
8430
+ function getGlobalCorrelationId() {
8431
+ return globalCorrelationId;
8432
+ }
8433
+ var CORRELATION_ID_PATTERN, globalCorrelationId;
8434
+ var init_correlation_id = __esm(() => {
8435
+ CORRELATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/;
8116
8436
  });
8117
8437
 
8118
- // resources/lang/en/ceremony.json
8119
- var ceremony_default;
8120
- var init_ceremony = __esm(() => {
8121
- ceremony_default = {
8122
- updated: "Updated {count} row(s) in {table}",
8123
- deleted: "Deleted {count} row(s) from {table}",
8124
- inserted: "Inserted {count} row(s) into {table}",
8125
- matched_nothing: "No rows matched in {table} \u2014 nothing changed",
8126
- cancelled: "Cancelled. {table} was not changed",
8127
- dry_run: "Preview only. {table} was not changed",
8128
- elapsed: "Took {seconds}s",
8129
- failed: "Failed: {reason}",
8130
- unknown_error: "unknown error",
8131
- confirm_sql: "Generated SQL:",
8132
- confirm_command: "Command to run:",
8133
- confirm_params: "Parameters:",
8134
- confirm_destructive_warning: "\u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!",
8135
- confirm_destructive_prompt: "Are you sure you want to execute this DELETE operation? This cannot be undone.",
8136
- confirm_destructive_warning_ddl: "\u26A0\uFE0F Warning: this schema change is destructive and cannot be undone!",
8137
- confirm_ddl_prompt: "Are you sure you want to run this {operation}? This cannot be undone.",
8138
- confirm_prompt: "Proceed with this operation?",
8139
- gate_summary: "This will {operation} rows in {table}.",
8140
- gate_summary_untargeted: "This will run a {operation} statement.",
8141
- gate_statement: "Statement:",
8142
- gate_cancelled: "Cancelled. Nothing was run.",
8143
- gate_full_table_warning: "\u26A0\uFE0F This affects EVERY row in {table} and cannot be undone.",
8144
- gate_ddl_warning: "\u26A0\uFE0F This destroys {table} and cannot be undone.",
8145
- gate_ddl_warning_untargeted: "\u26A0\uFE0F This is a destructive schema change and cannot be undone.",
8146
- gate_unparseable_warning: "\u26A0\uFE0F dbcli could not parse this statement, so it is treated as affecting everything.",
8147
- gate_multiple_statements_warning: "\u26A0\uFE0F This is more than one statement, and dbcli will not judge them one at a time.",
8148
- gate_multi_table_warning: "This write joins another table. How many rows of the target it touches depends on the data, not on the statement, so it cannot be checked before it runs.",
8149
- gate_nested_write_warning: "\u26A0\uFE0F This statement carries a second write inside it, and dbcli will not read the tier of that write off this one.",
8150
- gate_typed_prompt: "Type {phrase} to run it",
8151
- gate_typed_mismatch: "That did not match {phrase}. Nothing was run.",
8152
- gate_refused: "Refused: this statement is not limited to specific rows and nobody is here to confirm it (reason={reason}).",
8153
- gate_refused_in_shell: "Refused: a dbcli subcommand started from the shell has no way to ask you, even though you are right here (reason={reason}).",
8154
- gate_remedy_no_where: "Add a WHERE clause \u2014 WHERE 1=1 if you really do mean every row \u2014 or a LIMIT, then run it again.",
8155
- gate_remedy_ddl_destruction: "No flag skips this. Run it from an interactive terminal, or apply the schema change through a reviewed migration.",
8156
- gate_remedy_unparseable: "dbcli could not parse this statement and will not guess. Simplify it, or run it from an interactive terminal.",
8157
- gate_remedy_multiple_statements: "Run one statement per command.",
8158
- gate_remedy_multi_table: "Rewrite it as a single-table statement \u2014 move the other table into a subquery in the WHERE \u2014 or run it where someone can confirm it.",
8159
- gate_remedy_nested_write: "Run the nested write as its own statement, or run this one where someone can confirm it.",
8160
- gate_remedy_non_unique_where: "Match on a primary key or a uniquely indexed column, or run it from an interactive terminal.",
8161
- gate_remedy_in_shell: "Type the statement at the dbcli> prompt instead \u2014 there the shell asks you to type the target table name.",
8162
- recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
8163
- recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
8164
- blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
8165
- };
8166
- });
8438
+ // src/utils/connection-timeout.ts
8439
+ function setGlobalConnectionTimeout(timeout) {
8440
+ _globalConnectionTimeout = timeout;
8441
+ }
8442
+ function setGlobalStatementTimeout(timeout) {
8443
+ _globalStatementTimeout = timeout;
8444
+ }
8445
+ function resolveConnectionTimeout(configured) {
8446
+ return _globalConnectionTimeout ?? configured;
8447
+ }
8448
+ function resolveStatementTimeout(configured) {
8449
+ return _globalStatementTimeout ?? configured;
8450
+ }
8451
+ function withResolvedTimeout(options) {
8452
+ let timeout = resolveConnectionTimeout(options.timeout), statementTimeout = resolveStatementTimeout(options.statementTimeout);
8453
+ if (timeout === options.timeout && statementTimeout === options.statementTimeout)
8454
+ return options;
8455
+ return { ...options, timeout, statementTimeout };
8456
+ }
8457
+ var _globalConnectionTimeout, _globalStatementTimeout;
8167
8458
 
8168
- // resources/lang/zh-TW/ceremony.json
8169
- var ceremony_default2;
8170
- var init_ceremony2 = __esm(() => {
8171
- ceremony_default2 = {
8172
- updated: "\u5DF2\u66F4\u65B0 {table} \u7684 {count} \u5217",
8173
- deleted: "\u5DF2\u5F9E {table} \u522A\u9664 {count} \u5217",
8174
- inserted: "\u5DF2\u5BEB\u5165 {count} \u5217\u5230 {table}",
8175
- matched_nothing: "{table} \u6C92\u6709\u4EFB\u4F55\u5217\u7B26\u5408\u689D\u4EF6\uFF0C\u8CC7\u6599\u672A\u8B8A\u52D5",
8176
- cancelled: "\u5DF2\u53D6\u6D88\uFF0C{table} \u672A\u8B8A\u52D5",
8177
- dry_run: "\u50C5\u9810\u89BD\uFF0C{table} \u672A\u8B8A\u52D5",
8178
- elapsed: "\u8017\u6642 {seconds} \u79D2",
8179
- failed: "\u5931\u6557\uFF1A{reason}",
8180
- unknown_error: "\u672A\u77E5\u7684\u932F\u8AA4",
8181
- confirm_sql: "\u7522\u751F\u7684 SQL\uFF1A",
8182
- confirm_command: "\u5373\u5C07\u57F7\u884C\u7684\u6307\u4EE4\uFF1A",
8183
- confirm_params: "\u53C3\u6578\uFF1A",
8184
- confirm_destructive_warning: "\u26A0\uFE0F \u8B66\u544A\uFF1ADELETE \u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
8185
- confirm_destructive_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B DELETE \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
8186
- confirm_destructive_warning_ddl: "\u26A0\uFE0F \u8B66\u544A\uFF1A\u9019\u500B schema \u8B8A\u66F4\u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
8187
- confirm_ddl_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B {operation} \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
8188
- confirm_prompt: "\u8981\u7E7C\u7E8C\u57F7\u884C\u9019\u500B\u64CD\u4F5C\u55CE\uFF1F",
8189
- gate_summary: "\u9019\u53E5\u6703\u5C0D {table} \u57F7\u884C {operation}\u3002",
8190
- gate_summary_untargeted: "\u9019\u53E5\u6703\u57F7\u884C {operation}\u3002",
8191
- gate_statement: "\u8A9E\u53E5\uFF1A",
8192
- gate_cancelled: "\u5DF2\u53D6\u6D88\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
8193
- gate_full_table_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u5F71\u97FF {table} \u7684\u6BCF\u4E00\u5217\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
8194
- gate_ddl_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u6467\u6BC0 {table}\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
8195
- gate_ddl_warning_untargeted: "\u26A0\uFE0F \u9019\u662F\u7834\u58DE\u6027\u7684 schema \u8B8A\u66F4\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
8196
- gate_unparseable_warning: "\u26A0\uFE0F dbcli \u7121\u6CD5\u89E3\u6790\u9019\u53E5\uFF0C\u56E0\u6B64\u4E00\u5F8B\u8996\u70BA\u6703\u5F71\u97FF\u5168\u90E8\u8CC7\u6599\u3002",
8197
- gate_multiple_statements_warning: "\u26A0\uFE0F \u9019\u88E1\u4E0D\u53EA\u4E00\u53E5\u8A9E\u53E5\uFF0Cdbcli \u4E0D\u6703\u9010\u53E5\u5224\u65B7\u3002",
8198
- gate_multi_table_warning: "\u9019\u500B\u5BEB\u5165\u63A5\u4E86\u53E6\u4E00\u5F35\u8868\u3002\u5B83\u6703\u52D5\u5230\u76EE\u6A19\u8868\u5E7E\u5217\u53D6\u6C7A\u65BC\u8CC7\u6599\u800C\u4E0D\u662F\u8A9E\u53E5\u672C\u8EAB\uFF0C\u57F7\u884C\u524D\u7121\u6CD5\u6AA2\u67E5\u3002",
8199
- gate_nested_write_warning: "\u26A0\uFE0F \u9019\u53E5\u88E1\u9762\u9084\u593E\u8457\u53E6\u4E00\u500B\u5BEB\u5165\uFF0Cdbcli \u4E0D\u6703\u62FF\u9019\u53E5\u7684\u5C64\u7D1A\u4EE3\u66FF\u90A3\u500B\u5BEB\u5165\u7684\u5C64\u7D1A\u3002",
8200
- gate_typed_prompt: "\u8F38\u5165 {phrase} \u624D\u6703\u57F7\u884C",
8201
- gate_typed_mismatch: "\u8F38\u5165\u7684\u5167\u5BB9\u8207 {phrase} \u4E0D\u7B26\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
8202
- gate_refused: "\u5DF2\u62D2\u7D55\uFF1A\u9019\u53E5\u6C92\u6709\u9650\u5B9A\u8981\u52D5\u54EA\u4E9B\u5217\uFF0C\u800C\u73FE\u5728\u6C92\u6709\u4EBA\u80FD\u78BA\u8A8D\uFF08reason={reason}\uFF09\u3002",
8203
- gate_refused_in_shell: "\u5DF2\u62D2\u7D55\uFF1A\u5F9E shell \u555F\u52D5\u7684 dbcli \u5B50\u6307\u4EE4\u6C92\u6709\u8FA6\u6CD5\u5411\u4F60\u63D0\u554F\uFF0C\u5373\u4F7F\u4F60\u4EBA\u5C31\u5728\u9019\u88E1\uFF08reason={reason}\uFF09\u3002",
8204
- gate_remedy_no_where: "\u88DC\u4E0A WHERE\uFF08\u771F\u7684\u8981\u5168\u8868\u5C31\u5BEB WHERE 1=1\uFF09\u6216 LIMIT\uFF0C\u7136\u5F8C\u91CD\u8DD1\u3002",
8205
- gate_remedy_ddl_destruction: "\u6C92\u6709\u4EFB\u4F55\u65D7\u6A19\u53EF\u4EE5\u8DF3\u904E\u9019\u4E00\u95DC\u3002\u8ACB\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\uFF0C\u6216\u6539\u8D70\u7D93\u904E\u5BE9\u67E5\u7684 migration\u3002",
8206
- gate_remedy_unparseable: "dbcli \u7121\u6CD5\u89E3\u6790\u9019\u53E5\uFF0C\u4E5F\u4E0D\u6703\u7528\u731C\u7684\u3002\u8ACB\u7C21\u5316\u8A9E\u53E5\uFF0C\u6216\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\u3002",
8207
- gate_remedy_multiple_statements: "\u4E00\u6B21\u53EA\u57F7\u884C\u4E00\u53E5\u8A9E\u53E5\u3002",
8208
- gate_remedy_multi_table: "\u6539\u5BEB\u6210\u55AE\u8868\u8A9E\u53E5\u2014\u2014\u628A\u53E6\u4E00\u5F35\u8868\u79FB\u9032 WHERE \u7684\u5B50\u67E5\u8A62\u88E1\u2014\u2014\u6216\u6539\u5728\u6709\u4EBA\u53EF\u4EE5\u78BA\u8A8D\u7684\u5730\u65B9\u57F7\u884C\u3002",
8209
- gate_remedy_nested_write: "\u628A\u88E1\u9762\u90A3\u500B\u5BEB\u5165\u62C6\u6210\u7368\u7ACB\u7684\u4E00\u53E5\u4F86\u57F7\u884C\uFF0C\u6216\u6539\u5728\u6709\u4EBA\u53EF\u4EE5\u78BA\u8A8D\u7684\u5730\u65B9\u57F7\u884C\u3002",
8210
- gate_remedy_non_unique_where: "\u6539\u7528\u4E3B\u9375\u6216\u552F\u4E00\u7D22\u5F15\u6B04\u4F4D\u7576\u689D\u4EF6\uFF0C\u6216\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\u3002",
8211
- gate_remedy_in_shell: "\u8ACB\u76F4\u63A5\u5728 dbcli> \u63D0\u793A\u7B26\u6253\u9019\u53E5\u8A9E\u53E5\u2014\u2014\u5728\u90A3\u88E1 dbcli \u6703\u8ACB\u4F60\u6253\u51FA\u76EE\u6A19\u8CC7\u6599\u8868\u7684\u540D\u7A31\u4F86\u78BA\u8A8D\u3002",
8212
- recovery_none: "\u6B64\u64CD\u4F5C\u7121\u6CD5\u81EA\u52D5\u9084\u539F\uFF0C\u8981\u53D6\u56DE\u5148\u524D\u7684\u8CC7\u6599\u8ACB\u5F9E\u5099\u4EFD\u56DE\u5FA9\u3002",
8213
- recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
8214
- blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
8215
- };
8459
+ // src/core/connection-selector.ts
8460
+ function createConnectionSelectorOption() {
8461
+ return new Option2("--use <connection>", CONNECTION_SELECTOR_DESCRIPTION);
8462
+ }
8463
+ var CONNECTION_SELECTOR_DESCRIPTION = "Use a specific named connection for this invocation (v2 config)";
8464
+ var init_connection_selector = __esm(() => {
8465
+ init_esm();
8216
8466
  });
8217
8467
 
8218
- // src/i18n/message-loader.ts
8219
- class MessageLoader {
8220
- static instance = null;
8221
- messages = {};
8222
- fallbackMessages = {};
8223
- currentLang;
8224
- constructor() {
8225
- this.currentLang = Bun.env.DBCLI_LANG || "en", this.loadMessages();
8226
- }
8227
- static getInstance() {
8228
- if (!MessageLoader.instance)
8229
- MessageLoader.instance = new MessageLoader;
8230
- return MessageLoader.instance;
8231
- }
8232
- loadMessages() {
8233
- if (this.currentLang !== "en")
8234
- this.messages = BUNDLED_MESSAGES[this.currentLang] || {};
8235
- this.fallbackMessages = BUNDLED_MESSAGES.en || {};
8236
- }
8237
- t(key) {
8238
- let parts = key.split("."), value = this.messages;
8239
- for (let part of parts)
8240
- if (value && typeof value === "object")
8241
- value = value[part];
8242
- else {
8243
- value = void 0;
8244
- break;
8245
- }
8246
- if (typeof value === "string")
8247
- return value;
8248
- value = this.fallbackMessages;
8249
- for (let part of parts)
8250
- if (value && typeof value === "object")
8251
- value = value[part];
8252
- else {
8253
- value = void 0;
8254
- break;
8255
- }
8256
- if (typeof value === "string")
8257
- return value;
8258
- return key;
8468
+ // src/utils/config-path.ts
8469
+ function resolveConfigPath(command, options, fallback = ".dbcli") {
8470
+ for (let current = command;current; current = current.parent ?? void 0) {
8471
+ let source = current.getOptionValueSource("config");
8472
+ if (source && source !== "default") {
8473
+ let currentOptions = current.opts();
8474
+ if (typeof currentOptions.config === "string" && currentOptions.config.length > 0)
8475
+ return currentOptions.config;
8476
+ }
8259
8477
  }
8260
- setLanguage(lang) {
8261
- this.currentLang = lang, this.messages = {}, this.loadMessages();
8478
+ if (typeof options?.config === "string" && options.config.length > 0 && options.config !== fallback)
8479
+ return options.config;
8480
+ for (let current = command;current; current = current.parent ?? void 0) {
8481
+ let source = current.getOptionValueSource("global");
8482
+ if (source && source !== "default" && current.opts().global === !0)
8483
+ return getGlobalConfigPath();
8262
8484
  }
8263
- interpolate(key, vars) {
8264
- return this.t(key).replace(/\{([^{}]+)\}/g, (match, name) => Object.prototype.hasOwnProperty.call(vars, name) ? String(vars[name]) : match);
8485
+ if (options?.global === !0)
8486
+ return getGlobalConfigPath();
8487
+ if (typeof options?.config === "string" && options.config.length > 0)
8488
+ return options.config;
8489
+ return fallback;
8490
+ }
8491
+ var init_config_path = __esm(() => {
8492
+ init_config_binding();
8493
+ });
8494
+
8495
+ // src/utils/config-sidecar-path.ts
8496
+ import { dirname as dirname2, extname, join as join9 } from "path";
8497
+ function configSidecarPath(configPath, fileName) {
8498
+ let directory = extname(configPath) !== "" && !configPath.endsWith(".dbcli") ? dirname2(configPath) : configPath;
8499
+ return join9(directory, fileName);
8500
+ }
8501
+ var init_config_sidecar_path = () => {};
8502
+
8503
+ // src/utils/skill-check-cache.ts
8504
+ import { mkdir as mkdir5, rename as rename3, unlink as unlink4, writeFile as writeFile2 } from "fs/promises";
8505
+ import { dirname as dirname4 } from "path";
8506
+ function cachePath(configPath) {
8507
+ return configSidecarPath(configPath, CACHE_FILE_NAME);
8508
+ }
8509
+ async function readSkillCheckCache(configPath, dbcliVersion, options = {}) {
8510
+ try {
8511
+ let file = Bun.file(cachePath(configPath));
8512
+ if (!await file.exists())
8513
+ return null;
8514
+ let raw = await file.json();
8515
+ if (raw.version !== 1 || !Array.isArray(raw.outdated))
8516
+ return null;
8517
+ if (raw.dbcliVersion !== dbcliVersion)
8518
+ return null;
8519
+ if (typeof raw.checkedAt !== "number")
8520
+ return null;
8521
+ let age = (options.now ?? Date.now()) - raw.checkedAt;
8522
+ if (age < 0 || age > SKILL_CHECK_TTL_MS)
8523
+ return null;
8524
+ return raw.outdated;
8525
+ } catch {
8526
+ return null;
8265
8527
  }
8266
8528
  }
8267
- var BUNDLED_MESSAGES, messageLoader, t = (key) => messageLoader.t(key), t_vars = (key, vars) => messageLoader.interpolate(key, vars);
8268
- var init_message_loader = __esm(() => {
8269
- init_messages();
8270
- init_messages2();
8271
- init_shell();
8272
- init_shell2();
8273
- init_ceremony();
8274
- init_ceremony2();
8275
- BUNDLED_MESSAGES = {
8276
- en: { ...messages_default, shell: shell_default, ceremony: ceremony_default },
8277
- "zh-TW": {
8278
- ...messages_default2,
8279
- shell: shell_default2,
8280
- ceremony: ceremony_default2
8529
+ async function writeSkillCheckCache(configPath, dbcliVersion, outdated, options = {}) {
8530
+ let path = cachePath(configPath), payload = {
8531
+ version: 1,
8532
+ dbcliVersion,
8533
+ checkedAt: options.now ?? Date.now(),
8534
+ outdated
8535
+ };
8536
+ try {
8537
+ await mkdir5(dirname4(path), { recursive: !0 });
8538
+ let temporary = `${path}.tmp-${process.pid}`;
8539
+ await writeFile2(temporary, JSON.stringify(payload), "utf8"), await rename3(temporary, path);
8540
+ } catch {}
8541
+ }
8542
+ async function invalidateSkillCheckCache(configPath) {
8543
+ await unlink4(cachePath(configPath)).catch(() => {
8544
+ return;
8545
+ });
8546
+ }
8547
+ var SKILL_CHECK_TTL_MS = 86400000, CACHE_FILE_NAME = "skill-check.json";
8548
+ var init_skill_check_cache = __esm(() => {
8549
+ init_config_sidecar_path();
8550
+ });
8551
+
8552
+ // src/core/slow-query-advisory.ts
8553
+ function slowQueryThresholdFor(options) {
8554
+ if (options.recovery === !0)
8555
+ return 0;
8556
+ let threshold = options.slowMs ?? 1000;
8557
+ return assertValidSlowQueryThreshold(threshold), threshold;
8558
+ }
8559
+ function assertValidSlowQueryThreshold(value) {
8560
+ if (value === void 0)
8561
+ return;
8562
+ if (!Number.isSafeInteger(value) || value < 0)
8563
+ throw Error("slow-ms must be a non-negative integer");
8564
+ }
8565
+ function parseSlowQueryThreshold(value) {
8566
+ if (!/^\d+$/.test(value))
8567
+ throw Error("slow-ms must be a non-negative integer");
8568
+ let parsed = Number(value);
8569
+ return assertValidSlowQueryThreshold(parsed), parsed;
8570
+ }
8571
+ function recommendationFor(system) {
8572
+ return system === void 0 || GUIDE_SLOW_QUERY_SYSTEMS.includes(system) ? "Review safely with: dbcli guide slow-query --format markdown. This hint runs no additional database diagnostics." : `Re-check the query shape and the filters it scans; dbcli ships no slow-query diagnostics for ${system}. This hint runs no additional database diagnostics.`;
8573
+ }
8574
+ function buildSlowQueryAdvisory(executionTimeMs, options) {
8575
+ let thresholdMs = slowQueryThresholdFor(options);
8576
+ if (thresholdMs === 0 || executionTimeMs === void 0 || executionTimeMs < thresholdMs)
8577
+ return;
8578
+ return {
8579
+ code: "SLOW_QUERY",
8580
+ executionTimeMs,
8581
+ thresholdMs,
8582
+ recommendation: recommendationFor(options.system)
8583
+ };
8584
+ }
8585
+ function attachSlowQueryAdvisory(result, options) {
8586
+ let advisory = buildSlowQueryAdvisory(result.executionTimeMs, options);
8587
+ if (!advisory)
8588
+ return result;
8589
+ return {
8590
+ ...result,
8591
+ metadata: {
8592
+ statement: result.metadata?.statement ?? "UNKNOWN",
8593
+ ...result.metadata,
8594
+ performanceAdvisory: advisory
8281
8595
  }
8282
8596
  };
8283
- messageLoader = MessageLoader.getInstance();
8597
+ }
8598
+ var GUIDE_SLOW_QUERY_SYSTEMS;
8599
+ var init_slow_query_advisory = __esm(() => {
8600
+ GUIDE_SLOW_QUERY_SYSTEMS = ["mariadb", "mysql", "postgresql", "redis"];
8284
8601
  });
8285
8602
 
8286
8603
  // src/program-root.ts
@@ -8460,6 +8777,8 @@ function removeConnection(config, name) {
8460
8777
  return updated;
8461
8778
  }
8462
8779
  var init_config_v2_mutations = __esm(() => {
8780
+ init_message_loader();
8781
+ init_capabilities();
8463
8782
  init_validation();
8464
8783
  });
8465
8784
 
@@ -8792,250 +9111,6 @@ var init_types2 = __esm(() => {
8792
9111
  };
8793
9112
  });
8794
9113
 
8795
- // src/adapters/capabilities.ts
8796
- function cap(status, tier, note) {
8797
- return Object.freeze({ status, tier, note });
8798
- }
8799
- function getEngineCapabilities(system) {
8800
- return Object.freeze({ ...ENGINE_CAPABILITIES[system] });
8801
- }
8802
- function getEngineCapability(system, key) {
8803
- return ENGINE_CAPABILITIES[system][key];
8804
- }
8805
- var COMMAND_CAPABILITY_KEYS, ENGINE_INDEPENDENT, SQL_BASE, SQL_ONLY_UNSUPPORTED, SQL_MODE_LIMITED, ENGINE_CAPABILITIES;
8806
- var init_capabilities = __esm(() => {
8807
- COMMAND_CAPABILITY_KEYS = Object.freeze([
8808
- "init",
8809
- "use",
8810
- "list",
8811
- "schema",
8812
- "schemaSingle",
8813
- "schemaFullScan",
8814
- "query",
8815
- "lint",
8816
- "queryOutput",
8817
- "queryLimitGuard",
8818
- "q",
8819
- "queries",
8820
- "insert",
8821
- "update",
8822
- "delete",
8823
- "export",
8824
- "blacklistRead",
8825
- "blacklist",
8826
- "check",
8827
- "diff",
8828
- "migrate",
8829
- "shell",
8830
- "status",
8831
- "doctor",
8832
- "inspect",
8833
- "report",
8834
- "guide",
8835
- "completion",
8836
- "upgrade",
8837
- "recover",
8838
- "skill",
8839
- "auditTail",
8840
- "auditShow",
8841
- "auditClear",
8842
- "auditHealth",
8843
- "explain",
8844
- "plan",
8845
- "impactAssess",
8846
- "assert",
8847
- "snapshot",
8848
- "verify",
8849
- "semantic",
8850
- "design",
8851
- "proxy",
8852
- "proxyAnalyze",
8853
- "verification",
8854
- "verificationPrune",
8855
- "evidence",
8856
- "contract",
8857
- "recovery",
8858
- "backfillArtifact",
8859
- "password",
8860
- "capabilityDiscover",
8861
- "capabilityCheck"
8862
- ]);
8863
- ENGINE_INDEPENDENT = {
8864
- completion: cap("not-applicable", "none", "Shell completion is engine-independent."),
8865
- upgrade: cap("not-applicable", "local-write", "Package update checks are engine-independent."),
8866
- recover: cap("not-applicable", "dry-run", "Recovery operates on saved envelopes and gated command steps."),
8867
- skill: cap("not-applicable", "local-write", "Skill and task-pack generation are engine-independent."),
8868
- auditTail: cap("supported", "readonly", "Reads JSONL audit entries; never writes to engines."),
8869
- auditShow: cap("supported", "readonly", "Looks up a single audit entry by id prefix or recovery_ref."),
8870
- auditHealth: cap("supported", "readonly", "Renders AuditLogger.getHealth() snapshot."),
8871
- auditClear: cap("supported", "local-write", "Removes <conn>.jsonl + .jsonl.1 from local disk; never touches DB.")
8872
- }, SQL_BASE = {
8873
- init: cap("supported", "interactive", "SQL connection initialization is supported."),
8874
- use: cap("supported", "local-write", "V2 config supports named SQL connections."),
8875
- list: cap("supported", "readonly", "Lists relational tables."),
8876
- schema: cap("supported", "readonly", "Unified schema command (single-table or full scan)."),
8877
- schemaSingle: cap("supported", "readonly", "Reads a single table schema."),
8878
- schemaFullScan: cap("supported", "readonly", "Full scan, refresh, and reset are supported."),
8879
- query: cap("supported", "readonly", "Runs SQL through permission and blacklist guards."),
8880
- lint: cap("supported", "readonly", "Statically analyzes SQL without connecting to the database."),
8881
- queryOutput: cap("supported", "readonly", "table/json/csv output is supported."),
8882
- queryLimitGuard: cap("supported", "readonly", "Query-only auto-limit and size guard are supported."),
8883
- q: cap("supported", "readonly", "Saved SQL snippets allow SELECT/WITH only."),
8884
- queries: cap("supported", "local-write", "Snippet management is available regardless of active connection."),
8885
- insert: cap("supported", "db-write", "Dedicated INSERT command is supported."),
8886
- update: cap("supported", "db-write", "Dedicated UPDATE command is supported."),
8887
- delete: cap("supported", "db-write", "Dedicated DELETE command is supported."),
8888
- export: cap("supported", "readonly", "SQL export is supported."),
8889
- blacklistRead: cap("supported", "readonly", "Reads the configured blacklist rules."),
8890
- blacklist: cap("supported", "local-write", "Blacklist management and enforcement are supported."),
8891
- check: cap("supported", "readonly", "Data health checks are SQL-only."),
8892
- diff: cap("supported", "readonly", "Relational schema snapshots are supported."),
8893
- migrate: cap("supported", "db-write", "DDL migrations are supported for SQL engines."),
8894
- shell: cap("supported", "interactive", "Interactive SQL shell is supported."),
8895
- status: cap("supported", "readonly", "Safe config summary is supported."),
8896
- doctor: cap("supported", "readonly", "Engine-specific diagnostics are supported."),
8897
- inspect: cap("supported", "readonly", "Agent context snapshot is supported."),
8898
- report: cap("supported", "readonly", "Diagnostic report is supported."),
8899
- guide: cap("supported", "readonly", "Deterministic next-command planner is supported."),
8900
- explain: cap("supported", "readonly", "EXPLAIN plans are read from SQL engines."),
8901
- plan: cap("supported", "readonly", "Offline write-risk analysis of SQL against the schema cache."),
8902
- impactAssess: cap("supported", "local-write", "Offline impact assessment of a SQL schema change; writes its report locally."),
8903
- assert: cap("supported", "local-write", "Data assertions over SQL results; optionally writes a verification artifact."),
8904
- snapshot: cap("supported", "local-write", "Captures a SQL result fingerprint under .dbcli/snapshots/."),
8905
- verify: cap("supported", "local-write", "Scenario verification over SQL; writes a verification artifact."),
8906
- semantic: cap("supported", "readonly", "Governed semantic context and drift over a SQL schema cache."),
8907
- design: cap("supported", "local-write", "Schema design authoring and comparison for SQL dialects; init writes a template."),
8908
- proxy: cap("supported", "interactive", "Local observing proxy for the MySQL, MariaDB and PostgreSQL wire protocols."),
8909
- password: cap("supported", "interactive", "Rotates the stored connection credential; refused under agent mode."),
8910
- proxyAnalyze: cap("not-applicable", "readonly", "Reads locally recorded proxy events; never contacts an engine."),
8911
- verification: cap("not-applicable", "readonly", "Inspects verification artifacts under .dbcli/verification/."),
8912
- verificationPrune: cap("not-applicable", "local-write", "Deletes local verification artifacts; dry-run unless --execute --force."),
8913
- evidence: cap("not-applicable", "local-write", "Composes, validates and renders evidence packs from local artifacts."),
8914
- contract: cap("not-applicable", "readonly", "Validates and inspects governed semantic contracts offline."),
8915
- recovery: cap("not-applicable", "readonly", "Renders recovery codes and their remediation steps from a static table."),
8916
- backfillArtifact: cap("not-applicable", "local-write", "Builds a backfill artifact from a source manifest and v2 connection identity."),
8917
- capabilityDiscover: cap("not-applicable", "readonly", "Emits the static capability catalog; opens no connection."),
8918
- capabilityCheck: cap("not-applicable", "readonly", "Evaluates required capability ids against the local configuration only."),
8919
- ...ENGINE_INDEPENDENT
8920
- }, SQL_ONLY_UNSUPPORTED = {
8921
- explain: cap("unsupported", "none", "src/commands/explain.ts refuses a non-SQL connection."),
8922
- plan: cap("unsupported", "none", "Risk analysis is SQL-dialect based; toSqlDialect resolves no dialect here."),
8923
- assert: cap("unsupported", "none", "src/commands/assert.ts refuses a non-SQL connection."),
8924
- snapshot: cap("unsupported", "none", "src/commands/snapshot.ts refuses a non-SQL connection."),
8925
- verify: cap("unsupported", "none", "src/commands/verify.ts refuses a non-SQL connection."),
8926
- proxy: cap("unsupported", "none", "The proxy speaks the MySQL and PostgreSQL wire protocols only.")
8927
- }, SQL_MODE_LIMITED = {
8928
- impactAssess: cap("limited", "local-write", "Runs against an ORM artifact; --against-cache needs a SQL connection."),
8929
- semantic: cap("limited", "readonly", "Governed context, search and drift work; draft validate needs a SQL connection."),
8930
- design: cap("limited", "local-write", "Authoring and ORM comparison work; --against-cache needs a SQL connection.")
8931
- }, ENGINE_CAPABILITIES = Object.freeze({
8932
- postgresql: Object.freeze({
8933
- ...SQL_BASE,
8934
- check: cap("limited", "readonly", "SQL-only and strongest on MySQL/MariaDB.")
8935
- }),
8936
- sqlite: Object.freeze({
8937
- ...SQL_BASE,
8938
- init: cap("supported", "local-write", "Asks for a file path and a permission; the file must already exist."),
8939
- use: cap("supported", "local-write", "Switches connections and displays the file path."),
8940
- schemaFullScan: cap("unsupported", "none", "Full schema cache scan is not implemented for SQLite."),
8941
- lint: cap("unsupported", "none", "Static lint is not enabled for the SQLite dialect yet."),
8942
- queries: cap("supported", "local-write", "Snippets may declare `engine: sqlite` and bind with `?` placeholders."),
8943
- insert: cap("supported", "db-write", "Row insert through the shared data-executor path."),
8944
- update: cap("supported", "db-write", "Row update with a mandatory --where clause."),
8945
- delete: cap("supported", "db-write", "Row delete with a mandatory --where clause."),
8946
- export: cap("supported", "local-write", "Exports SELECT results to json, csv or html through the shared query path."),
8947
- check: cap("unsupported", "none", "Data health check is not enabled for SQLite."),
8948
- diff: cap("unsupported", "none", "Schema snapshots are not enabled for SQLite."),
8949
- migrate: cap("unsupported", "none", "SQLite ALTER TABLE covers few operations and a column change is a table rebuild; no DDL generator exists."),
8950
- shell: cap("unsupported", "none", "The REPL is not enabled for SQLite."),
8951
- doctor: cap("supported", "readonly", "Checks the file exists, is readable, and is writable when the permission allows writes."),
8952
- inspect: cap("unsupported", "none", "Inspection collectors are not enabled for SQLite."),
8953
- report: cap("unsupported", "none", "Report diagnostics query engine-specific statistics views."),
8954
- guide: cap("unsupported", "none", "Guides depend on engine-specific EXPLAIN output."),
8955
- explain: cap("unsupported", "none", "EXPLAIN handling is not enabled for SQLite."),
8956
- plan: cap("unsupported", "none", "Plan capture is not enabled for SQLite."),
8957
- impactAssess: cap("unsupported", "none", "Impact assessment is not enabled for SQLite."),
8958
- assert: cap("unsupported", "none", "Assertions are not enabled for SQLite."),
8959
- snapshot: cap("unsupported", "none", "Result snapshots are not enabled for SQLite."),
8960
- verify: cap("unsupported", "none", "Write verification is not enabled for SQLite."),
8961
- semantic: cap("unsupported", "none", "Semantic context work is not enabled for SQLite."),
8962
- design: cap("unsupported", "none", "Schema design work is not enabled for SQLite."),
8963
- proxy: cap("not-applicable", "none", "SQLite has no network protocol to proxy."),
8964
- proxyAnalyze: cap("not-applicable", "none", "There are no SQLite proxy events to analyse."),
8965
- password: cap("not-applicable", "none", "A SQLite connection has no credential to store or rotate."),
8966
- 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.")
8967
- }),
8968
- mysql: Object.freeze(SQL_BASE),
8969
- mariadb: Object.freeze(SQL_BASE),
8970
- mongodb: Object.freeze({
8971
- ...SQL_BASE,
8972
- ...SQL_ONLY_UNSUPPORTED,
8973
- ...SQL_MODE_LIMITED,
8974
- ...SQL_MODE_LIMITED,
8975
- ...SQL_MODE_LIMITED,
8976
- lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
8977
- schemaSingle: cap("limited", "readonly", "MongoDB schema is sampled from documents."),
8978
- schemaFullScan: cap("limited", "readonly", "Full scan is sampled and document-oriented."),
8979
- query: cap("limited", "readonly", "Uses JSON filter or aggregation syntax, not SQL."),
8980
- queryLimitGuard: cap("limited", "readonly", "Applies result limits and collection size guard."),
8981
- queries: cap("limited", "local-write", "Snippet management works with MongoDB-specific saved-query limitations."),
8982
- q: cap("limited", "readonly", "Saved MongoDB snippets require collection frontmatter, accept JSON find/aggregate bodies, and JSON-encode parameter substitutions."),
8983
- insert: cap("limited", "db-write", "Document insert is supported with Mongo-specific behavior."),
8984
- update: cap("limited", "db-write", "Document update is supported with Mongo-specific behavior."),
8985
- delete: cap("limited", "db-write", "Document delete is supported with Mongo-specific behavior."),
8986
- export: cap("limited", "readonly", "MongoDB export is supported with document-query syntax."),
8987
- blacklistRead: cap("limited", "readonly", "Reads the engine-specific blacklist rules."),
8988
- blacklist: cap("limited", "local-write", "Rule management works; enforcement differs from SQL column enforcement."),
8989
- check: cap("unsupported", "none", "Data health check is SQL-only."),
8990
- diff: cap("unsupported", "none", "Schema snapshots are relational only."),
8991
- migrate: cap("unsupported", "none", "DDL migrations are SQL-only."),
8992
- shell: cap("limited", "interactive", "MongoDB shell support is narrower than SQL shell support.")
8993
- }),
8994
- redis: Object.freeze({
8995
- ...SQL_BASE,
8996
- ...SQL_ONLY_UNSUPPORTED,
8997
- lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
8998
- schemaSingle: cap("limited", "readonly", "Per-key synthetic schema only."),
8999
- schemaFullScan: cap("unsupported", "none", "Redis has no full schema cache scan."),
9000
- query: cap("limited", "readonly", "Runs allow-listed Redis commands."),
9001
- queryLimitGuard: cap("limited", "readonly", "SCAN/LRANGE/ZRANGE COUNT/stop rewrite; HGETALL/SMEMBERS/KEYS truncate at 1000; --no-limit bypasses."),
9002
- q: cap("limited", "readonly", "Saved Redis snippets use a read-only allowlist and range guards."),
9003
- queries: cap("limited", "local-write", "Snippet management works with Redis-specific saved-query limitations."),
9004
- insert: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
9005
- update: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
9006
- delete: cap("limited", "db-write", "Basic delete via DEL/HDEL/LREM/SREM/ZREM (needs data-admin); supports --dry-run."),
9007
- export: cap("unsupported", "none", "Redis export is not supported."),
9008
- blacklistRead: cap("limited", "readonly", "Reads the engine-specific blacklist rules."),
9009
- blacklist: cap("limited", "local-write", "Key-glob rejection (Redis-native pattern) plus value/hash-field masking ([REDACTED])."),
9010
- check: cap("unsupported", "none", "Data health check is SQL-only."),
9011
- diff: cap("unsupported", "none", "Schema snapshots are relational only."),
9012
- migrate: cap("unsupported", "none", "DDL migrations are SQL-only."),
9013
- shell: cap("limited", "interactive", "Single-line Redis REPL (no trailing semicolon needed) with auto-limit and key-glob blacklist; narrower than SQL shell.")
9014
- }),
9015
- elasticsearch: Object.freeze({
9016
- ...SQL_BASE,
9017
- ...SQL_ONLY_UNSUPPORTED,
9018
- lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
9019
- schemaSingle: cap("limited", "readonly", "Schema flattens index mappings."),
9020
- schemaFullScan: cap("supported", "readonly", "Full scan iterates non-system indices."),
9021
- query: cap("limited", "readonly", "Uses JSON DSL or Lucene query strings with an index."),
9022
- queryLimitGuard: cap("limited", "readonly", "Applies Elasticsearch size guard."),
9023
- q: cap("limited", "readonly", "Saved ES snippets require index frontmatter and reject scripts."),
9024
- queries: cap("limited", "local-write", "Snippet management works with Elasticsearch-specific saved-query limitations."),
9025
- insert: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
9026
- update: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
9027
- delete: cap("unsupported", "none", "Dedicated write subcommand is not exposed."),
9028
- export: cap("limited", "readonly", "Exports DSL hits or a full index via scroll, capped at 1000 rows (use --no-limit)."),
9029
- blacklistRead: cap("limited", "readonly", "Reads the engine-specific blacklist rules."),
9030
- blacklist: cap("limited", "local-write", "Index-level and flattened-column enforcement differ from SQL."),
9031
- check: cap("unsupported", "none", "Data health check is SQL-only."),
9032
- diff: cap("unsupported", "none", "Schema snapshots are relational only."),
9033
- migrate: cap("unsupported", "none", "DDL migrations are SQL-only."),
9034
- shell: cap("limited", "interactive", "Dedicated Kibana Dev Tools-style REPL (method/path + JSON body, blank-line submit); read-focused.")
9035
- })
9036
- });
9037
- });
9038
-
9039
9114
  // src/adapters/error-mapper.ts
9040
9115
  function isTransportDriverCode(code) {
9041
9116
  return TLS_CODE_PREFIX.test(code) || TRANSPORT_CODES[code] !== void 0;
@@ -9115,6 +9190,12 @@ function mapError(error, system, options) {
9115
9190
  `Confirm the address is the one you meant: ${options.host}:${options.port}`
9116
9191
  ]);
9117
9192
  case "TLS_ERROR":
9193
+ if (options.sqlTls !== void 0)
9194
+ return new ConnectionError("TLS_ERROR", t_vars("errors.sql_tls_handshake", { detail: errMsg }), [
9195
+ t("errors.sql_tls_ca_hint"),
9196
+ t_vars("errors.sql_tls_identity_hint", { host: options.host }),
9197
+ t("errors.sql_tls_no_downgrade")
9198
+ ]);
9118
9199
  return new ConnectionError("TLS_ERROR", `TLS handshake failed: ${errMsg}`, [
9119
9200
  'Point the connection at the right CA bundle: set "caPath" in .dbcli',
9120
9201
  'For a self-signed certificate in a trusted network, set "rejectUnauthorized": false',
@@ -9180,8 +9261,11 @@ function mapError(error, system, options) {
9180
9261
  }
9181
9262
  var TRANSPORT_CODES, TLS_CODE_PREFIX, MYSQL_CODES, MYSQL_ERRNOS, SQLSTATES, REDIS_PREFIXES, FALLBACK_PATTERNS, PG_CANCEL_BY_TIMEOUT, SYSTEM_FACTS;
9182
9263
  var init_error_mapper = __esm(() => {
9264
+ init_message_loader();
9183
9265
  init_types2();
9184
9266
  TRANSPORT_CODES = {
9267
+ HANDSHAKE_SSL_ERROR: "TLS_ERROR",
9268
+ HANDSHAKE_NO_SSL_SUPPORT: "TLS_ERROR",
9185
9269
  ECONNREFUSED: "ECONNREFUSED",
9186
9270
  ETIMEDOUT: "ETIMEDOUT",
9187
9271
  ESOCKETTIMEDOUT: "ETIMEDOUT",
@@ -9249,6 +9333,7 @@ var init_error_mapper = __esm(() => {
9249
9333
  NOAUTH: "AUTH_FAILED",
9250
9334
  WRONGPASS: "AUTH_FAILED"
9251
9335
  }, FALLBACK_PATTERNS = [
9336
+ [/server does not support SSL connections/i, "TLS_ERROR"],
9252
9337
  [/connection refused/i, "ECONNREFUSED"],
9253
9338
  [/\bECONNREFUSED\b/, "ECONNREFUSED"],
9254
9339
  [/connect(?:ion)? time(?:d )?out/i, "ETIMEDOUT"],
@@ -9495,6 +9580,51 @@ function statementTimeoutSql(system, ms) {
9495
9580
  return `SET SESSION max_execution_time = ${Math.round(ms)}`;
9496
9581
  }
9497
9582
 
9583
+ // src/adapters/sql-tls.ts
9584
+ import { X509Certificate } from "crypto";
9585
+ async function sqlTlsCa(options) {
9586
+ if (options.sqlTls === void 0)
9587
+ return;
9588
+ let config = SqlTlsConfigSchema.parse(options.sqlTls);
9589
+ if (!options.host || options.host.startsWith("/"))
9590
+ throw new ConnectionError("TLS_ERROR", t("errors.sql_tls_network_host"), []);
9591
+ try {
9592
+ let ca = await Bun.file(config.caPath).text(), certificates = ca.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
9593
+ if (!certificates?.length)
9594
+ throw Error(t("errors.sql_tls_ca_empty"));
9595
+ for (let pem of certificates)
9596
+ new X509Certificate(pem);
9597
+ return ca;
9598
+ } catch (cause) {
9599
+ throw new ConnectionError("TLS_ERROR", t_vars("errors.sql_tls_ca_load", {
9600
+ detail: cause instanceof Error ? cause.message : String(cause)
9601
+ }), [t("errors.sql_tls_ca_restore")]);
9602
+ }
9603
+ }
9604
+ var init_sql_tls = __esm(() => {
9605
+ init_message_loader();
9606
+ init_validation();
9607
+ init_types2();
9608
+ });
9609
+
9610
+ // src/adapters/postgresql-tls.ts
9611
+ import { isIP } from "net";
9612
+ import { checkServerIdentity } from "tls";
9613
+ async function postgresqlTlsOptions(options) {
9614
+ let ca = await sqlTlsCa(options);
9615
+ if (ca === void 0)
9616
+ return;
9617
+ return {
9618
+ ca,
9619
+ rejectUnauthorized: !0,
9620
+ ...isIP(options.host) === 0 && { servername: options.host },
9621
+ checkServerIdentity: (_hostname, certificate) => checkServerIdentity(options.host, certificate)
9622
+ };
9623
+ }
9624
+ var init_postgresql_tls = __esm(() => {
9625
+ init_sql_tls();
9626
+ });
9627
+
9498
9628
  // src/utils/connection-error-message.ts
9499
9629
  function isTransportFailure(error) {
9500
9630
  if (error instanceof ConnectionError)
@@ -9547,7 +9677,7 @@ class PostgreSQLAdapter {
9547
9677
  }
9548
9678
  async connect() {
9549
9679
  await withMappedConnectionError("postgresql", this.options, async () => {
9550
- let timeouts = resolveTimeoutPolicy(this.options), { Pool } = await import("pg");
9680
+ let timeouts = resolveTimeoutPolicy(this.options), ssl = await postgresqlTlsOptions(this.options), { Pool } = await import("pg");
9551
9681
  this.pool = new Pool({
9552
9682
  host: this.options.host,
9553
9683
  port: this.options.port,
@@ -9555,6 +9685,7 @@ class PostgreSQLAdapter {
9555
9685
  password: this.options.password,
9556
9686
  database: this.options.database,
9557
9687
  connectionTimeoutMillis: timeouts.connectMs,
9688
+ ...ssl && { ssl },
9558
9689
  ...timeouts.statementMs !== void 0 && { statement_timeout: timeouts.statementMs }
9559
9690
  }), await this.testConnection();
9560
9691
  try {
@@ -9861,6 +9992,7 @@ var init_postgresql_adapter = __esm(() => {
9861
9992
  init_error_mapper();
9862
9993
  init_db_version_check();
9863
9994
  init_encoding();
9995
+ init_postgresql_tls();
9864
9996
  init_connection_error_message();
9865
9997
  });
9866
9998
 
@@ -10189,6 +10321,59 @@ var init_grant_redaction = __esm(() => {
10189
10321
  ]);
10190
10322
  });
10191
10323
 
10324
+ // src/adapters/mysql-tls.ts
10325
+ import { checkServerIdentity as checkServerIdentity2, TLSSocket } from "tls";
10326
+ async function connectVerifiedMysql(options, ca, host) {
10327
+ let { default: driver } = await import("mysql2"), connection = driver.createConnection({
10328
+ ...options,
10329
+ ssl: {
10330
+ ca,
10331
+ rejectUnauthorized: !0,
10332
+ verifyIdentity: !1
10333
+ }
10334
+ });
10335
+ return guardMysqlIdentity(connection, host), new Promise((resolve, reject) => {
10336
+ connection.connect((error) => {
10337
+ if (error)
10338
+ connection.destroy(), reject(error);
10339
+ else
10340
+ resolve(connection.promise());
10341
+ });
10342
+ });
10343
+ }
10344
+ function guardMysqlIdentity(connection, host) {
10345
+ let startTLS = connection.startTLS;
10346
+ if (typeof startTLS !== "function")
10347
+ throw connection.destroy(), new ConnectionError("TLS_ERROR", t("errors.sql_tls_driver_identity"), []);
10348
+ connection.startTLS = (onSecure) => {
10349
+ let completed = !1;
10350
+ startTLS.call(connection, (error) => {
10351
+ if (completed)
10352
+ return;
10353
+ completed = !0;
10354
+ let failure = error;
10355
+ if (!failure)
10356
+ try {
10357
+ let socket = connection.stream;
10358
+ if (!(socket instanceof TLSSocket) || !socket.encrypted || !socket.authorized)
10359
+ throw Error(t("errors.sql_tls_peer_unverified"));
10360
+ let certificate = socket.getPeerCertificate(!0);
10361
+ if (!certificate.raw?.length)
10362
+ throw Error(t("errors.sql_tls_peer_missing"));
10363
+ failure = checkServerIdentity2(host, certificate);
10364
+ } catch (cause) {
10365
+ failure = cause instanceof Error ? cause : Error(String(cause));
10366
+ }
10367
+ if (onSecure(failure), failure)
10368
+ connection.destroy();
10369
+ });
10370
+ };
10371
+ }
10372
+ var init_mysql_tls = __esm(() => {
10373
+ init_types2();
10374
+ init_message_loader();
10375
+ });
10376
+
10192
10377
  // src/adapters/mysql-adapter.ts
10193
10378
  function parseEnumValues(columnType) {
10194
10379
  let match = columnType.match(/^enum\((.+)\)$/i);
@@ -10209,8 +10394,7 @@ class MySQLAdapter {
10209
10394
  }
10210
10395
  async connect() {
10211
10396
  this.serverVersion = void 0, await withMappedConnectionError(this.system, this.options, async () => {
10212
- let timeouts = resolveTimeoutPolicy(this.options), { default: driver } = await import("mysql2/promise");
10213
- if (this.db = await driver.createConnection({
10397
+ let timeouts = resolveTimeoutPolicy(this.options), ca = await sqlTlsCa(this.options), connectionOptions = {
10214
10398
  host: this.options.host,
10215
10399
  port: this.options.port,
10216
10400
  user: this.options.user,
@@ -10218,7 +10402,14 @@ class MySQLAdapter {
10218
10402
  database: this.options.database,
10219
10403
  charset: "utf8mb4",
10220
10404
  connectTimeout: timeouts.connectMs
10221
- }), await this.db.execute("SET NAMES utf8mb4"), timeouts.statementMs !== void 0)
10405
+ };
10406
+ if (ca !== void 0)
10407
+ this.db = await connectVerifiedMysql(connectionOptions, ca, this.options.host);
10408
+ else {
10409
+ let { default: driver } = await import("mysql2/promise");
10410
+ this.db = await driver.createConnection(connectionOptions);
10411
+ }
10412
+ if (await this.db.execute("SET NAMES utf8mb4"), timeouts.statementMs !== void 0)
10222
10413
  try {
10223
10414
  await this.db.query(statementTimeoutSql(this.system, timeouts.statementMs));
10224
10415
  } catch {}
@@ -10453,6 +10644,8 @@ var init_mysql_adapter = __esm(() => {
10453
10644
  init_connection_error_message();
10454
10645
  init_grant_query();
10455
10646
  init_grant_redaction();
10647
+ init_sql_tls();
10648
+ init_mysql_tls();
10456
10649
  });
10457
10650
 
10458
10651
  // src/adapters/sqlite-adapter.ts
@@ -12946,8 +13139,14 @@ var init_blacklist_manager = __esm(() => {
12946
13139
  });
12947
13140
 
12948
13141
  // src/adapters/factory.ts
13142
+ function assertSqlTlsEngine(options) {
13143
+ if (options.sqlTls !== void 0 && !supportsSqlTls(options.system))
13144
+ throw Error(t("errors.sql_tls_unsupported"));
13145
+ }
13146
+
12949
13147
  class AdapterFactory {
12950
13148
  static createSqlAdapter(rawOptions) {
13149
+ assertSqlTlsEngine(rawOptions);
12951
13150
  let options = withResolvedTimeout(rawOptions);
12952
13151
  switch (options.system) {
12953
13152
  case "postgresql":
@@ -12962,6 +13161,7 @@ class AdapterFactory {
12962
13161
  }
12963
13162
  }
12964
13163
  static createQueryableAdapterWithoutRules(rawOptions) {
13164
+ assertSqlTlsEngine(rawOptions);
12965
13165
  let options = withResolvedTimeout(rawOptions);
12966
13166
  switch (options.system) {
12967
13167
  case "mongodb":
@@ -13016,7 +13216,7 @@ class AdapterFactory {
13016
13216
  }
13017
13217
  static createRedisAdapter(config) {
13018
13218
  let rawOptions = config.connection;
13019
- if (rawOptions.system !== "redis")
13219
+ if (assertSqlTlsEngine(rawOptions), rawOptions.system !== "redis")
13020
13220
  throw Error("createRedisAdapter requires system: redis");
13021
13221
  let options = withResolvedTimeout(rawOptions), adapter = new RedisAdapter(options), blacklistRules = config.blacklist?.tables ?? [], overrideEnabled = isBlacklistOverrideEnabled();
13022
13222
  if (overrideEnabled && blacklistRules.length > 0)
@@ -13038,6 +13238,7 @@ var init_factory = __esm(() => {
13038
13238
  init_elasticsearch_adapter();
13039
13239
  init_blacklist_manager();
13040
13240
  init_message_loader();
13241
+ init_capabilities();
13041
13242
  });
13042
13243
 
13043
13244
  // src/adapters/index.ts
@@ -13067,6 +13268,11 @@ async function checkOverwrite(configPath, shouldPrompt, force) {
13067
13268
  async function assertV2InitImportSupported(configPath) {
13068
13269
  if (isGlobalConfigPath(configPath))
13069
13270
  return;
13271
+ let legacyFile = Bun.file(configPath);
13272
+ if ((await legacyFile.stat().catch(() => null))?.isFile()) {
13273
+ if ((await legacyFile.json()).connection?.sqlTls !== void 0)
13274
+ throw Error(t("errors.sql_tls_legacy_import"));
13275
+ }
13070
13276
  let projectConfig = Bun.file(join10(configPath, "config.json"));
13071
13277
  if (!await projectConfig.exists())
13072
13278
  return;
@@ -16624,7 +16830,7 @@ var init_init_sqlite = __esm(() => {
16624
16830
  });
16625
16831
 
16626
16832
  // src/commands/init.ts
16627
- import { join as join11 } from "path";
16833
+ import { join as join11, resolve as resolve3 } from "path";
16628
16834
  import { mkdir as mkdir8 } from "fs/promises";
16629
16835
  async function handleRemove(configPath, name) {
16630
16836
  let storagePath = await resolveConfigStoragePath(configPath), configFile = Bun.file(join11(storagePath, "config.json"));
@@ -16710,6 +16916,25 @@ async function initCommandHandler(options, command) {
16710
16916
  system = await promptUser.select(t("init.select_system"), [...INIT_SYSTEM_CHOICES]);
16711
16917
  if (!DATABASE_SYSTEMS.includes(system))
16712
16918
  throw Error(t_vars("errors.invalid_system", { system }));
16919
+ let hasSqlTls = options.sqlTls !== void 0 || options.sqlTlsCa !== void 0, sqlTls;
16920
+ if (hasSqlTls) {
16921
+ if (!supportsSqlTls(system))
16922
+ throw Error(t("errors.sql_tls_unsupported"));
16923
+ if (options.sqlTls !== "verify-full" || typeof options.sqlTlsCa !== "string" || !options.sqlTlsCa.trim())
16924
+ throw Error(t("errors.sql_tls_options_required"));
16925
+ sqlTls = SqlTlsConfigSchema.parse({ mode: options.sqlTls, caPath: resolve3(options.sqlTlsCa) });
16926
+ }
16927
+ let existingStorage = await resolveConfigStoragePath(configPath), existingDocument = Bun.file(join11(existingStorage, "config.json"));
16928
+ if (await existingDocument.exists()) {
16929
+ if (detectConfigVersion(await existingDocument.json()) === 2) {
16930
+ let prior = (await readV2Config(existingStorage)).connections[connectionName];
16931
+ if (prior && "sqlTls" in prior)
16932
+ sqlTls ??= prior.sqlTls;
16933
+ } else if (connectionName === "default" && existingConfig && "sqlTls" in existingConfig.connection)
16934
+ sqlTls ??= existingConfig.connection.sqlTls;
16935
+ }
16936
+ if (sqlTls && !supportsSqlTls(system))
16937
+ throw Error(t("errors.sql_tls_change_engine"));
16713
16938
  if (system === "sqlite") {
16714
16939
  await handleSQLiteInit({
16715
16940
  options,
@@ -16734,7 +16959,8 @@ async function initCommandHandler(options, command) {
16734
16959
  return;
16735
16960
  }
16736
16961
  let connection = {
16737
- system
16962
+ system,
16963
+ ...sqlTls && { sqlTls }
16738
16964
  }, configForWrite;
16739
16965
  if (options.useEnvRefs && shouldPrompt) {
16740
16966
  let envHost = options.envHost || await promptUser.text(t("init.prompt_host"), "DB_HOST"), envPort = options.envPort || await promptUser.text(t("init.prompt_port"), "DB_PORT"), envUser = options.envUser || await promptUser.text(t("init.prompt_user"), "DB_USER"), envPassword = options.envPassword || await promptUser.text(t("init.prompt_password"), "DB_PASSWORD"), envDatabase = options.envDatabase || await promptUser.text(t("init.prompt_name"), "DB_DATABASE");
@@ -16744,7 +16970,8 @@ async function initCommandHandler(options, command) {
16744
16970
  port: { $env: envPort },
16745
16971
  user: { $env: envUser },
16746
16972
  password: { $env: envPassword },
16747
- database: { $env: envDatabase }
16973
+ database: { $env: envDatabase },
16974
+ ...sqlTls && { sqlTls }
16748
16975
  };
16749
16976
  let permission = (typeof options.permission === "string" ? options.permission : void 0) ?? "query-only";
16750
16977
  if (!options.permission)
@@ -16803,7 +17030,8 @@ async function initCommandHandler(options, command) {
16803
17030
  port: { $env: envPort },
16804
17031
  user: { $env: envUser },
16805
17032
  password: { $env: envPassword },
16806
- database: { $env: envDatabase }
17033
+ database: { $env: envDatabase },
17034
+ ...sqlTls && { sqlTls }
16807
17035
  };
16808
17036
  }
16809
17037
  let updates = {
@@ -16828,7 +17056,8 @@ async function initCommandHandler(options, command) {
16828
17056
  port: parseInt(String(resolveValue(newConfig.connection.port, "port")), 10) || 5432,
16829
17057
  user: String(resolveValue(newConfig.connection.user, "user")),
16830
17058
  password: String(resolveValue(newConfig.connection.password, "password")) || "",
16831
- database: String(resolveValue(newConfig.connection.database, "database"))
17059
+ database: String(resolveValue(newConfig.connection.database, "database")),
17060
+ ...sqlTls && { sqlTls }
16832
17061
  }, adapter = AdapterFactory.createAdapterWithoutRules(testConnection);
16833
17062
  try {
16834
17063
  if (await adapter.connect(), await adapter.testConnection())
@@ -16862,17 +17091,19 @@ var init_init = __esm(() => {
16862
17091
  init_config();
16863
17092
  init_config_v2();
16864
17093
  init_config_v2_mutations();
17094
+ init_capabilities();
16865
17095
  init_prompts();
16866
17096
  init_redaction();
16867
17097
  init_adapters();
16868
17098
  init_types2();
17099
+ init_validation();
16869
17100
  init_config_path();
16870
17101
  init_config_binding();
16871
17102
  init_init_shared();
16872
17103
  init_init_mongodb();
16873
17104
  init_init_sqlite();
16874
17105
  INIT_SYSTEM_CHOICES = DATABASE_SYSTEMS, VALID_PERMISSIONS3 = ["query-only", "read-write", "data-admin", "admin"];
16875
- initCommand = new Command2("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 string, decomposed into fields on write (mongodb://user:pass@host:27017/db?authSource=admin)").option("--auth-source <authSource>", "MongoDB auth database (default: admin when user/password are set)").option("--replica-set <name>", "MongoDB replica set name").option("--srv", "MongoDB: connect via a mongodb+srv:// DNS SRV domain (port is not written)").option("--tls", "MongoDB: enable TLS (omit both --tls and --no-tls to use the driver default)").option("--no-tls", "MongoDB: disable TLS").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)", !1).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 (required in non-interactive mode)").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) => {
17106
+ initCommand = new Command2("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("--sql-tls <mode>", t("init.sql_tls_help")).option("--sql-tls-ca <path>", t("init.sql_tls_ca_help")).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 string, decomposed into fields on write (mongodb://user:pass@host:27017/db?authSource=admin)").option("--auth-source <authSource>", "MongoDB auth database (default: admin when user/password are set)").option("--replica-set <name>", "MongoDB replica set name").option("--srv", "MongoDB: connect via a mongodb+srv:// DNS SRV domain (port is not written)").option("--tls", "MongoDB: enable TLS (omit both --tls and --no-tls to use the driver default)").option("--no-tls", "MongoDB: disable TLS").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)", !1).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 (required in non-interactive mode)").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) => {
16876
17107
  try {
16877
17108
  await initCommandHandler(options, initCommand);
16878
17109
  } catch (error) {
@@ -18917,7 +19148,7 @@ var init_assert_artifact = __esm(() => {
18917
19148
 
18918
19149
  // src/core/verification/reader.ts
18919
19150
  import { lstat as lstat2, readdir, readFile as readFile2 } from "fs/promises";
18920
- import { join as join13, isAbsolute, resolve as resolve3, sep } from "path";
19151
+ import { join as join13, isAbsolute as isAbsolute2, resolve as resolve4, sep } from "path";
18921
19152
  function isArtifactFilename(name) {
18922
19153
  return /^verification-.*\.json$/.test(name);
18923
19154
  }
@@ -19058,17 +19289,17 @@ function summarizeVerificationArtifacts(input, filters) {
19058
19289
  return { storageDir: input.storageDir, latest, counts, subjects };
19059
19290
  }
19060
19291
  function looksLikePath(selector) {
19061
- return selector.includes("/") || selector.includes("\\") || isAbsolute(selector);
19292
+ return selector.includes("/") || selector.includes("\\") || isAbsolute2(selector);
19062
19293
  }
19063
19294
  function findVerificationArtifact(input, selector) {
19064
19295
  if (looksLikePath(selector)) {
19065
- let resolved = resolve3(selector), root = resolve3(input.storageDir);
19296
+ let resolved = resolve4(selector), root = resolve4(input.storageDir);
19066
19297
  if (resolved !== root && !resolved.startsWith(root + sep))
19067
19298
  throw new VerificationArtifactSelectionError(`Path is outside the verification directory: ${resolved}`);
19068
- let hit = input.artifacts.find((r) => resolve3(r.path) === resolved);
19299
+ let hit = input.artifacts.find((r) => resolve4(r.path) === resolved);
19069
19300
  if (hit)
19070
19301
  return hit;
19071
- let bad = input.invalid.find((r) => resolve3(r.path) === resolved);
19302
+ let bad = input.invalid.find((r) => resolve4(r.path) === resolved);
19072
19303
  if (bad)
19073
19304
  throw new VerificationArtifactSelectionError(`Artifact at ${bad.filename} is invalid: ${bad.error}`);
19074
19305
  throw new VerificationArtifactSelectionError(`No artifact found at path: ${resolved}`);
@@ -19104,7 +19335,7 @@ var init_reader = __esm(() => {
19104
19335
 
19105
19336
  // src/core/verification/retention.ts
19106
19337
  import { lstat as lstat3, unlink as unlink6 } from "fs/promises";
19107
- import { basename as basename2, resolve as resolve4, sep as sep2 } from "path";
19338
+ import { basename as basename2, resolve as resolve5, sep as sep2 } from "path";
19108
19339
  function parseOlderThanDays(raw) {
19109
19340
  let match = /^(\d+)d$/.exec(raw);
19110
19341
  if (!match)
@@ -19153,14 +19384,14 @@ function boundError2(message) {
19153
19384
  return single.length <= 200 ? single : single.slice(0, 199) + "\u2026";
19154
19385
  }
19155
19386
  function isInsideStorageDir(storageDir, candidatePath) {
19156
- let base = resolve4(storageDir), resolved = resolve4(candidatePath);
19387
+ let base = resolve5(storageDir), resolved = resolve5(candidatePath);
19157
19388
  return resolved === base || resolved.startsWith(base + sep2);
19158
19389
  }
19159
19390
  function hasArtifactFilename(candidatePath) {
19160
19391
  return /^verification-.*\.json$/.test(basename2(candidatePath));
19161
19392
  }
19162
19393
  async function safeDeleteCandidate(storageDir, candidate) {
19163
- let resolved = resolve4(candidate.path);
19394
+ let resolved = resolve5(candidate.path);
19164
19395
  if (!isInsideStorageDir(storageDir, resolved))
19165
19396
  return { ok: !1, reason: "outside-storage-dir" };
19166
19397
  if (!hasArtifactFilename(resolved))
@@ -19306,7 +19537,7 @@ var init_legacy = __esm(() => {
19306
19537
  // src/core/evidence-receipt/index.ts
19307
19538
  import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
19308
19539
  import { link as link2, lstat as lstat4, mkdir as mkdir10, readFile as readFile3, realpath, unlink as unlink7, writeFile as writeFile4 } from "fs/promises";
19309
- import { basename as basename3, dirname as dirname5, relative, resolve as resolve5, sep as sep3 } from "path";
19540
+ import { basename as basename3, dirname as dirname5, relative, resolve as resolve6, sep as sep3 } from "path";
19310
19541
  function exact(value, keys, label) {
19311
19542
  if (Object.keys(value).length !== keys.length || Object.keys(value).some((key) => !keys.includes(key)))
19312
19543
  throw new EvidenceReceiptValidationError(`${label} contains an unknown field`);
@@ -19546,7 +19777,7 @@ async function existingAncestor(path) {
19546
19777
  }
19547
19778
  async function writeEvidenceReceipt(workspaceRoot, outputPath, receipt) {
19548
19779
  parseEvidenceReceipt(receipt);
19549
- let workspace = await realpath(workspaceRoot), requested = resolve5(workspace, outputPath);
19780
+ let workspace = await realpath(workspaceRoot), requested = resolve6(workspace, outputPath);
19550
19781
  if (!inside(workspace, requested))
19551
19782
  throw new EvidenceReceiptValidationError("output path must stay inside the workspace");
19552
19783
  let parent = dirname5(requested);
@@ -19556,7 +19787,7 @@ async function writeEvidenceReceipt(workspaceRoot, outputPath, receipt) {
19556
19787
  let realParent = await realpath(parent);
19557
19788
  if (!inside(workspace, realParent))
19558
19789
  throw new EvidenceReceiptValidationError("output path resolves outside the workspace");
19559
- let target = resolve5(realParent, basename3(requested));
19790
+ let target = resolve6(realParent, basename3(requested));
19560
19791
  try {
19561
19792
  throw await lstat4(target), new EvidenceReceiptValidationError("evidence receipt output already exists");
19562
19793
  } catch (error) {
@@ -19565,7 +19796,7 @@ async function writeEvidenceReceipt(workspaceRoot, outputPath, receipt) {
19565
19796
  if (error.code !== "ENOENT")
19566
19797
  throw error;
19567
19798
  }
19568
- let temp = resolve5(realParent, `.${basename3(target)}.${process.pid}.${randomUUID2()}.tmp`);
19799
+ let temp = resolve6(realParent, `.${basename3(target)}.${process.pid}.${randomUUID2()}.tmp`);
19569
19800
  try {
19570
19801
  await writeFile4(temp, `${JSON.stringify(receipt, null, 2)}
19571
19802
  `, "utf8");
@@ -25777,8 +26008,16 @@ var init_apply_gate = __esm(() => {
25777
26008
  });
25778
26009
 
25779
26010
  // src/core/recovery/apply-exec.ts
26011
+ import { fileURLToPath } from "url";
25780
26012
  async function executeStep(argv, opts) {
25781
- let stdoutCap = opts.stdoutCap ?? 65536, stderrCap = opts.stderrCap ?? 65536, startedAt = performance.now(), proc = Bun.spawn(argv, {
26013
+ let stdoutCap = opts.stdoutCap ?? DEFAULT_CAP, stderrCap = opts.stderrCap ?? DEFAULT_CAP, startedAt = performance.now(), command = argv[0] === "dbcli" ? [
26014
+ process.execPath,
26015
+ "--no-env-file",
26016
+ CLI_ENTRY,
26017
+ ...opts.target ? ["--config", opts.target.configPath] : [],
26018
+ ...opts.target?.connectionName ? ["--use", opts.target.connectionName] : [],
26019
+ ...argv.slice(1)
26020
+ ] : argv, proc = Bun.spawn(command, {
25782
26021
  cwd: opts.cwd,
25783
26022
  env: opts.env,
25784
26023
  stdin: "ignore",
@@ -25826,6 +26065,10 @@ async function capStream(stream, cap) {
25826
26065
  } catch {}
25827
26066
  return { text, truncated };
25828
26067
  }
26068
+ var CLI_ENTRY, DEFAULT_CAP = 65536;
26069
+ var init_apply_exec = __esm(() => {
26070
+ CLI_ENTRY = fileURLToPath(new URL(import.meta.url.endsWith(".ts") ? "../../cli.ts" : "./cli.mjs", import.meta.url));
26071
+ });
25829
26072
 
25830
26073
  // src/core/recovery/verify-heuristic.ts
25831
26074
  function evaluateVerify(code, outcome) {
@@ -25894,7 +26137,8 @@ async function runVerifyStep(step, ctx) {
25894
26137
  env: ctx.env,
25895
26138
  timeoutMs: ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS,
25896
26139
  stdoutCap: ctx.stdoutCap,
25897
- stderrCap: ctx.stderrCap
26140
+ stderrCap: ctx.stderrCap,
26141
+ target: ctx.target
25898
26142
  }), ok = outcome.exitCode === 0 && !outcome.timedOut, result = {
25899
26143
  order: VERIFY_ORDER_SENTINEL,
25900
26144
  command: step.command,
@@ -25915,6 +26159,7 @@ async function runVerifyStep(step, ctx) {
25915
26159
  var executor, DEFAULT_TIMEOUT_MS = 60000, VERIFY_ORDER_SENTINEL = 0;
25916
26160
  var init_apply_verify = __esm(() => {
25917
26161
  init_apply_gate();
26162
+ init_apply_exec();
25918
26163
  init_verification();
25919
26164
  executor = executeStep;
25920
26165
  });
@@ -25923,6 +26168,15 @@ var init_apply_verify = __esm(() => {
25923
26168
  async function runApply(input, opts) {
25924
26169
  let startedAt = (/* @__PURE__ */ new Date()).toISOString(), results = [], finalStatus = "skipped-only", stoppedAt = null;
25925
26170
  for (let step of input.envelope.recovery) {
26171
+ if (input.envelope.error.details?.connectionCode === "TLS_ERROR" && !opts.target) {
26172
+ results.push({
26173
+ order: step.order,
26174
+ command: step.command,
26175
+ status: "skipped:unsafe-command",
26176
+ reason: t("errors.sql_tls_recovery_target")
26177
+ });
26178
+ continue;
26179
+ }
25926
26180
  let decision = classifyStep(step, opts.allowWrite, input.envelope.error.code);
25927
26181
  if (decision.kind !== "run") {
25928
26182
  results.push({
@@ -25938,7 +26192,8 @@ async function runApply(input, opts) {
25938
26192
  env: process.env,
25939
26193
  timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
25940
26194
  stdoutCap: opts.stdoutCap,
25941
- stderrCap: opts.stderrCap
26195
+ stderrCap: opts.stderrCap,
26196
+ target: opts.target
25942
26197
  }), ok = outcome.exitCode === 0 && !outcome.timedOut;
25943
26198
  if (results.push({
25944
26199
  order: step.order,
@@ -25963,7 +26218,8 @@ async function runApply(input, opts) {
25963
26218
  env: process.env,
25964
26219
  timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
25965
26220
  stdoutCap: opts.stdoutCap,
25966
- stderrCap: opts.stderrCap
26221
+ stderrCap: opts.stderrCap,
26222
+ target: opts.target
25967
26223
  });
25968
26224
  verifyResult = v.result, verifyStatus = v.status, verificationStatus = v.contractStatus, verificationBlockedReason = v.blockedReason;
25969
26225
  }
@@ -25984,7 +26240,9 @@ async function runApply(input, opts) {
25984
26240
  }
25985
26241
  var executor2, DEFAULT_TIMEOUT_MS2 = 60000;
25986
26242
  var init_apply = __esm(() => {
26243
+ init_message_loader();
25987
26244
  init_apply_gate();
26245
+ init_apply_exec();
25988
26246
  init_apply_verify();
25989
26247
  init_apply_types();
25990
26248
  executor2 = executeStep;
@@ -26030,7 +26288,7 @@ var NEXT_SCHEMA_VERSION = 1, STEP_RESULT_SUMMARY_FIELD_CAP = 4096;
26030
26288
 
26031
26289
  // src/core/recovery/next-step-schema.ts
26032
26290
  import { stat as stat5 } from "fs/promises";
26033
- import { resolve as resolve7 } from "path";
26291
+ import { resolve as resolve8 } from "path";
26034
26292
  function summarizeZodError(err) {
26035
26293
  return err.issues.map((iss) => `${iss.path.join(".") || "<root>"}: ${iss.message}`).join("; ");
26036
26294
  }
@@ -26045,7 +26303,7 @@ async function loadStepResultSummary(arg, cwd) {
26045
26303
  return { ok: !1, reason: "empty --result value" };
26046
26304
  let raw;
26047
26305
  if (arg.startsWith("@")) {
26048
- let path = resolve7(cwd, arg.slice(1));
26306
+ let path = resolve8(cwd, arg.slice(1));
26049
26307
  try {
26050
26308
  await stat5(path);
26051
26309
  } catch {
@@ -31556,12 +31814,12 @@ function escapeXml(str) {
31556
31814
 
31557
31815
  // src/core/data-access/index.ts
31558
31816
  import { lstat as lstat5, realpath as realpath2 } from "fs/promises";
31559
- import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolve8, sep as sep5 } from "path";
31817
+ import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve9, sep as sep5 } from "path";
31560
31818
  function hasDataAccessReferenceIssue(issues) {
31561
31819
  return issues.some(({ message }) => REFERENCE_MESSAGES3.has(message));
31562
31820
  }
31563
31821
  async function loadDataAccessManifest(input) {
31564
- let workspace = await safeWorkspace(input.workspaceRoot, []), requested = resolve8(workspace, input.filePath ?? defaultDataAccessManifestFile(workspace));
31822
+ let workspace = await safeWorkspace(input.workspaceRoot, []), requested = resolve9(workspace, input.filePath ?? defaultDataAccessManifestFile(workspace));
31565
31823
  if (!inside2(workspace, requested))
31566
31824
  throw new DataAccessManifestValidationError([
31567
31825
  { path: "$", message: "file must stay inside the workspace" }
@@ -31655,16 +31913,16 @@ async function safeWorkspace(workspaceRoot, issues) {
31655
31913
  try {
31656
31914
  return await realpath2(workspaceRoot);
31657
31915
  } catch {
31658
- return issue3(issues, "$", "workspace is unavailable"), resolve8(workspaceRoot);
31916
+ return issue3(issues, "$", "workspace is unavailable"), resolve9(workspaceRoot);
31659
31917
  }
31660
31918
  }
31661
31919
  async function normalizeSource(operation, index, workspace, issues) {
31662
31920
  let path = `$.operations[${index}].source`;
31663
31921
  if (operation.source.length === 0 || operation.source.length > MAX_NAME_LENGTH2 || /[\0\r\n]/.test(operation.source))
31664
31922
  return issue3(issues, path, "must be a bounded single-line source path"), operation;
31665
- if (isAbsolute2(operation.source))
31923
+ if (isAbsolute3(operation.source))
31666
31924
  return issue3(issues, path, "must be workspace-relative"), operation;
31667
- let target = resolve8(workspace, operation.source);
31925
+ let target = resolve9(workspace, operation.source);
31668
31926
  if (!inside2(workspace, target))
31669
31927
  return issue3(issues, path, "must stay inside the workspace"), operation;
31670
31928
  try {
@@ -31682,7 +31940,7 @@ async function normalizeSource(operation, index, workspace, issues) {
31682
31940
  }
31683
31941
  function inside2(root, target) {
31684
31942
  let value = relative3(root, target);
31685
- return value !== "" && !isAbsolute2(value) && !value.startsWith(`..${sep5}`) && value !== ".." && !value.includes(`..${sep5}`);
31943
+ return value !== "" && !isAbsolute3(value) && !value.startsWith(`..${sep5}`) && value !== ".." && !value.includes(`..${sep5}`);
31686
31944
  }
31687
31945
  function record4(value, path, issues) {
31688
31946
  if (typeof value === "object" && value !== null && !Array.isArray(value))
@@ -31709,7 +31967,7 @@ function issue3(issues, path, message) {
31709
31967
  function codePointOrder(left, right) {
31710
31968
  return left < right ? -1 : left > right ? 1 : 0;
31711
31969
  }
31712
- var DATA_ACCESS_MANIFEST_VERSION = 1, MAX_FILE_BYTES3 = 524288, MAX_OPERATIONS = 500, MAX_REFERENCES = 100, MAX_NAME_LENGTH2 = 200, defaultDataAccessManifestFile = (workspaceRoot) => resolve8(workspaceRoot, "dbcli.data-access.json"), PROTECTED_REFERENCE = "must not contain a protected semantic reference", UNAVAILABLE_REFERENCE = "must reference an available semantic entity", REFERENCE_MESSAGES3, DataAccessManifestValidationError;
31970
+ var DATA_ACCESS_MANIFEST_VERSION = 1, MAX_FILE_BYTES3 = 524288, MAX_OPERATIONS = 500, MAX_REFERENCES = 100, MAX_NAME_LENGTH2 = 200, defaultDataAccessManifestFile = (workspaceRoot) => resolve9(workspaceRoot, "dbcli.data-access.json"), PROTECTED_REFERENCE = "must not contain a protected semantic reference", UNAVAILABLE_REFERENCE = "must reference an available semantic entity", REFERENCE_MESSAGES3, DataAccessManifestValidationError;
31713
31971
  var init_data_access = __esm(() => {
31714
31972
  init_semantic();
31715
31973
  REFERENCE_MESSAGES3 = /* @__PURE__ */ new Set([
@@ -31727,7 +31985,7 @@ var init_data_access = __esm(() => {
31727
31985
 
31728
31986
  // src/core/context/context-v2.ts
31729
31987
  import { lstat as lstat6, realpath as realpath3 } from "fs/promises";
31730
- import { join as join28, resolve as resolve9 } from "path";
31988
+ import { join as join28, resolve as resolve10 } from "path";
31731
31989
  async function gatherContextV2(workspaceRoot, configPath) {
31732
31990
  await rejectUnknownConfiguredEngine(configPath);
31733
31991
  let config = await configModule.read(configPath), system = config.connection?.system ?? "unknown";
@@ -31887,7 +32145,7 @@ function parseElasticsearchIndex(raw, manager, blockedTerms) {
31887
32145
  return unique(fields.map(({ path }) => path), "INVALID_SCHEMA_CACHE"), { id, name, fields };
31888
32146
  }
31889
32147
  async function collectRedisResources(workspaceRoot, config, blockedTerms, gaps) {
31890
- let filePath = resolve9(workspaceRoot, "dbcli.redis-context.json"), stats;
32148
+ let filePath = resolve10(workspaceRoot, "dbcli.redis-context.json"), stats;
31891
32149
  try {
31892
32150
  stats = await lstat6(filePath);
31893
32151
  } catch {
@@ -32682,6 +32940,14 @@ var init_registry = __esm(() => {
32682
32940
  init_types2();
32683
32941
  init_permission_guard();
32684
32942
  DECLARATIONS = [
32943
+ {
32944
+ id: "connection.tls",
32945
+ key: "connectionTls",
32946
+ command: "query",
32947
+ description: "Use explicit verified SQL TLS from a configured profile. Availability does not validate TLS policy, CA loading or handshake, or authorize configuration changes.",
32948
+ statementType: "SELECT",
32949
+ requiresConnection: !0
32950
+ },
32685
32951
  {
32686
32952
  id: "connection.init",
32687
32953
  key: "init",
@@ -36247,7 +36513,7 @@ var init_artifact_json = __esm(() => {
36247
36513
  });
36248
36514
 
36249
36515
  // src/core/orm-drift/input.ts
36250
- import { resolve as resolve10 } from "path";
36516
+ import { resolve as resolve11 } from "path";
36251
36517
  function isDdlFormat(format) {
36252
36518
  return format === "ddl" || format in ORM_ALIASES;
36253
36519
  }
@@ -36264,7 +36530,7 @@ async function expandOrmPaths(inputs) {
36264
36530
  let paths = parseAgainstOrmValues(inputs), expanded = /* @__PURE__ */ new Set;
36265
36531
  for (let path of paths) {
36266
36532
  if (!hasGlobMagic(path)) {
36267
- expanded.add(resolve10(path));
36533
+ expanded.add(resolve11(path));
36268
36534
  continue;
36269
36535
  }
36270
36536
  let matches = await Array.fromAsync(new Bun.Glob(path).scan({
@@ -36275,7 +36541,7 @@ async function expandOrmPaths(inputs) {
36275
36541
  if (matches.length === 0)
36276
36542
  throw Error(`ORM schema glob matched no files: ${path}`);
36277
36543
  for (let match of matches)
36278
- expanded.add(resolve10(match));
36544
+ expanded.add(resolve11(match));
36279
36545
  }
36280
36546
  return [...expanded].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
36281
36547
  }
@@ -38844,10 +39110,32 @@ var init_envelope_schema = __esm(() => {
38844
39110
 
38845
39111
  // src/commands/recover.ts
38846
39112
  import { stat as stat6 } from "fs/promises";
38847
- import { resolve as resolve11 } from "path";
39113
+ import { resolve as resolve12 } from "path";
39114
+ async function tlsRecoveryTarget() {
39115
+ let explicitConfig = !1;
39116
+ for (let command = recoverCommand;command; command = command.parent)
39117
+ for (let key of ["config", "global"]) {
39118
+ let source = command.getOptionValueSource(key);
39119
+ if (source && source !== "default")
39120
+ explicitConfig = !0;
39121
+ }
39122
+ if (!explicitConfig)
39123
+ throw new RecoverCliError(t("errors.sql_tls_recovery_config"), EXIT_CODE.malformed);
39124
+ let configPath = resolve12(resolveConfigPath(recoverCommand)), connectionName = resolveConnectionSelector({
39125
+ root: recoverCommand.optsWithGlobals().use,
39126
+ environment: process.env.DBCLI_CONNECTION
39127
+ });
39128
+ await Promise.resolve().then(() => init_config());
39129
+ let config = await configModule.read(configPath, connectionName, { loadLayeredSchema: !1 });
39130
+ if (config?.effectiveConnectionName && !connectionName)
39131
+ throw new RecoverCliError(t("errors.sql_tls_recovery_profile"), EXIT_CODE.malformed);
39132
+ if (!config || !("sqlTls" in config.connection) || config.connection.sqlTls?.mode !== "verify-full")
39133
+ throw new RecoverCliError(t("errors.sql_tls_recovery_required"), EXIT_CODE.malformed);
39134
+ return { configPath, ...connectionName && { connectionName } };
39135
+ }
38848
39136
  async function resolveApplySource(opts) {
38849
39137
  if (opts.from !== void 0) {
38850
- let path = resolve11(opts.cwd, opts.from), raw;
39138
+ let path = resolve12(opts.cwd, opts.from), raw;
38851
39139
  try {
38852
39140
  raw = await Bun.file(path).text();
38853
39141
  } catch {
@@ -38968,6 +39256,7 @@ async function runNext(options, source) {
38968
39256
  }
38969
39257
  var ALLOWED_FORMATS13, ALLOWED_TIERS, RecoverCliError, recoverCommand;
38970
39258
  var init_recover = __esm(() => {
39259
+ init_message_loader();
38971
39260
  init_esm();
38972
39261
  init_recovery();
38973
39262
  init_last_envelope();
@@ -38975,6 +39264,7 @@ var init_recover = __esm(() => {
38975
39264
  init_next_step();
38976
39265
  init_next_step_schema();
38977
39266
  init_validation();
39267
+ init_config_path();
38978
39268
  ALLOWED_FORMATS13 = ["json", "markdown"], ALLOWED_TIERS = ["readonly-cmd", "write-cmd"];
38979
39269
  RecoverCliError = class RecoverCliError extends Error {
38980
39270
  exitCode;
@@ -39026,11 +39316,11 @@ var init_recover = __esm(() => {
39026
39316
  console.log(JSON.stringify({ ...source.envelope, audit_recent }, null, 2));
39027
39317
  return;
39028
39318
  }
39029
- let noVerify = options.verify === !1, result = await runApply({
39319
+ let noVerify = options.verify === !1, target = source.envelope.error.details?.connectionCode === "TLS_ERROR" ? await tlsRecoveryTarget() : void 0, result = await runApply({
39030
39320
  envelope: source.envelope,
39031
39321
  cwd: source.cwd,
39032
39322
  source: { kind: source.kind, path: source.path }
39033
- }, { allowWrite, noVerify }), verificationArtifactPath;
39323
+ }, { allowWrite, noVerify, target }), verificationArtifactPath;
39034
39324
  if (options.writeVerificationArtifact === !0 && result.verificationStatus !== void 0)
39035
39325
  try {
39036
39326
  await Promise.resolve().then(() => init_verification());
@@ -50245,7 +50535,7 @@ var init_backfill_artifact = __esm(() => {
50245
50535
  });
50246
50536
 
50247
50537
  // src/commands/backfill.ts
50248
- import { dirname as dirname18, resolve as resolve12 } from "path";
50538
+ import { dirname as dirname18, resolve as resolve13 } from "path";
50249
50539
  import { mkdir as mkdir21 } from "fs/promises";
50250
50540
  function identityFor(config, name) {
50251
50541
  let connection = config.connections[name];
@@ -50272,7 +50562,7 @@ var init_backfill = __esm(() => {
50272
50562
  init_backfill_artifact();
50273
50563
  backfillCommand = new Command2("backfill").description("Generate reviewable source-to-SQL backfill artifacts; never executes writes");
50274
50564
  backfillCommand.command("artifact").description("Build a dry-run backfill artifact from a bounded JSON source catalog and two named connections").requiredOption("--source <path>", "JSON source catalog with table, keyColumns, rows, verifyQuery, and expect").requiredOption("--source-use <name>", "Named source connection used to identify the catalog environment").requiredOption("--target-use <name>", "Named target connection for generated SQL").option("--out <path>", "Write artifact JSON to this path").option("--stdout", "Print artifact JSON instead of writing it", !1).action(async (options, command) => {
50275
- let configPath = resolveConfigPath(command), config = await readV2Config(configPath), sourcePath = resolve12(options.source), sourceContent = await Bun.file(sourcePath).text(), raw;
50565
+ let configPath = resolveConfigPath(command), config = await readV2Config(configPath), sourcePath = resolve13(options.source), sourceContent = await Bun.file(sourcePath).text(), raw;
50276
50566
  try {
50277
50567
  raw = JSON.parse(sourceContent);
50278
50568
  } catch {
@@ -50289,7 +50579,7 @@ var init_backfill = __esm(() => {
50289
50579
  console.log(JSON.stringify(artifact, null, 2));
50290
50580
  return;
50291
50581
  }
50292
- let out = resolve12(options.out ?? `.dbcli/backfills/${artifact.source.sha256.slice(0, 12)}.json`);
50582
+ let out = resolve13(options.out ?? `.dbcli/backfills/${artifact.source.sha256.slice(0, 12)}.json`);
50293
50583
  await mkdir21(dirname18(out), { recursive: !0 }), await Bun.write(out, JSON.stringify(artifact, null, 2) + `
50294
50584
  `), console.log(JSON.stringify({ path: out, artifact }, null, 2));
50295
50585
  });
@@ -50423,7 +50713,7 @@ var init_legacy2 = __esm(() => {
50423
50713
  // src/core/evidence-pack/index.ts
50424
50714
  import { createHash as createHash10, randomUUID as randomUUID5 } from "crypto";
50425
50715
  import { link as link3, lstat as lstat7, mkdir as mkdir22, realpath as realpath4, unlink as unlink8, writeFile as writeFile10 } from "fs/promises";
50426
- import { basename as basename9, dirname as dirname19, relative as relative6, resolve as resolve13, sep as sep8 } from "path";
50716
+ import { basename as basename9, dirname as dirname19, relative as relative6, resolve as resolve14, sep as sep8 } from "path";
50427
50717
  function isRecord6(value) {
50428
50718
  return typeof value === "object" && value !== null && !Array.isArray(value);
50429
50719
  }
@@ -50662,7 +50952,7 @@ async function realExistingAncestor(path) {
50662
50952
  }
50663
50953
  }
50664
50954
  async function writeEvidencePack(workspaceRoot, outputPath, pack) {
50665
- let workspace = await realpath4(workspaceRoot), requestedTarget = resolve13(workspace, outputPath);
50955
+ let workspace = await realpath4(workspaceRoot), requestedTarget = resolve14(workspace, outputPath);
50666
50956
  if (!isInside(workspace, requestedTarget))
50667
50957
  throw new EvidencePackValidationError("output path must stay inside the workspace");
50668
50958
  let parent = dirname19(requestedTarget), existingAncestor = await realExistingAncestor(parent);
@@ -50672,7 +50962,7 @@ async function writeEvidencePack(workspaceRoot, outputPath, pack) {
50672
50962
  let realParent = await realpath4(parent);
50673
50963
  if (!isInside(workspace, realParent) && realParent !== workspace)
50674
50964
  throw new EvidencePackValidationError("output path resolves outside the workspace");
50675
- let target = resolve13(realParent, basename9(requestedTarget));
50965
+ let target = resolve14(realParent, basename9(requestedTarget));
50676
50966
  try {
50677
50967
  throw await lstat7(target), new EvidencePackValidationError("evidence pack output already exists");
50678
50968
  } catch (error) {
@@ -50681,7 +50971,7 @@ async function writeEvidencePack(workspaceRoot, outputPath, pack) {
50681
50971
  if (error.code !== "ENOENT")
50682
50972
  throw error;
50683
50973
  }
50684
- let temp = resolve13(realParent, `.${basename9(target)}.${process.pid}.${randomUUID5()}.tmp`);
50974
+ let temp = resolve14(realParent, `.${basename9(target)}.${process.pid}.${randomUUID5()}.tmp`);
50685
50975
  try {
50686
50976
  await writeFile10(temp, `${JSON.stringify(pack, null, 2)}
50687
50977
  `, "utf8");
@@ -50761,7 +51051,7 @@ var init_evidence_pack = __esm(() => {
50761
51051
  });
50762
51052
 
50763
51053
  // src/commands/evidence.ts
50764
- import { join as join45, relative as relative7, resolve as resolve14, sep as sep9 } from "path";
51054
+ import { join as join45, relative as relative7, resolve as resolve15, sep as sep9 } from "path";
50765
51055
  import { realpath as realpath5 } from "fs/promises";
50766
51056
  import { createHash as createHash11 } from "crypto";
50767
51057
  function safeMessage(error) {
@@ -50881,7 +51171,7 @@ async function resolveReferences(verificationSelectors, auditSelectors, receiptP
50881
51171
  references.push(auditReference(selectAudit(entries, selector), connectionName));
50882
51172
  }
50883
51173
  for (let receiptPath of receiptPaths) {
50884
- let workspace = await realpath5(process.cwd()), requested = resolve14(workspace, receiptPath), path = relative7(workspace, requested);
51174
+ let workspace = await realpath5(process.cwd()), requested = resolve15(workspace, receiptPath), path = relative7(workspace, requested);
50885
51175
  if (path === "" || path.startsWith(`..${sep9}`) || path === ".." || path.includes(`..${sep9}`))
50886
51176
  throw new EvidencePackValidationError("receipt evidence path must be workspace-relative");
50887
51177
  let resolved = await realpath5(requested).catch(() => {
@@ -50920,7 +51210,7 @@ async function expiredReferences(pack, configPath, config) {
50920
51210
  if (!found || audits.connectionName !== reference.connectionName || !sameReference(reference, auditReference(found, audits.connectionName)))
50921
51211
  throw Error("missing");
50922
51212
  } else {
50923
- let workspace = await realpath5(process.cwd()), target = await realpath5(resolve14(workspace, reference.path)), safePath = relative7(workspace, target);
51213
+ let workspace = await realpath5(process.cwd()), target = await realpath5(resolve15(workspace, reference.path)), safePath = relative7(workspace, target);
50924
51214
  if (safePath.startsWith(`..${sep9}`) || safePath === ".." || safePath.includes(`..${sep9}`))
50925
51215
  throw Error("outside");
50926
51216
  let receipt = await readEvidenceReceipt(target), digest = `sha256:${createHash11("sha256").update(JSON.stringify(receipt)).digest("hex")}`;
@@ -51928,7 +52218,7 @@ function escapeText2(value) {
51928
52218
  // src/commands/impact.ts
51929
52219
  import { randomUUID as randomUUID6 } from "crypto";
51930
52220
  import { link as link4, lstat as lstat9, mkdir as mkdir23, realpath as realpath6, unlink as unlink9, writeFile as writeFile11 } from "fs/promises";
51931
- import { basename as basename10, dirname as dirname20, relative as relative8, resolve as resolve15, sep as sep10 } from "path";
52221
+ import { basename as basename10, dirname as dirname20, relative as relative8, resolve as resolve16, sep as sep10 } from "path";
51932
52222
  function collectOption3(value, previous) {
51933
52223
  return [...previous, value];
51934
52224
  }
@@ -52094,7 +52384,7 @@ async function loadObservedWorkloadSource(path, workspaceRoot, blocked) {
52094
52384
  malformedLines: 0,
52095
52385
  issues: []
52096
52386
  };
52097
- return loadWorkloadSource({ path: resolve15(workspaceRoot, path), blockedIdentifiers: blocked });
52387
+ return loadWorkloadSource({ path: resolve16(workspaceRoot, path), blockedIdentifiers: blocked });
52098
52388
  }
52099
52389
  function shouldFail(report, failOn) {
52100
52390
  if (failOn === "never")
@@ -52127,7 +52417,7 @@ async function realExistingAncestor2(path) {
52127
52417
  }
52128
52418
  }
52129
52419
  async function resolveOutput(workspaceRoot, outputPath) {
52130
- let workspace = await realpath6(workspaceRoot), target = resolve15(workspace, outputPath);
52420
+ let workspace = await realpath6(workspaceRoot), target = resolve16(workspace, outputPath);
52131
52421
  if (!isInside2(workspace, target))
52132
52422
  throw Error("output path must stay inside the workspace");
52133
52423
  let parent = dirname20(target), existingAncestor = await realExistingAncestor2(parent);
@@ -52137,7 +52427,7 @@ async function resolveOutput(workspaceRoot, outputPath) {
52137
52427
  let realParent = await realpath6(parent);
52138
52428
  if (!isInside2(workspace, realParent) && realParent !== workspace)
52139
52429
  throw Error("output path must stay inside the workspace");
52140
- return resolve15(realParent, basename10(target));
52430
+ return resolve16(realParent, basename10(target));
52141
52431
  }
52142
52432
  async function assertOutputParent(workspaceRoot, path) {
52143
52433
  let workspace = await realpath6(workspaceRoot), parent = await realpath6(dirname20(path));
@@ -52152,7 +52442,7 @@ async function writeOutput(workspaceRoot, path, content) {
52152
52442
  if (error.code !== "ENOENT")
52153
52443
  throw error;
52154
52444
  }
52155
- let temp = resolve15(dirname20(path), `.${basename10(path)}.${process.pid}.${randomUUID6()}.tmp`);
52445
+ let temp = resolve16(dirname20(path), `.${basename10(path)}.${process.pid}.${randomUUID6()}.tmp`);
52156
52446
  try {
52157
52447
  await writeFile11(temp, content, "utf8"), await assertOutputParent(workspaceRoot, path), await link4(temp, path);
52158
52448
  } catch (error) {
@@ -52216,7 +52506,7 @@ var init_impact2 = __esm(() => {
52216
52506
  observedWorkload,
52217
52507
  blockedIdentifiers
52218
52508
  }), rendered = formatImpact(report, format), output = await resolveOutput(workspaceRoot, String(options.output));
52219
- if (resolve15(workspaceRoot, String(options.design)) === output)
52509
+ if (resolve16(workspaceRoot, String(options.design)) === output)
52220
52510
  throw Error("design and output paths must differ");
52221
52511
  await writeOutput(workspaceRoot, output, rendered), console.log(JSON.stringify({
52222
52512
  path: relative8(workspaceRoot, output),