postgresai 0.16.0-rc.4 → 0.16.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 (85) hide show
  1. package/README.md +154 -0
  2. package/dist/bin/postgres-ai.js +2911 -255
  3. package/package.json +12 -3
  4. package/schemas/A002.schema.json +63 -0
  5. package/schemas/A003.schema.json +73 -0
  6. package/schemas/A004.schema.json +81 -0
  7. package/schemas/A007.schema.json +71 -0
  8. package/schemas/A013.schema.json +61 -0
  9. package/schemas/D001.schema.json +71 -0
  10. package/schemas/D004.schema.json +136 -0
  11. package/schemas/F001.schema.json +73 -0
  12. package/schemas/F002.schema.json +108 -0
  13. package/schemas/F003.schema.json +138 -0
  14. package/schemas/F004.schema.json +125 -0
  15. package/schemas/F005.schema.json +131 -0
  16. package/schemas/F009.schema.json +155 -0
  17. package/schemas/G001.schema.json +135 -0
  18. package/schemas/G003.schema.json +90 -0
  19. package/schemas/H001.schema.json +141 -0
  20. package/schemas/H002.schema.json +129 -0
  21. package/schemas/H004.schema.json +128 -0
  22. package/schemas/I001.schema.json +149 -0
  23. package/schemas/K001.schema.json +161 -0
  24. package/schemas/K003.schema.json +163 -0
  25. package/schemas/K004.schema.json +110 -0
  26. package/schemas/K005.schema.json +110 -0
  27. package/schemas/K006.schema.json +110 -0
  28. package/schemas/K007.schema.json +110 -0
  29. package/schemas/K008.schema.json +110 -0
  30. package/schemas/M001.schema.json +119 -0
  31. package/schemas/M002.schema.json +110 -0
  32. package/schemas/M003.schema.json +128 -0
  33. package/schemas/N001.schema.json +161 -0
  34. package/schemas/query.schema.json +62 -0
  35. package/CHANGELOG.md +0 -11
  36. package/bin/postgres-ai.ts +0 -5578
  37. package/bun.lock +0 -258
  38. package/bunfig.toml +0 -20
  39. package/lib/aas-onboard.ts +0 -251
  40. package/lib/auth-server.ts +0 -285
  41. package/lib/checkup-api.ts +0 -526
  42. package/lib/checkup-dictionary.ts +0 -103
  43. package/lib/checkup-summary.ts +0 -338
  44. package/lib/checkup.ts +0 -2261
  45. package/lib/config.ts +0 -171
  46. package/lib/init.ts +0 -1152
  47. package/lib/instances.ts +0 -245
  48. package/lib/issues.ts +0 -1060
  49. package/lib/mcp-server.ts +0 -667
  50. package/lib/metrics-loader.ts +0 -134
  51. package/lib/pkce.ts +0 -79
  52. package/lib/reports.ts +0 -373
  53. package/lib/storage.ts +0 -367
  54. package/lib/supabase.ts +0 -826
  55. package/lib/util.ts +0 -134
  56. package/packages/postgres-ai/README.md +0 -26
  57. package/packages/postgres-ai/bin/postgres-ai.js +0 -27
  58. package/packages/postgres-ai/package.json +0 -27
  59. package/scripts/embed-checkup-dictionary.ts +0 -115
  60. package/scripts/embed-metrics.ts +0 -160
  61. package/scripts/generate-release-notes.ts +0 -668
  62. package/test/PERMISSION_CHECK_TEST_SUMMARY.md +0 -139
  63. package/test/aas-onboard.test.ts +0 -301
  64. package/test/auth.test.ts +0 -287
  65. package/test/checkup.integration.test.ts +0 -413
  66. package/test/checkup.test.ts +0 -3626
  67. package/test/compose-cmd.test.ts +0 -120
  68. package/test/config-consistency.test.ts +0 -352
  69. package/test/init.integration.test.ts +0 -438
  70. package/test/init.test.ts +0 -1816
  71. package/test/issues.cli.test.ts +0 -1162
  72. package/test/issues.test.ts +0 -456
  73. package/test/mcp-server.test.ts +0 -2530
  74. package/test/monitoring.test.ts +0 -746
  75. package/test/permission-check-sql.test.ts +0 -116
  76. package/test/reports.cli.test.ts +0 -793
  77. package/test/reports.test.ts +0 -977
  78. package/test/schema-validation.test.ts +0 -231
  79. package/test/storage.test.ts +0 -935
  80. package/test/supabase.test.ts +0 -709
  81. package/test/targets-add-config.test.ts +0 -28
  82. package/test/test-utils.ts +0 -190
  83. package/test/upgrade.test.ts +0 -1056
  84. package/test/util.test.ts +0 -44
  85. package/tsconfig.json +0 -20
package/lib/storage.ts DELETED
@@ -1,367 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { formatHttpError, maskSecret, normalizeBaseUrl } from "./util";
4
-
5
- const MAX_UPLOAD_SIZE = 500 * 1024 * 1024; // 500 MB
6
- const MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024; // 500 MB
7
-
8
- const MIME_TYPES: Record<string, string> = {
9
- ".png": "image/png",
10
- ".jpg": "image/jpeg",
11
- ".jpeg": "image/jpeg",
12
- ".gif": "image/gif",
13
- ".webp": "image/webp",
14
- ".svg": "image/svg+xml",
15
- ".bmp": "image/bmp",
16
- ".ico": "image/x-icon",
17
- ".pdf": "application/pdf",
18
- ".json": "application/json",
19
- ".xml": "application/xml",
20
- ".zip": "application/zip",
21
- ".gz": "application/gzip",
22
- ".tar": "application/x-tar",
23
- ".csv": "text/csv",
24
- ".txt": "text/plain",
25
- ".log": "text/plain",
26
- ".sql": "application/sql",
27
- ".html": "text/html",
28
- ".css": "text/css",
29
- ".js": "application/javascript",
30
- ".ts": "application/typescript",
31
- ".md": "text/markdown",
32
- ".yaml": "application/x-yaml",
33
- ".yml": "application/x-yaml",
34
- };
35
-
36
- export interface UploadFileMetadata {
37
- originalName: string;
38
- size: number;
39
- mimeType: string;
40
- uploadedAt: string;
41
- duration: number;
42
- }
43
-
44
- export interface UploadResult {
45
- success: boolean;
46
- url: string;
47
- metadata: UploadFileMetadata;
48
- requestId: string;
49
- }
50
-
51
- export interface UploadFileParams {
52
- apiKey: string;
53
- storageBaseUrl: string;
54
- filePath: string;
55
- debug?: boolean;
56
- }
57
-
58
- /**
59
- * Upload a file to the storage service.
60
- *
61
- * @param params.apiKey - API token for authentication
62
- * @param params.storageBaseUrl - Storage service base URL
63
- * @param params.filePath - Local file path to upload
64
- * @param params.debug - Enable debug logging
65
- * @returns Upload result with URL and metadata
66
- */
67
- export async function uploadFile(params: UploadFileParams): Promise<UploadResult> {
68
- const { apiKey, storageBaseUrl, filePath, debug } = params;
69
- if (!apiKey) {
70
- throw new Error("API key is required");
71
- }
72
- if (!storageBaseUrl) {
73
- throw new Error("storageBaseUrl is required");
74
- }
75
- if (!filePath) {
76
- throw new Error("filePath is required");
77
- }
78
-
79
- const resolvedPath = path.resolve(filePath);
80
- if (!fs.existsSync(resolvedPath)) {
81
- throw new Error(`File not found: ${resolvedPath}`);
82
- }
83
- const stat = fs.statSync(resolvedPath);
84
- if (!stat.isFile()) {
85
- throw new Error(`Not a file: ${resolvedPath}`);
86
- }
87
- if (stat.size > MAX_UPLOAD_SIZE) {
88
- throw new Error(`File too large: ${stat.size} bytes (max ${MAX_UPLOAD_SIZE})`);
89
- }
90
-
91
- const base = normalizeBaseUrl(storageBaseUrl);
92
- // Warn but don't reject HTTP — CLI needs to work with localhost during development.
93
- // Rejecting would require an --allow-insecure flag that adds friction for local setups.
94
- if (new URL(base).protocol === "http:") {
95
- console.error("Warning: storage URL uses HTTP — API key will be sent unencrypted");
96
- }
97
- const url = `${base}/upload`;
98
-
99
- // readFileSync is intentional: FormData/Blob API requires the full buffer anyway,
100
- // and the 500MB size check above prevents excessive memory use. Streaming would
101
- // need a custom multipart encoder (no native stream support in fetch FormData).
102
- const fileBuffer = fs.readFileSync(resolvedPath);
103
- const fileName = path.basename(resolvedPath);
104
-
105
- const ext = path.extname(fileName).toLowerCase();
106
- const mimeType = MIME_TYPES[ext] || "application/octet-stream";
107
-
108
- const formData = new FormData();
109
- formData.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
110
-
111
- const headers: Record<string, string> = {
112
- "access-token": apiKey,
113
- };
114
-
115
- if (debug) {
116
- const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
117
- console.error(`Debug: Storage base URL: ${base}`);
118
- console.error(`Debug: POST URL: ${url}`);
119
- console.error(`Debug: File: ${resolvedPath} (${stat.size} bytes, ${mimeType})`);
120
- console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
121
- }
122
-
123
- const response = await fetch(url, {
124
- method: "POST",
125
- headers,
126
- body: formData,
127
- });
128
-
129
- if (debug) {
130
- console.error(`Debug: Response status: ${response.status}`);
131
- console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
132
- }
133
-
134
- const data = await response.text();
135
-
136
- if (response.ok) {
137
- try {
138
- return JSON.parse(data) as UploadResult;
139
- } catch {
140
- throw new Error(`Failed to parse upload response: ${data}`);
141
- }
142
- } else {
143
- throw new Error(formatHttpError("Failed to upload file", response.status, data));
144
- }
145
- }
146
-
147
- export interface DownloadFileParams {
148
- apiKey: string;
149
- storageBaseUrl: string;
150
- fileUrl: string;
151
- outputPath?: string;
152
- debug?: boolean;
153
- }
154
-
155
- export interface DownloadResult {
156
- savedTo: string;
157
- size: number;
158
- mimeType: string | null;
159
- }
160
-
161
- /**
162
- * Download a file from the storage service.
163
- *
164
- * @param params.apiKey - API token for authentication
165
- * @param params.storageBaseUrl - Storage service base URL
166
- * @param params.fileUrl - File URL path (e.g. /files/123/xxx.png) or full URL
167
- * @param params.outputPath - Local path to save the file (default: derive from URL)
168
- * @param params.debug - Enable debug logging
169
- * @returns Download result with saved path and size
170
- */
171
- export async function downloadFile(params: DownloadFileParams): Promise<DownloadResult> {
172
- const { apiKey, storageBaseUrl, fileUrl, outputPath, debug } = params;
173
- if (!apiKey) {
174
- throw new Error("API key is required");
175
- }
176
- if (!storageBaseUrl) {
177
- throw new Error("storageBaseUrl is required");
178
- }
179
- if (!fileUrl) {
180
- throw new Error("fileUrl is required");
181
- }
182
-
183
- const base = normalizeBaseUrl(storageBaseUrl);
184
- // Warn but don't reject HTTP — same rationale as uploadFile (localhost dev).
185
- if (new URL(base).protocol === "http:") {
186
- console.error("Warning: storage URL uses HTTP — API key will be sent unencrypted");
187
- }
188
-
189
- // Support both full URLs and relative paths
190
- let fullUrl: string;
191
- if (fileUrl.startsWith("http://") || fileUrl.startsWith("https://")) {
192
- if (!fileUrl.startsWith(base + "/")) {
193
- throw new Error(`URL must be under storage base URL: ${base}`);
194
- }
195
- fullUrl = fileUrl;
196
- } else {
197
- // Relative path like /files/123/xxx.png
198
- const relativePath = fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`;
199
- fullUrl = `${base}${relativePath}`;
200
- }
201
-
202
- // Derive output filename from URL if not specified
203
- const urlFilename = path.basename(new URL(fullUrl).pathname);
204
- if (!urlFilename) {
205
- throw new Error("Cannot derive filename from URL; please specify --output");
206
- }
207
- const saveTo = outputPath ? path.resolve(outputPath) : path.resolve(urlFilename);
208
-
209
- // Path traversal guard only for URL-derived filenames (untrusted input).
210
- // Explicit --output (-o) is trusted: the user intentionally chose the path,
211
- // and restricting it to cwd would break legitimate use (e.g. -o /tmp/file.png).
212
- if (!outputPath) {
213
- const normalizedSave = path.normalize(saveTo);
214
- const cwd = path.normalize(process.cwd());
215
- // Append path.sep so that cwd "/home/u/proj" doesn't allow a sibling
216
- // "/home/u/proj-evil/x" via plain prefix match.
217
- if (normalizedSave !== cwd && !normalizedSave.startsWith(cwd + path.sep)) {
218
- throw new Error("Derived output path escapes current directory; please specify --output");
219
- }
220
- }
221
-
222
- const headers: Record<string, string> = {
223
- "access-token": apiKey,
224
- };
225
-
226
- if (debug) {
227
- const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
228
- console.error(`Debug: Storage base URL: ${base}`);
229
- console.error(`Debug: GET URL: ${fullUrl}`);
230
- console.error(`Debug: Output: ${saveTo}`);
231
- console.error(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
232
- }
233
-
234
- const response = await fetch(fullUrl, {
235
- method: "GET",
236
- headers,
237
- });
238
-
239
- if (debug) {
240
- console.error(`Debug: Response status: ${response.status}`);
241
- console.error(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
242
- }
243
-
244
- if (!response.ok) {
245
- const data = await response.text();
246
- throw new Error(formatHttpError("Failed to download file", response.status, data));
247
- }
248
-
249
- const contentLength = response.headers.get("content-length");
250
- if (contentLength && parseInt(contentLength, 10) > MAX_DOWNLOAD_SIZE) {
251
- throw new Error(`File too large: ${contentLength} bytes (max ${MAX_DOWNLOAD_SIZE})`);
252
- }
253
-
254
- const arrayBuffer = await response.arrayBuffer();
255
- const buffer = Buffer.from(arrayBuffer);
256
-
257
- // Create parent dirs for the output path. recursive:true is intentional —
258
- // the user may specify -o deeply/nested/path.png and expect it to work,
259
- // same as curl --create-dirs or wget -P.
260
- const parentDir = path.dirname(saveTo);
261
- if (!fs.existsSync(parentDir)) {
262
- fs.mkdirSync(parentDir, { recursive: true });
263
- }
264
-
265
- fs.writeFileSync(saveTo, buffer);
266
-
267
- return {
268
- savedTo: saveTo,
269
- size: buffer.length,
270
- mimeType: response.headers.get("content-type"),
271
- };
272
- }
273
-
274
- const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".ico"]);
275
-
276
- /**
277
- * Build a markdown link for a file URL.
278
- * Returns `![name](url)` for images, `[name](url)` for other files.
279
- */
280
- export function buildMarkdownLink(fileUrl: string, storageBaseUrl: string, filename?: string): string {
281
- const base = normalizeBaseUrl(storageBaseUrl);
282
- const normalizedFileUrl = fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`;
283
- const fullUrl = (fileUrl.startsWith("http://") || fileUrl.startsWith("https://")) ? fileUrl : `${base}${normalizedFileUrl}`;
284
- const name = filename || path.basename(new URL(fullUrl).pathname);
285
- const safeName = name.replace(/[\[\]()]/g, "\\$&");
286
- const ext = path.extname(name).toLowerCase();
287
- // fullUrl is not escaped — storage URLs are server-generated (UUID-based paths
288
- // like /files/641/1770646021425_019c42ba.png) and never contain parentheses.
289
- // Escaping would break the URL for renderers that don't decode %29 in href.
290
- if (IMAGE_EXTENSIONS.has(ext)) {
291
- return `![${safeName}](${fullUrl})`;
292
- }
293
- return `[${safeName}](${fullUrl})`;
294
- }
295
-
296
- export interface UploadedAttachment {
297
- path: string;
298
- url: string;
299
- markdown: string;
300
- metadata: UploadFileMetadata;
301
- }
302
-
303
- export interface UploadAttachmentsParams {
304
- apiKey: string;
305
- storageBaseUrl: string;
306
- attachmentPaths: string[];
307
- debug?: boolean;
308
- }
309
-
310
- /**
311
- * Upload a list of local files to storage and return one markdown link per file.
312
- *
313
- * Shared by both the CLI `--attach` flag and the MCP `attachments` parameter so
314
- * that the two surfaces produce identical output. Uploads are sequential (not
315
- * parallel) so that on failure of file N, the error from `uploadFile` (which
316
- * includes the resolved path, e.g. `File not found: /abs/path`) pinpoints
317
- * which file failed. Note: any files already uploaded successfully before
318
- * the failure remain on the storage server; a retry of the same call will
319
- * re-upload them.
320
- *
321
- * Returns an empty array if `attachmentPaths` is empty (callers don't have to
322
- * guard).
323
- */
324
- export async function uploadAttachments(params: UploadAttachmentsParams): Promise<UploadedAttachment[]> {
325
- const { apiKey, storageBaseUrl, attachmentPaths, debug } = params;
326
- if (!attachmentPaths || attachmentPaths.length === 0) {
327
- return [];
328
- }
329
- const out: UploadedAttachment[] = [];
330
- for (const attachmentPath of attachmentPaths) {
331
- const result = await uploadFile({
332
- apiKey,
333
- storageBaseUrl,
334
- filePath: attachmentPath,
335
- debug,
336
- });
337
- const markdown = buildMarkdownLink(result.url, storageBaseUrl, result.metadata.originalName);
338
- out.push({
339
- path: attachmentPath,
340
- url: result.url,
341
- markdown,
342
- metadata: result.metadata,
343
- });
344
- }
345
- return out;
346
- }
347
-
348
- /**
349
- * Append uploaded-attachment markdown links to a body of content.
350
- *
351
- * - If `attachments` is empty, returns `content` unchanged.
352
- * - If `content` is empty/whitespace, returns just the links.
353
- * - Otherwise: `${content}\n\n${links joined by \n}`.
354
- *
355
- * One link per line keeps the renderer happy whether the surface is GFM
356
- * (which collapses adjacent lines) or strict CommonMark.
357
- */
358
- export function appendAttachmentsToContent(content: string, attachments: UploadedAttachment[]): string {
359
- if (!attachments || attachments.length === 0) {
360
- return content;
361
- }
362
- const links = attachments.map((a) => a.markdown).join("\n");
363
- if (!content || !content.trim()) {
364
- return links;
365
- }
366
- return `${content}\n\n${links}`;
367
- }