@troykelly/openclaw-projects 0.0.29 → 0.0.30

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,631 @@
1
+ /**
2
+ * Terminal connection and credential management tools.
3
+ * Provides tools for managing SSH connections and credentials for OpenClaw agents.
4
+ */
5
+ import { z } from 'zod';
6
+ import { sanitizeErrorMessage } from '../utils/sanitize.js';
7
+ /** UUID validation regex */
8
+ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
9
+ /**
10
+ * Validate UUID format.
11
+ */
12
+ function isValidUuid(id) {
13
+ return UUID_REGEX.test(id);
14
+ }
15
+ /**
16
+ * Strip HTML tags from a string.
17
+ */
18
+ function stripHtml(text) {
19
+ return text
20
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
21
+ .replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
22
+ .replace(/<[^>]*>/g, '')
23
+ .trim();
24
+ }
25
+ // ==================== terminal_connection_list ====================
26
+ /** Parameters for terminal_connection_list */
27
+ export const TerminalConnectionListParamsSchema = z.object({
28
+ tags: z.string().max(500, 'Tags must be 500 characters or less').optional(),
29
+ search: z.string().max(200, 'Search must be 200 characters or less').optional(),
30
+ is_local: z.boolean().optional(),
31
+ });
32
+ /**
33
+ * Creates the terminal_connection_list tool.
34
+ */
35
+ export function createTerminalConnectionListTool(options) {
36
+ const { client, logger, user_id } = options;
37
+ return {
38
+ name: 'terminal_connection_list',
39
+ description: 'List saved terminal connections. Optionally filter by tags, search term, or local/remote.',
40
+ parameters: TerminalConnectionListParamsSchema,
41
+ async execute(params) {
42
+ const parseResult = TerminalConnectionListParamsSchema.safeParse(params);
43
+ if (!parseResult.success) {
44
+ const errorMessage = parseResult.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
45
+ return { success: false, error: errorMessage };
46
+ }
47
+ const { tags, search, is_local } = parseResult.data;
48
+ logger.info('terminal_connection_list invoked', { user_id, hasTags: !!tags, hasSearch: !!search, is_local });
49
+ try {
50
+ const queryParams = new URLSearchParams();
51
+ if (tags)
52
+ queryParams.set('tags', tags);
53
+ if (search)
54
+ queryParams.set('search', search);
55
+ if (is_local !== undefined)
56
+ queryParams.set('is_local', String(is_local));
57
+ const queryString = queryParams.toString();
58
+ const path = `/api/terminal/connections${queryString ? `?${queryString}` : ''}`;
59
+ const response = await client.get(path, { user_id });
60
+ if (!response.success) {
61
+ logger.error('terminal_connection_list API error', {
62
+ user_id,
63
+ status: response.error.status,
64
+ code: response.error.code,
65
+ });
66
+ return { success: false, error: response.error.message || 'Failed to list connections' };
67
+ }
68
+ const connections = response.data.connections ?? response.data.items ?? [];
69
+ const total = response.data.total ?? connections.length;
70
+ if (connections.length === 0) {
71
+ return {
72
+ success: true,
73
+ data: {
74
+ content: 'No terminal connections found.',
75
+ details: { connections: [], total: 0, user_id },
76
+ },
77
+ };
78
+ }
79
+ const content = connections
80
+ .map((c) => {
81
+ const parts = [c.name];
82
+ if (c.host)
83
+ parts.push(`(${c.username ? `${c.username}@` : ''}${c.host}${c.port && c.port !== 22 ? `:${c.port}` : ''})`);
84
+ if (c.is_local)
85
+ parts.push('[local]');
86
+ if (c.tags && c.tags.length > 0)
87
+ parts.push(`[${c.tags.join(', ')}]`);
88
+ return `- ${parts.join(' ')}`;
89
+ })
90
+ .join('\n');
91
+ logger.debug('terminal_connection_list completed', { user_id, count: connections.length });
92
+ return {
93
+ success: true,
94
+ data: {
95
+ content,
96
+ details: { connections, total, user_id },
97
+ },
98
+ };
99
+ }
100
+ catch (error) {
101
+ logger.error('terminal_connection_list failed', {
102
+ user_id,
103
+ error: error instanceof Error ? error.message : String(error),
104
+ });
105
+ return { success: false, error: sanitizeErrorMessage(error) };
106
+ }
107
+ },
108
+ };
109
+ }
110
+ // ==================== terminal_connection_create ====================
111
+ /** Auth method enum */
112
+ export const TerminalAuthMethod = z.enum(['key', 'password', 'agent', 'command']);
113
+ /** Parameters for terminal_connection_create */
114
+ export const TerminalConnectionCreateParamsSchema = z.object({
115
+ name: z.string().min(1, 'Connection name is required').max(200, 'Name must be 200 characters or less'),
116
+ host: z.string().max(253, 'Host must be 253 characters or less').optional(),
117
+ port: z.number().int().min(1).max(65535).optional(),
118
+ username: z.string().max(100, 'Username must be 100 characters or less').optional(),
119
+ auth_method: TerminalAuthMethod.optional(),
120
+ credential_id: z.string().optional(),
121
+ is_local: z.boolean().optional(),
122
+ tags: z.string().max(500, 'Tags must be 500 characters or less').optional(),
123
+ notes: z.string().max(2000, 'Notes must be 2000 characters or less').optional(),
124
+ });
125
+ /**
126
+ * Creates the terminal_connection_create tool.
127
+ */
128
+ export function createTerminalConnectionCreateTool(options) {
129
+ const { client, logger, user_id } = options;
130
+ return {
131
+ name: 'terminal_connection_create',
132
+ description: 'Create a new terminal connection definition. Specify host/port/username for SSH connections, or set is_local=true for local tmux.',
133
+ parameters: TerminalConnectionCreateParamsSchema,
134
+ async execute(params) {
135
+ const parseResult = TerminalConnectionCreateParamsSchema.safeParse(params);
136
+ if (!parseResult.success) {
137
+ const errorMessage = parseResult.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
138
+ return { success: false, error: errorMessage };
139
+ }
140
+ const { name, host, port, username, auth_method, credential_id, is_local, tags, notes } = parseResult.data;
141
+ const sanitizedName = stripHtml(name);
142
+ if (sanitizedName.length === 0) {
143
+ return { success: false, error: 'Connection name cannot be empty after sanitization' };
144
+ }
145
+ if (credential_id && !isValidUuid(credential_id)) {
146
+ return { success: false, error: 'Invalid credential_id format. Expected UUID.' };
147
+ }
148
+ logger.info('terminal_connection_create invoked', {
149
+ user_id,
150
+ nameLength: sanitizedName.length,
151
+ hasHost: !!host,
152
+ is_local: is_local ?? false,
153
+ });
154
+ try {
155
+ const body = { name: sanitizedName };
156
+ if (host)
157
+ body.host = host;
158
+ if (port !== undefined)
159
+ body.port = port;
160
+ if (username)
161
+ body.username = username;
162
+ if (auth_method)
163
+ body.auth_method = auth_method;
164
+ if (credential_id)
165
+ body.credential_id = credential_id;
166
+ if (is_local !== undefined)
167
+ body.is_local = is_local;
168
+ if (tags)
169
+ body.tags = tags.split(',').map((t) => t.trim()).filter(Boolean);
170
+ if (notes)
171
+ body.notes = stripHtml(notes);
172
+ const response = await client.post('/api/terminal/connections', body, { user_id });
173
+ if (!response.success) {
174
+ logger.error('terminal_connection_create API error', {
175
+ user_id,
176
+ status: response.error.status,
177
+ code: response.error.code,
178
+ });
179
+ return { success: false, error: response.error.message || 'Failed to create connection' };
180
+ }
181
+ logger.debug('terminal_connection_create completed', { user_id, connectionId: response.data.id });
182
+ return {
183
+ success: true,
184
+ data: {
185
+ content: `Created connection "${sanitizedName}" (ID: ${response.data.id})`,
186
+ details: { id: response.data.id, name: sanitizedName, user_id },
187
+ },
188
+ };
189
+ }
190
+ catch (error) {
191
+ logger.error('terminal_connection_create failed', {
192
+ user_id,
193
+ error: error instanceof Error ? error.message : String(error),
194
+ });
195
+ return { success: false, error: sanitizeErrorMessage(error) };
196
+ }
197
+ },
198
+ };
199
+ }
200
+ // ==================== terminal_connection_update ====================
201
+ /** Parameters for terminal_connection_update */
202
+ export const TerminalConnectionUpdateParamsSchema = z.object({
203
+ id: z.string().min(1, 'Connection ID is required'),
204
+ name: z.string().min(1).max(200, 'Name must be 200 characters or less').optional(),
205
+ host: z.string().max(253, 'Host must be 253 characters or less').optional(),
206
+ port: z.number().int().min(1).max(65535).optional(),
207
+ username: z.string().max(100, 'Username must be 100 characters or less').optional(),
208
+ auth_method: TerminalAuthMethod.optional(),
209
+ credential_id: z.string().optional(),
210
+ tags: z.string().max(500, 'Tags must be 500 characters or less').optional(),
211
+ notes: z.string().max(2000, 'Notes must be 2000 characters or less').optional(),
212
+ });
213
+ /**
214
+ * Creates the terminal_connection_update tool.
215
+ */
216
+ export function createTerminalConnectionUpdateTool(options) {
217
+ const { client, logger, user_id } = options;
218
+ return {
219
+ name: 'terminal_connection_update',
220
+ description: 'Update an existing terminal connection. Only provided fields will be changed.',
221
+ parameters: TerminalConnectionUpdateParamsSchema,
222
+ async execute(params) {
223
+ const parseResult = TerminalConnectionUpdateParamsSchema.safeParse(params);
224
+ if (!parseResult.success) {
225
+ const errorMessage = parseResult.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
226
+ return { success: false, error: errorMessage };
227
+ }
228
+ const { id, name, host, port, username, auth_method, credential_id, tags, notes } = parseResult.data;
229
+ if (!isValidUuid(id)) {
230
+ return { success: false, error: 'Invalid connection ID format. Expected UUID.' };
231
+ }
232
+ if (credential_id && !isValidUuid(credential_id)) {
233
+ return { success: false, error: 'Invalid credential_id format. Expected UUID.' };
234
+ }
235
+ logger.info('terminal_connection_update invoked', { user_id, connectionId: id });
236
+ try {
237
+ const body = {};
238
+ if (name)
239
+ body.name = stripHtml(name);
240
+ if (host)
241
+ body.host = host;
242
+ if (port !== undefined)
243
+ body.port = port;
244
+ if (username)
245
+ body.username = username;
246
+ if (auth_method)
247
+ body.auth_method = auth_method;
248
+ if (credential_id)
249
+ body.credential_id = credential_id;
250
+ if (tags)
251
+ body.tags = tags.split(',').map((t) => t.trim()).filter(Boolean);
252
+ if (notes)
253
+ body.notes = stripHtml(notes);
254
+ const response = await client.patch(`/api/terminal/connections/${id}`, body, { user_id });
255
+ if (!response.success) {
256
+ if (response.error.code === 'NOT_FOUND') {
257
+ return { success: false, error: 'Connection not found.' };
258
+ }
259
+ logger.error('terminal_connection_update API error', {
260
+ user_id,
261
+ connectionId: id,
262
+ status: response.error.status,
263
+ });
264
+ return { success: false, error: response.error.message || 'Failed to update connection' };
265
+ }
266
+ logger.debug('terminal_connection_update completed', { user_id, connectionId: id });
267
+ return {
268
+ success: true,
269
+ data: {
270
+ content: `Connection ${id} updated.`,
271
+ details: { id, user_id },
272
+ },
273
+ };
274
+ }
275
+ catch (error) {
276
+ logger.error('terminal_connection_update failed', {
277
+ user_id,
278
+ connectionId: id,
279
+ error: error instanceof Error ? error.message : String(error),
280
+ });
281
+ return { success: false, error: sanitizeErrorMessage(error) };
282
+ }
283
+ },
284
+ };
285
+ }
286
+ // ==================== terminal_connection_delete ====================
287
+ /** Parameters for terminal_connection_delete */
288
+ export const TerminalConnectionDeleteParamsSchema = z.object({
289
+ id: z.string().min(1, 'Connection ID is required'),
290
+ });
291
+ /**
292
+ * Creates the terminal_connection_delete tool.
293
+ */
294
+ export function createTerminalConnectionDeleteTool(options) {
295
+ const { client, logger, user_id } = options;
296
+ return {
297
+ name: 'terminal_connection_delete',
298
+ description: 'Soft-delete a terminal connection. The connection will be marked as deleted but retained for history.',
299
+ parameters: TerminalConnectionDeleteParamsSchema,
300
+ async execute(params) {
301
+ const parseResult = TerminalConnectionDeleteParamsSchema.safeParse(params);
302
+ if (!parseResult.success) {
303
+ const errorMessage = parseResult.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
304
+ return { success: false, error: errorMessage };
305
+ }
306
+ const { id } = parseResult.data;
307
+ if (!isValidUuid(id)) {
308
+ return { success: false, error: 'Invalid connection ID format. Expected UUID.' };
309
+ }
310
+ logger.info('terminal_connection_delete invoked', { user_id, connectionId: id });
311
+ try {
312
+ const response = await client.delete(`/api/terminal/connections/${id}`, { user_id });
313
+ if (!response.success) {
314
+ if (response.error.code === 'NOT_FOUND') {
315
+ return { success: false, error: 'Connection not found.' };
316
+ }
317
+ logger.error('terminal_connection_delete API error', {
318
+ user_id,
319
+ connectionId: id,
320
+ status: response.error.status,
321
+ });
322
+ return { success: false, error: response.error.message || 'Failed to delete connection' };
323
+ }
324
+ logger.debug('terminal_connection_delete completed', { user_id, connectionId: id });
325
+ return {
326
+ success: true,
327
+ data: {
328
+ content: `Connection ${id} deleted.`,
329
+ details: { id, user_id },
330
+ },
331
+ };
332
+ }
333
+ catch (error) {
334
+ logger.error('terminal_connection_delete failed', {
335
+ user_id,
336
+ connectionId: id,
337
+ error: error instanceof Error ? error.message : String(error),
338
+ });
339
+ return { success: false, error: sanitizeErrorMessage(error) };
340
+ }
341
+ },
342
+ };
343
+ }
344
+ // ==================== terminal_connection_test ====================
345
+ /** Parameters for terminal_connection_test */
346
+ export const TerminalConnectionTestParamsSchema = z.object({
347
+ id: z.string().min(1, 'Connection ID is required'),
348
+ });
349
+ /**
350
+ * Creates the terminal_connection_test tool.
351
+ */
352
+ export function createTerminalConnectionTestTool(options) {
353
+ const { client, logger, user_id } = options;
354
+ return {
355
+ name: 'terminal_connection_test',
356
+ description: 'Test SSH connectivity to a saved connection. Returns latency and host key fingerprint on success.',
357
+ parameters: TerminalConnectionTestParamsSchema,
358
+ async execute(params) {
359
+ const parseResult = TerminalConnectionTestParamsSchema.safeParse(params);
360
+ if (!parseResult.success) {
361
+ const errorMessage = parseResult.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
362
+ return { success: false, error: errorMessage };
363
+ }
364
+ const { id } = parseResult.data;
365
+ if (!isValidUuid(id)) {
366
+ return { success: false, error: 'Invalid connection ID format. Expected UUID.' };
367
+ }
368
+ logger.info('terminal_connection_test invoked', { user_id, connectionId: id });
369
+ try {
370
+ const response = await client.post(`/api/terminal/connections/${id}/test`, undefined, { user_id });
371
+ if (!response.success) {
372
+ if (response.error.code === 'NOT_FOUND') {
373
+ return { success: false, error: 'Connection not found.' };
374
+ }
375
+ logger.error('terminal_connection_test API error', {
376
+ user_id,
377
+ connectionId: id,
378
+ status: response.error.status,
379
+ });
380
+ return { success: false, error: response.error.message || 'Failed to test connection' };
381
+ }
382
+ const testResult = response.data;
383
+ const content = testResult.success
384
+ ? `Connection test successful. Latency: ${testResult.latency_ms ?? 'N/A'}ms${testResult.host_key_fingerprint ? ` | Fingerprint: ${testResult.host_key_fingerprint}` : ''}`
385
+ : `Connection test failed: ${testResult.error ?? 'Unknown error'}`;
386
+ logger.debug('terminal_connection_test completed', {
387
+ user_id,
388
+ connectionId: id,
389
+ testSuccess: testResult.success,
390
+ });
391
+ return {
392
+ success: true,
393
+ data: {
394
+ content,
395
+ details: {
396
+ id,
397
+ test_success: testResult.success,
398
+ latency_ms: testResult.latency_ms,
399
+ host_key_fingerprint: testResult.host_key_fingerprint,
400
+ error: testResult.error,
401
+ user_id,
402
+ },
403
+ },
404
+ };
405
+ }
406
+ catch (error) {
407
+ logger.error('terminal_connection_test failed', {
408
+ user_id,
409
+ connectionId: id,
410
+ error: error instanceof Error ? error.message : String(error),
411
+ });
412
+ return { success: false, error: sanitizeErrorMessage(error) };
413
+ }
414
+ },
415
+ };
416
+ }
417
+ // ==================== terminal_credential_create ====================
418
+ /** Credential kind enum */
419
+ export const TerminalCredentialKind = z.enum(['ssh_key', 'password', 'command']);
420
+ /** Parameters for terminal_credential_create */
421
+ export const TerminalCredentialCreateParamsSchema = z.object({
422
+ name: z.string().min(1, 'Credential name is required').max(200, 'Name must be 200 characters or less'),
423
+ kind: TerminalCredentialKind,
424
+ private_key: z.string().optional(),
425
+ password: z.string().optional(),
426
+ command: z.string().max(500, 'Command must be 500 characters or less').optional(),
427
+ command_timeout_s: z.number().int().min(1).max(300).optional(),
428
+ });
429
+ /**
430
+ * Creates the terminal_credential_create tool.
431
+ */
432
+ export function createTerminalCredentialCreateTool(options) {
433
+ const { client, logger, user_id } = options;
434
+ return {
435
+ name: 'terminal_credential_create',
436
+ description: 'Create a terminal credential. Upload an SSH key, set a password, or configure a command-based credential provider.',
437
+ parameters: TerminalCredentialCreateParamsSchema,
438
+ async execute(params) {
439
+ const parseResult = TerminalCredentialCreateParamsSchema.safeParse(params);
440
+ if (!parseResult.success) {
441
+ const errorMessage = parseResult.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
442
+ return { success: false, error: errorMessage };
443
+ }
444
+ const { name, kind, private_key, password, command, command_timeout_s } = parseResult.data;
445
+ const sanitizedName = stripHtml(name);
446
+ if (sanitizedName.length === 0) {
447
+ return { success: false, error: 'Credential name cannot be empty after sanitization' };
448
+ }
449
+ logger.info('terminal_credential_create invoked', {
450
+ user_id,
451
+ kind,
452
+ nameLength: sanitizedName.length,
453
+ });
454
+ try {
455
+ const body = { name: sanitizedName, kind };
456
+ if (private_key)
457
+ body.private_key = private_key;
458
+ if (password)
459
+ body.password = password;
460
+ if (command)
461
+ body.command = command;
462
+ if (command_timeout_s !== undefined)
463
+ body.command_timeout_s = command_timeout_s;
464
+ const response = await client.post('/api/terminal/credentials', body, { user_id });
465
+ if (!response.success) {
466
+ logger.error('terminal_credential_create API error', {
467
+ user_id,
468
+ status: response.error.status,
469
+ code: response.error.code,
470
+ });
471
+ return { success: false, error: response.error.message || 'Failed to create credential' };
472
+ }
473
+ logger.debug('terminal_credential_create completed', { user_id, credentialId: response.data.id });
474
+ return {
475
+ success: true,
476
+ data: {
477
+ content: `Created ${kind} credential "${sanitizedName}" (ID: ${response.data.id})${response.data.fingerprint ? ` | Fingerprint: ${response.data.fingerprint}` : ''}`,
478
+ details: {
479
+ id: response.data.id,
480
+ name: sanitizedName,
481
+ kind,
482
+ fingerprint: response.data.fingerprint,
483
+ user_id,
484
+ },
485
+ },
486
+ };
487
+ }
488
+ catch (error) {
489
+ logger.error('terminal_credential_create failed', {
490
+ user_id,
491
+ error: error instanceof Error ? error.message : String(error),
492
+ });
493
+ return { success: false, error: sanitizeErrorMessage(error) };
494
+ }
495
+ },
496
+ };
497
+ }
498
+ // ==================== terminal_credential_list ====================
499
+ /** Parameters for terminal_credential_list */
500
+ export const TerminalCredentialListParamsSchema = z.object({
501
+ kind: TerminalCredentialKind.optional(),
502
+ });
503
+ /**
504
+ * Creates the terminal_credential_list tool.
505
+ */
506
+ export function createTerminalCredentialListTool(options) {
507
+ const { client, logger, user_id } = options;
508
+ return {
509
+ name: 'terminal_credential_list',
510
+ description: 'List terminal credentials (metadata only, no secrets). Optionally filter by kind.',
511
+ parameters: TerminalCredentialListParamsSchema,
512
+ async execute(params) {
513
+ const parseResult = TerminalCredentialListParamsSchema.safeParse(params);
514
+ if (!parseResult.success) {
515
+ const errorMessage = parseResult.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
516
+ return { success: false, error: errorMessage };
517
+ }
518
+ const { kind } = parseResult.data;
519
+ logger.info('terminal_credential_list invoked', { user_id, kind });
520
+ try {
521
+ const queryParams = new URLSearchParams();
522
+ if (kind)
523
+ queryParams.set('kind', kind);
524
+ const queryString = queryParams.toString();
525
+ const path = `/api/terminal/credentials${queryString ? `?${queryString}` : ''}`;
526
+ const response = await client.get(path, { user_id });
527
+ if (!response.success) {
528
+ logger.error('terminal_credential_list API error', {
529
+ user_id,
530
+ status: response.error.status,
531
+ code: response.error.code,
532
+ });
533
+ return { success: false, error: response.error.message || 'Failed to list credentials' };
534
+ }
535
+ const credentials = response.data.credentials ?? response.data.items ?? [];
536
+ const total = response.data.total ?? credentials.length;
537
+ if (credentials.length === 0) {
538
+ return {
539
+ success: true,
540
+ data: {
541
+ content: 'No terminal credentials found.',
542
+ details: { credentials: [], total: 0, user_id },
543
+ },
544
+ };
545
+ }
546
+ const content = credentials
547
+ .map((c) => {
548
+ const parts = [c.name, `(${c.kind})`];
549
+ if (c.fingerprint)
550
+ parts.push(`fingerprint: ${c.fingerprint}`);
551
+ return `- ${parts.join(' ')}`;
552
+ })
553
+ .join('\n');
554
+ logger.debug('terminal_credential_list completed', { user_id, count: credentials.length });
555
+ return {
556
+ success: true,
557
+ data: {
558
+ content,
559
+ details: { credentials, total, user_id },
560
+ },
561
+ };
562
+ }
563
+ catch (error) {
564
+ logger.error('terminal_credential_list failed', {
565
+ user_id,
566
+ error: error instanceof Error ? error.message : String(error),
567
+ });
568
+ return { success: false, error: sanitizeErrorMessage(error) };
569
+ }
570
+ },
571
+ };
572
+ }
573
+ // ==================== terminal_credential_delete ====================
574
+ /** Parameters for terminal_credential_delete */
575
+ export const TerminalCredentialDeleteParamsSchema = z.object({
576
+ id: z.string().min(1, 'Credential ID is required'),
577
+ });
578
+ /**
579
+ * Creates the terminal_credential_delete tool.
580
+ */
581
+ export function createTerminalCredentialDeleteTool(options) {
582
+ const { client, logger, user_id } = options;
583
+ return {
584
+ name: 'terminal_credential_delete',
585
+ description: 'Delete a terminal credential. The credential will be removed and can no longer be used by connections.',
586
+ parameters: TerminalCredentialDeleteParamsSchema,
587
+ async execute(params) {
588
+ const parseResult = TerminalCredentialDeleteParamsSchema.safeParse(params);
589
+ if (!parseResult.success) {
590
+ const errorMessage = parseResult.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
591
+ return { success: false, error: errorMessage };
592
+ }
593
+ const { id } = parseResult.data;
594
+ if (!isValidUuid(id)) {
595
+ return { success: false, error: 'Invalid credential ID format. Expected UUID.' };
596
+ }
597
+ logger.info('terminal_credential_delete invoked', { user_id, credentialId: id });
598
+ try {
599
+ const response = await client.delete(`/api/terminal/credentials/${id}`, { user_id });
600
+ if (!response.success) {
601
+ if (response.error.code === 'NOT_FOUND') {
602
+ return { success: false, error: 'Credential not found.' };
603
+ }
604
+ logger.error('terminal_credential_delete API error', {
605
+ user_id,
606
+ credentialId: id,
607
+ status: response.error.status,
608
+ });
609
+ return { success: false, error: response.error.message || 'Failed to delete credential' };
610
+ }
611
+ logger.debug('terminal_credential_delete completed', { user_id, credentialId: id });
612
+ return {
613
+ success: true,
614
+ data: {
615
+ content: `Credential ${id} deleted.`,
616
+ details: { id, user_id },
617
+ },
618
+ };
619
+ }
620
+ catch (error) {
621
+ logger.error('terminal_credential_delete failed', {
622
+ user_id,
623
+ credentialId: id,
624
+ error: error instanceof Error ? error.message : String(error),
625
+ });
626
+ return { success: false, error: sanitizeErrorMessage(error) };
627
+ }
628
+ },
629
+ };
630
+ }
631
+ //# sourceMappingURL=terminal-connections.js.map