agentuity-vscode 0.0.86

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.
@@ -0,0 +1,362 @@
1
+ import * as vscode from 'vscode';
2
+ import { DataTreeDataProvider, DataTreeItem } from './dataTreeData';
3
+ import { onAuthStatusChanged } from '../../core/auth';
4
+ import { onProjectChanged } from '../../core/project';
5
+ import { getCliClient } from '../../core/cliClient';
6
+ import { openReadonlyDocument } from '../../core/readonlyDocument';
7
+
8
+ export function registerDataExplorer(context: vscode.ExtensionContext): DataTreeDataProvider {
9
+ const provider = new DataTreeDataProvider();
10
+
11
+ const treeView = vscode.window.createTreeView('agentuity.data', {
12
+ treeDataProvider: provider,
13
+ showCollapseAll: true,
14
+ });
15
+
16
+ treeView.onDidChangeSelection(async (e) => {
17
+ if (e.selection.length === 0) return;
18
+ const item = e.selection[0];
19
+
20
+ if (item.itemType === 'key' && item.parentName) {
21
+ await openDataValue(item);
22
+ } else if (item.itemType === 'database') {
23
+ await copyDatabaseConnectionString(item);
24
+ } else if (item.itemType === 'vectorResult' && item.parentName) {
25
+ await openVectorDocument(item);
26
+ } else if (item.itemType === 'storageFile' && item.parentName) {
27
+ await openStorageFile(item);
28
+ } else if (item.itemType === 'streamItem' && item.streamInfo) {
29
+ await openStreamDetails(item);
30
+ }
31
+ });
32
+
33
+ const authSub = onAuthStatusChanged(() => {
34
+ provider.refresh();
35
+ });
36
+
37
+ const projectSub = onProjectChanged(() => {
38
+ provider.refresh();
39
+ });
40
+
41
+ // Database commands
42
+ context.subscriptions.push(
43
+ vscode.commands.registerCommand(
44
+ 'agentuity.db.copyConnectionString',
45
+ async (item: DataTreeItem) => {
46
+ if (item?.itemType === 'database') {
47
+ await copyDatabaseConnectionString(item);
48
+ }
49
+ }
50
+ )
51
+ );
52
+
53
+ context.subscriptions.push(
54
+ vscode.commands.registerCommand(
55
+ 'agentuity.db.openConnectionUri',
56
+ async (item: DataTreeItem) => {
57
+ if (item?.itemType !== 'database') return;
58
+
59
+ const cli = getCliClient();
60
+ const name = String(item.label);
61
+ const result = await cli.getDatabase(name);
62
+
63
+ if (!result.success || !result.data) {
64
+ vscode.window.showErrorMessage(
65
+ `Failed to get database "${name}": ${result.error ?? 'Unknown error'}`
66
+ );
67
+ return;
68
+ }
69
+
70
+ try {
71
+ await vscode.env.openExternal(vscode.Uri.parse(result.data.url));
72
+ } catch {
73
+ vscode.window.showErrorMessage(`Could not open URI: ${result.data.url}`);
74
+ }
75
+ }
76
+ )
77
+ );
78
+
79
+ context.subscriptions.push(
80
+ vscode.commands.registerCommand('agentuity.db.viewLogs', async (item: DataTreeItem) => {
81
+ if (item?.itemType !== 'database') return;
82
+
83
+ const cli = getCliClient();
84
+ const name = String(item.label);
85
+
86
+ await vscode.window.withProgress(
87
+ {
88
+ location: vscode.ProgressLocation.Notification,
89
+ title: `Fetching database logs for "${name}"...`,
90
+ cancellable: false,
91
+ },
92
+ async () => {
93
+ const result = await cli.getDbLogs(name, { limit: 100 });
94
+
95
+ if (!result.success || !result.data) {
96
+ vscode.window.showErrorMessage(
97
+ `Failed to fetch database logs: ${result.error ?? 'Unknown error'}`
98
+ );
99
+ return;
100
+ }
101
+
102
+ if (result.data.length === 0) {
103
+ vscode.window.showInformationMessage('No logs found for this database');
104
+ return;
105
+ }
106
+
107
+ const logContent = result.data
108
+ .map((log) => {
109
+ const timestamp = new Date(log.timestamp).toLocaleString();
110
+ const duration = `${log.duration}ms`;
111
+ const sql =
112
+ log.sql.length > 200 ? log.sql.substring(0, 200) + '...' : log.sql;
113
+ const errorLine = log.error ? `\n ERROR: ${log.error}` : '';
114
+ return `[${timestamp}] [${log.command}] (${duration})\n ${sql}${errorLine}`;
115
+ })
116
+ .join('\n\n');
117
+
118
+ await openReadonlyDocument(logContent, 'log', `db-logs-${name}`);
119
+ }
120
+ );
121
+ })
122
+ );
123
+
124
+ // Vector commands
125
+ context.subscriptions.push(
126
+ vscode.commands.registerCommand('agentuity.vector.search', async () => {
127
+ const cli = getCliClient();
128
+
129
+ const namespace = await vscode.window.showInputBox({
130
+ prompt: 'Vector namespace',
131
+ placeHolder: 'e.g., default, products, knowledge-base',
132
+ ignoreFocusOut: true,
133
+ });
134
+ if (!namespace) return;
135
+
136
+ const query = await vscode.window.showInputBox({
137
+ prompt: 'Search query (text to find similar vectors)',
138
+ placeHolder: 'Enter search text...',
139
+ ignoreFocusOut: true,
140
+ });
141
+ if (!query) return;
142
+
143
+ const result = await cli.vectorSearch(namespace, query);
144
+ if (!result.success || !result.data) {
145
+ vscode.window.showErrorMessage(
146
+ `Vector search failed: ${result.error ?? 'Unknown error'}`
147
+ );
148
+ return;
149
+ }
150
+
151
+ provider.addVectorSearchGroup({
152
+ id: `${namespace}:${Date.now()}`,
153
+ label: `"${query}" in ${namespace}`,
154
+ namespace,
155
+ query,
156
+ results: result.data.results ?? [],
157
+ });
158
+
159
+ vscode.window.showInformationMessage(
160
+ `Found ${result.data.count} result${result.data.count !== 1 ? 's' : ''}`
161
+ );
162
+ })
163
+ );
164
+
165
+ context.subscriptions.push(
166
+ vscode.commands.registerCommand('agentuity.vector.clearSearches', () => {
167
+ provider.clearVectorSearchGroups();
168
+ vscode.window.showInformationMessage('Cleared vector search results');
169
+ })
170
+ );
171
+
172
+ context.subscriptions.push(treeView, authSub, projectSub, { dispose: () => provider.dispose() });
173
+
174
+ return provider;
175
+ }
176
+
177
+ async function copyDatabaseConnectionString(item: DataTreeItem): Promise<void> {
178
+ const cli = getCliClient();
179
+ const name = String(item.label);
180
+ const result = await cli.getDatabase(name);
181
+
182
+ if (!result.success || !result.data) {
183
+ vscode.window.showErrorMessage(
184
+ `Failed to get database "${name}": ${result.error ?? 'Unknown error'}`
185
+ );
186
+ return;
187
+ }
188
+
189
+ await vscode.env.clipboard.writeText(result.data.url);
190
+ vscode.window.showInformationMessage(`Copied connection string for "${name}" to clipboard`);
191
+ }
192
+
193
+ async function openVectorDocument(item: DataTreeItem): Promise<void> {
194
+ const cli = getCliClient();
195
+ const key = item.label as string;
196
+ const namespace = item.parentName!;
197
+
198
+ const result = await cli.getVector(namespace, key);
199
+ if (!result.success || !result.data) {
200
+ vscode.window.showErrorMessage(`Failed to get vector: ${result.error}`);
201
+ return;
202
+ }
203
+
204
+ if (!result.data.exists) {
205
+ vscode.window.showWarningMessage(`Vector "${key}" does not exist`);
206
+ return;
207
+ }
208
+
209
+ // Build content with metadata at top, then document
210
+ const lines: string[] = [];
211
+
212
+ // Add metadata section
213
+ lines.push('=== Metadata ===');
214
+ lines.push(`Key: ${result.data.key}`);
215
+ lines.push(`ID: ${result.data.id}`);
216
+ if (result.data.metadata && Object.keys(result.data.metadata).length > 0) {
217
+ lines.push(`Metadata: ${JSON.stringify(result.data.metadata, null, 2)}`);
218
+ }
219
+
220
+ lines.push('');
221
+ lines.push('=== Document ===');
222
+ lines.push('');
223
+ lines.push(result.data.document);
224
+
225
+ await openReadonlyDocument(lines.join('\n'), 'plaintext', `vector-${key}`);
226
+ }
227
+
228
+ async function openDataValue(item: DataTreeItem): Promise<void> {
229
+ const cli = getCliClient();
230
+ const key = item.label as string;
231
+ const namespace = item.parentName!;
232
+
233
+ const result = await cli.getKvValue(namespace, key);
234
+ if (result.success && result.data) {
235
+ if (!result.data.exists) {
236
+ vscode.window.showWarningMessage(`Key "${key}" does not exist`);
237
+ return;
238
+ }
239
+ await openContent(result.data.data, result.data.contentType);
240
+ } else {
241
+ vscode.window.showErrorMessage(`Failed to get value: ${result.error}`);
242
+ }
243
+ }
244
+
245
+ async function openStorageFile(item: DataTreeItem): Promise<void> {
246
+ const cli = getCliClient();
247
+ const filename = item.label as string;
248
+ const bucket = item.parentName!;
249
+
250
+ const result = await cli.getStorageFileMetadata(bucket, filename);
251
+ if (!result.success || !result.data) {
252
+ vscode.window.showErrorMessage(`Failed to get file metadata: ${result.error}`);
253
+ return;
254
+ }
255
+
256
+ const lines: string[] = [];
257
+ lines.push('=== Storage File Metadata ===');
258
+ lines.push(`Bucket: ${result.data.bucket}`);
259
+ lines.push(`Filename: ${result.data.filename}`);
260
+ if (result.data.size !== undefined) {
261
+ lines.push(`Size: ${formatFileSize(result.data.size)}`);
262
+ }
263
+ if (result.data.contentType) {
264
+ lines.push(`Content-Type: ${result.data.contentType}`);
265
+ }
266
+ if (result.data.lastModified) {
267
+ lines.push(`Last Modified: ${result.data.lastModified}`);
268
+ }
269
+
270
+ await openReadonlyDocument(lines.join('\n'), 'plaintext', `storage-${filename}`);
271
+ }
272
+
273
+ async function openStreamDetails(item: DataTreeItem): Promise<void> {
274
+ const stream = item.streamInfo!;
275
+
276
+ const content = JSON.stringify(
277
+ {
278
+ id: stream.id,
279
+ name: stream.name,
280
+ url: stream.url,
281
+ sizeBytes: stream.sizeBytes,
282
+ metadata: stream.metadata,
283
+ },
284
+ null,
285
+ 2
286
+ );
287
+
288
+ await openReadonlyDocument(content, 'json', `stream-${stream.name}`);
289
+ }
290
+
291
+ function formatFileSize(bytes: number): string {
292
+ if (bytes < 1024) return `${bytes} B`;
293
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
294
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
295
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
296
+ }
297
+
298
+ function getLanguageFromContentType(contentType: string): string {
299
+ const typeMap: Record<string, string> = {
300
+ 'application/json': 'json',
301
+ 'text/html': 'html',
302
+ 'text/xml': 'xml',
303
+ 'application/xml': 'xml',
304
+ 'text/yaml': 'yaml',
305
+ 'application/x-yaml': 'yaml',
306
+ 'text/markdown': 'markdown',
307
+ 'text/css': 'css',
308
+ 'text/javascript': 'javascript',
309
+ 'application/javascript': 'javascript',
310
+ };
311
+
312
+ for (const [type, lang] of Object.entries(typeMap)) {
313
+ if (contentType.startsWith(type)) {
314
+ return lang;
315
+ }
316
+ }
317
+
318
+ if (contentType.startsWith('text/')) {
319
+ return 'plaintext';
320
+ }
321
+
322
+ return 'plaintext';
323
+ }
324
+
325
+ async function openContent(data: unknown, contentType: string): Promise<void> {
326
+ const language = getLanguageFromContentType(contentType);
327
+ let content: string;
328
+
329
+ if (typeof data === 'string') {
330
+ content = data;
331
+ } else if (isRawByteObject(data)) {
332
+ // CLI returns binary data as {0: byte, 1: byte, ...} object
333
+ content = bytesToString(data as Record<string, number>);
334
+ } else {
335
+ content = JSON.stringify(data, null, 2);
336
+ }
337
+
338
+ // Format JSON nicely
339
+ if (language === 'json' && typeof data !== 'string') {
340
+ content = JSON.stringify(data, null, 2);
341
+ }
342
+
343
+ await openReadonlyDocument(content, language, 'kv-value');
344
+ }
345
+
346
+ function isRawByteObject(data: unknown): boolean {
347
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) {
348
+ return false;
349
+ }
350
+ const keys = Object.keys(data);
351
+ if (keys.length === 0) return false;
352
+ // Check if keys are numeric indices
353
+ return keys.every((k) => /^\d+$/.test(k));
354
+ }
355
+
356
+ function bytesToString(data: Record<string, number>): string {
357
+ const indices = Object.keys(data)
358
+ .map(Number)
359
+ .sort((a, b) => a - b);
360
+ const bytes = indices.map((i) => data[String(i)]);
361
+ return String.fromCharCode(...bytes);
362
+ }
@@ -0,0 +1,238 @@
1
+ import * as vscode from 'vscode';
2
+ import { getCliClient, type Deployment } from '../../core/cliClient';
3
+ import { getAuthStatus } from '../../core/auth';
4
+ import { hasProject } from '../../core/project';
5
+
6
+ export type DeploymentItemType = 'deployment' | 'info' | 'message';
7
+
8
+ export class DeploymentTreeItem extends vscode.TreeItem {
9
+ constructor(
10
+ public readonly label: string,
11
+ public readonly collapsibleState: vscode.TreeItemCollapsibleState,
12
+ public readonly itemType: DeploymentItemType,
13
+ public readonly deploymentData?: Deployment
14
+ ) {
15
+ super(label, collapsibleState);
16
+ this.setIcon();
17
+ this.contextValue = itemType;
18
+ }
19
+
20
+ private setIcon(): void {
21
+ switch (this.itemType) {
22
+ case 'deployment':
23
+ if (this.deploymentData?.active) {
24
+ this.iconPath = new vscode.ThemeIcon(
25
+ 'rocket',
26
+ new vscode.ThemeColor('charts.green')
27
+ );
28
+ } else {
29
+ this.iconPath = new vscode.ThemeIcon('history');
30
+ }
31
+ break;
32
+ case 'info':
33
+ case 'message':
34
+ this.iconPath = new vscode.ThemeIcon('info');
35
+ break;
36
+ }
37
+ }
38
+ }
39
+
40
+ export class DeploymentTreeDataProvider implements vscode.TreeDataProvider<DeploymentTreeItem> {
41
+ private _onDidChangeTreeData = new vscode.EventEmitter<
42
+ DeploymentTreeItem | undefined | null | void
43
+ >();
44
+ readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
45
+
46
+ private deployments: Deployment[] = [];
47
+ private loading = false;
48
+ private error: string | undefined;
49
+
50
+ refresh(): void {
51
+ this._onDidChangeTreeData.fire();
52
+ }
53
+
54
+ getTreeItem(element: DeploymentTreeItem): vscode.TreeItem {
55
+ return element;
56
+ }
57
+
58
+ async getChildren(element?: DeploymentTreeItem): Promise<DeploymentTreeItem[]> {
59
+ if (element) {
60
+ if (element.itemType === 'deployment' && element.deploymentData) {
61
+ return this.getDeploymentDetails(element.deploymentData);
62
+ }
63
+ return [];
64
+ }
65
+
66
+ const authStatus = getAuthStatus();
67
+ if (authStatus.state === 'unknown') {
68
+ return [
69
+ new DeploymentTreeItem(
70
+ 'Checking auth...',
71
+ vscode.TreeItemCollapsibleState.None,
72
+ 'message'
73
+ ),
74
+ ];
75
+ }
76
+
77
+ if (authStatus.state === 'cli-missing') {
78
+ return [
79
+ new DeploymentTreeItem(
80
+ 'CLI not installed',
81
+ vscode.TreeItemCollapsibleState.None,
82
+ 'message'
83
+ ),
84
+ ];
85
+ }
86
+
87
+ if (authStatus.state === 'unauthenticated') {
88
+ return [
89
+ new DeploymentTreeItem(
90
+ 'Not logged in',
91
+ vscode.TreeItemCollapsibleState.None,
92
+ 'message'
93
+ ),
94
+ ];
95
+ }
96
+
97
+ if (!hasProject()) {
98
+ return [
99
+ new DeploymentTreeItem(
100
+ 'No project detected',
101
+ vscode.TreeItemCollapsibleState.None,
102
+ 'message'
103
+ ),
104
+ ];
105
+ }
106
+
107
+ if (this.loading) {
108
+ return [
109
+ new DeploymentTreeItem('Loading...', vscode.TreeItemCollapsibleState.None, 'message'),
110
+ ];
111
+ }
112
+
113
+ if (this.error) {
114
+ return [
115
+ new DeploymentTreeItem(
116
+ `Error: ${this.error}`,
117
+ vscode.TreeItemCollapsibleState.None,
118
+ 'message'
119
+ ),
120
+ ];
121
+ }
122
+
123
+ if (this.deployments.length === 0) {
124
+ await this.loadDeployments();
125
+ }
126
+
127
+ if (this.deployments.length === 0) {
128
+ return [
129
+ new DeploymentTreeItem(
130
+ 'No deployments found',
131
+ vscode.TreeItemCollapsibleState.None,
132
+ 'message'
133
+ ),
134
+ ];
135
+ }
136
+
137
+ return this.deployments.map((dep) => {
138
+ const label = dep.active ? `${dep.id} (active)` : dep.id;
139
+ const item = new DeploymentTreeItem(
140
+ label,
141
+ vscode.TreeItemCollapsibleState.Collapsed,
142
+ 'deployment',
143
+ dep
144
+ );
145
+ item.description = dep.state || '';
146
+ item.tooltip = this.formatTooltip(dep);
147
+ return item;
148
+ });
149
+ }
150
+
151
+ private getDeploymentDetails(dep: Deployment): DeploymentTreeItem[] {
152
+ const items: DeploymentTreeItem[] = [];
153
+
154
+ items.push(
155
+ new DeploymentTreeItem(
156
+ `State: ${dep.state || 'unknown'}`,
157
+ vscode.TreeItemCollapsibleState.None,
158
+ 'info'
159
+ )
160
+ );
161
+ items.push(
162
+ new DeploymentTreeItem(
163
+ `Created: ${new Date(dep.createdAt).toLocaleString()}`,
164
+ vscode.TreeItemCollapsibleState.None,
165
+ 'info'
166
+ )
167
+ );
168
+ if (dep.message) {
169
+ items.push(
170
+ new DeploymentTreeItem(
171
+ `Message: ${dep.message}`,
172
+ vscode.TreeItemCollapsibleState.None,
173
+ 'info'
174
+ )
175
+ );
176
+ }
177
+ if (dep.tags.length > 0) {
178
+ items.push(
179
+ new DeploymentTreeItem(
180
+ `Tags: ${dep.tags.join(', ')}`,
181
+ vscode.TreeItemCollapsibleState.None,
182
+ 'info'
183
+ )
184
+ );
185
+ }
186
+
187
+ return items;
188
+ }
189
+
190
+ private formatTooltip(dep: Deployment): string {
191
+ const lines = [
192
+ `ID: ${dep.id}`,
193
+ `State: ${dep.state || 'unknown'}`,
194
+ `Active: ${dep.active ? 'Yes' : 'No'}`,
195
+ `Created: ${new Date(dep.createdAt).toLocaleString()}`,
196
+ ];
197
+ if (dep.message) {
198
+ lines.push(`Message: ${dep.message}`);
199
+ }
200
+ if (dep.tags.length > 0) {
201
+ lines.push(`Tags: ${dep.tags.join(', ')}`);
202
+ }
203
+ return lines.join('\n');
204
+ }
205
+
206
+ private async loadDeployments(): Promise<void> {
207
+ this.loading = true;
208
+ this.error = undefined;
209
+
210
+ try {
211
+ const cli = getCliClient();
212
+ const result = await cli.listDeployments(10);
213
+
214
+ if (result.success && result.data) {
215
+ this.deployments = Array.isArray(result.data) ? result.data : [];
216
+ } else {
217
+ this.error = result.error || 'Failed to load deployments';
218
+ this.deployments = [];
219
+ }
220
+ } catch (err) {
221
+ this.error = err instanceof Error ? err.message : 'Unknown error';
222
+ this.deployments = [];
223
+ } finally {
224
+ this.loading = false;
225
+ }
226
+ }
227
+
228
+ async forceRefresh(): Promise<void> {
229
+ this.deployments = [];
230
+ this.error = undefined;
231
+ await this.loadDeployments();
232
+ this.refresh();
233
+ }
234
+
235
+ dispose(): void {
236
+ this._onDidChangeTreeData.dispose();
237
+ }
238
+ }
@@ -0,0 +1,107 @@
1
+ import * as vscode from 'vscode';
2
+ import { DeploymentTreeDataProvider, DeploymentTreeItem } from './deploymentTreeData';
3
+ import { onAuthStatusChanged } from '../../core/auth';
4
+ import { onProjectChanged } from '../../core/project';
5
+ import { getCliClient } from '../../core/cliClient';
6
+ import { openReadonlyDocument } from '../../core/readonlyDocument';
7
+
8
+ export function registerDeploymentExplorer(
9
+ context: vscode.ExtensionContext
10
+ ): DeploymentTreeDataProvider {
11
+ const provider = new DeploymentTreeDataProvider();
12
+
13
+ const treeView = vscode.window.createTreeView('agentuity.deployments', {
14
+ treeDataProvider: provider,
15
+ showCollapseAll: true,
16
+ });
17
+
18
+ treeView.onDidChangeSelection(async (e) => {
19
+ if (e.selection.length === 0) return;
20
+ const item = e.selection[0];
21
+
22
+ if (item.itemType === 'deployment' && item.deploymentData) {
23
+ await showDeploymentDetails(item.deploymentData.id);
24
+ }
25
+ });
26
+
27
+ const authSub = onAuthStatusChanged(() => {
28
+ provider.refresh();
29
+ });
30
+
31
+ const projectSub = onProjectChanged(() => {
32
+ void provider.forceRefresh();
33
+ });
34
+
35
+ context.subscriptions.push(
36
+ vscode.commands.registerCommand(
37
+ 'agentuity.deployment.viewLogs',
38
+ async (item: DeploymentTreeItem) => {
39
+ if (item?.deploymentData) {
40
+ await viewDeploymentLogs(item.deploymentData.id);
41
+ }
42
+ }
43
+ )
44
+ );
45
+
46
+ context.subscriptions.push(
47
+ vscode.commands.registerCommand(
48
+ 'agentuity.deployment.showDetails',
49
+ async (item: DeploymentTreeItem) => {
50
+ if (item?.deploymentData) {
51
+ await showDeploymentDetails(item.deploymentData.id);
52
+ }
53
+ }
54
+ )
55
+ );
56
+
57
+ context.subscriptions.push(treeView, authSub, projectSub, { dispose: () => provider.dispose() });
58
+
59
+ return provider;
60
+ }
61
+
62
+ async function showDeploymentDetails(deploymentId: string): Promise<void> {
63
+ const cli = getCliClient();
64
+ const result = await cli.getDeployment(deploymentId);
65
+
66
+ if (result.success && result.data) {
67
+ const content = JSON.stringify(result.data, null, 2);
68
+ await openReadonlyDocument(content, 'json', `deployment-${deploymentId.substring(0, 8)}`);
69
+ } else {
70
+ vscode.window.showErrorMessage(`Failed to get deployment details: ${result.error}`);
71
+ }
72
+ }
73
+
74
+ async function viewDeploymentLogs(deploymentId: string): Promise<void> {
75
+ const cli = getCliClient();
76
+
77
+ await vscode.window.withProgress(
78
+ {
79
+ location: vscode.ProgressLocation.Notification,
80
+ title: 'Fetching deployment logs...',
81
+ cancellable: false,
82
+ },
83
+ async () => {
84
+ const result = await cli.getDeploymentLogs(deploymentId, 100);
85
+
86
+ if (result.success && result.data) {
87
+ if (result.data.length === 0) {
88
+ vscode.window.showInformationMessage('No logs found for this deployment');
89
+ return;
90
+ }
91
+
92
+ const logContent = result.data
93
+ .map((log) => {
94
+ const timestamp = new Date(log.timestamp).toLocaleString();
95
+ return `[${timestamp}] [${log.severity}] ${log.body}`;
96
+ })
97
+ .join('\n');
98
+
99
+ await openReadonlyDocument(logContent, 'log', `deployment-logs-${deploymentId.substring(0, 8)}`);
100
+ } else {
101
+ vscode.window.showErrorMessage(`Failed to fetch logs: ${result.error}`);
102
+ }
103
+ }
104
+ );
105
+ }
106
+
107
+ export { DeploymentTreeDataProvider };