@hienlh/ppm 0.9.0-beta.8 → 0.9.1

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 (159) hide show
  1. package/CHANGELOG.md +238 -0
  2. package/bun.lock +17 -0
  3. package/dist/web/assets/api-settings-BUvk6Saw.js +1 -0
  4. package/dist/web/assets/arrow-up-BYhx9ckd.js +1 -0
  5. package/dist/web/assets/browser-tab-CrkhFCaw.js +1 -0
  6. package/dist/web/assets/chat-tab-C6jpiwh7.js +8 -0
  7. package/dist/web/assets/chevron-right-5HgK6l7K.js +1 -0
  8. package/dist/web/assets/code-editor-CBIPzlP2.js +2 -0
  9. package/dist/web/assets/columns-2-cEVJHYd7.js +1 -0
  10. package/dist/web/assets/createLucideIcon-PuMiQgHl.js +1 -0
  11. package/dist/web/assets/{csv-preview-DLqYtXxt.js → csv-preview-ncSOnJSC.js} +2 -2
  12. package/dist/web/assets/database-viewer-BqOJR_zi.js +1 -0
  13. package/dist/web/assets/diff-viewer-CcLyp4eY.js +4 -0
  14. package/dist/web/assets/{dist-CALwEtco.js → dist-DIV6WgAG.js} +1 -1
  15. package/dist/web/assets/{dist-DGDPTxs1.js → dist-ovWkrgO-.js} +1 -1
  16. package/dist/web/assets/extension-webview-NiZ7Ybvv.js +3 -0
  17. package/dist/web/assets/git-graph-CoTvMrIo.js +1 -0
  18. package/dist/web/assets/index-C8byznLO.js +37 -0
  19. package/dist/web/assets/index-KwC2YrG4.css +2 -0
  20. package/dist/web/assets/jsx-runtime-kMwlnEGE.js +1 -0
  21. package/dist/web/assets/keybindings-store-DPYzBe_M.js +1 -0
  22. package/dist/web/assets/{markdown-renderer-DklUd_Gv.js → markdown-renderer-DPLdR9xc.js} +4 -4
  23. package/dist/web/assets/postgres-viewer-BeiK4lCa.js +1 -0
  24. package/dist/web/assets/settings-tab-D3AvU4lu.js +1 -0
  25. package/dist/web/assets/sqlite-viewer-nA2sD4Yv.js +1 -0
  26. package/dist/web/assets/tab-store-BOgTrqRr.js +1 -0
  27. package/dist/web/assets/table-DFevCOMd.js +1 -0
  28. package/dist/web/assets/tag-CXMT0QB6.js +1 -0
  29. package/dist/web/assets/{terminal-tab-CqRuiIFn.js → terminal-tab-BBi0pEji.js} +2 -2
  30. package/dist/web/assets/{use-monaco-theme-Dcz3aLAE.js → use-monaco-theme-B5pG2d1w.js} +1 -1
  31. package/dist/web/index.html +8 -8
  32. package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
  33. package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
  34. package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
  35. package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
  36. package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
  37. package/dist/web/sw.js +1 -1
  38. package/docs/code-standards.md +128 -1
  39. package/docs/codebase-summary.md +79 -12
  40. package/docs/extension-development-guide.md +532 -0
  41. package/docs/project-changelog.md +51 -1
  42. package/docs/project-roadmap.md +9 -3
  43. package/docs/streaming-input-guide.md +267 -0
  44. package/docs/system-architecture.md +432 -3
  45. package/package.json +6 -3
  46. package/packages/ext-database/package.json +41 -0
  47. package/packages/ext-database/src/connection-tree.ts +142 -0
  48. package/packages/ext-database/src/extension.ts +346 -0
  49. package/packages/ext-database/src/query-panel.ts +120 -0
  50. package/packages/ext-database/src/table-viewer-panel.ts +410 -0
  51. package/packages/ext-database/tsconfig.json +8 -0
  52. package/packages/vscode-compat/package.json +16 -0
  53. package/packages/vscode-compat/src/commands.ts +39 -0
  54. package/packages/vscode-compat/src/context.ts +65 -0
  55. package/packages/vscode-compat/src/disposable.ts +21 -0
  56. package/packages/vscode-compat/src/env.ts +20 -0
  57. package/packages/vscode-compat/src/event-emitter.ts +28 -0
  58. package/packages/vscode-compat/src/index.ts +93 -0
  59. package/packages/vscode-compat/src/not-supported.ts +15 -0
  60. package/packages/vscode-compat/src/types.ts +167 -0
  61. package/packages/vscode-compat/src/uri.ts +65 -0
  62. package/packages/vscode-compat/src/window.ts +229 -0
  63. package/packages/vscode-compat/src/workspace.ts +76 -0
  64. package/packages/vscode-compat/tsconfig.json +10 -0
  65. package/snapshot-state.md +1526 -0
  66. package/src/cli/commands/autostart.ts +1 -1
  67. package/src/cli/commands/ext-cmd.ts +121 -0
  68. package/src/cli/commands/restart.ts +9 -1
  69. package/src/cli/commands/status.ts +19 -0
  70. package/src/index.ts +5 -3
  71. package/src/providers/claude-agent-sdk.ts +221 -17
  72. package/src/providers/cli-provider-base.ts +6 -0
  73. package/src/server/index.ts +55 -155
  74. package/src/server/routes/chat.ts +81 -11
  75. package/src/server/routes/extensions.ts +81 -0
  76. package/src/server/routes/project-scoped.ts +2 -0
  77. package/src/server/routes/settings.ts +27 -0
  78. package/src/server/routes/workspace.ts +35 -0
  79. package/src/server/ws/chat.ts +9 -3
  80. package/src/server/ws/extensions.ts +175 -0
  81. package/src/services/account-selector.service.ts +14 -5
  82. package/src/services/account.service.ts +20 -15
  83. package/src/services/claude-usage.service.ts +29 -24
  84. package/src/services/cloud-ws.service.ts +228 -0
  85. package/src/services/cloud.service.ts +11 -6
  86. package/src/services/contribution-registry.ts +110 -0
  87. package/src/services/db.service.ts +181 -4
  88. package/src/services/extension-host-worker.ts +160 -0
  89. package/src/services/extension-installer.ts +112 -0
  90. package/src/services/extension-manifest.ts +65 -0
  91. package/src/services/extension-rpc-handlers.ts +235 -0
  92. package/src/services/extension-rpc.ts +105 -0
  93. package/src/services/extension.service.ts +228 -0
  94. package/src/services/mcp-config.service.ts +15 -6
  95. package/src/services/supervisor.ts +271 -25
  96. package/src/types/api.ts +1 -0
  97. package/src/types/chat.ts +4 -0
  98. package/src/types/extension-messages.ts +64 -0
  99. package/src/types/extension.ts +131 -0
  100. package/src/web/app.tsx +69 -48
  101. package/src/web/components/chat/account-rotation-settings.tsx +163 -0
  102. package/src/web/components/chat/chat-history-bar.tsx +106 -10
  103. package/src/web/components/chat/chat-tab.tsx +15 -10
  104. package/src/web/components/chat/chat-welcome.tsx +148 -0
  105. package/src/web/components/chat/message-list.tsx +19 -6
  106. package/src/web/components/chat/session-picker.tsx +80 -32
  107. package/src/web/components/chat/usage-badge.tsx +68 -8
  108. package/src/web/components/editor/editor-breadcrumb.tsx +20 -29
  109. package/src/web/components/extensions/extension-inputbox.tsx +92 -0
  110. package/src/web/components/extensions/extension-quickpick.tsx +194 -0
  111. package/src/web/components/extensions/extension-tree-view.tsx +240 -0
  112. package/src/web/components/extensions/extension-webview.tsx +83 -0
  113. package/src/web/components/layout/command-palette.tsx +22 -2
  114. package/src/web/components/layout/editor-panel.tsx +163 -18
  115. package/src/web/components/layout/mobile-nav.tsx +2 -1
  116. package/src/web/components/layout/sidebar.tsx +21 -3
  117. package/src/web/components/layout/status-bar.tsx +64 -0
  118. package/src/web/components/layout/tab-bar.tsx +2 -0
  119. package/src/web/components/layout/tab-content.tsx +5 -0
  120. package/src/web/components/layout/upgrade-banner.tsx +15 -5
  121. package/src/web/components/settings/change-password-section.tsx +128 -0
  122. package/src/web/components/settings/extension-manager-section.tsx +214 -0
  123. package/src/web/components/settings/settings-tab.tsx +9 -2
  124. package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
  125. package/src/web/hooks/use-chat.ts +28 -0
  126. package/src/web/hooks/use-extension-ws.ts +181 -0
  127. package/src/web/hooks/use-global-keybindings.ts +18 -2
  128. package/src/web/hooks/use-server-reload.ts +9 -0
  129. package/src/web/hooks/use-url-sync.ts +173 -21
  130. package/src/web/stores/connection-store.ts +39 -0
  131. package/src/web/stores/extension-store.ts +204 -0
  132. package/src/web/stores/panel-store.ts +63 -9
  133. package/src/web/stores/panel-utils.ts +145 -3
  134. package/src/web/stores/settings-store.ts +7 -2
  135. package/src/web/stores/tab-store.ts +2 -1
  136. package/test-session-ops.mjs +444 -0
  137. package/test-tokens.mjs +212 -0
  138. package/tsconfig.json +3 -1
  139. package/dist/web/assets/api-settings-D21InCnR.js +0 -1
  140. package/dist/web/assets/arrow-up--LjUXLEt.js +0 -1
  141. package/dist/web/assets/browser-tab-BEe89aSD.js +0 -1
  142. package/dist/web/assets/chat-tab-9lqvWozA.js +0 -7
  143. package/dist/web/assets/chevron-right-CHnjJt4E.js +0 -1
  144. package/dist/web/assets/code-editor-COAIZx-B.js +0 -2
  145. package/dist/web/assets/columns-2-DbesTfa7.js +0 -1
  146. package/dist/web/assets/database-viewer-aRR9n_Ui.js +0 -1
  147. package/dist/web/assets/diff-viewer-C4KMvpHr.js +0 -4
  148. package/dist/web/assets/dist-CVTST7Gc.js +0 -1
  149. package/dist/web/assets/git-graph-CfJjl4E3.js +0 -1
  150. package/dist/web/assets/index-Db8uky1a.css +0 -2
  151. package/dist/web/assets/index-DxZuwBDe.js +0 -37
  152. package/dist/web/assets/jsx-runtime-BRW_vwa9.js +0 -1
  153. package/dist/web/assets/keybindings-store-_uWVCZMv.js +0 -1
  154. package/dist/web/assets/postgres-viewer-DEAvAyaX.js +0 -1
  155. package/dist/web/assets/settings-tab-BQedc-No.js +0 -1
  156. package/dist/web/assets/sqlite-viewer-BPA5idzT.js +0 -1
  157. package/dist/web/assets/tab-store-DhK6EpBT.js +0 -1
  158. package/dist/web/assets/table-CQVQM2SB.js +0 -1
  159. package/dist/web/assets/tag-Q2dZiSPX.js +0 -1
@@ -0,0 +1,346 @@
1
+ /**
2
+ * @ppm/ext-database — Database Viewer extension for PPM.
3
+ * Provides a sidebar tree view of DB connections/tables/columns
4
+ * and a full-featured table viewer webview with inline editing,
5
+ * pagination, and SQL query panel.
6
+ */
7
+ import type { ExtensionContext } from "@ppm/vscode-compat";
8
+ import { ConnectionTreeProvider } from "./connection-tree.ts";
9
+ import { getQueryPanelHtml } from "./query-panel.ts";
10
+ import { getTableViewerHtml } from "./table-viewer-panel.ts";
11
+
12
+ /** PPM vscode-compat API namespace (passed as second arg by Worker) */
13
+ interface VscodeApi {
14
+ commands: {
15
+ registerCommand(command: string, callback: (...args: unknown[]) => unknown): { dispose(): void };
16
+ };
17
+ window: {
18
+ showInformationMessage(message: string, ...items: string[]): Promise<string | undefined>;
19
+ showErrorMessage(message: string, ...items: string[]): Promise<string | undefined>;
20
+ showQuickPick(items: string[], options?: { placeHolder?: string }): Promise<string | undefined>;
21
+ showInputBox(options?: { prompt?: string; placeHolder?: string; value?: string }): Promise<string | undefined>;
22
+ createTreeView(viewId: string, options: { treeDataProvider: unknown }): { dispose(): void };
23
+ createWebviewPanel(viewType: string, title: string, showOptions: unknown): {
24
+ webview: {
25
+ html: string;
26
+ onDidReceiveMessage: (listener: (msg: unknown) => void) => { dispose(): void };
27
+ postMessage(message: unknown): Promise<boolean>;
28
+ };
29
+ onDidDispose: (listener: () => void) => { dispose(): void };
30
+ dispose(): void;
31
+ };
32
+ createStatusBarItem(alignment?: unknown, priority?: number): {
33
+ text: string; tooltip?: string; command?: string;
34
+ show(): void; hide(): void; dispose(): void;
35
+ };
36
+ };
37
+ EventEmitter: new <T>() => { fire(data?: T): void; event: unknown; dispose(): void };
38
+ StatusBarAlignment: { Left: number; Right: number };
39
+ ViewColumn: { Active: number };
40
+ }
41
+
42
+ // Server base URL for fetch() calls (set by Worker before activation)
43
+ let baseUrl = "";
44
+
45
+ export function activate(context: ExtensionContext, vscode: VscodeApi): void {
46
+ baseUrl = (globalThis as any).__PPM_BASE_URL__ || "";
47
+
48
+ // --- Tree View ---
49
+ const emitter = new vscode.EventEmitter<unknown>();
50
+ const treeProvider = new ConnectionTreeProvider(
51
+ { fire: (el?: unknown) => emitter.fire(el), event: emitter.event },
52
+ baseUrl,
53
+ );
54
+
55
+ const treeView = vscode.window.createTreeView("ppm-db.connections", {
56
+ treeDataProvider: treeProvider,
57
+ });
58
+ context.subscriptions.push(treeView);
59
+
60
+ // --- Commands ---
61
+ context.subscriptions.push(
62
+ vscode.commands.registerCommand("ppm-db.openViewer", (...args: unknown[]) => {
63
+ const connectionId = (args[0] as number) ?? 1;
64
+ const connectionName = (args[1] as string) ?? "Database";
65
+ const tableName = args[2] as string | undefined;
66
+ const schemaName = (args[3] as string) ?? "public";
67
+ if (tableName) {
68
+ openTableViewer(vscode, context, connectionId, connectionName, tableName, schemaName);
69
+ } else {
70
+ openQueryPanel(vscode, context, connectionId, connectionName);
71
+ }
72
+ }),
73
+ );
74
+
75
+ context.subscriptions.push(
76
+ vscode.commands.registerCommand("ppm-db.runQuery", () => {
77
+ openQueryPanel(vscode, context, 1, "Default");
78
+ }),
79
+ );
80
+
81
+ context.subscriptions.push(
82
+ vscode.commands.registerCommand("ppm-db.refreshConnections", () => {
83
+ treeProvider.refresh();
84
+ }),
85
+ );
86
+
87
+ context.subscriptions.push(
88
+ vscode.commands.registerCommand("ppm-db.refreshConnection", () => {
89
+ treeProvider.refresh();
90
+ }),
91
+ );
92
+
93
+ context.subscriptions.push(
94
+ vscode.commands.registerCommand("ppm-db.addConnection", async () => {
95
+ await addConnection(vscode, treeProvider);
96
+ }),
97
+ );
98
+
99
+ // --- Status Bar ---
100
+ const statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 10);
101
+ statusItem.text = "DB";
102
+ statusItem.tooltip = "Database Viewer";
103
+ statusItem.command = "ppm-db.runQuery";
104
+ statusItem.show();
105
+ context.subscriptions.push(statusItem);
106
+
107
+ console.log("[ext-database] activated");
108
+ }
109
+
110
+ export function deactivate(): void {
111
+ console.log("[ext-database] deactivated");
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Table Viewer — full-featured data grid with inline editing & SQL panel
116
+ // ---------------------------------------------------------------------------
117
+
118
+ function openTableViewer(
119
+ vscode: VscodeApi,
120
+ context: ExtensionContext,
121
+ connectionId: number,
122
+ connectionName: string,
123
+ tableName: string,
124
+ schemaName: string,
125
+ ): void {
126
+ const panel = vscode.window.createWebviewPanel(
127
+ "ppm-db.tableViewer",
128
+ `${connectionName} · ${tableName}`,
129
+ vscode.ViewColumn.Active,
130
+ );
131
+
132
+ panel.webview.html = getTableViewerHtml({ connectionId, connectionName, tableName, schemaName });
133
+
134
+ const msgDisposable = panel.webview.onDidReceiveMessage(async (raw: unknown) => {
135
+ const msg = raw as Record<string, unknown>;
136
+ const connId = (msg.connectionId as number) ?? connectionId;
137
+ const tbl = (msg.tableName as string) ?? tableName;
138
+ const schema = (msg.schemaName as string) ?? schemaName;
139
+
140
+ switch (msg.type) {
141
+ case "init":
142
+ case "refresh":
143
+ await sendTableData(panel, connId, tbl, schema, (msg.page as number) ?? 1);
144
+ break;
145
+
146
+ case "fetchPage":
147
+ await sendTableData(panel, connId, tbl, schema, (msg.page as number) ?? 1);
148
+ break;
149
+
150
+ case "executeQuery":
151
+ await handleQuery(panel, connId, msg.sql as string);
152
+ break;
153
+
154
+ case "updateCell":
155
+ await handleCellUpdate(panel, connId, tbl, schema, msg);
156
+ break;
157
+ }
158
+ });
159
+ context.subscriptions.push(msgDisposable);
160
+ panel.onDidDispose(() => msgDisposable.dispose());
161
+ }
162
+
163
+ /** Fetch table data + schema and send to webview */
164
+ async function sendTableData(
165
+ panel: ReturnType<VscodeApi["window"]["createWebviewPanel"]>,
166
+ connectionId: number,
167
+ tableName: string,
168
+ schemaName: string,
169
+ page: number,
170
+ ): Promise<void> {
171
+ try {
172
+ const [dataRes, schemaRes] = await Promise.all([
173
+ fetch(`${baseUrl}/api/db/connections/${connectionId}/data?table=${encodeURIComponent(tableName)}&schema=${schemaName}&page=${page}&limit=100`),
174
+ fetch(`${baseUrl}/api/db/connections/${connectionId}/schema?table=${encodeURIComponent(tableName)}&schema=${schemaName}`),
175
+ ]);
176
+ const dataJson = await dataRes.json() as { ok: boolean; data?: { columns: string[]; rows: Record<string, unknown>[]; total: number; page: number; limit: number } };
177
+ const schemaJson = await schemaRes.json() as { ok: boolean; data?: { name: string; type: string; nullable: boolean; pk: boolean; defaultValue: string | null }[] };
178
+
179
+ if (!dataJson.ok) {
180
+ await panel.webview.postMessage({ type: "error", message: "Failed to load table data" });
181
+ return;
182
+ }
183
+
184
+ await panel.webview.postMessage({
185
+ type: "tableData",
186
+ columns: dataJson.data?.columns ?? [],
187
+ rows: dataJson.data?.rows ?? [],
188
+ total: dataJson.data?.total ?? 0,
189
+ page: dataJson.data?.page ?? page,
190
+ limit: dataJson.data?.limit ?? 100,
191
+ schema: schemaJson.data ?? [],
192
+ });
193
+ } catch (e) {
194
+ const errMsg = e instanceof Error ? e.message : String(e);
195
+ await panel.webview.postMessage({ type: "error", message: errMsg });
196
+ }
197
+ }
198
+
199
+ /** Execute SQL query and send result to webview */
200
+ async function handleQuery(
201
+ panel: ReturnType<VscodeApi["window"]["createWebviewPanel"]>,
202
+ connectionId: number,
203
+ sql: string,
204
+ ): Promise<void> {
205
+ if (!sql) return;
206
+ try {
207
+ const start = Date.now();
208
+ const res = await fetch(`${baseUrl}/api/db/connections/${connectionId}/query`, {
209
+ method: "POST",
210
+ headers: { "Content-Type": "application/json" },
211
+ body: JSON.stringify({ sql }),
212
+ });
213
+ const json = await res.json() as { ok: boolean; data?: { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: string } };
214
+ const duration = Date.now() - start;
215
+
216
+ if (!json.ok) {
217
+ await panel.webview.postMessage({ type: "queryError", error: (json as any).error ?? "Query failed" });
218
+ return;
219
+ }
220
+
221
+ await panel.webview.postMessage({
222
+ type: "queryResult",
223
+ columns: json.data?.columns ?? [],
224
+ rows: json.data?.rows ?? [],
225
+ rowsAffected: json.data?.rowsAffected ?? 0,
226
+ changeType: json.data?.changeType ?? "select",
227
+ duration,
228
+ });
229
+ } catch (e) {
230
+ const errMsg = e instanceof Error ? e.message : String(e);
231
+ await panel.webview.postMessage({ type: "queryError", error: errMsg });
232
+ }
233
+ }
234
+
235
+ /** Update a single cell value */
236
+ async function handleCellUpdate(
237
+ panel: ReturnType<VscodeApi["window"]["createWebviewPanel"]>,
238
+ connectionId: number,
239
+ tableName: string,
240
+ schemaName: string,
241
+ msg: Record<string, unknown>,
242
+ ): Promise<void> {
243
+ try {
244
+ const res = await fetch(`${baseUrl}/api/db/connections/${connectionId}/cell`, {
245
+ method: "PUT",
246
+ headers: { "Content-Type": "application/json" },
247
+ body: JSON.stringify({
248
+ table: tableName,
249
+ schema: schemaName,
250
+ pkColumn: msg.pkColumn,
251
+ pkValue: msg.pkValue,
252
+ column: msg.column,
253
+ value: msg.value,
254
+ }),
255
+ });
256
+ const json = await res.json() as { ok: boolean; error?: string };
257
+ if (json.ok) {
258
+ await panel.webview.postMessage({ type: "cellUpdated" });
259
+ } else {
260
+ await panel.webview.postMessage({ type: "error", message: json.error ?? "Cell update failed" });
261
+ }
262
+ } catch (e) {
263
+ const errMsg = e instanceof Error ? e.message : String(e);
264
+ await panel.webview.postMessage({ type: "error", message: errMsg });
265
+ }
266
+ }
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // Query Panel — standalone SQL editor (opened from status bar)
270
+ // ---------------------------------------------------------------------------
271
+
272
+ function openQueryPanel(
273
+ vscode: VscodeApi,
274
+ context: ExtensionContext,
275
+ connectionId: number,
276
+ connectionName: string,
277
+ tableName?: string,
278
+ ): void {
279
+ const panel = vscode.window.createWebviewPanel(
280
+ "ppm-db.queryPanel",
281
+ `Query: ${connectionName}`,
282
+ vscode.ViewColumn.Active,
283
+ );
284
+
285
+ panel.webview.html = getQueryPanelHtml(connectionName, tableName);
286
+
287
+ const msgDisposable = panel.webview.onDidReceiveMessage(async (raw: unknown) => {
288
+ const msg = raw as { type: string; sql?: string };
289
+ if (msg.type !== "executeQuery" || !msg.sql) return;
290
+ await handleQuery(panel, connectionId, msg.sql);
291
+ });
292
+ context.subscriptions.push(msgDisposable);
293
+ panel.onDidDispose(() => msgDisposable.dispose());
294
+ }
295
+
296
+ // ---------------------------------------------------------------------------
297
+ // Add Connection — collect info via QuickPick + InputBox, then POST to API
298
+ // ---------------------------------------------------------------------------
299
+
300
+ async function addConnection(
301
+ vscode: VscodeApi,
302
+ treeProvider: ConnectionTreeProvider,
303
+ ): Promise<void> {
304
+ // 1. Pick type
305
+ const type = await vscode.window.showQuickPick(["postgres", "sqlite"], {
306
+ placeHolder: "Select database type",
307
+ }) as string | undefined;
308
+ if (!type) return;
309
+
310
+ // 2. Name
311
+ const name = await vscode.window.showInputBox({ prompt: "Connection name", placeHolder: "e.g. Production DB" });
312
+ if (!name) return;
313
+
314
+ // 3. Connection config
315
+ let connectionConfig: Record<string, string>;
316
+ if (type === "sqlite") {
317
+ const path = await vscode.window.showInputBox({ prompt: "SQLite file path", placeHolder: "/path/to/database.db" });
318
+ if (!path) return;
319
+ connectionConfig = { type: "sqlite", path };
320
+ } else {
321
+ const connStr = await vscode.window.showInputBox({
322
+ prompt: "PostgreSQL connection string",
323
+ placeHolder: "postgres://user:pass@host:5432/dbname",
324
+ });
325
+ if (!connStr) return;
326
+ connectionConfig = { type: "postgres", connectionString: connStr };
327
+ }
328
+
329
+ // 4. Create via API
330
+ try {
331
+ const res = await fetch(`${baseUrl}/api/db/connections`, {
332
+ method: "POST",
333
+ headers: { "Content-Type": "application/json" },
334
+ body: JSON.stringify({ type, name, connectionConfig }),
335
+ });
336
+ const json = await res.json() as { ok: boolean; error?: string };
337
+ if (json.ok) {
338
+ await vscode.window.showInformationMessage(`Connection "${name}" created`);
339
+ treeProvider.refresh();
340
+ } else {
341
+ await vscode.window.showErrorMessage(json.error ?? "Failed to create connection");
342
+ }
343
+ } catch (e) {
344
+ await vscode.window.showErrorMessage(`Error: ${e instanceof Error ? e.message : String(e)}`);
345
+ }
346
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * WebviewPanel for SQL query editor + results display.
3
+ * Communicates with the extension via postMessage.
4
+ */
5
+
6
+ function escHtml(s: string): string {
7
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
8
+ }
9
+
10
+ /** Create the HTML content for the query webview panel */
11
+ export function getQueryPanelHtml(connectionName: string, tableName?: string): string {
12
+ const initialQuery = tableName ? `SELECT * FROM ${escHtml(tableName)} LIMIT 100;` : "";
13
+ return `<!DOCTYPE html>
14
+ <html>
15
+ <head>
16
+ <meta charset="utf-8">
17
+ <style>
18
+ * { box-sizing: border-box; margin: 0; padding: 0; }
19
+ body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; background: #1e1e2e; color: #cdd6f4; padding: 12px; font-size: 13px; }
20
+ .header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
21
+ .header h3 { font-size: 14px; font-weight: 600; }
22
+ .header .badge { background: #313244; padding: 2px 8px; border-radius: 4px; font-size: 11px; color: #a6adc8; }
23
+ textarea { width: 100%; height: 80px; background: #313244; border: 1px solid #45475a; border-radius: 6px; color: #cdd6f4; padding: 8px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; resize: vertical; }
24
+ textarea:focus { outline: none; border-color: #89b4fa; }
25
+ .actions { display: flex; gap: 8px; margin: 8px 0; }
26
+ button { background: #89b4fa; color: #1e1e2e; border: none; padding: 6px 16px; border-radius: 4px; font-size: 12px; font-weight: 600; cursor: pointer; }
27
+ button:hover { background: #74c7ec; }
28
+ button.secondary { background: #313244; color: #cdd6f4; }
29
+ .status { font-size: 11px; color: #a6adc8; margin: 4px 0; }
30
+ .error { color: #f38ba8; background: #45475a; padding: 8px; border-radius: 4px; margin: 8px 0; font-size: 12px; }
31
+ table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12px; }
32
+ th { background: #313244; text-align: left; padding: 6px 8px; border-bottom: 1px solid #45475a; font-weight: 600; position: sticky; top: 0; }
33
+ td { padding: 4px 8px; border-bottom: 1px solid #313244; max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
34
+ tr:hover td { background: #313244; }
35
+ .results { max-height: 60vh; overflow: auto; border: 1px solid #45475a; border-radius: 6px; }
36
+ .empty { text-align: center; padding: 24px; color: #6c7086; }
37
+ </style>
38
+ </head>
39
+ <body>
40
+ <div class="header">
41
+ <h3>Query</h3>
42
+ <span class="badge">${escHtml(connectionName)}</span>
43
+ </div>
44
+ <textarea id="sql" placeholder="Enter SQL query...">${initialQuery}</textarea>
45
+ <div class="actions">
46
+ <button id="run">Run Query</button>
47
+ <button class="secondary" id="clear">Clear</button>
48
+ </div>
49
+ <div id="status" class="status"></div>
50
+ <div id="error" style="display:none"></div>
51
+ <div id="results"></div>
52
+ <script>
53
+ const vscode = acquireVsCodeApi();
54
+ const sqlEl = document.getElementById('sql');
55
+ const statusEl = document.getElementById('status');
56
+ const errorEl = document.getElementById('error');
57
+ const resultsEl = document.getElementById('results');
58
+
59
+ document.getElementById('run').addEventListener('click', () => {
60
+ const sql = sqlEl.value.trim();
61
+ if (!sql) return;
62
+ statusEl.textContent = 'Running...';
63
+ errorEl.style.display = 'none';
64
+ resultsEl.innerHTML = '';
65
+ vscode.postMessage({ type: 'executeQuery', sql });
66
+ });
67
+
68
+ document.getElementById('clear').addEventListener('click', () => {
69
+ sqlEl.value = '';
70
+ statusEl.textContent = '';
71
+ errorEl.style.display = 'none';
72
+ resultsEl.innerHTML = '';
73
+ });
74
+
75
+ sqlEl.addEventListener('keydown', (e) => {
76
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
77
+ document.getElementById('run').click();
78
+ }
79
+ });
80
+
81
+ window.addEventListener('message', (event) => {
82
+ const msg = event.data;
83
+ if (msg.type === 'queryResult') {
84
+ if (msg.changeType === 'modify') {
85
+ statusEl.textContent = (msg.rowsAffected ?? 0) + ' row(s) affected' + (msg.duration ? ' in ' + msg.duration + 'ms' : '');
86
+ resultsEl.innerHTML = '';
87
+ return;
88
+ }
89
+ const rows = msg.rows || [];
90
+ statusEl.textContent = rows.length + ' row(s) returned' + (msg.duration ? ' in ' + msg.duration + 'ms' : '');
91
+ if (rows.length === 0) {
92
+ resultsEl.innerHTML = '<div class="empty">No results</div>';
93
+ return;
94
+ }
95
+ function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
96
+ const cols = msg.columns || Object.keys(rows[0]);
97
+ let html = '<div class="results"><table><thead><tr>';
98
+ cols.forEach(c => { html += '<th>' + esc(c) + '</th>'; });
99
+ html += '</tr></thead><tbody>';
100
+ rows.forEach(row => {
101
+ html += '<tr>';
102
+ cols.forEach(c => {
103
+ const v = row[c];
104
+ html += '<td>' + (v === null ? '<span style="color:#6c7086">NULL</span>' : esc(v)) + '</td>';
105
+ });
106
+ html += '</tr>';
107
+ });
108
+ html += '</tbody></table></div>';
109
+ resultsEl.innerHTML = html;
110
+ } else if (msg.type === 'queryError') {
111
+ statusEl.textContent = '';
112
+ errorEl.textContent = msg.error;
113
+ errorEl.style.display = 'block';
114
+ errorEl.className = 'error';
115
+ }
116
+ });
117
+ </script>
118
+ </body>
119
+ </html>`;
120
+ }