@indiekitai/pg-dash 0.3.4 → 0.3.5
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/README.md +85 -4
- package/README.zh-CN.md +85 -4
- package/dist/cli.js +23 -20
- package/dist/cli.js.map +1 -1
- package/dist/mcp.js +19 -18
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -186,6 +186,59 @@ PG_DASH_CONNECTION_STRING=postgres://... pg-dash-mcp
|
|
|
186
186
|
| `pg_dash_export` | Export full health report (JSON or Markdown) |
|
|
187
187
|
| `pg_dash_diff` | Compare current health with last saved snapshot |
|
|
188
188
|
|
|
189
|
+
## MCP Setup
|
|
190
|
+
|
|
191
|
+
Connect pg-dash to Claude Desktop or Cursor for AI-assisted database management.
|
|
192
|
+
|
|
193
|
+
### Claude Desktop
|
|
194
|
+
|
|
195
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"mcpServers": {
|
|
200
|
+
"pg-dash": {
|
|
201
|
+
"command": "npx",
|
|
202
|
+
"args": ["-y", "-p", "@indiekitai/pg-dash", "pg-dash-mcp", "postgresql://user:pass@host/db"]
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Cursor
|
|
209
|
+
|
|
210
|
+
Add to `.cursor/mcp.json` in your project:
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"mcpServers": {
|
|
215
|
+
"pg-dash": {
|
|
216
|
+
"command": "npx",
|
|
217
|
+
"args": ["-y", "-p", "@indiekitai/pg-dash", "pg-dash-mcp", "postgresql://user:pass@host/db"]
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Example Conversations
|
|
224
|
+
|
|
225
|
+
Once connected, you can ask your AI assistant:
|
|
226
|
+
|
|
227
|
+
**Diagnosis:**
|
|
228
|
+
- "What's wrong with my database right now?"
|
|
229
|
+
- "Why is my `users` table slow? Check for missing indexes."
|
|
230
|
+
- "Show me the top 5 slowest queries this week."
|
|
231
|
+
|
|
232
|
+
**Optimization:**
|
|
233
|
+
- "Generate SQL to fix all missing FK indexes in one go."
|
|
234
|
+
- "EXPLAIN this query for me: SELECT * FROM orders WHERE user_id = 123"
|
|
235
|
+
- "Which tables are taking up the most space?"
|
|
236
|
+
|
|
237
|
+
**Pre-migration check:**
|
|
238
|
+
- "Run a health check and tell me if it's safe to deploy."
|
|
239
|
+
- "What changed in the schema since last week?"
|
|
240
|
+
- "Check if there are any idle connections blocking my migration."
|
|
241
|
+
|
|
189
242
|
## CI Integration
|
|
190
243
|
|
|
191
244
|
### GitHub Actions
|
|
@@ -212,17 +265,45 @@ Sample workflow (`.github/workflows/pg-check.yml`):
|
|
|
212
265
|
name: Database Health Check
|
|
213
266
|
on:
|
|
214
267
|
push:
|
|
215
|
-
paths: ['migrations/**', 'prisma/**', 'drizzle/**']
|
|
268
|
+
paths: ['migrations/**', 'prisma/**', 'drizzle/**', 'supabase/migrations/**']
|
|
269
|
+
pull_request:
|
|
270
|
+
paths: ['migrations/**', 'prisma/**', 'drizzle/**', 'supabase/migrations/**']
|
|
216
271
|
schedule:
|
|
217
|
-
- cron: '0 8 * * 1' # Weekly Monday 8am
|
|
272
|
+
- cron: '0 8 * * 1' # Weekly Monday 8am UTC
|
|
218
273
|
jobs:
|
|
219
|
-
|
|
274
|
+
db-health:
|
|
220
275
|
runs-on: ubuntu-latest
|
|
221
276
|
steps:
|
|
222
277
|
- uses: actions/checkout@v4
|
|
223
|
-
|
|
278
|
+
# Cache snapshot across ephemeral runners for --diff to work
|
|
279
|
+
- name: Restore health snapshot
|
|
280
|
+
uses: actions/cache@v4
|
|
281
|
+
with:
|
|
282
|
+
path: .pg-dash-cache
|
|
283
|
+
key: pg-dash-snapshot-${{ github.ref }}
|
|
284
|
+
restore-keys: pg-dash-snapshot-
|
|
285
|
+
- name: Run pg-dash health check
|
|
286
|
+
id: pg-check
|
|
287
|
+
run: |
|
|
288
|
+
mkdir -p .pg-dash-cache
|
|
289
|
+
npx @indiekitai/pg-dash check ${{ secrets.DATABASE_URL }} \
|
|
290
|
+
--ci --diff --snapshot-path ./.pg-dash-cache/last-check.json \
|
|
291
|
+
--format md > pg-dash-report.md 2>&1
|
|
292
|
+
echo "exit_code=$?" >> $GITHUB_OUTPUT
|
|
293
|
+
continue-on-error: true
|
|
294
|
+
- name: Save health snapshot
|
|
295
|
+
uses: actions/cache/save@v4
|
|
296
|
+
if: always()
|
|
297
|
+
with:
|
|
298
|
+
path: .pg-dash-cache
|
|
299
|
+
key: pg-dash-snapshot-${{ github.ref }}-${{ github.run_id }}
|
|
300
|
+
- name: Fail if unhealthy
|
|
301
|
+
if: steps.pg-check.outputs.exit_code != '0'
|
|
302
|
+
run: exit 1
|
|
224
303
|
```
|
|
225
304
|
|
|
305
|
+
See [`examples/github-actions-pg-check.yml`](examples/github-actions-pg-check.yml) for a full workflow with PR comments.
|
|
306
|
+
|
|
226
307
|
## Health Checks
|
|
227
308
|
|
|
228
309
|
pg-dash runs 46+ automated checks:
|
package/README.zh-CN.md
CHANGED
|
@@ -186,6 +186,59 @@ PG_DASH_CONNECTION_STRING=postgres://... pg-dash-mcp
|
|
|
186
186
|
| `pg_dash_export` | 导出完整健康报告(JSON 或 Markdown) |
|
|
187
187
|
| `pg_dash_diff` | 与上次快照对比当前健康状态 |
|
|
188
188
|
|
|
189
|
+
## MCP 配置
|
|
190
|
+
|
|
191
|
+
将 pg-dash 接入 Claude Desktop 或 Cursor,实现 AI 辅助的数据库管理。
|
|
192
|
+
|
|
193
|
+
### Claude Desktop
|
|
194
|
+
|
|
195
|
+
在 macOS 上编辑 `~/Library/Application Support/Claude/claude_desktop_config.json`,Windows 上编辑 `%APPDATA%\Claude\claude_desktop_config.json`:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"mcpServers": {
|
|
200
|
+
"pg-dash": {
|
|
201
|
+
"command": "npx",
|
|
202
|
+
"args": ["-y", "-p", "@indiekitai/pg-dash", "pg-dash-mcp", "postgresql://user:pass@host/db"]
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Cursor
|
|
209
|
+
|
|
210
|
+
在项目的 `.cursor/mcp.json` 中添加:
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"mcpServers": {
|
|
215
|
+
"pg-dash": {
|
|
216
|
+
"command": "npx",
|
|
217
|
+
"args": ["-y", "-p", "@indiekitai/pg-dash", "pg-dash-mcp", "postgresql://user:pass@host/db"]
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 示例对话
|
|
224
|
+
|
|
225
|
+
连接后,你可以直接问 AI 助手:
|
|
226
|
+
|
|
227
|
+
**诊断问题:**
|
|
228
|
+
- "我的数据库现在有什么问题?"
|
|
229
|
+
- "为什么我的 `users` 表这么慢?检查一下缺失的索引。"
|
|
230
|
+
- "显示本周最慢的 5 条查询。"
|
|
231
|
+
|
|
232
|
+
**性能优化:**
|
|
233
|
+
- "一次性生成 SQL,修复所有缺失的外键索引。"
|
|
234
|
+
- "帮我分析这条查询:SELECT * FROM orders WHERE user_id = 123"
|
|
235
|
+
- "哪些表占用空间最多?"
|
|
236
|
+
|
|
237
|
+
**迁移前检查:**
|
|
238
|
+
- "跑一次健康检查,告诉我现在部署安不安全。"
|
|
239
|
+
- "上周以来 schema 有哪些变化?"
|
|
240
|
+
- "检查是否有空闲连接会阻塞我的迁移。"
|
|
241
|
+
|
|
189
242
|
## CI 集成
|
|
190
243
|
|
|
191
244
|
### GitHub Actions
|
|
@@ -212,17 +265,45 @@ pg-dash check postgres://... --ci --diff --format md
|
|
|
212
265
|
name: Database Health Check
|
|
213
266
|
on:
|
|
214
267
|
push:
|
|
215
|
-
paths: ['migrations/**', 'prisma/**', 'drizzle/**']
|
|
268
|
+
paths: ['migrations/**', 'prisma/**', 'drizzle/**', 'supabase/migrations/**']
|
|
269
|
+
pull_request:
|
|
270
|
+
paths: ['migrations/**', 'prisma/**', 'drizzle/**', 'supabase/migrations/**']
|
|
216
271
|
schedule:
|
|
217
|
-
- cron: '0 8 * * 1' #
|
|
272
|
+
- cron: '0 8 * * 1' # 每周一 UTC 早 8 点
|
|
218
273
|
jobs:
|
|
219
|
-
|
|
274
|
+
db-health:
|
|
220
275
|
runs-on: ubuntu-latest
|
|
221
276
|
steps:
|
|
222
277
|
- uses: actions/checkout@v4
|
|
223
|
-
|
|
278
|
+
# 缓存快照,解决 ephemeral runner 丢失 ~/.pg-dash 的问题
|
|
279
|
+
- name: Restore health snapshot
|
|
280
|
+
uses: actions/cache@v4
|
|
281
|
+
with:
|
|
282
|
+
path: .pg-dash-cache
|
|
283
|
+
key: pg-dash-snapshot-${{ github.ref }}
|
|
284
|
+
restore-keys: pg-dash-snapshot-
|
|
285
|
+
- name: Run pg-dash health check
|
|
286
|
+
id: pg-check
|
|
287
|
+
run: |
|
|
288
|
+
mkdir -p .pg-dash-cache
|
|
289
|
+
npx @indiekitai/pg-dash check ${{ secrets.DATABASE_URL }} \
|
|
290
|
+
--ci --diff --snapshot-path ./.pg-dash-cache/last-check.json \
|
|
291
|
+
--format md > pg-dash-report.md 2>&1
|
|
292
|
+
echo "exit_code=$?" >> $GITHUB_OUTPUT
|
|
293
|
+
continue-on-error: true
|
|
294
|
+
- name: Save health snapshot
|
|
295
|
+
uses: actions/cache/save@v4
|
|
296
|
+
if: always()
|
|
297
|
+
with:
|
|
298
|
+
path: .pg-dash-cache
|
|
299
|
+
key: pg-dash-snapshot-${{ github.ref }}-${{ github.run_id }}
|
|
300
|
+
- name: Fail if unhealthy
|
|
301
|
+
if: steps.pg-check.outputs.exit_code != '0'
|
|
302
|
+
run: exit 1
|
|
224
303
|
```
|
|
225
304
|
|
|
305
|
+
完整工作流(包含 PR 评论)请参考 [`examples/github-actions-pg-check.yml`](examples/github-actions-pg-check.yml)。
|
|
306
|
+
|
|
226
307
|
## 健康检查
|
|
227
308
|
|
|
228
309
|
pg-dash 运行 46+ 项自动化检查:
|
package/dist/cli.js
CHANGED
|
@@ -763,26 +763,28 @@ __export(snapshot_exports, {
|
|
|
763
763
|
});
|
|
764
764
|
import fs4 from "fs";
|
|
765
765
|
import path4 from "path";
|
|
766
|
-
function
|
|
767
|
-
|
|
766
|
+
function normalizeIssueId(id) {
|
|
767
|
+
return id.replace(/-\d+$/, "");
|
|
768
|
+
}
|
|
769
|
+
function saveSnapshot(snapshotPath, result) {
|
|
770
|
+
fs4.mkdirSync(path4.dirname(snapshotPath), { recursive: true });
|
|
768
771
|
const snapshot = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), result };
|
|
769
|
-
fs4.writeFileSync(
|
|
772
|
+
fs4.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2));
|
|
770
773
|
}
|
|
771
|
-
function loadSnapshot(
|
|
772
|
-
|
|
773
|
-
if (!fs4.existsSync(filePath)) return null;
|
|
774
|
+
function loadSnapshot(snapshotPath) {
|
|
775
|
+
if (!fs4.existsSync(snapshotPath)) return null;
|
|
774
776
|
try {
|
|
775
|
-
return JSON.parse(fs4.readFileSync(
|
|
777
|
+
return JSON.parse(fs4.readFileSync(snapshotPath, "utf-8"));
|
|
776
778
|
} catch {
|
|
777
779
|
return null;
|
|
778
780
|
}
|
|
779
781
|
}
|
|
780
782
|
function diffSnapshots2(prev, current) {
|
|
781
|
-
const
|
|
782
|
-
const
|
|
783
|
-
const newIssues = current.issues.filter((i) => !
|
|
784
|
-
const resolvedIssues = prev.issues.filter((i) => !
|
|
785
|
-
const unchanged = current.issues.filter((i) =>
|
|
783
|
+
const prevNormIds = new Set(prev.issues.map((i) => normalizeIssueId(i.id)));
|
|
784
|
+
const currNormIds = new Set(current.issues.map((i) => normalizeIssueId(i.id)));
|
|
785
|
+
const newIssues = current.issues.filter((i) => !prevNormIds.has(normalizeIssueId(i.id)));
|
|
786
|
+
const resolvedIssues = prev.issues.filter((i) => !currNormIds.has(normalizeIssueId(i.id)));
|
|
787
|
+
const unchanged = current.issues.filter((i) => prevNormIds.has(normalizeIssueId(i.id)));
|
|
786
788
|
return {
|
|
787
789
|
scoreDelta: current.score - prev.score,
|
|
788
790
|
previousScore: prev.score,
|
|
@@ -794,11 +796,9 @@ function diffSnapshots2(prev, current) {
|
|
|
794
796
|
unchanged
|
|
795
797
|
};
|
|
796
798
|
}
|
|
797
|
-
var SNAPSHOT_FILE;
|
|
798
799
|
var init_snapshot = __esm({
|
|
799
800
|
"src/server/snapshot.ts"() {
|
|
800
801
|
"use strict";
|
|
801
|
-
SNAPSHOT_FILE = "last-check.json";
|
|
802
802
|
}
|
|
803
803
|
});
|
|
804
804
|
|
|
@@ -2707,7 +2707,7 @@ import { WebSocketServer, WebSocket } from "ws";
|
|
|
2707
2707
|
import http from "http";
|
|
2708
2708
|
var __dirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
2709
2709
|
async function startServer(opts) {
|
|
2710
|
-
const pool = new Pool({ connectionString: opts.connectionString });
|
|
2710
|
+
const pool = new Pool({ connectionString: opts.connectionString, connectionTimeoutMillis: 1e4 });
|
|
2711
2711
|
try {
|
|
2712
2712
|
const client = await pool.connect();
|
|
2713
2713
|
client.release();
|
|
@@ -3068,7 +3068,8 @@ var { values, positionals } = parseArgs({
|
|
|
3068
3068
|
threshold: { type: "string" },
|
|
3069
3069
|
format: { type: "string", short: "f" },
|
|
3070
3070
|
ci: { type: "boolean", default: false },
|
|
3071
|
-
diff: { type: "boolean", default: false }
|
|
3071
|
+
diff: { type: "boolean", default: false },
|
|
3072
|
+
"snapshot-path": { type: "string" }
|
|
3072
3073
|
}
|
|
3073
3074
|
});
|
|
3074
3075
|
if (values.version) {
|
|
@@ -3115,7 +3116,8 @@ Options:
|
|
|
3115
3116
|
--threshold <score> Health score threshold for check command (default: 70)
|
|
3116
3117
|
-f, --format <fmt> Output format: text|json|md (default: text)
|
|
3117
3118
|
--ci Output GitHub Actions compatible annotations
|
|
3118
|
-
--diff Compare with previous run (saves
|
|
3119
|
+
--diff Compare with previous run (saves snapshot for next run)
|
|
3120
|
+
--snapshot-path <path> Path to snapshot file for --diff (default: ~/.pg-dash/last-check.json)
|
|
3119
3121
|
-v, --version Show version
|
|
3120
3122
|
-h, --help Show this help
|
|
3121
3123
|
|
|
@@ -3152,18 +3154,19 @@ if (subcommand === "check") {
|
|
|
3152
3154
|
const { getAdvisorReport: getAdvisorReport2 } = await Promise.resolve().then(() => (init_advisor(), advisor_exports));
|
|
3153
3155
|
const { saveSnapshot: saveSnapshot2, loadSnapshot: loadSnapshot2, diffSnapshots: diffSnapshots3 } = await Promise.resolve().then(() => (init_snapshot(), snapshot_exports));
|
|
3154
3156
|
const os4 = await import("os");
|
|
3155
|
-
const pool = new Pool2({ connectionString });
|
|
3157
|
+
const pool = new Pool2({ connectionString, connectionTimeoutMillis: 1e4 });
|
|
3156
3158
|
const checkDataDir = values["data-dir"] || path5.join(os4.homedir(), ".pg-dash");
|
|
3159
|
+
const snapshotPath = values["snapshot-path"] || path5.join(checkDataDir, "last-check.json");
|
|
3157
3160
|
try {
|
|
3158
3161
|
const lqt = parseInt(values["long-query-threshold"] || process.env.PG_DASH_LONG_QUERY_THRESHOLD || "5", 10);
|
|
3159
3162
|
const report = await getAdvisorReport2(pool, lqt);
|
|
3160
3163
|
let diff = null;
|
|
3161
3164
|
if (useDiff) {
|
|
3162
|
-
const prev = loadSnapshot2(
|
|
3165
|
+
const prev = loadSnapshot2(snapshotPath);
|
|
3163
3166
|
if (prev) {
|
|
3164
3167
|
diff = diffSnapshots3(prev.result, report);
|
|
3165
3168
|
}
|
|
3166
|
-
saveSnapshot2(
|
|
3169
|
+
saveSnapshot2(snapshotPath, report);
|
|
3167
3170
|
}
|
|
3168
3171
|
if (format === "json") {
|
|
3169
3172
|
const output = { ...report };
|