@carllee1983/dbcli 7.0.1 → 8.0.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.
Files changed (44) hide show
  1. package/.agents/skills/story-development/SKILL.md +70 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor/rules/dbcli.mdc +64 -1
  5. package/.cursor/skills/dbcli/reference.md +172 -6
  6. package/.cursor-plugin/plugin.json +1 -1
  7. package/.github/skills/dbcli/SKILL.md +64 -1
  8. package/.github/skills/dbcli/reference.md +172 -6
  9. package/CHANGELOG.md +285 -0
  10. package/assets/SKILL.md +64 -1
  11. package/assets/SKILL.zh-TW.md +59 -1
  12. package/assets/integration-kit/README.md +43 -0
  13. package/assets/integration-kit/external-consumers.ts +9 -0
  14. package/assets/integration-kit/skill-author-consumer.ts +77 -0
  15. package/assets/integration-kit/task-pack.md +18 -0
  16. package/assets/reference.md +172 -6
  17. package/assets/tasks/README.md +6 -0
  18. package/assets/tasks/analyze-table-perf.md +3 -3
  19. package/assets/tasks/audit-permissions.md +2 -2
  20. package/assets/tasks/connection-health.md +2 -2
  21. package/assets/tasks/design-review.md +3 -3
  22. package/assets/tasks/diagnose-slow-query.md +3 -3
  23. package/assets/tasks/migration-review.md +3 -3
  24. package/assets/tasks/mongo-safe-backfill.md +2 -2
  25. package/assets/tasks/mongo-schema-drift-review.md +2 -2
  26. package/assets/tasks/orm-drift-review.md +3 -3
  27. package/assets/tasks/pr-database-review.md +3 -3
  28. package/assets/tasks/safe-backfill-verify.md +3 -3
  29. package/assets/tasks/safe-backfill.md +3 -3
  30. package/assets/tasks/schema-drift-review.md +3 -3
  31. package/assets/tasks/slow-endpoint-investigation.md +19 -6
  32. package/assets/ui-template.html +18 -18
  33. package/dist/cli-runtime.mjs +25533 -22188
  34. package/dist/cli.mjs +7 -4
  35. package/dist/core.d.ts +297 -2
  36. package/dist/core.mjs +2075 -3
  37. package/dist/ui-style.css +1 -1
  38. package/gemini-extension.json +1 -1
  39. package/package.json +6 -3
  40. package/plugins/dbcli-agent/.codex-plugin/plugin.json +1 -1
  41. package/plugins/dbcli-agent/skills/dbcli/SKILL.md +64 -1
  42. package/plugins/dbcli-agent/skills/dbcli/reference.md +172 -6
  43. package/skills/dbcli/SKILL.md +64 -1
  44. package/skills/dbcli/reference.md +172 -6
package/dist/core.mjs CHANGED
@@ -1,5 +1,35 @@
1
1
  // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
2
4
  var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
3
33
  var __returnValue = (v) => v;
4
34
  function __exportSetter(name, newValue) {
5
35
  this[name] = __returnValue.bind(null, newValue);
@@ -16,6 +46,76 @@ var __export = (target, all) => {
16
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
47
  var __require = import.meta.require;
18
48
 
49
+ // node_modules/picocolors/picocolors.js
50
+ var require_picocolors = __commonJS((exports, module) => {
51
+ var p = process || {};
52
+ var argv = p.argv || [];
53
+ var env = p.env || {};
54
+ var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
55
+ var formatter = (open, close, replace = open) => (input) => {
56
+ let string = "" + input, index = string.indexOf(close, open.length);
57
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
58
+ };
59
+ var replaceClose = (string, close, replace, index) => {
60
+ let result = "", cursor = 0;
61
+ do {
62
+ result += string.substring(cursor, index) + replace;
63
+ cursor = index + close.length;
64
+ index = string.indexOf(close, cursor);
65
+ } while (~index);
66
+ return result + string.substring(cursor);
67
+ };
68
+ var createColors = (enabled = isColorSupported) => {
69
+ let f = enabled ? formatter : () => String;
70
+ return {
71
+ isColorSupported: enabled,
72
+ reset: f("\x1B[0m", "\x1B[0m"),
73
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
74
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
75
+ italic: f("\x1B[3m", "\x1B[23m"),
76
+ underline: f("\x1B[4m", "\x1B[24m"),
77
+ inverse: f("\x1B[7m", "\x1B[27m"),
78
+ hidden: f("\x1B[8m", "\x1B[28m"),
79
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
80
+ black: f("\x1B[30m", "\x1B[39m"),
81
+ red: f("\x1B[31m", "\x1B[39m"),
82
+ green: f("\x1B[32m", "\x1B[39m"),
83
+ yellow: f("\x1B[33m", "\x1B[39m"),
84
+ blue: f("\x1B[34m", "\x1B[39m"),
85
+ magenta: f("\x1B[35m", "\x1B[39m"),
86
+ cyan: f("\x1B[36m", "\x1B[39m"),
87
+ white: f("\x1B[37m", "\x1B[39m"),
88
+ gray: f("\x1B[90m", "\x1B[39m"),
89
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
90
+ bgRed: f("\x1B[41m", "\x1B[49m"),
91
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
92
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
93
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
94
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
95
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
96
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
97
+ blackBright: f("\x1B[90m", "\x1B[39m"),
98
+ redBright: f("\x1B[91m", "\x1B[39m"),
99
+ greenBright: f("\x1B[92m", "\x1B[39m"),
100
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
101
+ blueBright: f("\x1B[94m", "\x1B[39m"),
102
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
103
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
104
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
105
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
106
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
107
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
108
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
109
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
110
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
111
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
112
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
113
+ };
114
+ };
115
+ module.exports = createColors();
116
+ module.exports.createColors = createColors;
117
+ });
118
+
19
119
  // node_modules/lru-cache/dist/esm/node/index.min.js
20
120
  import { tracingChannel as j, channel as G } from "diagnostics_channel";
21
121
  var w, W, C, D = () => w.hasSubscribers || W.hasSubscribers, I, L, P = (u, e, t2, i) => {
@@ -988,15 +1088,26 @@ var init_schema_loader = __esm(() => {
988
1088
  });
989
1089
 
990
1090
  // src/adapters/types.ts
1091
+ var DATABASE_SYSTEMS = Object.freeze([
1092
+ "postgresql",
1093
+ "mysql",
1094
+ "mariadb",
1095
+ "mongodb",
1096
+ "redis",
1097
+ "elasticsearch"
1098
+ ]);
1099
+
991
1100
  class ConnectionError extends Error {
992
1101
  code;
993
1102
  hints;
994
1103
  limitMs;
995
- constructor(code, message, hints, limitMs) {
1104
+ retrySafe;
1105
+ constructor(code, message, hints, limitMs, retrySafe = true) {
996
1106
  super(message);
997
1107
  this.code = code;
998
1108
  this.hints = hints;
999
1109
  this.limitMs = limitMs;
1110
+ this.retrySafe = retrySafe;
1000
1111
  this.name = "ConnectionError";
1001
1112
  Object.setPrototypeOf(this, ConnectionError.prototype);
1002
1113
  }
@@ -1036,7 +1147,26 @@ var COMMAND_CAPABILITY_KEYS = Object.freeze([
1036
1147
  "auditTail",
1037
1148
  "auditShow",
1038
1149
  "auditClear",
1039
- "auditHealth"
1150
+ "auditHealth",
1151
+ "explain",
1152
+ "plan",
1153
+ "impactAssess",
1154
+ "assert",
1155
+ "snapshot",
1156
+ "verify",
1157
+ "semantic",
1158
+ "design",
1159
+ "proxy",
1160
+ "proxyAnalyze",
1161
+ "verification",
1162
+ "verificationPrune",
1163
+ "evidence",
1164
+ "contract",
1165
+ "recovery",
1166
+ "backfillArtifact",
1167
+ "password",
1168
+ "capabilityDiscover",
1169
+ "capabilityCheck"
1040
1170
  ]);
1041
1171
  function cap(status, tier, note) {
1042
1172
  return Object.freeze({ status, tier, note });
@@ -1078,8 +1208,40 @@ var SQL_BASE = {
1078
1208
  inspect: cap("supported", "readonly", "Agent context snapshot is supported."),
1079
1209
  report: cap("supported", "readonly", "Diagnostic report is supported."),
1080
1210
  guide: cap("supported", "readonly", "Deterministic next-command planner is supported."),
1211
+ explain: cap("supported", "readonly", "EXPLAIN plans are read from SQL engines."),
1212
+ plan: cap("supported", "readonly", "Offline write-risk analysis of SQL against the schema cache."),
1213
+ impactAssess: cap("supported", "local-write", "Offline impact assessment of a SQL schema change; writes its report locally."),
1214
+ assert: cap("supported", "local-write", "Data assertions over SQL results; optionally writes a verification artifact."),
1215
+ snapshot: cap("supported", "local-write", "Captures a SQL result fingerprint under .dbcli/snapshots/."),
1216
+ verify: cap("supported", "local-write", "Scenario verification over SQL; writes a verification artifact."),
1217
+ semantic: cap("supported", "readonly", "Governed semantic context and drift over a SQL schema cache."),
1218
+ design: cap("supported", "local-write", "Schema design authoring and comparison for SQL dialects; init writes a template."),
1219
+ proxy: cap("supported", "interactive", "Local observing proxy for the MySQL, MariaDB and PostgreSQL wire protocols."),
1220
+ password: cap("supported", "interactive", "Rotates the stored connection credential; refused under agent mode."),
1221
+ proxyAnalyze: cap("not-applicable", "readonly", "Reads locally recorded proxy events; never contacts an engine."),
1222
+ verification: cap("not-applicable", "readonly", "Inspects verification artifacts under .dbcli/verification/."),
1223
+ verificationPrune: cap("not-applicable", "local-write", "Deletes local verification artifacts; dry-run unless --execute --force."),
1224
+ evidence: cap("not-applicable", "local-write", "Composes, validates and renders evidence packs from local artifacts."),
1225
+ contract: cap("not-applicable", "readonly", "Validates and inspects governed semantic contracts offline."),
1226
+ recovery: cap("not-applicable", "readonly", "Renders recovery codes and their remediation steps from a static table."),
1227
+ backfillArtifact: cap("not-applicable", "local-write", "Builds a backfill artifact from a source manifest and v2 connection identity."),
1228
+ capabilityDiscover: cap("not-applicable", "readonly", "Emits the static capability catalog; opens no connection."),
1229
+ capabilityCheck: cap("not-applicable", "readonly", "Evaluates required capability ids against the local configuration only."),
1081
1230
  ...ENGINE_INDEPENDENT
1082
1231
  };
1232
+ var SQL_ONLY_UNSUPPORTED = {
1233
+ explain: cap("unsupported", "none", "src/commands/explain.ts refuses a non-SQL connection."),
1234
+ plan: cap("unsupported", "none", "Risk analysis is SQL-dialect based; toSqlDialect resolves no dialect here."),
1235
+ assert: cap("unsupported", "none", "src/commands/assert.ts refuses a non-SQL connection."),
1236
+ snapshot: cap("unsupported", "none", "src/commands/snapshot.ts refuses a non-SQL connection."),
1237
+ verify: cap("unsupported", "none", "src/commands/verify.ts refuses a non-SQL connection."),
1238
+ proxy: cap("unsupported", "none", "The proxy speaks the MySQL and PostgreSQL wire protocols only.")
1239
+ };
1240
+ var SQL_MODE_LIMITED = {
1241
+ impactAssess: cap("limited", "local-write", "Runs against an ORM artifact; --against-cache needs a SQL connection."),
1242
+ semantic: cap("limited", "readonly", "Governed context, search and drift work; draft validate needs a SQL connection."),
1243
+ design: cap("limited", "local-write", "Authoring and ORM comparison work; --against-cache needs a SQL connection.")
1244
+ };
1083
1245
  var ENGINE_CAPABILITIES = Object.freeze({
1084
1246
  postgresql: Object.freeze({
1085
1247
  ...SQL_BASE,
@@ -1089,6 +1251,10 @@ var ENGINE_CAPABILITIES = Object.freeze({
1089
1251
  mariadb: Object.freeze(SQL_BASE),
1090
1252
  mongodb: Object.freeze({
1091
1253
  ...SQL_BASE,
1254
+ ...SQL_ONLY_UNSUPPORTED,
1255
+ ...SQL_MODE_LIMITED,
1256
+ ...SQL_MODE_LIMITED,
1257
+ ...SQL_MODE_LIMITED,
1092
1258
  lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
1093
1259
  schemaSingle: cap("limited", "readonly", "MongoDB schema is sampled from documents."),
1094
1260
  schemaFullScan: cap("limited", "readonly", "Full scan is sampled and document-oriented."),
@@ -1108,6 +1274,7 @@ var ENGINE_CAPABILITIES = Object.freeze({
1108
1274
  }),
1109
1275
  redis: Object.freeze({
1110
1276
  ...SQL_BASE,
1277
+ ...SQL_ONLY_UNSUPPORTED,
1111
1278
  lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
1112
1279
  schemaSingle: cap("limited", "readonly", "Per-key synthetic schema only."),
1113
1280
  schemaFullScan: cap("unsupported", "none", "Redis has no full schema cache scan."),
@@ -1127,6 +1294,7 @@ var ENGINE_CAPABILITIES = Object.freeze({
1127
1294
  }),
1128
1295
  elasticsearch: Object.freeze({
1129
1296
  ...SQL_BASE,
1297
+ ...SQL_ONLY_UNSUPPORTED,
1130
1298
  lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
1131
1299
  schemaSingle: cap("limited", "readonly", "Schema flattens index mappings."),
1132
1300
  schemaFullScan: cap("supported", "readonly", "Full scan iterates non-system indices."),
@@ -1145,8 +1313,272 @@ var ENGINE_CAPABILITIES = Object.freeze({
1145
1313
  shell: cap("limited", "interactive", "Dedicated Kibana Dev Tools-style REPL (method/path + JSON body, blank-line submit); read-focused.")
1146
1314
  })
1147
1315
  });
1316
+ // src/adapters/error-mapper.ts
1317
+ var TRANSPORT_CODES = {
1318
+ ECONNREFUSED: "ECONNREFUSED",
1319
+ ETIMEDOUT: "ETIMEDOUT",
1320
+ ESOCKETTIMEDOUT: "ETIMEDOUT",
1321
+ ENOTFOUND: "ENOTFOUND",
1322
+ EAI_AGAIN: "ENOTFOUND",
1323
+ PROTOCOL_CONNECTION_LOST: "CONNECTION_LOST",
1324
+ PROTOCOL_SEQUENCE_TIMEOUT: "ETIMEDOUT",
1325
+ ECONNRESET: "CONNECTION_LOST",
1326
+ EPIPE: "CONNECTION_LOST",
1327
+ ECONNABORTED: "CONNECTION_LOST",
1328
+ EHOSTUNREACH: "EHOSTUNREACH",
1329
+ ENETUNREACH: "EHOSTUNREACH",
1330
+ ENETDOWN: "EHOSTUNREACH"
1331
+ };
1332
+ function isTransportDriverCode(code) {
1333
+ return TLS_CODE_PREFIX.test(code) || TRANSPORT_CODES[code] !== undefined;
1334
+ }
1335
+ var TLS_CODE_PREFIX = /^(CERT_|SELF_SIGNED_|DEPTH_ZERO_|UNABLE_TO_(?:GET|VERIFY)_|ERR_TLS_|ERR_SSL_)/;
1336
+ var MYSQL_CODES = {
1337
+ ER_NO_SUCH_TABLE: "TABLE_NOT_FOUND",
1338
+ ER_BAD_FIELD_ERROR: "COLUMN_NOT_FOUND",
1339
+ ER_PARSE_ERROR: "SQL_SYNTAX_ERROR",
1340
+ ER_ACCESS_DENIED_ERROR: "AUTH_FAILED",
1341
+ ER_DBACCESS_DENIED_ERROR: "AUTH_FAILED",
1342
+ ER_NOT_SUPPORTED_AUTH_MODE: "AUTH_FAILED",
1343
+ ER_MUST_CHANGE_PASSWORD_LOGIN: "AUTH_FAILED",
1344
+ ER_QUERY_TIMEOUT: "STATEMENT_TIMEOUT",
1345
+ ER_STATEMENT_TIMEOUT: "STATEMENT_TIMEOUT",
1346
+ ER_CON_COUNT_ERROR: "TOO_MANY_CONNECTIONS",
1347
+ ER_SERVER_SHUTDOWN: "CONNECTION_LOST"
1348
+ };
1349
+ var MYSQL_ERRNOS = {
1350
+ 1045: "AUTH_FAILED",
1351
+ 1044: "AUTH_FAILED",
1352
+ 1054: "COLUMN_NOT_FOUND",
1353
+ 1064: "SQL_SYNTAX_ERROR",
1354
+ 1146: "TABLE_NOT_FOUND",
1355
+ 1251: "AUTH_FAILED",
1356
+ 1698: "AUTH_FAILED",
1357
+ 1040: "TOO_MANY_CONNECTIONS",
1358
+ 1053: "CONNECTION_LOST",
1359
+ 2006: "CONNECTION_LOST",
1360
+ 2013: "CONNECTION_LOST",
1361
+ 1969: "STATEMENT_TIMEOUT",
1362
+ 3024: "STATEMENT_TIMEOUT"
1363
+ };
1364
+ var SQLSTATES = {
1365
+ "28000": "AUTH_FAILED",
1366
+ "28P01": "AUTH_FAILED",
1367
+ "42601": "SQL_SYNTAX_ERROR",
1368
+ "42P01": "TABLE_NOT_FOUND",
1369
+ "42703": "COLUMN_NOT_FOUND",
1370
+ "57014": "STATEMENT_TIMEOUT",
1371
+ "08000": "CONNECTION_LOST",
1372
+ "08003": "CONNECTION_LOST",
1373
+ "08006": "CONNECTION_LOST",
1374
+ "08001": "ECONNREFUSED",
1375
+ "08004": "CONNECTION_REJECTED",
1376
+ "53300": "TOO_MANY_CONNECTIONS",
1377
+ "57P01": "CONNECTION_LOST",
1378
+ "57P02": "CONNECTION_LOST",
1379
+ "57P03": "SERVER_NOT_READY"
1380
+ };
1381
+ var REDIS_PREFIXES = {
1382
+ NOAUTH: "AUTH_FAILED",
1383
+ WRONGPASS: "AUTH_FAILED"
1384
+ };
1385
+ var FALLBACK_PATTERNS = [
1386
+ [/connection refused/i, "ECONNREFUSED"],
1387
+ [/\bECONNREFUSED\b/, "ECONNREFUSED"],
1388
+ [/connect(?:ion)? time(?:d )?out/i, "ETIMEDOUT"],
1389
+ [/timeout exceeded/i, "ETIMEDOUT"],
1390
+ [/timed out/i, "ETIMEDOUT"],
1391
+ [/connection terminated/i, "CONNECTION_LOST"],
1392
+ [/server closed the connection/i, "CONNECTION_LOST"],
1393
+ [/server has gone away/i, "CONNECTION_LOST"],
1394
+ [/lost connection to \S+ server/i, "CONNECTION_LOST"],
1395
+ [/getaddrinfo/i, "ENOTFOUND"],
1396
+ [/authentication failed/i, "AUTH_FAILED"],
1397
+ [/access denied for user/i, "AUTH_FAILED"],
1398
+ [/no pg_hba\.conf entry/i, "AUTH_FAILED"],
1399
+ [/role\s+".*?"\s+does not exist/i, "AUTH_FAILED"],
1400
+ [/password supplied/i, "AUTH_FAILED"]
1401
+ ];
1402
+ var PG_CANCEL_BY_TIMEOUT = /statement timeout/i;
1403
+ function categorize(errCode, errno, errMsg, system) {
1404
+ if (errCode) {
1405
+ if (TLS_CODE_PREFIX.test(errCode))
1406
+ return "TLS_ERROR";
1407
+ const known = TRANSPORT_CODES[errCode] ?? MYSQL_CODES[errCode] ?? SQLSTATES[errCode] ?? REDIS_PREFIXES[errCode];
1408
+ if (known === "STATEMENT_TIMEOUT" && errCode === "57014" && !PG_CANCEL_BY_TIMEOUT.test(errMsg)) {
1409
+ return null;
1410
+ }
1411
+ if (known)
1412
+ return known;
1413
+ }
1414
+ const isMysqlFamily = system === "mysql" || system === "mariadb";
1415
+ if (isMysqlFamily && typeof errno === "number" && MYSQL_ERRNOS[errno]) {
1416
+ return MYSQL_ERRNOS[errno];
1417
+ }
1418
+ const redisPrefix = errMsg.match(/^([A-Z]+)\s/)?.[1];
1419
+ if (redisPrefix && REDIS_PREFIXES[redisPrefix])
1420
+ return REDIS_PREFIXES[redisPrefix];
1421
+ if (errCode || isMysqlFamily && typeof errno === "number")
1422
+ return null;
1423
+ for (const [pattern, category] of FALLBACK_PATTERNS) {
1424
+ if (pattern.test(errMsg))
1425
+ return category;
1426
+ }
1427
+ return null;
1428
+ }
1429
+ var SYSTEM_FACTS = {
1430
+ postgresql: {
1431
+ serviceCheck: "systemctl status postgresql",
1432
+ defaultPort: "default 5432",
1433
+ firewallRule: "allow TCP 5432",
1434
+ client: "psql",
1435
+ logFile: "postgresql.log"
1436
+ },
1437
+ mysql: {
1438
+ serviceCheck: "systemctl status mysql",
1439
+ defaultPort: "default 3306",
1440
+ firewallRule: "allow TCP 3306",
1441
+ client: "mysql",
1442
+ logFile: "mysql.log"
1443
+ },
1444
+ mariadb: {
1445
+ serviceCheck: "systemctl status mariadb",
1446
+ defaultPort: "default 3306",
1447
+ firewallRule: "allow TCP 3306",
1448
+ client: "mariadb",
1449
+ logFile: "mariadb.log"
1450
+ },
1451
+ redis: {
1452
+ serviceCheck: "redis-cli ping",
1453
+ defaultPort: "default 6379",
1454
+ firewallRule: "allow TCP 6379",
1455
+ client: "redis-cli",
1456
+ logFile: "redis.log"
1457
+ }
1458
+ };
1459
+ function serviceHints(system, options) {
1460
+ const facts = SYSTEM_FACTS[system];
1461
+ return [
1462
+ `Check that the ${system} service is running: ${facts.serviceCheck}`,
1463
+ `Verify the port is correct: ${facts.defaultPort}`,
1464
+ `Check that ${options.host} is reachable: ping ${options.host} or telnet ${options.host} ${options.port}`
1465
+ ];
1466
+ }
1467
+ function mapError(error, system, options) {
1468
+ if (error instanceof ConnectionError)
1469
+ return error;
1470
+ const err = error;
1471
+ const errMsg = String(err?.message || String(error));
1472
+ const errCode = String(err?.code || "");
1473
+ const errno = err?.errno;
1474
+ switch (categorize(errCode, errno, errMsg, system)) {
1475
+ case "ECONNREFUSED":
1476
+ return new ConnectionError("ECONNREFUSED", `Cannot connect to ${options.host}:${options.port} \u2014 server is not running or not listening on this port`, serviceHints(system, options));
1477
+ case "ETIMEDOUT":
1478
+ return new ConnectionError("ETIMEDOUT", `Connection timed out (${options.timeout || 5000}ms) \u2014 may be blocked by a firewall or slow network`, [
1479
+ `Check firewall rules: ${SYSTEM_FACTS[system].firewallRule}`,
1480
+ `Increase timeout: edit .dbcli and add "timeout": 15000`,
1481
+ `Verify network connectivity: ping ${options.host} -c 3`
1482
+ ]);
1483
+ case "CONNECTION_LOST":
1484
+ return new ConnectionError("CONNECTION_LOST", `Connection to ${options.host}:${options.port} was lost mid-session \u2014 the server closed it or the network dropped`, [
1485
+ "Re-run the command: a dropped connection is often transient",
1486
+ `Check whether the server restarted or is cycling: ${SYSTEM_FACTS[system].logFile}`,
1487
+ "If it happens on long-running work, look at the server idle/wait timeout"
1488
+ ]);
1489
+ case "SERVER_NOT_READY":
1490
+ return new ConnectionError("SERVER_NOT_READY", `${options.host}:${options.port} is not accepting connections yet \u2014 the server is starting up or recovering`, [
1491
+ "Retry shortly: this clears on its own once startup or recovery finishes",
1492
+ `Watch progress in the server log: ${SYSTEM_FACTS[system].logFile}`,
1493
+ "On a replica, this also appears while it catches up with the primary"
1494
+ ]);
1495
+ case "CONNECTION_REJECTED":
1496
+ return new ConnectionError("CONNECTION_REJECTED", `${options.host}:${options.port} answered but rejected the connection attempt`, [
1497
+ "Check the server access rules (pg_hba.conf) for this user, database, and client address",
1498
+ "If a pooler (pgbouncer / ProxySQL) sits in front, check its own rules and limits",
1499
+ "Check whether the connection limit for this user or database is exhausted"
1500
+ ]);
1501
+ case "TOO_MANY_CONNECTIONS":
1502
+ return new ConnectionError("TOO_MANY_CONNECTIONS", `${options.host}:${options.port} refused the connection \u2014 the server has no connection slots left`, [
1503
+ "Wait and retry: the limit is on concurrent connections, not on you",
1504
+ system === "postgresql" ? "Inspect usage: SELECT count(*) FROM pg_stat_activity \u2014 compare against max_connections" : 'Inspect usage: SHOW STATUS LIKE "Threads_connected" \u2014 compare against max_connections',
1505
+ "Close idle sessions, or raise max_connections if the load is legitimate"
1506
+ ]);
1507
+ case "EHOSTUNREACH":
1508
+ return new ConnectionError("EHOSTUNREACH", `No route to ${options.host} \u2014 the name resolved, but the host or network is unreachable`, [
1509
+ "Check that you are on the right network or VPN",
1510
+ `Check routing and firewall between here and ${options.host}`,
1511
+ `Confirm the address is the one you meant: ${options.host}:${options.port}`
1512
+ ]);
1513
+ case "TLS_ERROR":
1514
+ return new ConnectionError("TLS_ERROR", `TLS handshake failed: ${errMsg}`, [
1515
+ 'Point the connection at the right CA bundle: set "caPath" in .dbcli',
1516
+ 'For a self-signed certificate in a trusted network, set "rejectUnauthorized": false',
1517
+ `Confirm the certificate covers the host you connected to: ${options.host}`
1518
+ ]);
1519
+ case "ENOTFOUND":
1520
+ return new ConnectionError("ENOTFOUND", `Host not found: ${options.host}`, [
1521
+ `Check the hostname spelling: ${options.host}`,
1522
+ `Verify DNS resolution: nslookup ${options.host}`,
1523
+ `If using localhost, try 127.0.0.1 (IPv4 vs IPv6 issue)`
1524
+ ]);
1525
+ case "AUTH_FAILED":
1526
+ return new ConnectionError("AUTH_FAILED", `Authentication failed \u2014 check your username or password`, [
1527
+ `Verify credentials: ${SYSTEM_FACTS[system].client} ${system === "postgresql" ? "-U" : "-u"} ${options.user} -h ${options.host}`,
1528
+ `Check pg_hba.conf (PostgreSQL) or user privileges (MySQL)`,
1529
+ `Re-run dbcli init to update credentials`
1530
+ ]);
1531
+ case "TABLE_NOT_FOUND": {
1532
+ const mysqlMatch = errMsg.match(/Table\s+'(?:[^.']+\.)?([^']+)'/i);
1533
+ const pgMatch = errMsg.match(/relation\s+"([^"]+)"/i);
1534
+ const tableName = mysqlMatch?.[1] || pgMatch?.[1] || "unknown";
1535
+ return new ConnectionError("TABLE_NOT_FOUND", `Table '${tableName}' not found in database '${options.database || "unknown"}'`, [
1536
+ "Run `dbcli list` to see available tables",
1537
+ "Run `dbcli schema <table>` to confirm the exact name (ORM export names may differ from DB table names)"
1538
+ ]);
1539
+ }
1540
+ case "COLUMN_NOT_FOUND": {
1541
+ const mysqlMatch = errMsg.match(/Unknown column\s+'([^']+)'/i);
1542
+ const pgMatch = errMsg.match(/column\s+"([^"]+)"\s+does not exist/i);
1543
+ const columnName = mysqlMatch?.[1] || pgMatch?.[1] || "unknown";
1544
+ return new ConnectionError("COLUMN_NOT_FOUND", `Column '${columnName}' not found`, [
1545
+ "Run `dbcli schema <table>` to see the actual column names",
1546
+ "Column names are case-sensitive on some databases (PostgreSQL with quoted identifiers)"
1547
+ ]);
1548
+ }
1549
+ case "STATEMENT_TIMEOUT": {
1550
+ const limitMs = options.statementTimeout ?? options.timeout;
1551
+ return new ConnectionError("STATEMENT_TIMEOUT", `Statement timed out${limitMs ? ` (${limitMs}ms)` : ""} \u2014 the server canceled this query before it finished`, [
1552
+ 'Inspect the query plan: dbcli explain "<sql>"',
1553
+ "Raise the ceiling for this run: dbcli --statement-timeout <ms> \u2026 (0 removes it)",
1554
+ "Narrow the query: add WHERE filters, reduce the LIMIT, or index the scanned columns"
1555
+ ], limitMs);
1556
+ }
1557
+ case "SQL_SYNTAX_ERROR":
1558
+ return new ConnectionError("SQL_SYNTAX_ERROR", `SQL syntax error: ${errMsg}`, [
1559
+ "Check your SQL syntax near the position reported above",
1560
+ "In query-only mode, dbcli auto-appends LIMIT 1000; use --no-limit to disable",
1561
+ "For SHOW/DESCRIBE/EXPLAIN statements, LIMIT is not allowed \u2014 these are not auto-limited"
1562
+ ]);
1563
+ }
1564
+ const reportedCode = errCode || (typeof errno === "number" ? String(errno) : "");
1565
+ if (reportedCode) {
1566
+ return new ConnectionError("UNKNOWN", `Database error (${reportedCode}): ${errMsg}`, [
1567
+ "Run `dbcli schema <table>` to confirm the objects referenced by this statement",
1568
+ `Look up ${reportedCode} in the ${system} error reference for the exact cause`
1569
+ ]);
1570
+ }
1571
+ return new ConnectionError("UNKNOWN", `Connection failed: ${errMsg}`, [
1572
+ `Check connection parameters: host=${options.host}, port=${options.port}, user=${options.user}`,
1573
+ `View server logs: ${SYSTEM_FACTS[system].logFile}`,
1574
+ `Try connecting directly with the ${SYSTEM_FACTS[system].client} command-line tool`
1575
+ ]);
1576
+ }
1148
1577
  // resources/lang/en/messages.json
1149
1578
  var messages_default = {
1579
+ cli: {
1580
+ agent_output_option: "Emit one Operation Envelope v1 (capabilities and capabilities check only)"
1581
+ },
1150
1582
  init: {
1151
1583
  description: "Initialize dbcli configuration with .env parsing and interactive prompts",
1152
1584
  welcome: "Welcome to dbcli",
@@ -1155,6 +1587,9 @@ var messages_default = {
1155
1587
  prompt_port: "Database port: ",
1156
1588
  prompt_user: "Database user: ",
1157
1589
  prompt_password: "Database password: ",
1590
+ secret_alternatives_sql: "pass --password, supply the credential through .env or the process environment, or store a reference with --use-env-refs",
1591
+ secret_alternatives_mongo_password: "pass --password, supply the credential through .env or the process environment, or paste the connection string with --uri",
1592
+ secret_alternatives_mongo_uri: "pass the connection string with --uri",
1158
1593
  prompt_name: "Database name: ",
1159
1594
  prompt_permission: "Permission level (query-only, read-write, data-admin, admin): ",
1160
1595
  env_parse_note: "Note: Unable to parse .env configuration, using interactive prompts",
@@ -1310,6 +1745,14 @@ var messages_default = {
1310
1745
  },
1311
1746
  errors: {
1312
1747
  message: "Error: {message}",
1748
+ query_only_boundary_failed: "Could not establish the query-only database boundary for {system}: {detail}",
1749
+ query_only_boundary_verify: "Verify that {system} supports read-only transactions and that this account may start one",
1750
+ query_only_boundary_not_executed: "The target statement was not executed; fix the database setting or connection before retrying",
1751
+ query_only_cleanup_completed: "The query-only target completed, but transaction cleanup failed for {system}: {detail}",
1752
+ query_only_cleanup_uncertain: "The query-only target failed and its outcome is uncertain because transaction cleanup also failed for {system}: {detail}",
1753
+ query_only_cleanup_completed_retry: "The target statement ran; do not retry it unless repeating its external effects is safe",
1754
+ query_only_cleanup_uncertain_retry: "The target may have produced effects before failing; do not retry it unless repeating them is safe",
1755
+ query_only_cleanup_reconnect: "Reconnect before running another statement because this connection was discarded",
1313
1756
  invalid_config: "Invalid configuration: {field}",
1314
1757
  connection_failed: "Failed to connect to database: {message}",
1315
1758
  permission_denied: "Permission denied (required: {required})",
@@ -1463,6 +1906,9 @@ Reference: {referencePath}`,
1463
1906
  };
1464
1907
  // resources/lang/zh-TW/messages.json
1465
1908
  var messages_default2 = {
1909
+ cli: {
1910
+ agent_output_option: "\u8F38\u51FA\u4E00\u4EFD Operation Envelope v1\uFF08\u50C5\u652F\u63F4 capabilities \u8207 capabilities check\uFF09"
1911
+ },
1466
1912
  init: {
1467
1913
  description: "\u521D\u59CB\u5316 dbcli \u914D\u7F6E\uFF0C\u89E3\u6790 .env \u4E26\u9032\u884C\u4E92\u52D5\u5F0F\u63D0\u793A",
1468
1914
  welcome: "\u6B61\u8FCE\u4F7F\u7528 dbcli",
@@ -1471,6 +1917,9 @@ var messages_default2 = {
1471
1917
  prompt_port: "\u8CC7\u6599\u5EAB\u57E0\u865F\uFF1A",
1472
1918
  prompt_user: "\u8CC7\u6599\u5EAB\u4F7F\u7528\u8005\uFF1A",
1473
1919
  prompt_password: "\u8CC7\u6599\u5EAB\u5BC6\u78BC\uFF1A",
1920
+ 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",
1921
+ 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",
1922
+ secret_alternatives_mongo_uri: "\u4EE5 --uri \u50B3\u5165\u9023\u7DDA\u5B57\u4E32",
1474
1923
  prompt_name: "\u8CC7\u6599\u5EAB\u540D\u7A31\uFF1A",
1475
1924
  prompt_permission: "\u6B0A\u9650\u7B49\u7D1A\uFF08query-only, read-write, data-admin, admin\uFF09\uFF1A",
1476
1925
  env_parse_note: "\u63D0\u793A\uFF1A\u7121\u6CD5\u89E3\u6790 .env \u914D\u7F6E\uFF0C\u4F7F\u7528\u4E92\u52D5\u5F0F\u63D0\u793A",
@@ -1626,6 +2075,14 @@ var messages_default2 = {
1626
2075
  },
1627
2076
  errors: {
1628
2077
  message: "\u932F\u8AA4\uFF1A{message}",
2078
+ query_only_boundary_failed: "\u7121\u6CD5\u70BA {system} \u5EFA\u7ACB query-only \u8CC7\u6599\u5EAB\u908A\u754C\uFF1A{detail}",
2079
+ 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",
2080
+ 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",
2081
+ query_only_cleanup_completed: "query-only \u76EE\u6A19\u5DF2\u5B8C\u6210\uFF0C\u4F46 {system} \u7684\u4EA4\u6613\u6E05\u7406\u5931\u6557\uFF1A{detail}",
2082
+ 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}",
2083
+ 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",
2084
+ 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",
2085
+ query_only_cleanup_reconnect: "\u6B64\u9023\u7DDA\u5DF2\u4E1F\u68C4\uFF1B\u57F7\u884C\u4E0B\u4E00\u53E5\u524D\u8ACB\u5148\u91CD\u65B0\u9023\u7DDA",
1629
2086
  invalid_config: "\u914D\u7F6E\u7121\u6548\uFF1A{field}",
1630
2087
  connection_failed: "\u7121\u6CD5\u9023\u63A5\u5230\u8CC7\u6599\u5EAB\uFF1A{message}",
1631
2088
  permission_denied: "\u6B0A\u9650\u88AB\u62D2\uFF08\u9700\u8981\uFF1A{required}\uFF09",
@@ -2028,6 +2485,495 @@ var messageLoader = MessageLoader.getInstance();
2028
2485
  var t = (key) => messageLoader.t(key);
2029
2486
  var t_vars = (key, vars) => messageLoader.interpolate(key, vars);
2030
2487
 
2488
+ // src/adapters/sql-adapter-utils.ts
2489
+ function queryOnlyBoundaryError(system, error) {
2490
+ const detail = error instanceof Error ? error.message : String(error);
2491
+ return new ConnectionError("QUERY_ONLY_BOUNDARY_FAILED", t_vars("errors.query_only_boundary_failed", { system, detail }), [
2492
+ t_vars("errors.query_only_boundary_verify", { system }),
2493
+ t_vars("errors.query_only_boundary_not_executed", {})
2494
+ ]);
2495
+ }
2496
+ function queryOnlyCleanupError(system, error, targetCompleted) {
2497
+ const detail = error instanceof Error ? error.message : String(error);
2498
+ return new ConnectionError("CONNECTION_LOST", targetCompleted ? t_vars("errors.query_only_cleanup_completed", { system, detail }) : t_vars("errors.query_only_cleanup_uncertain", { system, detail }), [
2499
+ targetCompleted ? t_vars("errors.query_only_cleanup_completed_retry", {}) : t_vars("errors.query_only_cleanup_uncertain_retry", {}),
2500
+ t_vars("errors.query_only_cleanup_reconnect", {})
2501
+ ], undefined, false);
2502
+ }
2503
+ function requireConnected(connection) {
2504
+ if (!connection) {
2505
+ throw new ConnectionError("UNKNOWN", "Database connection not established", [
2506
+ "Call connect() to establish a connection"
2507
+ ]);
2508
+ }
2509
+ return connection;
2510
+ }
2511
+ async function withMappedConnectionError(system, options, operation) {
2512
+ try {
2513
+ return await operation();
2514
+ } catch (error) {
2515
+ throw mapError(error, system, options);
2516
+ }
2517
+ }
2518
+
2519
+ // src/utils/colors.ts
2520
+ var import_picocolors = __toESM(require_picocolors(), 1);
2521
+ var colors = {
2522
+ success: (text) => import_picocolors.default.green(text),
2523
+ error: (text) => import_picocolors.default.red(text),
2524
+ warn: (text) => import_picocolors.default.yellow(text),
2525
+ info: (text) => import_picocolors.default.blue(text),
2526
+ dim: (text) => import_picocolors.default.dim(text),
2527
+ bold: (text) => import_picocolors.default.bold(text),
2528
+ keyword: (text) => import_picocolors.default.blue(import_picocolors.default.bold(text))
2529
+ };
2530
+
2531
+ // src/utils/db-version-check.ts
2532
+ var MIN_SUPPORTED_VERSIONS = {
2533
+ postgresql: "12.0",
2534
+ mysql: "8.0",
2535
+ mariadb: "10.5",
2536
+ elasticsearch: "7.10"
2537
+ };
2538
+ function parseVersionSegments(version) {
2539
+ const match = version.match(/^(\d+(?:\.\d+)*)/);
2540
+ if (!match)
2541
+ return [];
2542
+ return (match[1] ?? "").split(".").map(Number);
2543
+ }
2544
+ function isMariaDBVersion(versionString) {
2545
+ return /mariadb/i.test(versionString);
2546
+ }
2547
+ function extractMariaDBVersion(versionString) {
2548
+ const match = versionString.match(/(\d+\.\d+\.\d+)-MariaDB/i);
2549
+ if (match)
2550
+ return match[1] ?? versionString;
2551
+ const prefixMatch = versionString.match(/^5\.5\.5-(\d+\.\d+\.\d+)/);
2552
+ if (prefixMatch)
2553
+ return prefixMatch[1] ?? versionString;
2554
+ return versionString;
2555
+ }
2556
+ function compareVersions(a, b) {
2557
+ const segA = parseVersionSegments(a);
2558
+ const segB = parseVersionSegments(b);
2559
+ const len = Math.max(segA.length, segB.length);
2560
+ for (let i = 0;i < len; i++) {
2561
+ const diff = (segA[i] ?? 0) - (segB[i] ?? 0);
2562
+ if (diff !== 0)
2563
+ return diff;
2564
+ }
2565
+ return 0;
2566
+ }
2567
+ function checkDbVersion(rawVersion, declaredSystem) {
2568
+ const isMariaDB = isMariaDBVersion(rawVersion);
2569
+ const system = isMariaDB ? "mariadb" : declaredSystem;
2570
+ const serverVersion = isMariaDB ? extractMariaDBVersion(rawVersion) : rawVersion;
2571
+ const minVersion = MIN_SUPPORTED_VERSIONS[system];
2572
+ const supported = compareVersions(serverVersion, minVersion ?? "0") >= 0;
2573
+ return {
2574
+ serverVersion,
2575
+ system,
2576
+ supported,
2577
+ minVersion: minVersion ?? "0"
2578
+ };
2579
+ }
2580
+ function warnIfUnsupported(result) {
2581
+ if (result.supported)
2582
+ return;
2583
+ const message = t_vars("version.unsupported_warning", {
2584
+ system: result.system,
2585
+ version: result.serverVersion,
2586
+ minVersion: result.minVersion
2587
+ });
2588
+ process.stderr.write(colors.warn(`\u26A0 ${message}`) + `
2589
+ `);
2590
+ }
2591
+
2592
+ // src/utils/encoding.ts
2593
+ var CP1252_REVERSE = {
2594
+ 8364: 128,
2595
+ 8218: 130,
2596
+ 402: 131,
2597
+ 8222: 132,
2598
+ 8230: 133,
2599
+ 8224: 134,
2600
+ 8225: 135,
2601
+ 710: 136,
2602
+ 8240: 137,
2603
+ 352: 138,
2604
+ 8249: 139,
2605
+ 338: 140,
2606
+ 381: 142,
2607
+ 8216: 145,
2608
+ 8217: 146,
2609
+ 8220: 147,
2610
+ 8221: 148,
2611
+ 8226: 149,
2612
+ 8211: 150,
2613
+ 8212: 151,
2614
+ 732: 152,
2615
+ 8482: 153,
2616
+ 353: 154,
2617
+ 8250: 155,
2618
+ 339: 156,
2619
+ 382: 158,
2620
+ 376: 159
2621
+ };
2622
+ function fixDoubleEncodedUtf8(str) {
2623
+ if (!str)
2624
+ return str;
2625
+ const bytes = [];
2626
+ for (const char of str) {
2627
+ const code = char.codePointAt(0);
2628
+ if (code < 128) {
2629
+ bytes.push(code);
2630
+ } else if (code <= 255) {
2631
+ bytes.push(code);
2632
+ } else {
2633
+ const cp1252Byte = CP1252_REVERSE[code];
2634
+ if (cp1252Byte !== undefined) {
2635
+ bytes.push(cp1252Byte);
2636
+ } else {
2637
+ return str;
2638
+ }
2639
+ }
2640
+ }
2641
+ try {
2642
+ const decoded = new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes));
2643
+ if (decoded !== str) {
2644
+ return decoded;
2645
+ }
2646
+ } catch {}
2647
+ return str;
2648
+ }
2649
+
2650
+ // src/adapters/identifier-quote.ts
2651
+ function quoteCharFor(dialect) {
2652
+ return dialect === "postgresql" ? '"' : "`";
2653
+ }
2654
+ function assertQuotable(name, what) {
2655
+ if (name.length === 0) {
2656
+ throw new Error(`${what} must not be empty`);
2657
+ }
2658
+ if (name.includes(String.fromCharCode(0))) {
2659
+ throw new Error(`${what} must not contain a NUL character`);
2660
+ }
2661
+ }
2662
+ function quoteIdentifier(name, dialect) {
2663
+ assertQuotable(name, "identifier");
2664
+ const q = quoteCharFor(dialect);
2665
+ return `${q}${name.split(q).join(q + q)}${q}`;
2666
+ }
2667
+
2668
+ // src/adapters/timeout-policy.ts
2669
+ var DEFAULT_CONNECT_TIMEOUT_MS = 5000;
2670
+ function resolveTimeoutPolicy(options) {
2671
+ return {
2672
+ connectMs: options.timeout ?? DEFAULT_CONNECT_TIMEOUT_MS,
2673
+ statementMs: options.statementTimeout ?? options.timeout
2674
+ };
2675
+ }
2676
+ function statementTimeoutSql(system, ms) {
2677
+ if (system === "mariadb") {
2678
+ return `SET SESSION max_statement_time = ${ms / 1000}`;
2679
+ }
2680
+ return `SET SESSION max_execution_time = ${Math.round(ms)}`;
2681
+ }
2682
+
2683
+ // src/utils/connection-error-message.ts
2684
+ var IS_TRANSPORT_FAILURE = {
2685
+ ECONNREFUSED: true,
2686
+ ETIMEDOUT: true,
2687
+ AUTH_FAILED: true,
2688
+ ENOTFOUND: true,
2689
+ EHOSTUNREACH: true,
2690
+ CONNECTION_LOST: true,
2691
+ TOO_MANY_CONNECTIONS: true,
2692
+ TLS_ERROR: true,
2693
+ SERVER_NOT_READY: true,
2694
+ CONNECTION_REJECTED: true,
2695
+ STATEMENT_TIMEOUT: false,
2696
+ SQL_SYNTAX_ERROR: false,
2697
+ TABLE_NOT_FOUND: false,
2698
+ COLUMN_NOT_FOUND: false,
2699
+ QUERY_ONLY_BOUNDARY_FAILED: false,
2700
+ UNKNOWN: false
2701
+ };
2702
+ function isTransportFailure(error) {
2703
+ if (error instanceof ConnectionError)
2704
+ return IS_TRANSPORT_FAILURE[error.code];
2705
+ const raw = error?.code;
2706
+ return typeof raw === "string" && isTransportDriverCode(raw);
2707
+ }
2708
+
2709
+ // src/adapters/mysql-adapter.ts
2710
+ function parseEnumValues(columnType) {
2711
+ const match = columnType.match(/^enum\((.+)\)$/i);
2712
+ if (!match)
2713
+ return;
2714
+ return (match[1] ?? "").split(",").map((v) => v.trim().replace(/^'|'$/g, ""));
2715
+ }
2716
+
2717
+ class MySQLAdapter {
2718
+ db = null;
2719
+ executionTail = Promise.resolve();
2720
+ options;
2721
+ system;
2722
+ constructor(options) {
2723
+ this.options = options;
2724
+ this.system = options.system === "mariadb" ? "mariadb" : "mysql";
2725
+ if (options.port < 1 || options.port > 65535) {
2726
+ throw new Error(`Invalid port number: ${options.port}`);
2727
+ }
2728
+ }
2729
+ async connect() {
2730
+ await withMappedConnectionError(this.system, this.options, async () => {
2731
+ const timeouts = resolveTimeoutPolicy(this.options);
2732
+ const { default: driver } = await import("mysql2/promise");
2733
+ this.db = await driver.createConnection({
2734
+ host: this.options.host,
2735
+ port: this.options.port,
2736
+ user: this.options.user,
2737
+ password: this.options.password || undefined,
2738
+ database: this.options.database,
2739
+ charset: "utf8mb4",
2740
+ connectTimeout: timeouts.connectMs
2741
+ });
2742
+ await this.db.execute("SET NAMES utf8mb4");
2743
+ if (timeouts.statementMs !== undefined) {
2744
+ try {
2745
+ await this.db.query(statementTimeoutSql(this.system, timeouts.statementMs));
2746
+ } catch {}
2747
+ }
2748
+ await this.testConnection();
2749
+ try {
2750
+ const rawVersion = await this.getServerVersion();
2751
+ const result = checkDbVersion(rawVersion, this.system);
2752
+ warnIfUnsupported(result);
2753
+ } catch {}
2754
+ });
2755
+ }
2756
+ async disconnect() {
2757
+ try {
2758
+ if (this.db) {
2759
+ await this.db.end();
2760
+ this.db = null;
2761
+ }
2762
+ } catch {}
2763
+ }
2764
+ async testConnection() {
2765
+ requireConnected(this.db);
2766
+ return withMappedConnectionError(this.system, this.options, async () => {
2767
+ const result = await this.execute("SELECT 1 as count");
2768
+ return result.rows.length > 0;
2769
+ });
2770
+ }
2771
+ async execute(sql, params, options) {
2772
+ const db = requireConnected(this.db);
2773
+ return withMappedConnectionError(this.system, this.options, async () => {
2774
+ const execution = this.executionTail.then(async () => {
2775
+ if (options?.sqlMode === "native-read-only") {
2776
+ return this.executeReadOnly(db, sql, params);
2777
+ }
2778
+ const [result] = params ? await db.execute(sql, params) : await db.execute(sql);
2779
+ if (Array.isArray(result)) {
2780
+ return {
2781
+ rows: result,
2782
+ affectedRows: result.length
2783
+ };
2784
+ }
2785
+ const header = result;
2786
+ return {
2787
+ rows: [],
2788
+ affectedRows: header.affectedRows || 0,
2789
+ lastInsertId: header.insertId
2790
+ };
2791
+ });
2792
+ this.executionTail = execution.then(() => {
2793
+ return;
2794
+ }, () => {
2795
+ return;
2796
+ });
2797
+ return execution;
2798
+ });
2799
+ }
2800
+ async executeReadOnly(db, sql, params) {
2801
+ let primaryError;
2802
+ let cleanupError;
2803
+ let executionResult;
2804
+ let transactionStarted = false;
2805
+ try {
2806
+ try {
2807
+ await db.query("START TRANSACTION READ ONLY");
2808
+ transactionStarted = true;
2809
+ } catch (error) {
2810
+ const mapped = mapError(error, this.system, this.options);
2811
+ if (isTransportFailure(mapped))
2812
+ throw mapped;
2813
+ throw queryOnlyBoundaryError(this.system, error);
2814
+ }
2815
+ const [result] = params ? await db.execute(sql, params) : await db.execute(sql);
2816
+ if (Array.isArray(result)) {
2817
+ executionResult = { rows: result, affectedRows: result.length };
2818
+ } else {
2819
+ const header = result;
2820
+ executionResult = {
2821
+ rows: [],
2822
+ affectedRows: header.affectedRows || 0,
2823
+ lastInsertId: header.insertId
2824
+ };
2825
+ }
2826
+ } catch (error) {
2827
+ primaryError = error;
2828
+ } finally {
2829
+ if (transactionStarted) {
2830
+ try {
2831
+ await db.query("ROLLBACK");
2832
+ } catch (error) {
2833
+ db.destroy();
2834
+ this.db = null;
2835
+ cleanupError = error;
2836
+ }
2837
+ }
2838
+ }
2839
+ if (cleanupError !== undefined) {
2840
+ throw queryOnlyCleanupError(this.system, cleanupError, primaryError === undefined);
2841
+ }
2842
+ if (primaryError !== undefined)
2843
+ throw primaryError;
2844
+ return executionResult;
2845
+ }
2846
+ async getServerVersion() {
2847
+ const result = await this.execute("SELECT VERSION() as version");
2848
+ return result.rows[0]?.version ?? "unknown";
2849
+ }
2850
+ async listTables() {
2851
+ requireConnected(this.db);
2852
+ return withMappedConnectionError(this.system, this.options, async () => {
2853
+ const query = `
2854
+ SELECT
2855
+ t.TABLE_NAME as table_name,
2856
+ t.TABLE_ROWS as row_count,
2857
+ t.ENGINE as engine,
2858
+ t.TABLE_TYPE as table_type,
2859
+ COUNT(c.COLUMN_NAME) as column_count
2860
+ FROM information_schema.TABLES t
2861
+ LEFT JOIN information_schema.COLUMNS c
2862
+ ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME
2863
+ WHERE t.TABLE_SCHEMA = DATABASE()
2864
+ GROUP BY t.TABLE_NAME, t.TABLE_ROWS, t.ENGINE, t.TABLE_TYPE
2865
+ ORDER BY t.TABLE_NAME
2866
+ `;
2867
+ const result = await this.execute(query);
2868
+ return result.rows.map((row) => ({
2869
+ name: row.table_name,
2870
+ columns: [],
2871
+ columnCount: row.column_count,
2872
+ rowCount: row.row_count || 0,
2873
+ engine: row.engine,
2874
+ estimatedRowCount: row.row_count || 0,
2875
+ tableType: row.table_type === "VIEW" ? "view" : "table"
2876
+ }));
2877
+ });
2878
+ }
2879
+ async getTableSchema(tableName, options) {
2880
+ requireConnected(this.db);
2881
+ return withMappedConnectionError(this.system, this.options, async () => {
2882
+ const columnQuery = `
2883
+ SELECT
2884
+ COLUMN_NAME as name,
2885
+ COLUMN_TYPE as type,
2886
+ IS_NULLABLE = 'YES' as nullable,
2887
+ COLUMN_DEFAULT as default_value,
2888
+ COLUMN_KEY = 'PRI' as is_primary_key,
2889
+ EXTRA LIKE '%auto_increment%' as auto_increment,
2890
+ COLUMN_COMMENT as comment
2891
+ FROM information_schema.COLUMNS
2892
+ WHERE TABLE_SCHEMA = DATABASE()
2893
+ AND TABLE_NAME = ?
2894
+ ORDER BY ORDINAL_POSITION
2895
+ `;
2896
+ const columnResult = await this.execute(columnQuery, [tableName]);
2897
+ const columns = columnResult.rows;
2898
+ const fkQuery = `
2899
+ SELECT
2900
+ rc.CONSTRAINT_NAME as name,
2901
+ GROUP_CONCAT(kcu.COLUMN_NAME) as columns,
2902
+ rc.REFERENCED_TABLE_NAME as ref_table,
2903
+ GROUP_CONCAT(kcu.REFERENCED_COLUMN_NAME) as ref_columns
2904
+ FROM information_schema.REFERENTIAL_CONSTRAINTS rc
2905
+ JOIN information_schema.KEY_COLUMN_USAGE kcu
2906
+ ON rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND rc.TABLE_NAME = kcu.TABLE_NAME
2907
+ WHERE kcu.TABLE_NAME = ? AND rc.CONSTRAINT_SCHEMA = DATABASE()
2908
+ GROUP BY rc.CONSTRAINT_NAME, rc.REFERENCED_TABLE_NAME
2909
+ `;
2910
+ const fkResult = await this.execute(fkQuery, [tableName]);
2911
+ const fkResults = fkResult.rows;
2912
+ const fkMap = new Map;
2913
+ for (const fk of fkResults) {
2914
+ const cols = fk.columns.split(",").map((c) => c.trim());
2915
+ const refCols = fk.ref_columns.split(",").map((c) => c.trim());
2916
+ if (cols.length === 1 && refCols.length === 1) {
2917
+ fkMap.set(cols[0], { table: fk.ref_table, column: refCols[0] });
2918
+ }
2919
+ }
2920
+ const primaryKeyColumns = columns.filter((col) => col.is_primary_key).map((col) => col.name);
2921
+ const indexQuery = `
2922
+ SELECT
2923
+ INDEX_NAME as name,
2924
+ GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as columns,
2925
+ NOT NON_UNIQUE as is_unique
2926
+ FROM information_schema.STATISTICS
2927
+ WHERE TABLE_SCHEMA = DATABASE()
2928
+ AND TABLE_NAME = ?
2929
+ AND INDEX_NAME != 'PRIMARY'
2930
+ GROUP BY INDEX_NAME, NON_UNIQUE
2931
+ `;
2932
+ const indexResult = await this.execute(indexQuery, [tableName]);
2933
+ const indexResults = indexResult.rows;
2934
+ const tableMetaQuery = `
2935
+ SELECT ENGINE as engine, TABLE_ROWS as estimated_rows
2936
+ FROM information_schema.TABLES
2937
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
2938
+ `;
2939
+ const tableMetaResult = await this.execute(tableMetaQuery, [tableName]);
2940
+ const tableMeta = tableMetaResult.rows[0];
2941
+ const estimatedRowCount = Math.max(0, tableMeta?.estimated_rows || 0);
2942
+ const exactRowCount = options?.exactRowCount === false ? undefined : (await this.execute(`SELECT COUNT(*) as count FROM ${quoteIdentifier(tableName, this.system)}`)).rows[0]?.count;
2943
+ const schema = {
2944
+ name: tableName,
2945
+ columns: columns.map((col) => ({
2946
+ name: col.name,
2947
+ type: col.type,
2948
+ nullable: col.nullable,
2949
+ default: col.default_value || undefined,
2950
+ primaryKey: col.is_primary_key,
2951
+ foreignKey: fkMap.get(col.name),
2952
+ autoIncrement: col.auto_increment,
2953
+ comment: col.comment ? fixDoubleEncodedUtf8(col.comment) : null,
2954
+ enumValues: parseEnumValues(col.type)
2955
+ })),
2956
+ rowCount: exactRowCount ?? estimatedRowCount,
2957
+ engine: tableMeta?.engine || "MySQL",
2958
+ primaryKey: primaryKeyColumns,
2959
+ foreignKeys: fkResults.map((fk) => ({
2960
+ name: fk.name,
2961
+ columns: fk.columns.split(",").map((c) => c.trim()),
2962
+ refTable: fk.ref_table,
2963
+ refColumns: fk.ref_columns.split(",").map((c) => c.trim())
2964
+ })),
2965
+ indexes: indexResults.map((idx) => ({
2966
+ name: idx.name,
2967
+ columns: idx.columns.split(",").map((c) => c.trim()),
2968
+ unique: idx.is_unique
2969
+ })),
2970
+ estimatedRowCount
2971
+ };
2972
+ return schema;
2973
+ });
2974
+ }
2975
+ }
2976
+
2031
2977
  // src/utils/case-fold.ts
2032
2978
  function foldCase(text) {
2033
2979
  const lowered = (text.includes("\u0130") ? text.replaceAll("\u0130", "i") : text).toLowerCase();
@@ -3177,6 +4123,17 @@ function removeParameterMarkers(sql) {
3177
4123
  return sql.replace(/\$\d+/g, "").replace(/\?/g, "");
3178
4124
  }
3179
4125
 
4126
+ // src/core/permission/rank.ts
4127
+ var PERMISSION_RANK = Object.freeze({
4128
+ "query-only": 1,
4129
+ "read-write": 2,
4130
+ "data-admin": 3,
4131
+ admin: 4
4132
+ });
4133
+ function permissionAtLeast(actual, required) {
4134
+ return PERMISSION_RANK[actual] >= PERMISSION_RANK[required];
4135
+ }
4136
+
3180
4137
  // src/core/permission-guard.ts
3181
4138
  class PermissionError extends Error {
3182
4139
  classification;
@@ -4172,7 +5129,9 @@ class QueryExecutor {
4172
5129
  if (this.blacklistValidator && referencedTables.length > 0) {
4173
5130
  this.blacklistValidator.checkTablesBlacklist(classification.type, referencedTables);
4174
5131
  }
4175
- const resultData = await this.adapter.execute(executeSql);
5132
+ const resultData = await this.adapter.execute(executeSql, undefined, {
5133
+ sqlMode: this.permission === "query-only" ? "native-read-only" : "normal"
5134
+ });
4176
5135
  const executionTimeMs = Math.round(performance.now() - start);
4177
5136
  const limitedResult = appliedLimit === undefined ? undefined : trimAppliedLimit(resultData.rows, appliedLimit);
4178
5137
  const rows = limitedResult?.rows ?? resultData.rows;
@@ -9590,6 +10549,1106 @@ DBCLI_PASSWORD=${password}
9590
10549
  }
9591
10550
  }
9592
10551
  };
10552
+ // src/core/capabilities/types.ts
10553
+ var CAPABILITY_CONTRACT_SCHEMA_VERSION = 1;
10554
+ // src/core/capabilities/registry.ts
10555
+ var DECLARATIONS = [
10556
+ {
10557
+ id: "connection.init",
10558
+ key: "init",
10559
+ command: "init",
10560
+ description: "Create or update a local connection configuration interactively.",
10561
+ permission: "query-only",
10562
+ requiresConnection: true,
10563
+ mutatesConfiguration: true
10564
+ },
10565
+ {
10566
+ id: "connection.select",
10567
+ key: "use",
10568
+ command: "use",
10569
+ description: "Switch the default named connection in a v2 configuration.",
10570
+ permission: "query-only",
10571
+ requiresConnection: false,
10572
+ mutatesConfiguration: true
10573
+ },
10574
+ {
10575
+ id: "connection.status",
10576
+ key: "status",
10577
+ command: "status",
10578
+ description: "Report the configured engine, permission and blacklist counts without credentials.",
10579
+ permission: "query-only",
10580
+ requiresConnection: false
10581
+ },
10582
+ {
10583
+ id: "connection.diagnose",
10584
+ key: "doctor",
10585
+ command: "doctor",
10586
+ description: "Run engine-specific connectivity and configuration diagnostics.",
10587
+ permission: "query-only",
10588
+ requiresConnection: true
10589
+ },
10590
+ {
10591
+ id: "schema.list-tables",
10592
+ key: "list",
10593
+ command: "list",
10594
+ description: "List the tables, collections or indices visible to the connection.",
10595
+ permission: "query-only",
10596
+ requiresConnection: true
10597
+ },
10598
+ {
10599
+ id: "schema.read",
10600
+ key: "schema",
10601
+ command: "schema",
10602
+ description: "Read visible schema metadata for one object or the whole database.",
10603
+ permission: "query-only",
10604
+ requiresConnection: true
10605
+ },
10606
+ {
10607
+ id: "schema.read-object",
10608
+ key: "schemaSingle",
10609
+ command: "schema",
10610
+ description: "Read the column or field structure of a single named object.",
10611
+ permission: "query-only",
10612
+ requiresConnection: true
10613
+ },
10614
+ {
10615
+ id: "schema.scan",
10616
+ key: "schemaFullScan",
10617
+ command: "schema",
10618
+ description: "Scan every visible object and refresh the local schema cache.",
10619
+ permission: "query-only",
10620
+ requiresConnection: true
10621
+ },
10622
+ {
10623
+ id: "schema.diff",
10624
+ key: "diff",
10625
+ command: "diff",
10626
+ description: "Compare schema snapshots and report structural differences.",
10627
+ permission: "query-only",
10628
+ requiresConnection: true
10629
+ },
10630
+ {
10631
+ id: "schema.migrate",
10632
+ key: "migrate",
10633
+ command: "migrate",
10634
+ description: "Apply guarded DDL changes such as columns, indexes and constraints.",
10635
+ statementType: "ALTER",
10636
+ requiresConnection: true
10637
+ },
10638
+ {
10639
+ id: "query.read",
10640
+ key: "query",
10641
+ command: "query",
10642
+ description: "Run a read query through the permission, blacklist and audit gates.",
10643
+ statementType: "SELECT",
10644
+ requiresConnection: true
10645
+ },
10646
+ {
10647
+ id: "query.lint",
10648
+ key: "lint",
10649
+ command: "lint",
10650
+ description: "Statically analyse SQL without connecting to the database.",
10651
+ permission: "query-only",
10652
+ requiresConnection: false
10653
+ },
10654
+ {
10655
+ id: "query.format",
10656
+ key: "queryOutput",
10657
+ command: "query",
10658
+ description: "Render query results as a table, JSON or CSV.",
10659
+ permission: "query-only",
10660
+ requiresConnection: true
10661
+ },
10662
+ {
10663
+ id: "query.limit-guard",
10664
+ key: "queryLimitGuard",
10665
+ command: "query",
10666
+ description: "Bound result size automatically and report the limit that was applied.",
10667
+ permission: "query-only",
10668
+ requiresConnection: true
10669
+ },
10670
+ {
10671
+ id: "query.shell",
10672
+ key: "shell",
10673
+ command: "shell",
10674
+ description: "Open an interactive gated query shell.",
10675
+ permission: "query-only",
10676
+ requiresConnection: true
10677
+ },
10678
+ {
10679
+ id: "snippet.run",
10680
+ key: "q",
10681
+ command: "q",
10682
+ description: "Execute a saved read-only query snippet with parameters.",
10683
+ permission: "query-only",
10684
+ requiresConnection: true
10685
+ },
10686
+ {
10687
+ id: "snippet.manage",
10688
+ key: "queries",
10689
+ command: "queries",
10690
+ description: "Create, edit, search and validate saved query snippets.",
10691
+ permission: "query-only",
10692
+ requiresConnection: false
10693
+ },
10694
+ {
10695
+ id: "data.insert",
10696
+ key: "insert",
10697
+ command: "insert",
10698
+ description: "Insert rows or documents through the write gate.",
10699
+ statementType: "INSERT",
10700
+ requiresConnection: true
10701
+ },
10702
+ {
10703
+ id: "data.update",
10704
+ key: "update",
10705
+ command: "update",
10706
+ description: "Update rows or documents through the write gate.",
10707
+ statementType: "UPDATE",
10708
+ requiresConnection: true
10709
+ },
10710
+ {
10711
+ id: "data.delete",
10712
+ key: "delete",
10713
+ command: "delete",
10714
+ description: "Delete rows or documents through the write gate.",
10715
+ statementType: "DELETE",
10716
+ requiresConnection: true
10717
+ },
10718
+ {
10719
+ id: "data.export",
10720
+ key: "export",
10721
+ command: "export",
10722
+ description: "Export query or object contents to a file, subject to the blacklist.",
10723
+ permission: "query-only",
10724
+ requiresConnection: true
10725
+ },
10726
+ {
10727
+ id: "data.health-check",
10728
+ key: "check",
10729
+ command: "check",
10730
+ description: "Check data health: nulls, orphans, duplicates and empty strings.",
10731
+ permission: "query-only",
10732
+ requiresConnection: true
10733
+ },
10734
+ {
10735
+ id: "blacklist.manage",
10736
+ key: "blacklist",
10737
+ command: "blacklist",
10738
+ description: "Inspect and edit the table, column and key rules that hide sensitive data.",
10739
+ permission: "query-only",
10740
+ requiresConnection: false,
10741
+ mutatesConfiguration: true
10742
+ },
10743
+ {
10744
+ id: "context.inspect",
10745
+ key: "inspect",
10746
+ command: "inspect",
10747
+ description: "Produce a bounded agent context snapshot of the configured database.",
10748
+ permission: "query-only",
10749
+ requiresConnection: true
10750
+ },
10751
+ {
10752
+ id: "diagnostic.report",
10753
+ key: "report",
10754
+ command: "report",
10755
+ description: "Produce a diagnostic report about the configured database.",
10756
+ permission: "query-only",
10757
+ requiresConnection: true
10758
+ },
10759
+ {
10760
+ id: "guide.plan",
10761
+ key: "guide",
10762
+ command: "guide",
10763
+ description: "Suggest the next deterministic command for a stated goal.",
10764
+ permission: "query-only",
10765
+ requiresConnection: true
10766
+ },
10767
+ {
10768
+ id: "recovery.plan",
10769
+ key: "recover",
10770
+ command: "recover",
10771
+ description: "Read the saved recovery envelope and plan or apply safe remediation steps.",
10772
+ permission: "query-only",
10773
+ requiresConnection: false
10774
+ },
10775
+ {
10776
+ id: "audit.tail",
10777
+ key: "auditTail",
10778
+ command: "audit tail",
10779
+ description: "Read recent audit entries from the local log.",
10780
+ permission: "query-only",
10781
+ requiresConnection: false
10782
+ },
10783
+ {
10784
+ id: "audit.show",
10785
+ key: "auditShow",
10786
+ command: "audit show",
10787
+ description: "Look up one audit entry by id prefix or recovery reference.",
10788
+ permission: "query-only",
10789
+ requiresConnection: false
10790
+ },
10791
+ {
10792
+ id: "audit.health",
10793
+ key: "auditHealth",
10794
+ command: "audit health",
10795
+ description: "Report audit log health and rotation state.",
10796
+ permission: "query-only",
10797
+ requiresConnection: false
10798
+ },
10799
+ {
10800
+ id: "audit.clear",
10801
+ key: "auditClear",
10802
+ command: "audit clear",
10803
+ description: "Remove the local audit log files for a connection.",
10804
+ permission: "admin",
10805
+ requiresConnection: false
10806
+ },
10807
+ {
10808
+ id: "skill.install",
10809
+ key: "skill",
10810
+ command: "skill",
10811
+ description: "Write the dbcli skill and task-pack assets into a platform directory.",
10812
+ permission: "query-only",
10813
+ requiresConnection: false
10814
+ },
10815
+ {
10816
+ id: "shell-completion.generate",
10817
+ key: "completion",
10818
+ command: "completion",
10819
+ description: "Emit a shell completion script for the current command tree.",
10820
+ permission: "query-only",
10821
+ requiresConnection: false
10822
+ },
10823
+ {
10824
+ id: "package.upgrade-check",
10825
+ key: "upgrade",
10826
+ command: "upgrade",
10827
+ description: "Check whether a newer dbcli release is available.",
10828
+ permission: "query-only",
10829
+ requiresConnection: false
10830
+ },
10831
+ {
10832
+ id: "query.explain",
10833
+ key: "explain",
10834
+ command: "explain",
10835
+ description: "Read the engine execution plan for a statement without running it as a write.",
10836
+ permission: "query-only",
10837
+ requiresConnection: true
10838
+ },
10839
+ {
10840
+ id: "query.plan-risk",
10841
+ key: "plan",
10842
+ command: "plan",
10843
+ description: "Assess the write risk of a statement offline, against the cached schema.",
10844
+ permission: "query-only",
10845
+ requiresConnection: false
10846
+ },
10847
+ {
10848
+ id: "schema.impact-assess",
10849
+ key: "impactAssess",
10850
+ command: "impact assess",
10851
+ description: "Assess the known effects of a proposed schema change offline.",
10852
+ permission: "query-only",
10853
+ requiresConnection: false
10854
+ },
10855
+ {
10856
+ id: "data.assert",
10857
+ key: "assert",
10858
+ command: "assert",
10859
+ description: "Check a data expectation and report a pass or fail verdict.",
10860
+ permission: "query-only",
10861
+ requiresConnection: true
10862
+ },
10863
+ {
10864
+ id: "data.snapshot",
10865
+ key: "snapshot",
10866
+ command: "snapshot",
10867
+ description: "Capture a result fingerprint for later comparison.",
10868
+ permission: "query-only",
10869
+ requiresConnection: true
10870
+ },
10871
+ {
10872
+ id: "verification.run",
10873
+ key: "verify",
10874
+ command: "verify",
10875
+ description: "Verify a named change scenario and record the outcome.",
10876
+ permission: "query-only",
10877
+ requiresConnection: true
10878
+ },
10879
+ {
10880
+ id: "verification.inspect",
10881
+ key: "verification",
10882
+ command: "verification",
10883
+ description: "Inspect locally stored verification artifacts.",
10884
+ permission: "query-only",
10885
+ requiresConnection: false
10886
+ },
10887
+ {
10888
+ id: "verification.prune",
10889
+ key: "verificationPrune",
10890
+ command: "verification prune",
10891
+ description: "Delete locally stored verification artifacts by age or subject.",
10892
+ permission: "query-only",
10893
+ requiresConnection: false
10894
+ },
10895
+ {
10896
+ id: "evidence.pack",
10897
+ key: "evidence",
10898
+ command: "evidence",
10899
+ description: "Compose, validate and render an evidence pack from local artifacts.",
10900
+ permission: "query-only",
10901
+ requiresConnection: false
10902
+ },
10903
+ {
10904
+ id: "semantic.context",
10905
+ key: "semantic",
10906
+ command: "semantic",
10907
+ description: "Author and validate governed semantic context over the cached schema.",
10908
+ permission: "query-only",
10909
+ requiresConnection: false
10910
+ },
10911
+ {
10912
+ id: "semantic.contract",
10913
+ key: "contract",
10914
+ command: "contract",
10915
+ description: "Validate and search governed business contracts offline.",
10916
+ permission: "query-only",
10917
+ requiresConnection: false
10918
+ },
10919
+ {
10920
+ id: "schema.design",
10921
+ key: "design",
10922
+ command: "design",
10923
+ description: "Author a schema design and compare it against the database or an ORM.",
10924
+ permission: "query-only",
10925
+ requiresConnection: false
10926
+ },
10927
+ {
10928
+ id: "diagnostic.proxy",
10929
+ key: "proxy",
10930
+ command: "proxy",
10931
+ description: "Run a local observing proxy in front of a supported SQL engine.",
10932
+ permission: "query-only",
10933
+ requiresConnection: true
10934
+ },
10935
+ {
10936
+ id: "diagnostic.proxy-analyze",
10937
+ key: "proxyAnalyze",
10938
+ command: "proxy analyze",
10939
+ description: "Analyse locally recorded proxy events without contacting an engine.",
10940
+ permission: "query-only",
10941
+ requiresConnection: false
10942
+ },
10943
+ {
10944
+ id: "recovery.codes",
10945
+ key: "recovery",
10946
+ command: "recovery",
10947
+ description: "Describe a recovery code and its remediation steps.",
10948
+ permission: "query-only",
10949
+ requiresConnection: false
10950
+ },
10951
+ {
10952
+ id: "data.backfill-artifact",
10953
+ key: "backfillArtifact",
10954
+ command: "backfill artifact",
10955
+ description: "Build a backfill artifact from a source manifest and a connection identity.",
10956
+ permission: "query-only",
10957
+ requiresConnection: false
10958
+ },
10959
+ {
10960
+ id: "connection.rotate-credential",
10961
+ key: "password",
10962
+ command: "password",
10963
+ description: "Change the stored credential for a single connection.",
10964
+ permission: "query-only",
10965
+ requiresConnection: true,
10966
+ mutatesConfiguration: true
10967
+ },
10968
+ {
10969
+ id: "capability.discover",
10970
+ key: "capabilityDiscover",
10971
+ command: "capabilities",
10972
+ description: "List the static capability catalog this build publishes.",
10973
+ permission: "query-only",
10974
+ requiresConnection: false
10975
+ },
10976
+ {
10977
+ id: "capability.check",
10978
+ key: "capabilityCheck",
10979
+ command: "capabilities check",
10980
+ description: "Check whether required capability ids are available in this project.",
10981
+ permission: "query-only",
10982
+ requiresConnection: false
10983
+ }
10984
+ ];
10985
+ function riskForSideEffect(tier) {
10986
+ switch (tier) {
10987
+ case "readonly":
10988
+ case "none":
10989
+ return "readonly";
10990
+ case "dry-run":
10991
+ return "dry-run";
10992
+ case "local-write":
10993
+ case "db-write":
10994
+ case "interactive":
10995
+ return "write";
10996
+ default:
10997
+ return "unknown";
10998
+ }
10999
+ }
11000
+ function enginesFor(key) {
11001
+ const statuses = DATABASE_SYSTEMS.map((system) => ({
11002
+ system,
11003
+ status: ENGINE_CAPABILITIES[system][key].status
11004
+ }));
11005
+ if (statuses.every((entry) => entry.status === "not-applicable")) {
11006
+ return { engines: [...DATABASE_SYSTEMS], limitedEngines: [], engineIndependent: true };
11007
+ }
11008
+ return {
11009
+ engines: statuses.filter((entry) => entry.status === "supported" || entry.status === "limited").map((entry) => entry.system),
11010
+ limitedEngines: statuses.filter((entry) => entry.status === "limited").map((entry) => entry.system),
11011
+ engineIndependent: false
11012
+ };
11013
+ }
11014
+ var TIER_SEVERITY = Object.freeze({
11015
+ none: 0,
11016
+ readonly: 1,
11017
+ "dry-run": 2,
11018
+ "local-write": 3,
11019
+ interactive: 4,
11020
+ "db-write": 5
11021
+ });
11022
+ function sideEffectFor(key, engines) {
11023
+ const considered = engines.length > 0 ? engines : DATABASE_SYSTEMS;
11024
+ let worst = "none";
11025
+ for (const system of considered) {
11026
+ const tier = ENGINE_CAPABILITIES[system][key].tier;
11027
+ if (TIER_SEVERITY[tier] > TIER_SEVERITY[worst])
11028
+ worst = tier;
11029
+ }
11030
+ return worst;
11031
+ }
11032
+ var COMMAND_SURFACE = Object.freeze({
11033
+ jsonCommands: Object.freeze(new Set([
11034
+ "use",
11035
+ "status",
11036
+ "doctor",
11037
+ "list",
11038
+ "schema",
11039
+ "diff",
11040
+ "query",
11041
+ "lint",
11042
+ "q",
11043
+ "insert",
11044
+ "update",
11045
+ "delete",
11046
+ "export",
11047
+ "check",
11048
+ "inspect",
11049
+ "report",
11050
+ "guide",
11051
+ "recover",
11052
+ "audit tail",
11053
+ "audit show",
11054
+ "audit health",
11055
+ "explain",
11056
+ "plan",
11057
+ "impact assess",
11058
+ "assert",
11059
+ "snapshot",
11060
+ "verification prune",
11061
+ "proxy",
11062
+ "proxy analyze",
11063
+ "recovery",
11064
+ "password",
11065
+ "capabilities",
11066
+ "capabilities check"
11067
+ ])),
11068
+ evidenceCommands: Object.freeze(new Set([
11069
+ "assert",
11070
+ "insert",
11071
+ "update",
11072
+ "delete",
11073
+ "verify",
11074
+ "recover",
11075
+ "evidence",
11076
+ "inspect",
11077
+ "report",
11078
+ "schema",
11079
+ "plan",
11080
+ "lint",
11081
+ "explain",
11082
+ "impact assess"
11083
+ ]))
11084
+ });
11085
+ function minimumPermissionOf(declaration) {
11086
+ if (declaration.statementType)
11087
+ return minimumPermissionFor(declaration.statementType);
11088
+ if (declaration.permission)
11089
+ return declaration.permission;
11090
+ throw new Error(`capability ${declaration.id} declares neither statementType nor permission`);
11091
+ }
11092
+ function build(declaration, surface) {
11093
+ const { engines, limitedEngines, engineIndependent } = enginesFor(declaration.key);
11094
+ const sideEffect = sideEffectFor(declaration.key, engines);
11095
+ return Object.freeze({
11096
+ id: declaration.id,
11097
+ description: declaration.description,
11098
+ command: declaration.command,
11099
+ risk: riskForSideEffect(sideEffect),
11100
+ sideEffect,
11101
+ engines: Object.freeze([...engines]),
11102
+ limitedEngines: Object.freeze([...limitedEngines]),
11103
+ engineIndependent,
11104
+ minimumPermission: minimumPermissionOf(declaration),
11105
+ requiresConnection: declaration.requiresConnection,
11106
+ mutatesConfiguration: declaration.mutatesConfiguration ?? false,
11107
+ supportsJson: surface.jsonCommands.has(declaration.command),
11108
+ supportsEvidence: surface.evidenceCommands.has(declaration.command)
11109
+ });
11110
+ }
11111
+ var CAPABILITIES = Object.freeze(DECLARATIONS.map((declaration) => build(declaration, COMMAND_SURFACE)).sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0));
11112
+ var BY_ID = new Map(CAPABILITIES.map((capability) => [capability.id, capability]));
11113
+ function findCapability(id) {
11114
+ return BY_ID.get(id);
11115
+ }
11116
+ function listCapabilityIds() {
11117
+ return CAPABILITIES.map((capability) => capability.id);
11118
+ }
11119
+ function buildCapabilityCatalog() {
11120
+ return { schemaVersion: CAPABILITY_CONTRACT_SCHEMA_VERSION, capabilities: CAPABILITIES };
11121
+ }
11122
+ var DECLARED_CAPABILITY_KEYS = Object.freeze(COMMAND_CAPABILITY_KEYS);
11123
+ // src/core/capabilities/check.ts
11124
+ class CapabilityRequirementError extends Error {
11125
+ constructor(message) {
11126
+ super(message);
11127
+ this.name = "CapabilityRequirementError";
11128
+ Object.setPrototypeOf(this, CapabilityRequirementError.prototype);
11129
+ }
11130
+ }
11131
+ function parseRequirements(raw) {
11132
+ const parts = raw.split(",").map((part) => part.trim());
11133
+ if (parts.some((part) => part === "")) {
11134
+ throw new CapabilityRequirementError('--require contains an empty capability id; give a comma-separated list such as "schema.read,query.read"');
11135
+ }
11136
+ const ids = [];
11137
+ const duplicates = [];
11138
+ for (const part of parts) {
11139
+ if (ids.includes(part)) {
11140
+ if (!duplicates.includes(part))
11141
+ duplicates.push(part);
11142
+ continue;
11143
+ }
11144
+ ids.push(part);
11145
+ }
11146
+ return {
11147
+ ids,
11148
+ warnings: duplicates.map((id) => `Duplicate capability id '${id}' in --require was ignored.`)
11149
+ };
11150
+ }
11151
+ function evaluate(id, context, failure) {
11152
+ const capability = findCapability(id);
11153
+ if (!capability)
11154
+ return { id, status: "unknown", reason: "unknown-capability" };
11155
+ if (!context) {
11156
+ return {
11157
+ id,
11158
+ status: "unavailable",
11159
+ reason: failure === "absent" ? "context-unavailable" : "context-unresolvable"
11160
+ };
11161
+ }
11162
+ if (!capability.engineIndependent && !capability.engines.includes(context.engine)) {
11163
+ return { id, status: "unavailable", reason: "engine" };
11164
+ }
11165
+ if (context.agentMode && capability.mutatesConfiguration) {
11166
+ return { id, status: "unavailable", reason: "agent-mode" };
11167
+ }
11168
+ if (!permissionAtLeast(context.permission, capability.minimumPermission)) {
11169
+ return { id, status: "unavailable", reason: "permission" };
11170
+ }
11171
+ return { id, status: "available", reason: null };
11172
+ }
11173
+ function checkCapabilities(required, context, warnings = [], contextFailure = "absent") {
11174
+ const results = required.map((id) => evaluate(id, context, contextFailure));
11175
+ const allWarnings = [...warnings];
11176
+ if (!context) {
11177
+ allWarnings.push(contextFailure === "absent" ? "No dbcli configuration was found here, so engine and permission could not be evaluated." : "A dbcli configuration exists here but could not be resolved into an engine and a permission, so nothing was evaluated against it.");
11178
+ } else if (context.agentMode) {
11179
+ allWarnings.push("Agent mode is active (DBCLI_AGENT_MODE=1): configuration, permission and credential changes are refused regardless of permission level.");
11180
+ }
11181
+ return {
11182
+ schemaVersion: CAPABILITY_CONTRACT_SCHEMA_VERSION,
11183
+ ok: results.every((result) => result.status === "available"),
11184
+ required: [...required],
11185
+ context,
11186
+ results,
11187
+ warnings: allWarnings
11188
+ };
11189
+ }
11190
+ // src/core/capabilities/schema.ts
11191
+ class CapabilityContractError extends Error {
11192
+ constructor(message) {
11193
+ super(message);
11194
+ this.name = "CapabilityContractError";
11195
+ Object.setPrototypeOf(this, CapabilityContractError.prototype);
11196
+ }
11197
+ }
11198
+ var PermissionSchema2 = exports_external.enum(["query-only", "read-write", "data-admin", "admin"]);
11199
+ var EngineSchema = exports_external.enum(DATABASE_SYSTEMS);
11200
+ var RiskSchema = exports_external.enum(["readonly", "dry-run", "write", "unknown"]);
11201
+ var SideEffectSchema = exports_external.enum([
11202
+ "readonly",
11203
+ "dry-run",
11204
+ "local-write",
11205
+ "db-write",
11206
+ "interactive",
11207
+ "none"
11208
+ ]);
11209
+ var CAPABILITY_ID_PATTERN = /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/;
11210
+ var CapabilityIdSchema = exports_external.string().regex(CAPABILITY_ID_PATTERN, {
11211
+ message: "capability id must be dotted lower-case segments, e.g. schema.read"
11212
+ });
11213
+ var CAPABILITY_COMMAND_PATTERN = /^[a-z][a-z0-9]*(?: [a-z][a-z0-9]*)*$/;
11214
+ var CapabilityCommandSchema = exports_external.string().regex(CAPABILITY_COMMAND_PATTERN, {
11215
+ message: "capability command must be space-separated lower-case CLI command segments"
11216
+ });
11217
+ var CapabilitySchema = exports_external.object({
11218
+ id: CapabilityIdSchema,
11219
+ description: exports_external.string().min(1),
11220
+ command: CapabilityCommandSchema,
11221
+ risk: RiskSchema,
11222
+ sideEffect: SideEffectSchema,
11223
+ engines: exports_external.array(EngineSchema),
11224
+ limitedEngines: exports_external.array(EngineSchema),
11225
+ engineIndependent: exports_external.boolean(),
11226
+ minimumPermission: PermissionSchema2,
11227
+ requiresConnection: exports_external.boolean(),
11228
+ mutatesConfiguration: exports_external.boolean(),
11229
+ supportsJson: exports_external.boolean(),
11230
+ supportsEvidence: exports_external.boolean()
11231
+ }).strict();
11232
+ var CapabilityCatalogSchema = exports_external.object({
11233
+ schemaVersion: exports_external.literal(CAPABILITY_CONTRACT_SCHEMA_VERSION),
11234
+ capabilities: exports_external.array(CapabilitySchema)
11235
+ }).strict();
11236
+ var CheckContextSchema = exports_external.object({
11237
+ engine: EngineSchema,
11238
+ permission: PermissionSchema2,
11239
+ connectionName: exports_external.string().max(200).nullable(),
11240
+ agentMode: exports_external.boolean()
11241
+ }).strict();
11242
+ var CheckResultSchema = exports_external.object({
11243
+ id: exports_external.string().min(1),
11244
+ status: exports_external.enum(["available", "unavailable", "unknown"]),
11245
+ reason: exports_external.enum([
11246
+ "unknown-capability",
11247
+ "context-unavailable",
11248
+ "context-unresolvable",
11249
+ "engine",
11250
+ "agent-mode",
11251
+ "permission"
11252
+ ]).nullable()
11253
+ }).strict();
11254
+ var CapabilityCheckReportSchema = exports_external.object({
11255
+ schemaVersion: exports_external.literal(CAPABILITY_CONTRACT_SCHEMA_VERSION),
11256
+ ok: exports_external.boolean(),
11257
+ required: exports_external.array(exports_external.string().min(1)),
11258
+ context: CheckContextSchema.nullable(),
11259
+ results: exports_external.array(CheckResultSchema),
11260
+ warnings: exports_external.array(exports_external.string())
11261
+ }).strict();
11262
+ function parse(schema, value, what) {
11263
+ const result = schema.safeParse(value);
11264
+ if (result.success)
11265
+ return result.data;
11266
+ throw new CapabilityContractError(`${what} does not match capability contract v${CAPABILITY_CONTRACT_SCHEMA_VERSION}: ${result.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ")}`);
11267
+ }
11268
+ function parseCapabilityCatalog(value) {
11269
+ return parse(CapabilityCatalogSchema, value, "capability catalog");
11270
+ }
11271
+ function parseCapabilityCheckReport(value) {
11272
+ return parse(CapabilityCheckReportSchema, value, "capability check report");
11273
+ }
11274
+ // src/core/recovery/types.ts
11275
+ var RECOVERY_CODES = [
11276
+ "CONFIG_MISSING",
11277
+ "CONN_REFUSED",
11278
+ "CONN_AUTH_FAILED",
11279
+ "CONN_TIMEOUT",
11280
+ "CONN_HOST_NOT_FOUND",
11281
+ "CONN_UNKNOWN",
11282
+ "PERMISSION_DENIED",
11283
+ "BLACKLIST_TABLE",
11284
+ "BLACKLIST_COLUMN_WRITE",
11285
+ "SNIPPET_NOT_FOUND",
11286
+ "SNIPPET_AMBIGUOUS",
11287
+ "SNIPPET_PARAM_MISSING",
11288
+ "SCHEMA_CACHE_MISSING",
11289
+ "UNKNOWN"
11290
+ ];
11291
+ var MAX_BRANCH_STEPS = 6;
11292
+ var MAX_BRANCH_COUNT = 8;
11293
+
11294
+ // src/core/recovery/envelope-schema.ts
11295
+ var recoveryCodeSchema = exports_external.enum(RECOVERY_CODES);
11296
+ var recoveryCategorySchema = exports_external.enum([
11297
+ "config",
11298
+ "connection",
11299
+ "permission",
11300
+ "blacklist",
11301
+ "snippet",
11302
+ "schema-cache",
11303
+ "unknown"
11304
+ ]);
11305
+ var guideRiskSchema = exports_external.enum(["readonly", "dry-run", "write", "unknown"]);
11306
+ var branchIdSchema = exports_external.string().min(1).max(64).regex(/^[a-z0-9-]+$/, "branchId must match /^[a-z0-9-]+$/");
11307
+ var guideStepSchema = exports_external.object({
11308
+ order: exports_external.number().int(),
11309
+ command: exports_external.string().min(1),
11310
+ rationale: exports_external.string(),
11311
+ risk: guideRiskSchema,
11312
+ expects: exports_external.string(),
11313
+ snippet: exports_external.string().optional(),
11314
+ intent: exports_external.string().optional(),
11315
+ interactive: exports_external.boolean().optional(),
11316
+ dbWrite: exports_external.boolean().optional(),
11317
+ placeholders: exports_external.array(exports_external.string()).optional(),
11318
+ branchId: branchIdSchema.optional()
11319
+ }).strict();
11320
+ var branchPlanSchema = exports_external.object({
11321
+ description: exports_external.string().min(1),
11322
+ steps: exports_external.array(guideStepSchema).min(1).max(MAX_BRANCH_STEPS)
11323
+ }).strict();
11324
+ var branchesSchema = exports_external.record(branchIdSchema, branchPlanSchema).superRefine((map, ctx) => {
11325
+ const keys = Object.keys(map);
11326
+ if (keys.length > MAX_BRANCH_COUNT) {
11327
+ ctx.addIssue({
11328
+ code: exports_external.ZodIssueCode.custom,
11329
+ message: `branches has ${keys.length} entries; cap is ${MAX_BRANCH_COUNT}`
11330
+ });
11331
+ }
11332
+ for (const [id, plan] of Object.entries(map)) {
11333
+ for (const step of plan.steps) {
11334
+ if (step.branchId !== undefined && step.branchId !== id) {
11335
+ ctx.addIssue({
11336
+ code: exports_external.ZodIssueCode.custom,
11337
+ message: `step.branchId '${step.branchId}' does not match enclosing key '${id}'`
11338
+ });
11339
+ }
11340
+ }
11341
+ }
11342
+ });
11343
+ var branchForkSchema = exports_external.object({
11344
+ after: exports_external.number().int().min(1),
11345
+ branchIds: exports_external.array(branchIdSchema).min(1)
11346
+ }).strict();
11347
+ var recoveryEnvelopeSchema = exports_external.object({
11348
+ schemaVersion: exports_external.literal(1),
11349
+ generatedAt: exports_external.string().min(1),
11350
+ ok: exports_external.literal(false),
11351
+ error: exports_external.object({
11352
+ code: recoveryCodeSchema,
11353
+ category: recoveryCategorySchema,
11354
+ message: exports_external.string(),
11355
+ details: exports_external.object({
11356
+ table: exports_external.string().optional(),
11357
+ columns: exports_external.string().optional(),
11358
+ requiredPermission: exports_external.string().optional(),
11359
+ connectionCode: exports_external.string().optional(),
11360
+ snippet: exports_external.string().optional(),
11361
+ intent: exports_external.string().optional(),
11362
+ paramName: exports_external.string().optional()
11363
+ }).strict().optional()
11364
+ }).strict(),
11365
+ recovery: exports_external.array(guideStepSchema),
11366
+ verify: guideStepSchema.optional(),
11367
+ branches: branchesSchema.optional(),
11368
+ branchFork: branchForkSchema.optional()
11369
+ }).strict().superRefine((env, ctx) => {
11370
+ const hasBranches = env.branches !== undefined;
11371
+ const hasFork = env.branchFork !== undefined;
11372
+ if (hasBranches !== hasFork) {
11373
+ ctx.addIssue({
11374
+ code: exports_external.ZodIssueCode.custom,
11375
+ message: "branches and branchFork must be set together (or both omitted)"
11376
+ });
11377
+ return;
11378
+ }
11379
+ if (hasBranches && hasFork) {
11380
+ const keys = Object.keys(env.branches).sort();
11381
+ const ids = [...env.branchFork.branchIds].sort();
11382
+ if (keys.length !== ids.length || keys.some((k, i) => k !== ids[i])) {
11383
+ ctx.addIssue({
11384
+ code: exports_external.ZodIssueCode.custom,
11385
+ message: "branchFork.branchIds must equal Object.keys(branches)"
11386
+ });
11387
+ }
11388
+ }
11389
+ });
11390
+ var savedRecoveryEnvelopeSchema = exports_external.object({
11391
+ schemaVersion: exports_external.literal(1),
11392
+ id: exports_external.string().optional(),
11393
+ audit_ref: exports_external.string().optional(),
11394
+ savedAt: exports_external.string().min(1),
11395
+ command: exports_external.string(),
11396
+ cwd: exports_external.string().min(1),
11397
+ envelope: recoveryEnvelopeSchema
11398
+ }).strict();
11399
+
11400
+ // src/core/correlation-id.ts
11401
+ var CORRELATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/;
11402
+
11403
+ // src/core/operation-envelope.ts
11404
+ var OPERATION_ENVELOPE_SCHEMA_VERSION = 1;
11405
+ var MAX_OPERATION_ENVELOPE_BYTES = 65536;
11406
+ var MAX_OPERATION_ENVELOPE_IDENTIFIER_LENGTH = 160;
11407
+ var MAX_OPERATION_ENVELOPE_ITEMS = 128;
11408
+ var MAX_MESSAGE_LENGTH = 2000;
11409
+ var MAX_EVIDENCE_ITEMS = 16;
11410
+ var OPERATION_ID = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/;
11411
+ var CODE = /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/;
11412
+ var EVIDENCE_ID = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,159}$/;
11413
+ var SHA256_DIGEST = /^sha256:[a-f0-9]{64}$/;
11414
+ var identifierSchema = exports_external.string().min(1).max(MAX_OPERATION_ENVELOPE_IDENTIFIER_LENGTH);
11415
+ var capabilityIdSchema = identifierSchema.regex(CAPABILITY_ID_PATTERN, "capability id must be dotted lower-case segments");
11416
+ var codeSchema = identifierSchema.regex(CODE, "code must be an uppercase snake-case identifier");
11417
+ var messageSchema = exports_external.string().min(1).max(MAX_MESSAGE_LENGTH);
11418
+ var contextSchema = exports_external.object({
11419
+ engine: exports_external.enum(DATABASE_SYSTEMS),
11420
+ permission: exports_external.enum(["query-only", "read-write", "data-admin", "admin"]),
11421
+ connectionName: identifierSchema.nullable(),
11422
+ agentMode: exports_external.boolean(),
11423
+ correlationId: exports_external.string().regex(CORRELATION_ID_PATTERN, "invalid correlation id").optional()
11424
+ }).strict();
11425
+ var resultSchema = exports_external.object({
11426
+ id: capabilityIdSchema,
11427
+ status: exports_external.enum(["available", "unavailable", "unknown"]),
11428
+ reason: exports_external.enum([
11429
+ "unknown-capability",
11430
+ "context-unavailable",
11431
+ "context-unresolvable",
11432
+ "engine",
11433
+ "agent-mode",
11434
+ "permission"
11435
+ ]).nullable()
11436
+ }).strict().superRefine((result, ctx) => {
11437
+ if (result.status === "available" && result.reason !== null) {
11438
+ ctx.addIssue({
11439
+ code: exports_external.ZodIssueCode.custom,
11440
+ message: "available results require reason null"
11441
+ });
11442
+ }
11443
+ if (result.status === "unknown" && result.reason !== "unknown-capability") {
11444
+ ctx.addIssue({
11445
+ code: exports_external.ZodIssueCode.custom,
11446
+ message: "unknown results require reason unknown-capability"
11447
+ });
11448
+ }
11449
+ if (result.status === "unavailable" && (result.reason === null || result.reason === "unknown-capability")) {
11450
+ ctx.addIssue({
11451
+ code: exports_external.ZodIssueCode.custom,
11452
+ message: "unavailable results require an availability reason"
11453
+ });
11454
+ }
11455
+ });
11456
+ var capabilitiesCheckDataSchema = exports_external.object({
11457
+ required: exports_external.array(capabilityIdSchema).min(1).max(MAX_OPERATION_ENVELOPE_ITEMS),
11458
+ results: exports_external.array(resultSchema).min(1).max(MAX_OPERATION_ENVELOPE_ITEMS)
11459
+ }).strict().superRefine((data, ctx) => {
11460
+ if (new Set(data.required).size !== data.required.length) {
11461
+ ctx.addIssue({
11462
+ code: exports_external.ZodIssueCode.custom,
11463
+ message: "required capability ids must be unique"
11464
+ });
11465
+ }
11466
+ if (data.required.length !== data.results.length || data.required.some((id, index) => data.results[index]?.id !== id)) {
11467
+ ctx.addIssue({
11468
+ code: exports_external.ZodIssueCode.custom,
11469
+ message: "results must match required capability ids in order"
11470
+ });
11471
+ }
11472
+ });
11473
+ var warningSchema = exports_external.object({
11474
+ code: codeSchema,
11475
+ message: messageSchema
11476
+ }).strict();
11477
+ var evidenceSchema = exports_external.object({
11478
+ kind: exports_external.enum(["receipt", "audit", "verification-artifact"]),
11479
+ id: exports_external.string().regex(EVIDENCE_ID, "invalid evidence id"),
11480
+ digest: exports_external.string().regex(SHA256_DIGEST, "invalid evidence digest").optional()
11481
+ }).strict();
11482
+ var errorSchema = exports_external.object({
11483
+ code: codeSchema,
11484
+ message: messageSchema
11485
+ }).strict();
11486
+ function hasOversizedRecoveryString(value) {
11487
+ if (typeof value === "string")
11488
+ return value.length > MAX_MESSAGE_LENGTH;
11489
+ if (Array.isArray(value))
11490
+ return value.some(hasOversizedRecoveryString);
11491
+ if (value === null || typeof value !== "object")
11492
+ return false;
11493
+ return Object.values(value).some(hasOversizedRecoveryString);
11494
+ }
11495
+ var operationEnvelopeFieldSchema = exports_external.object({
11496
+ schemaVersion: exports_external.literal(OPERATION_ENVELOPE_SCHEMA_VERSION),
11497
+ ok: exports_external.boolean(),
11498
+ operation: exports_external.enum(["capabilities.check", "capabilities.list"]).refine((value) => value.length <= MAX_OPERATION_ENVELOPE_IDENTIFIER_LENGTH && OPERATION_ID.test(value)),
11499
+ status: exports_external.enum(["succeeded", "failed"]),
11500
+ context: contextSchema.nullable(),
11501
+ data: exports_external.union([capabilitiesCheckDataSchema, CapabilityCatalogSchema]).nullable(),
11502
+ warnings: exports_external.array(warningSchema).max(MAX_OPERATION_ENVELOPE_ITEMS),
11503
+ evidence: exports_external.array(evidenceSchema).max(MAX_EVIDENCE_ITEMS),
11504
+ recovery: recoveryEnvelopeSchema.nullable(),
11505
+ error: errorSchema.nullable()
11506
+ }).strict().superRefine((envelope, ctx) => {
11507
+ if (envelope.ok !== (envelope.status === "succeeded")) {
11508
+ ctx.addIssue({
11509
+ code: exports_external.ZodIssueCode.custom,
11510
+ message: 'ok must equal (status === "succeeded")'
11511
+ });
11512
+ }
11513
+ if (envelope.operation === "capabilities.check") {
11514
+ if (envelope.status === "succeeded") {
11515
+ if (envelope.error !== null) {
11516
+ ctx.addIssue({
11517
+ code: exports_external.ZodIssueCode.custom,
11518
+ message: "successful envelopes require error null"
11519
+ });
11520
+ }
11521
+ if (envelope.data === null) {
11522
+ ctx.addIssue({
11523
+ code: exports_external.ZodIssueCode.custom,
11524
+ message: "successful capabilities.check envelopes require data"
11525
+ });
11526
+ } else if ("results" in envelope.data) {
11527
+ if (envelope.data.results.some((result) => result.status !== "available")) {
11528
+ ctx.addIssue({
11529
+ code: exports_external.ZodIssueCode.custom,
11530
+ message: "successful capabilities.check results must all be available"
11531
+ });
11532
+ }
11533
+ } else {
11534
+ ctx.addIssue({
11535
+ code: exports_external.ZodIssueCode.custom,
11536
+ message: "capabilities.check requires capabilitiesCheckDataSchema shape"
11537
+ });
11538
+ }
11539
+ } else if (envelope.error === null) {
11540
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, message: "failed envelopes require an error" });
11541
+ }
11542
+ const completedNegative = envelope.error?.code === "CAPABILITY_REQUIREMENTS_UNMET";
11543
+ if (envelope.status === "failed" && completedNegative !== (envelope.data !== null)) {
11544
+ ctx.addIssue({
11545
+ code: exports_external.ZodIssueCode.custom,
11546
+ message: "only completed negative capability results retain data"
11547
+ });
11548
+ }
11549
+ if (completedNegative && envelope.data !== null && "results" in envelope.data && envelope.data.results.every((result) => result.status === "available")) {
11550
+ ctx.addIssue({
11551
+ code: exports_external.ZodIssueCode.custom,
11552
+ message: "completed negative capability results require an unmet result"
11553
+ });
11554
+ }
11555
+ } else if (envelope.operation === "capabilities.list") {
11556
+ if (envelope.status === "succeeded") {
11557
+ if (envelope.context !== null) {
11558
+ ctx.addIssue({
11559
+ code: exports_external.ZodIssueCode.custom,
11560
+ message: "successful capabilities.list envelopes require context null"
11561
+ });
11562
+ }
11563
+ if (envelope.warnings.length !== 0) {
11564
+ ctx.addIssue({
11565
+ code: exports_external.ZodIssueCode.custom,
11566
+ message: "successful capabilities.list envelopes require warnings empty"
11567
+ });
11568
+ }
11569
+ if (envelope.evidence.length !== 0) {
11570
+ ctx.addIssue({
11571
+ code: exports_external.ZodIssueCode.custom,
11572
+ message: "successful capabilities.list envelopes require evidence empty"
11573
+ });
11574
+ }
11575
+ if (envelope.recovery !== null) {
11576
+ ctx.addIssue({
11577
+ code: exports_external.ZodIssueCode.custom,
11578
+ message: "successful capabilities.list envelopes require recovery null"
11579
+ });
11580
+ }
11581
+ if (envelope.error !== null) {
11582
+ ctx.addIssue({
11583
+ code: exports_external.ZodIssueCode.custom,
11584
+ message: "successful envelopes require error null"
11585
+ });
11586
+ }
11587
+ if (envelope.data === null) {
11588
+ ctx.addIssue({
11589
+ code: exports_external.ZodIssueCode.custom,
11590
+ message: "successful capabilities.list envelopes require data"
11591
+ });
11592
+ } else if (!("capabilities" in envelope.data)) {
11593
+ ctx.addIssue({
11594
+ code: exports_external.ZodIssueCode.custom,
11595
+ message: "capabilities.list requires CapabilityCatalogSchema shape"
11596
+ });
11597
+ }
11598
+ } else {
11599
+ if (envelope.error === null) {
11600
+ ctx.addIssue({
11601
+ code: exports_external.ZodIssueCode.custom,
11602
+ message: "failed envelopes require an error"
11603
+ });
11604
+ }
11605
+ if (envelope.data !== null) {
11606
+ ctx.addIssue({
11607
+ code: exports_external.ZodIssueCode.custom,
11608
+ message: "failed capabilities.list envelopes require data null"
11609
+ });
11610
+ }
11611
+ }
11612
+ }
11613
+ if (envelope.recovery !== null) {
11614
+ if (envelope.error === null || envelope.recovery.error.code !== envelope.error.code || envelope.recovery.error.message !== envelope.error.message) {
11615
+ ctx.addIssue({
11616
+ code: exports_external.ZodIssueCode.custom,
11617
+ message: "recovery error code and message must match the envelope error"
11618
+ });
11619
+ }
11620
+ if (hasOversizedRecoveryString(envelope.recovery)) {
11621
+ ctx.addIssue({
11622
+ code: exports_external.ZodIssueCode.custom,
11623
+ message: `recovery strings must be at most ${MAX_MESSAGE_LENGTH} characters`
11624
+ });
11625
+ }
11626
+ }
11627
+ });
11628
+ function summarize(error) {
11629
+ return error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
11630
+ }
11631
+ function encode(envelope) {
11632
+ return `${JSON.stringify(envelope)}
11633
+ `;
11634
+ }
11635
+ function byteLength(value) {
11636
+ return new TextEncoder().encode(value).byteLength;
11637
+ }
11638
+ function parseOperationEnvelope(input) {
11639
+ const parsed = operationEnvelopeFieldSchema.safeParse(input);
11640
+ if (!parsed.success)
11641
+ return { ok: false, reason: summarize(parsed.error) };
11642
+ const value = parsed.data;
11643
+ const size = byteLength(encode(value));
11644
+ if (size > MAX_OPERATION_ENVELOPE_BYTES) {
11645
+ return {
11646
+ ok: false,
11647
+ reason: `<root>: serialized envelope is ${size} bytes; cap is ${MAX_OPERATION_ENVELOPE_BYTES}`
11648
+ };
11649
+ }
11650
+ return { ok: true, value };
11651
+ }
9593
11652
  // src/utils/es-index-target.ts
9594
11653
  var MAX_DECODE_PASSES = 4;
9595
11654
  function unwrap(target) {
@@ -9954,19 +12013,32 @@ export {
9954
12013
  removeConnection,
9955
12014
  readV2Config,
9956
12015
  readConfig,
12016
+ parseRequirements,
12017
+ parseOperationEnvelope,
12018
+ parseCapabilityCheckReport,
12019
+ parseCapabilityCatalog,
9957
12020
  migrateV1ToV2,
9958
12021
  loadConnectionEnv,
9959
12022
  listConnections,
12023
+ listCapabilityIds,
9960
12024
  isGlobalConfigPath,
9961
12025
  getProjectStoragePath,
9962
12026
  getGlobalConfigPath,
9963
12027
  getDbcliConfigHome,
12028
+ findCapability,
9964
12029
  envVarNameFor,
9965
12030
  detectConfigVersion,
12031
+ checkCapabilities,
12032
+ buildCapabilityCatalog,
9966
12033
  SchemaLayeredLoader,
9967
12034
  QueryExecutor,
12035
+ OPERATION_ENVELOPE_SCHEMA_VERSION,
9968
12036
  DataExecutor,
9969
12037
  ConnectionError,
12038
+ CapabilityRequirementError,
12039
+ CapabilityContractError,
12040
+ CAPABILITY_CONTRACT_SCHEMA_VERSION,
12041
+ CAPABILITIES,
9970
12042
  BlacklistValidator,
9971
12043
  BlacklistManager,
9972
12044
  BlacklistError