@carllee1983/dbcli 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to dbcli are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.5.2] - 2026-04-22
9
+
10
+ ### Fixed
11
+
12
+ - **Doctor diagnostics for MongoDB SRV**: `dbcli doctor` now reports whether the current execution environment can resolve `mongodb+srv://` connections directly or only through the DNS-over-HTTPS fallback used by the MongoDB adapter.
13
+ - **Documentation**: Clarified the new MongoDB SRV environment diagnostic in README, README.zh-TW, and `assets/SKILL.md`.
14
+
15
+ ## [1.5.1] - 2026-04-22
16
+
17
+ ### Fixed
18
+
19
+ - **MongoDB SRV Connections**: `mongodb+srv://` URIs are now expanded and connected through the MongoDB adapter, and MongoDB operations consistently use the configured database.
20
+ - **MongoDB Documentation**: Clarified SRV URI support and configured-database behavior in README, README.zh-TW, and `assets/SKILL.md`.
21
+
8
22
  ## [1.5.0] - 2026-04-21
9
23
 
10
24
  ### Added
package/README.md CHANGED
@@ -89,6 +89,23 @@ dbcli query "SELECT * FROM users"
89
89
  dbcli skill --install claude
90
90
  ```
91
91
 
92
+ ### MongoDB Atlas / SRV Connections
93
+
94
+ MongoDB connections are supported via both standard `mongodb://` URIs and Atlas-style `mongodb+srv://` URIs.
95
+
96
+ ```bash
97
+ # Atlas / SRV connection
98
+ dbcli init --system mongodb --conn-name atlas --uri "mongodb+srv://user:pass@cluster.example.mongodb.net/mydb"
99
+
100
+ # List collections in the configured MongoDB database
101
+ dbcli list --use atlas
102
+
103
+ # Query a collection with JSON filter or pipeline
104
+ dbcli query '{"status":"active"}' --collection users --use atlas
105
+ ```
106
+
107
+ For MongoDB, `list` and `query` operate on the database configured for the connection, and `query` requires `--collection <name>`.
108
+
92
109
  ---
93
110
 
94
111
  ## Multi-connection Support (v2)
@@ -637,6 +654,7 @@ dbcli doctor --format json # JSON output for AI agents
637
654
  - **Environment:** Bun version compatibility, dbcli version (compares with npm registry)
638
655
  - **Configuration:** Config file exists/valid, permission level, blacklist completeness
639
656
  - **Connection & Data:** Database connectivity, schema cache freshness (> 7 days warning), large table warnings (> 1M rows)
657
+ - **MongoDB SRV diagnostics:** For `mongodb+srv://` connections, `doctor` reports whether the current execution environment can resolve SRV records directly or only through the DNS-over-HTTPS fallback used by `dbcli`
640
658
 
641
659
  **Options:** `--format <text|json>`
642
660
  **Exit code:** 0 = all pass or warnings only, 1 = errors found
package/README.zh-TW.md CHANGED
@@ -95,6 +95,23 @@ dbcli migrate create posts --column "id:int:pk" "title:varchar(100)"
95
95
  dbcli skill --install claude
96
96
  ```
97
97
 
98
+ ### MongoDB Atlas / SRV 連線
99
+
100
+ MongoDB 連線同時支援標準 `mongodb://` URI 與 Atlas 常用的 `mongodb+srv://` URI。
101
+
102
+ ```bash
103
+ # Atlas / SRV 連線
104
+ dbcli init --system mongodb --conn-name atlas --uri "mongodb+srv://user:pass@cluster.example.mongodb.net/mydb"
105
+
106
+ # 列出該 MongoDB 連線中的集合
107
+ dbcli list --use atlas
108
+
109
+ # 以 JSON filter 或 pipeline 查詢某個集合
110
+ dbcli query '{"status":"active"}' --collection users --use atlas
111
+ ```
112
+
113
+ 對 MongoDB 而言,`list` 與 `query` 會使用該連線設定中的資料庫;`query` 也必須指定 `--collection <名稱>`。
114
+
98
115
  ---
99
116
 
100
117
  ## 多重連線支援 (v2)
@@ -647,6 +664,7 @@ dbcli doctor --format json # JSON 輸出(供 AI 代理)
647
664
  - **環境:** Bun 版本相容性、dbcli 版本(與 npm registry 比對)
648
665
  - **設定:** 設定檔是否存在/有效、權限等級、黑名單完整性
649
666
  - **連線與資料:** 資料庫連線、schema 快取新鮮度(超過 7 天警告)、大表警告(超過 100 萬列)
667
+ - **MongoDB SRV 偵測:** 對 `mongodb+srv://` 連線,`doctor` 會回報目前執行環境能否直接解析 SRV 記錄,或只能依賴 dbcli 內建的 DNS-over-HTTPS fallback
650
668
 
651
669
  **選項:** `--format <text|json>`
652
670
  **結束代碼:** 0 = 全部通過或僅警告,1 = 有錯誤
package/assets/SKILL.md CHANGED
@@ -248,6 +248,8 @@ dbcli doctor --format json # JSON output for AI agents
248
248
  - Configuration: config file exists/valid, permission level, blacklist completeness (detects unprotected sensitive columns)
249
249
  - Connection & Data: database connectivity, schema cache freshness (warns if > 7 days), large table warnings (> 1M rows)
250
250
 
251
+ > **MongoDB SRV diagnostics:** When the active connection uses `mongodb+srv://`, `doctor` reports whether the current runtime can resolve SRV records directly or only through the DNS-over-HTTPS fallback used by dbcli. This helps spot execution-environment DNS restrictions even when Compass can connect.
252
+
251
253
  **Exit code:** 0 if all pass or warnings only, 1 if any error
252
254
  **Options:** `--format <text|json>`
253
255
 
@@ -351,6 +353,8 @@ dbcli migrate drop-enum status --execute --force
351
353
 
352
354
  MongoDB connections use a JSON-based query model instead of SQL.
353
355
 
356
+ Atlas-style `mongodb+srv://` URIs are supported. `list` and `query` run against the database configured for the connection, and `query` always requires `--collection <name>`.
357
+
354
358
  **Supported commands:** `init`, `list`, `query`, `status`, `use`, `shell`, `doctor`, `upgrade`, `completion`
355
359
 
356
360
  **Not supported (exit with error):** `schema`, `insert`, `update`, `delete`, `export`, `diff`, `migrate`, `check`
@@ -359,7 +363,7 @@ MongoDB connections use a JSON-based query model instead of SQL.
359
363
 
360
364
  ```bash
361
365
  # 1. Initialize (URI or individual params)
362
- dbcli init --system mongodb --uri "mongodb://localhost:27017/mydb"
366
+ dbcli init --system mongodb --uri "mongodb+srv://user:pass@cluster.example.mongodb.net/mydb"
363
367
 
364
368
  # 2. List collections
365
369
  dbcli list --format json
package/dist/cli.mjs CHANGED
@@ -63644,7 +63644,7 @@ var require_URLSearchParams = __commonJS((exports) => {
63644
63644
  }
63645
63645
  const ctorRegistry = utils.initCtorRegistry(globalObject);
63646
63646
 
63647
- class URLSearchParams {
63647
+ class URLSearchParams2 {
63648
63648
  constructor() {
63649
63649
  const args = [];
63650
63650
  {
@@ -63923,7 +63923,7 @@ var require_URLSearchParams = __commonJS((exports) => {
63923
63923
  return esValue[implSymbol]["size"];
63924
63924
  }
63925
63925
  }
63926
- Object.defineProperties(URLSearchParams.prototype, {
63926
+ Object.defineProperties(URLSearchParams2.prototype, {
63927
63927
  append: { enumerable: true },
63928
63928
  delete: { enumerable: true },
63929
63929
  get: { enumerable: true },
@@ -63938,9 +63938,9 @@ var require_URLSearchParams = __commonJS((exports) => {
63938
63938
  forEach: { enumerable: true },
63939
63939
  size: { enumerable: true },
63940
63940
  [Symbol.toStringTag]: { value: "URLSearchParams", configurable: true },
63941
- [Symbol.iterator]: { value: URLSearchParams.prototype.entries, configurable: true, writable: true }
63941
+ [Symbol.iterator]: { value: URLSearchParams2.prototype.entries, configurable: true, writable: true }
63942
63942
  });
63943
- ctorRegistry[interfaceName] = URLSearchParams;
63943
+ ctorRegistry[interfaceName] = URLSearchParams2;
63944
63944
  ctorRegistry["URLSearchParams Iterator"] = Object.create(ctorRegistry["%IteratorPrototype%"], {
63945
63945
  [Symbol.toStringTag]: {
63946
63946
  configurable: true,
@@ -63967,7 +63967,7 @@ var require_URLSearchParams = __commonJS((exports) => {
63967
63967
  Object.defineProperty(globalObject, interfaceName, {
63968
63968
  configurable: true,
63969
63969
  writable: true,
63970
- value: URLSearchParams
63970
+ value: URLSearchParams2
63971
63971
  });
63972
63972
  };
63973
63973
  var Impl = require_URLSearchParams_impl();
@@ -63977,7 +63977,7 @@ var require_URLSearchParams = __commonJS((exports) => {
63977
63977
  var require_URL_impl = __commonJS((exports) => {
63978
63978
  var usm = require_url_state_machine();
63979
63979
  var urlencoded = require_urlencoded();
63980
- var URLSearchParams = require_URLSearchParams();
63980
+ var URLSearchParams2 = require_URLSearchParams();
63981
63981
  exports.implementation = class URLImpl {
63982
63982
  constructor(globalObject, [url, base]) {
63983
63983
  let parsedBase = null;
@@ -63993,7 +63993,7 @@ var require_URL_impl = __commonJS((exports) => {
63993
63993
  }
63994
63994
  const query = parsedURL.query !== null ? parsedURL.query : "";
63995
63995
  this._url = parsedURL;
63996
- this._query = URLSearchParams.createImpl(globalObject, [query], { doNotStripQMark: true });
63996
+ this._query = URLSearchParams2.createImpl(globalObject, [query], { doNotStripQMark: true });
63997
63997
  this._query._url = this;
63998
63998
  }
63999
63999
  static parse(globalObject, input, base) {
@@ -64551,19 +64551,19 @@ var require_URL = __commonJS((exports) => {
64551
64551
  // node_modules/whatwg-url/webidl2js-wrapper.js
64552
64552
  var require_webidl2js_wrapper = __commonJS((exports) => {
64553
64553
  var URL2 = require_URL();
64554
- var URLSearchParams = require_URLSearchParams();
64554
+ var URLSearchParams2 = require_URLSearchParams();
64555
64555
  exports.URL = URL2;
64556
- exports.URLSearchParams = URLSearchParams;
64556
+ exports.URLSearchParams = URLSearchParams2;
64557
64557
  });
64558
64558
 
64559
64559
  // node_modules/whatwg-url/index.js
64560
64560
  var require_whatwg_url = __commonJS((exports) => {
64561
- var { URL: URL2, URLSearchParams } = require_webidl2js_wrapper();
64561
+ var { URL: URL2, URLSearchParams: URLSearchParams2 } = require_webidl2js_wrapper();
64562
64562
  var urlStateMachine = require_url_state_machine();
64563
64563
  var percentEncoding = require_percent_encoding();
64564
64564
  var sharedGlobalObject = { Array, Object, Promise, String, TypeError };
64565
64565
  URL2.install(sharedGlobalObject, ["Window"]);
64566
- URLSearchParams.install(sharedGlobalObject, ["Window"]);
64566
+ URLSearchParams2.install(sharedGlobalObject, ["Window"]);
64567
64567
  exports.URL = sharedGlobalObject.URL;
64568
64568
  exports.URLSearchParams = sharedGlobalObject.URLSearchParams;
64569
64569
  exports.parseURL = urlStateMachine.parseURL;
@@ -77140,7 +77140,7 @@ var {
77140
77140
  // package.json
77141
77141
  var package_default = {
77142
77142
  name: "@carllee1983/dbcli",
77143
- version: "1.5.0",
77143
+ version: "1.5.2",
77144
77144
  description: "Database CLI for AI agents",
77145
77145
  type: "module",
77146
77146
  publishConfig: {
@@ -78976,6 +78976,7 @@ class MySQLAdapter {
78976
78976
  }
78977
78977
 
78978
78978
  // src/adapters/mongodb-adapter.ts
78979
+ import { resolveSrv, resolveTxt } from "dns/promises";
78979
78980
  class MongoDBAdapter {
78980
78981
  options;
78981
78982
  ClientClass;
@@ -79000,9 +79001,101 @@ class MongoDBAdapter {
79000
79001
  }
79001
79002
  return `mongodb://${host}:${port}/${database}`;
79002
79003
  }
79004
+ parseTxtRecords(records) {
79005
+ const combined = records.flat().map((record) => record.replace(/^"|"$/g, "")).join("&");
79006
+ if (!combined)
79007
+ return {};
79008
+ const params = new URLSearchParams(combined);
79009
+ return Object.fromEntries(params.entries());
79010
+ }
79011
+ async resolveSrvHosts(hostname) {
79012
+ const srvName = `_mongodb._tcp.${hostname}`;
79013
+ try {
79014
+ const records = await resolveSrv(srvName);
79015
+ if (!records.length) {
79016
+ throw new Error(`No SRV records found for ${srvName}`);
79017
+ }
79018
+ return records.map((record) => {
79019
+ const target = String(record.name || record.target || "").replace(/\.$/, "");
79020
+ return `${target}:${record.port}`;
79021
+ });
79022
+ } catch (error) {
79023
+ const code = error?.code;
79024
+ if (code !== "ECONNREFUSED") {
79025
+ throw error;
79026
+ }
79027
+ }
79028
+ const response = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(srvName)}&type=SRV`);
79029
+ if (!response.ok) {
79030
+ throw new Error(`SRV lookup failed with HTTP ${response.status}`);
79031
+ }
79032
+ const payload = await response.json();
79033
+ if (payload.Status !== 0 || !payload.Answer?.length) {
79034
+ throw new Error(payload.Comment || `No SRV records found for ${srvName}`);
79035
+ }
79036
+ return payload.Answer.map((answer) => {
79037
+ const parts = answer.data.trim().split(/\s+/);
79038
+ const port = parts[2];
79039
+ const target = parts.slice(3).join(" ").replace(/\.$/, "");
79040
+ return `${target}:${port}`;
79041
+ });
79042
+ }
79043
+ async resolveTxtOptions(hostname) {
79044
+ try {
79045
+ const records = await resolveTxt(hostname);
79046
+ return this.parseTxtRecords(records);
79047
+ } catch (error) {
79048
+ const code = error?.code;
79049
+ if (code !== "ECONNREFUSED") {
79050
+ throw error;
79051
+ }
79052
+ }
79053
+ const response = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(hostname)}&type=TXT`);
79054
+ if (!response.ok) {
79055
+ throw new Error(`TXT lookup failed with HTTP ${response.status}`);
79056
+ }
79057
+ const payload = await response.json();
79058
+ if (payload.Status !== 0 || !payload.Answer?.length) {
79059
+ return {};
79060
+ }
79061
+ return this.parseTxtRecords(payload.Answer.map((answer) => [answer.data]));
79062
+ }
79063
+ async buildResolvedUri() {
79064
+ if (!this.options.uri) {
79065
+ return this.buildUri();
79066
+ }
79067
+ if (!this.options.uri.startsWith("mongodb+srv://")) {
79068
+ return this.options.uri;
79069
+ }
79070
+ const url = new URL(this.options.uri);
79071
+ const hosts = await this.resolveSrvHosts(url.hostname);
79072
+ const txtOptions = await this.resolveTxtOptions(url.hostname);
79073
+ const query = new URLSearchParams(url.searchParams);
79074
+ for (const [key2, value] of Object.entries(txtOptions)) {
79075
+ if (!query.has(key2)) {
79076
+ query.set(key2, value);
79077
+ }
79078
+ }
79079
+ if (!query.has("tls") && !query.has("ssl")) {
79080
+ query.set("tls", "true");
79081
+ }
79082
+ if ((url.username || url.password) && !query.has("authSource")) {
79083
+ query.set("authSource", "admin");
79084
+ }
79085
+ const userInfo = url.username || url.password ? `${encodeURIComponent(decodeURIComponent(url.username))}${url.password ? `:${encodeURIComponent(decodeURIComponent(url.password))}` : ""}@` : "";
79086
+ const path = url.pathname && url.pathname !== "/" ? url.pathname : `/${this.options.database || "test"}`;
79087
+ const search = query.toString();
79088
+ return `mongodb://${userInfo}${hosts.join(",")}${path}${search ? `?${search}` : ""}`;
79089
+ }
79090
+ getDatabase() {
79091
+ if (!this.client) {
79092
+ throw new ConnectionError("UNKNOWN", "\u5C1A\u672A\u9023\u7DDA\uFF0C\u8ACB\u5148\u547C\u53EB connect()", []);
79093
+ }
79094
+ return this.client.db(this.options.database || undefined);
79095
+ }
79003
79096
  async connect() {
79004
79097
  const ClientClass = await this.resolveClientClass();
79005
- const uri = this.buildUri();
79098
+ const uri = await this.buildResolvedUri();
79006
79099
  try {
79007
79100
  this.client = new ClientClass(uri, { serverSelectionTimeoutMS: this.options.timeout ?? 5000 });
79008
79101
  await this.client.connect();
@@ -79022,11 +79115,8 @@ class MongoDBAdapter {
79022
79115
  }
79023
79116
  }
79024
79117
  async execute(query, params) {
79025
- if (!this.client) {
79026
- throw new ConnectionError("UNKNOWN", "\u5C1A\u672A\u9023\u7DDA\uFF0C\u8ACB\u5148\u547C\u53EB connect()", []);
79027
- }
79118
+ const db = this.getDatabase();
79028
79119
  const collectionName = params?.[0];
79029
- const db = this.client.db();
79030
79120
  const collection = db.collection(collectionName);
79031
79121
  let parsed;
79032
79122
  try {
@@ -79043,10 +79133,7 @@ class MongoDBAdapter {
79043
79133
  return { rows: docs, affectedRows: docs.length };
79044
79134
  }
79045
79135
  async listCollections() {
79046
- if (!this.client) {
79047
- throw new ConnectionError("UNKNOWN", "\u5C1A\u672A\u9023\u7DDA\uFF0C\u8ACB\u5148\u547C\u53EB connect()", []);
79048
- }
79049
- const db = this.client.db();
79136
+ const db = this.getDatabase();
79050
79137
  const colls = await db.listCollections().toArray();
79051
79138
  const results = await Promise.all(colls.map(async (col) => {
79052
79139
  try {
@@ -79061,14 +79148,11 @@ class MongoDBAdapter {
79061
79148
  async testConnection() {
79062
79149
  if (!this.client)
79063
79150
  return false;
79064
- await this.client.db().command({ ping: 1 });
79151
+ await this.getDatabase().command({ ping: 1 });
79065
79152
  return true;
79066
79153
  }
79067
79154
  async getServerVersion() {
79068
- if (!this.client) {
79069
- throw new ConnectionError("UNKNOWN", "\u5C1A\u672A\u9023\u7DDA\uFF0C\u8ACB\u5148\u547C\u53EB connect()", []);
79070
- }
79071
- const info = await this.client.db().admin().serverInfo();
79155
+ const info = await this.getDatabase().admin().serverInfo();
79072
79156
  return info.version ?? "unknown";
79073
79157
  }
79074
79158
  }
@@ -82687,6 +82771,7 @@ var statusCommand = new Command("status").description("Show current configuratio
82687
82771
  init_validation();
82688
82772
  init_schema_path();
82689
82773
  import { join as join10 } from "path";
82774
+ import { resolveSrv as resolveSrv2 } from "dns/promises";
82690
82775
  var ALLOWED_FORMATS7 = ["text", "json"];
82691
82776
  var SENSITIVE_PATTERNS = [
82692
82777
  "password",
@@ -82831,6 +82916,68 @@ var runDoctorChecks = {
82831
82916
  message: versionResult.supported ? t_vars("version.doctor_pass", vars) : t_vars("version.doctor_warn", vars)
82832
82917
  };
82833
82918
  },
82919
+ async checkMongoSrvConnectivity(uri, deps = {}) {
82920
+ if (!uri || !uri.startsWith("mongodb+srv://")) {
82921
+ return null;
82922
+ }
82923
+ const url = new URL(uri);
82924
+ const srvName = `_mongodb._tcp.${url.hostname}`;
82925
+ const resolveSrvFn = deps.resolveSrvFn ?? resolveSrv2;
82926
+ const fetchFn = deps.fetchFn ?? fetch;
82927
+ try {
82928
+ const records = await resolveSrvFn(srvName);
82929
+ if (!records.length) {
82930
+ return {
82931
+ group: "Environment",
82932
+ label: "MongoDB SRV lookup",
82933
+ status: "error",
82934
+ message: `No SRV records found for ${url.hostname}`
82935
+ };
82936
+ }
82937
+ return {
82938
+ group: "Environment",
82939
+ label: "MongoDB SRV lookup",
82940
+ status: "pass",
82941
+ message: `MongoDB SRV lookup reachable for ${url.hostname} (${records.length} record(s))`
82942
+ };
82943
+ } catch (error) {
82944
+ const message = error instanceof Error ? error.message : String(error);
82945
+ const code = error?.code;
82946
+ const isExecutionEnvIssue = code === "ECONNREFUSED" || code === "ETIMEDOUT" || message.includes("Unable to connect. Is the computer able to access the url?") || message.includes("ConnectionRefused");
82947
+ if (!isExecutionEnvIssue) {
82948
+ return {
82949
+ group: "Environment",
82950
+ label: "MongoDB SRV lookup",
82951
+ status: "error",
82952
+ message: `MongoDB SRV lookup failed for ${url.hostname}: ${message}`
82953
+ };
82954
+ }
82955
+ try {
82956
+ const response = await fetchFn(`https://dns.google/resolve?name=${encodeURIComponent(srvName)}&type=SRV`, { signal: AbortSignal.timeout(5000) });
82957
+ if (!response.ok) {
82958
+ throw new Error(`HTTP ${response.status}`);
82959
+ }
82960
+ const payload = await response.json();
82961
+ if (payload.Status !== 0 || !payload.Answer?.length) {
82962
+ throw new Error(payload.Comment || `No SRV records found for ${url.hostname}`);
82963
+ }
82964
+ return {
82965
+ group: "Environment",
82966
+ label: "MongoDB SRV lookup",
82967
+ status: "warn",
82968
+ message: `Direct SRV DNS lookup failed in this shell, but DNS-over-HTTPS fallback resolved ` + `${url.hostname}. dbcli can still connect here, but this runtime environment cannot perform direct SRV lookups.`
82969
+ };
82970
+ } catch (fallbackError) {
82971
+ const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
82972
+ return {
82973
+ group: "Environment",
82974
+ label: "MongoDB SRV lookup",
82975
+ status: "error",
82976
+ message: `MongoDB SRV lookup failed in this environment (${code || "unknown"}); ` + `DNS-over-HTTPS fallback also failed: ${fallbackMessage}`
82977
+ };
82978
+ }
82979
+ }
82980
+ },
82834
82981
  checkLargeTables(tables) {
82835
82982
  const large = tables.filter((t7) => (t7.estimatedRowCount ?? 0) > 1e6);
82836
82983
  if (large.length === 0) {
@@ -82927,6 +83074,13 @@ var runDoctorChecks = {
82927
83074
  };
82928
83075
  async function collectMongoDoctorResults(config) {
82929
83076
  const results = [];
83077
+ const srvCheck = await runDoctorChecks.checkMongoSrvConnectivity(config.connection?.uri);
83078
+ if (srvCheck) {
83079
+ results.push(srvCheck);
83080
+ if (srvCheck.status === "error") {
83081
+ return results;
83082
+ }
83083
+ }
82930
83084
  const adapter = AdapterFactory.createMongoDBAdapter(config.connection);
82931
83085
  await adapter.connect();
82932
83086
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carllee1983/dbcli",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Database CLI for AI agents",
5
5
  "type": "module",
6
6
  "publishConfig": {