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,777 @@
1
+ import * as vscode from 'vscode';
2
+ import { spawn, type ChildProcess } from 'child_process';
3
+ import * as fs from 'fs';
4
+ import * as os from 'os';
5
+ import * as path from 'path';
6
+ import * as jsonc from 'jsonc-parser';
7
+
8
+ export interface StructuredCliError {
9
+ _tag?: string;
10
+ message: string;
11
+ code?: string;
12
+ details?: Record<string, unknown>;
13
+ }
14
+
15
+ export interface CliResult<T = unknown> {
16
+ success: boolean;
17
+ data?: T;
18
+ error?: string;
19
+ structuredError?: StructuredCliError;
20
+ exitCode: number;
21
+ }
22
+
23
+ export interface CliOptions {
24
+ cwd?: string;
25
+ timeout?: number;
26
+ format?: 'json' | 'text';
27
+ }
28
+
29
+ export class CliClient {
30
+ private outputChannel: vscode.OutputChannel;
31
+
32
+ constructor() {
33
+ this.outputChannel = vscode.window.createOutputChannel('Agentuity CLI');
34
+ }
35
+
36
+ /**
37
+ * Get the CLI executable path from settings, common install locations, or PATH.
38
+ */
39
+ getCliPath(): string {
40
+ const config = vscode.workspace.getConfiguration('agentuity');
41
+ const customPath = config.get<string>('cliPath');
42
+ if (customPath && customPath.trim() !== '') {
43
+ return customPath;
44
+ }
45
+
46
+ // Check common install location: ~/.agentuity/bin/agentuity
47
+ const homeDir = os.homedir();
48
+ const defaultInstallPath = path.join(homeDir, '.agentuity', 'bin', 'agentuity');
49
+ if (fs.existsSync(defaultInstallPath)) {
50
+ return defaultInstallPath;
51
+ }
52
+
53
+ // Fall back to PATH lookup
54
+ return 'agentuity';
55
+ }
56
+
57
+ private getProjectCwd(): string | undefined {
58
+ const workspaceFolders = vscode.workspace.workspaceFolders;
59
+ if (!workspaceFolders || workspaceFolders.length === 0) {
60
+ return undefined;
61
+ }
62
+ return workspaceFolders[0].uri.fsPath;
63
+ }
64
+
65
+ /**
66
+ * Read the region from the project's agentuity.json file.
67
+ */
68
+ private getProjectRegion(): string | undefined {
69
+ const projectDir = this.getProjectCwd();
70
+ if (!projectDir) return undefined;
71
+
72
+ const configPath = path.join(projectDir, 'agentuity.json');
73
+ if (!fs.existsSync(configPath)) return undefined;
74
+
75
+ try {
76
+ const content = fs.readFileSync(configPath, 'utf-8');
77
+ const config = jsonc.parse(content) as Record<string, unknown>;
78
+ return config.region as string | undefined;
79
+ } catch {
80
+ return undefined;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Append --dir flag to args if we have a project directory.
86
+ * Used for commands that accept --dir (those with requires/optional project).
87
+ * The --dir flag is a subcommand option, so it must come after the command.
88
+ */
89
+ private withProjectDir(args: string[]): string[] {
90
+ const projectDir = this.getProjectCwd();
91
+ if (projectDir) {
92
+ return [...args, '--dir', projectDir];
93
+ }
94
+ return args;
95
+ }
96
+
97
+ /**
98
+ * Append --region flag to args using region from agentuity.json.
99
+ * Used for commands that require region but don't accept --dir.
100
+ * The --region flag is a subcommand option, so it must come after the command.
101
+ */
102
+ private withRegion(args: string[]): string[] {
103
+ const region = this.getProjectRegion();
104
+ if (region) {
105
+ return [...args, '--region', region];
106
+ }
107
+ return args;
108
+ }
109
+
110
+ /**
111
+ * Get the environment variables for CLI execution.
112
+ * Sets TERM_PROGRAM=vscode to ensure CLI disables interactive mode.
113
+ */
114
+ getCliEnv(): NodeJS.ProcessEnv {
115
+ return {
116
+ ...process.env,
117
+ TERM_PROGRAM: 'vscode',
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Try to parse a structured error from CLI output.
123
+ * The CLI may emit JSON errors with _tag, message, code, and details fields.
124
+ */
125
+ private tryParseStructuredError(output: string): StructuredCliError | undefined {
126
+ if (!output) return undefined;
127
+
128
+ try {
129
+ const trimmed = output.trim();
130
+ if (!trimmed.startsWith('{')) return undefined;
131
+
132
+ const parsed = JSON.parse(trimmed);
133
+ if (parsed && typeof parsed === 'object' && 'message' in parsed) {
134
+ return {
135
+ _tag: parsed._tag,
136
+ message: parsed.message,
137
+ code: parsed.code,
138
+ details: parsed.details,
139
+ };
140
+ }
141
+ } catch {
142
+ // Not valid JSON, ignore
143
+ }
144
+
145
+ return undefined;
146
+ }
147
+
148
+ async exec<T = unknown>(args: string[], options: CliOptions = {}): Promise<CliResult<T>> {
149
+ const cliPath = this.getCliPath();
150
+ const cwd = options.cwd ?? this.getProjectCwd();
151
+ const timeout = options.timeout ?? 30000;
152
+
153
+ if (options.format === 'json') {
154
+ args = ['--json', ...args];
155
+ }
156
+
157
+ return new Promise((resolve) => {
158
+ let stdout = '';
159
+ let stderr = '';
160
+ let resolved = false;
161
+
162
+ const resolveOnce = (result: CliResult<T>) => {
163
+ if (!resolved) {
164
+ resolved = true;
165
+ resolve(result);
166
+ }
167
+ };
168
+
169
+ this.outputChannel.appendLine(`$ ${cliPath} ${args.join(' ')}`);
170
+
171
+ const child: ChildProcess = spawn(cliPath, args, {
172
+ cwd,
173
+ shell: true,
174
+ env: this.getCliEnv(),
175
+ });
176
+
177
+ const timeoutId = setTimeout(() => {
178
+ child.kill();
179
+ resolveOnce({
180
+ success: false,
181
+ error: `Command timed out after ${timeout}ms`,
182
+ exitCode: -1,
183
+ });
184
+ }, timeout);
185
+
186
+ child.stdout?.on('data', (data: Buffer) => {
187
+ stdout += data.toString();
188
+ });
189
+
190
+ child.stderr?.on('data', (data: Buffer) => {
191
+ stderr += data.toString();
192
+ });
193
+
194
+ child.on('error', (err: Error) => {
195
+ clearTimeout(timeoutId);
196
+ this.outputChannel.appendLine(`Error: ${err.message}`);
197
+ resolveOnce({
198
+ success: false,
199
+ error: err.message,
200
+ exitCode: -1,
201
+ });
202
+ });
203
+
204
+ child.on('close', (code: number | null) => {
205
+ clearTimeout(timeoutId);
206
+ const exitCode = code ?? 0;
207
+
208
+ if (stdout) {
209
+ this.outputChannel.appendLine(stdout);
210
+ }
211
+ if (stderr) {
212
+ this.outputChannel.appendLine(`stderr: ${stderr}`);
213
+ }
214
+
215
+ if (exitCode !== 0) {
216
+ // Try to parse structured error from CLI output
217
+ const structuredError = this.tryParseStructuredError(
218
+ options.format === 'json' ? stdout : stderr || stdout
219
+ );
220
+ resolveOnce({
221
+ success: false,
222
+ error: structuredError?.message || stderr || stdout || `Command failed with exit code ${exitCode}`,
223
+ structuredError,
224
+ exitCode,
225
+ });
226
+ return;
227
+ }
228
+
229
+ if (options.format === 'json') {
230
+ try {
231
+ // Handle CLI bug where some commands output JSON twice
232
+ // Try to parse the first valid JSON object/array
233
+ const trimmed = stdout.trim();
234
+ let jsonStr = trimmed;
235
+
236
+ // If output contains multiple JSON values, take the first one
237
+ if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
238
+ const firstChar = trimmed[0];
239
+ const closeChar = firstChar === '[' ? ']' : '}';
240
+ let depth = 0;
241
+ let endIdx = 0;
242
+
243
+ for (let i = 0; i < trimmed.length; i++) {
244
+ if (trimmed[i] === firstChar) depth++;
245
+ if (trimmed[i] === closeChar) depth--;
246
+ if (depth === 0) {
247
+ endIdx = i + 1;
248
+ break;
249
+ }
250
+ }
251
+
252
+ if (endIdx > 0) {
253
+ jsonStr = trimmed.substring(0, endIdx);
254
+ }
255
+ }
256
+
257
+ const data = JSON.parse(jsonStr) as T;
258
+ resolveOnce({ success: true, data, exitCode });
259
+ } catch {
260
+ resolveOnce({
261
+ success: false,
262
+ error: `Failed to parse JSON: ${stdout}`,
263
+ exitCode,
264
+ });
265
+ }
266
+ } else {
267
+ resolveOnce({
268
+ success: true,
269
+ data: stdout.trim() as unknown as T,
270
+ exitCode,
271
+ });
272
+ }
273
+ });
274
+ });
275
+ }
276
+
277
+ async version(): Promise<CliResult<string>> {
278
+ return this.exec<string>(['--version'], { format: 'text' });
279
+ }
280
+
281
+ async whoami(): Promise<CliResult<WhoamiResponse>> {
282
+ return this.exec<WhoamiResponse>(['auth', 'whoami'], { format: 'json' });
283
+ }
284
+
285
+ async listAgents(): Promise<CliResult<AgentListResponse>> {
286
+ return this.exec<AgentListResponse>(['cloud', 'agent', 'list'], { format: 'json' });
287
+ }
288
+
289
+ async listKvNamespaces(): Promise<CliResult<KvNamespaceListResponse>> {
290
+ return this.exec<KvNamespaceListResponse>(['cloud', 'keyvalue', 'list-namespaces'], {
291
+ format: 'json',
292
+ });
293
+ }
294
+
295
+ async listKvKeys(namespace: string): Promise<CliResult<KvKeysResponse>> {
296
+ return this.exec<KvKeysResponse>(['cloud', 'keyvalue', 'keys', namespace], {
297
+ format: 'json',
298
+ });
299
+ }
300
+
301
+
302
+
303
+ async getKvValue(namespace: string, key: string): Promise<CliResult<KvGetResponse>> {
304
+ return this.exec<KvGetResponse>(['cloud', 'keyvalue', 'get', namespace, key], {
305
+ format: 'json',
306
+ });
307
+ }
308
+
309
+
310
+
311
+ async getAiCapabilities(): Promise<CliResult<AiCapabilitiesResponse>> {
312
+ return this.exec<AiCapabilitiesResponse>(['ai', 'capabilities', 'show'], { format: 'json' });
313
+ }
314
+
315
+ async getAiSchema(): Promise<CliResult<AiSchemaResponse>> {
316
+ return this.exec<AiSchemaResponse>(['ai', 'schema', 'show'], { format: 'json' });
317
+ }
318
+
319
+ async getAiPrompt(): Promise<CliResult<string>> {
320
+ return this.exec<string>(['ai', 'prompt', 'llm'], { format: 'text' });
321
+ }
322
+
323
+ // Database methods (require region - pass --region from agentuity.json)
324
+ async listDatabases(): Promise<CliResult<DbListResponse>> {
325
+ return this.exec<DbListResponse>(this.withRegion(['cloud', 'db', 'list']), {
326
+ format: 'json',
327
+ });
328
+ }
329
+
330
+ async getDatabase(name: string): Promise<CliResult<DbInfo>> {
331
+ return this.exec<DbInfo>(this.withRegion(['cloud', 'db', 'get', name]), {
332
+ format: 'json',
333
+ });
334
+ }
335
+
336
+ async getDbLogs(
337
+ name: string,
338
+ opts?: { limit?: number; hasError?: boolean; sessionId?: string }
339
+ ): Promise<CliResult<DbQueryLog[]>> {
340
+ const args = ['cloud', 'db', 'logs', name];
341
+ if (opts?.limit) {
342
+ args.push('--limit', String(opts.limit));
343
+ }
344
+ if (opts?.hasError) {
345
+ args.push('--has-error');
346
+ }
347
+ if (opts?.sessionId) {
348
+ args.push('--session-id', opts.sessionId);
349
+ }
350
+ return this.exec<DbQueryLog[]>(this.withRegion(args), { format: 'json', timeout: 60000 });
351
+ }
352
+
353
+ // Storage methods (require region - pass --region from agentuity.json)
354
+ async listStorageBuckets(): Promise<CliResult<StorageListResponse>> {
355
+ return this.exec<StorageListResponse>(this.withRegion(['cloud', 'storage', 'list']), {
356
+ format: 'json',
357
+ });
358
+ }
359
+
360
+ async listStorageFiles(
361
+ bucket: string,
362
+ prefix?: string
363
+ ): Promise<CliResult<StorageListResponse>> {
364
+ const args = ['cloud', 'storage', 'list', bucket];
365
+ if (prefix) {
366
+ args.push(prefix);
367
+ }
368
+ return this.exec<StorageListResponse>(this.withRegion(args), { format: 'json' });
369
+ }
370
+
371
+ async getStorageFileMetadata(
372
+ bucket: string,
373
+ filename: string
374
+ ): Promise<CliResult<StorageFileMetadataResponse>> {
375
+ return this.exec<StorageFileMetadataResponse>(
376
+ this.withRegion(['cloud', 'storage', 'download', bucket, filename, '--metadata']),
377
+ { format: 'json' }
378
+ );
379
+ }
380
+
381
+ // Stream methods
382
+ async listStreams(opts?: { size?: number; name?: string }): Promise<CliResult<StreamListResponse>> {
383
+ const args = ['cloud', 'stream', 'list'];
384
+ if (opts?.size) {
385
+ args.push('--size', String(opts.size));
386
+ }
387
+ if (opts?.name) {
388
+ args.push('--name', opts.name);
389
+ }
390
+ return this.exec<StreamListResponse>(args, { format: 'json' });
391
+ }
392
+
393
+ async getStream(id: string): Promise<CliResult<StreamInfo>> {
394
+ return this.exec<StreamInfo>(['cloud', 'stream', 'get', id], { format: 'json' });
395
+ }
396
+
397
+ async deleteStream(id: string): Promise<CliResult<void>> {
398
+ return this.exec<void>(['cloud', 'stream', 'delete', id], { format: 'json' });
399
+ }
400
+
401
+ // Profile methods
402
+ async getCurrentProfile(): Promise<CliResult<string>> {
403
+ return this.exec<string>(['profile', 'current'], { format: 'json' });
404
+ }
405
+
406
+ // Vector methods
407
+ async vectorSearch(
408
+ namespace: string,
409
+ query: string,
410
+ opts?: { limit?: number; similarity?: number }
411
+ ): Promise<CliResult<VectorSearchResponse>> {
412
+ const args = ['cloud', 'vector', 'search', namespace, query];
413
+ if (opts?.limit) {
414
+ args.push('--limit', String(opts.limit));
415
+ }
416
+ if (opts?.similarity) {
417
+ args.push('--similarity', String(opts.similarity));
418
+ }
419
+ return this.exec<VectorSearchResponse>(args, { format: 'json' });
420
+ }
421
+
422
+ async getVector(namespace: string, key: string): Promise<CliResult<VectorGetResponse>> {
423
+ return this.exec<VectorGetResponse>(['cloud', 'vector', 'get', namespace, key], {
424
+ format: 'json',
425
+ });
426
+ }
427
+
428
+ async deploy(): Promise<CliResult<DeployResponse>> {
429
+ return this.exec<DeployResponse>(['cloud', 'deploy'], { format: 'json', timeout: 120000 });
430
+ }
431
+
432
+ async listDeployments(count?: number): Promise<CliResult<DeploymentListResponse>> {
433
+ const args = ['cloud', 'deployment', 'list'];
434
+ if (count) {
435
+ args.push('--count', String(count));
436
+ }
437
+ return this.exec<DeploymentListResponse>(args, { format: 'json' });
438
+ }
439
+
440
+ async getDeployment(deploymentId: string): Promise<CliResult<DeploymentShowResponse>> {
441
+ return this.exec<DeploymentShowResponse>(['cloud', 'deployment', 'show', deploymentId], {
442
+ format: 'json',
443
+ });
444
+ }
445
+
446
+ async getDeploymentLogs(
447
+ deploymentId: string,
448
+ limit?: number
449
+ ): Promise<CliResult<DeploymentLog[]>> {
450
+ const args = ['cloud', 'deployment', 'logs', deploymentId];
451
+ if (limit) {
452
+ args.push('--limit', String(limit));
453
+ }
454
+ return this.exec<DeploymentLog[]>(args, { format: 'json', timeout: 60000 });
455
+ }
456
+
457
+ // Session methods (require region - use --dir to ensure CLI finds agentuity.json)
458
+ async listSessions(opts?: SessionListOptions): Promise<CliResult<SessionListResponse>> {
459
+ const args = ['cloud', 'session', 'list'];
460
+ if (opts?.count) {
461
+ args.push('--count', String(opts.count));
462
+ }
463
+ if (opts?.deploymentId) {
464
+ args.push('--deployment-id', opts.deploymentId);
465
+ }
466
+ if (opts?.agentIdentifier) {
467
+ args.push('--agent-identifier', opts.agentIdentifier);
468
+ }
469
+ if (opts?.success !== undefined) {
470
+ args.push('--success', String(opts.success));
471
+ }
472
+ if (opts?.devmode !== undefined) {
473
+ args.push('--devmode', String(opts.devmode));
474
+ }
475
+ if (opts?.trigger) {
476
+ args.push('--trigger', opts.trigger);
477
+ }
478
+ if (opts?.env) {
479
+ args.push('--env', opts.env);
480
+ }
481
+ return this.exec<SessionListResponse>(this.withProjectDir(args), { format: 'json' });
482
+ }
483
+
484
+ async getSession(sessionId: string): Promise<CliResult<SessionGetResponse>> {
485
+ return this.exec<SessionGetResponse>(
486
+ this.withProjectDir(['cloud', 'session', 'get', sessionId]),
487
+ { format: 'json' }
488
+ );
489
+ }
490
+
491
+ async getSessionLogs(sessionId: string): Promise<CliResult<SessionLog[]>> {
492
+ return this.exec<SessionLog[]>(
493
+ this.withProjectDir(['cloud', 'session', 'logs', sessionId]),
494
+ { format: 'json', timeout: 60000 }
495
+ );
496
+ }
497
+
498
+ dispose(): void {
499
+ this.outputChannel.dispose();
500
+ }
501
+ }
502
+
503
+ // Auth types
504
+ export interface WhoamiResponse {
505
+ id: string;
506
+ email: string;
507
+ name?: string;
508
+ }
509
+
510
+ // Agent types
511
+ export interface Agent {
512
+ id: string;
513
+ name: string;
514
+ description?: string;
515
+ identifier?: string;
516
+ metadata?: {
517
+ filename?: string;
518
+ identifier?: string;
519
+ };
520
+ }
521
+
522
+ export type AgentListResponse = Agent[];
523
+
524
+ // KV types
525
+ export type KvNamespaceListResponse = string[];
526
+
527
+ export interface KvKeysResponse {
528
+ namespace: string;
529
+ keys: string[];
530
+ }
531
+
532
+ export interface KvGetResponse {
533
+ exists: boolean;
534
+ data: unknown;
535
+ contentType: string;
536
+ }
537
+
538
+
539
+
540
+ // Database types
541
+ export interface DbInfo {
542
+ name: string;
543
+ url: string;
544
+ }
545
+
546
+ export interface DbListResponse {
547
+ databases: DbInfo[];
548
+ }
549
+
550
+ export interface DbQueryLog {
551
+ timestamp: string;
552
+ command: string;
553
+ sql: string;
554
+ duration: number;
555
+ username: string;
556
+ sessionId?: string;
557
+ error?: string;
558
+ }
559
+
560
+ // Storage types
561
+ export interface StorageBucket {
562
+ bucket_name: string;
563
+ access_key?: string;
564
+ secret_key?: string;
565
+ region?: string;
566
+ endpoint?: string;
567
+ }
568
+
569
+ export interface StorageFile {
570
+ key: string;
571
+ size: number;
572
+ lastModified: string;
573
+ }
574
+
575
+ export interface StorageListResponse {
576
+ buckets?: StorageBucket[];
577
+ files?: StorageFile[];
578
+ }
579
+
580
+ export interface StorageFileMetadataResponse {
581
+ success: boolean;
582
+ bucket: string;
583
+ filename: string;
584
+ size?: number;
585
+ contentType?: string;
586
+ lastModified?: string;
587
+ }
588
+
589
+ // Stream types
590
+ export interface StreamInfo {
591
+ id: string;
592
+ name: string;
593
+ metadata: Record<string, string>;
594
+ url: string;
595
+ sizeBytes: number;
596
+ }
597
+
598
+ export interface StreamListResponse {
599
+ streams: StreamInfo[];
600
+ total: number;
601
+ }
602
+
603
+ // Vector types
604
+ export interface VectorSearchResult {
605
+ id: string;
606
+ key: string;
607
+ similarity: number;
608
+ metadata?: Record<string, unknown>;
609
+ }
610
+
611
+ export interface VectorSearchResponse {
612
+ namespace: string;
613
+ query: string;
614
+ results: VectorSearchResult[];
615
+ count: number;
616
+ }
617
+
618
+ export interface VectorGetResponse {
619
+ exists: boolean;
620
+ key: string;
621
+ id: string;
622
+ document: string;
623
+ metadata?: Record<string, unknown>;
624
+ }
625
+
626
+ // AI types
627
+ export interface AiCapabilitiesResponse {
628
+ capabilities: unknown;
629
+ }
630
+
631
+ export interface AiSchemaResponse {
632
+ schema: unknown;
633
+ }
634
+
635
+ // Deploy types
636
+ export interface DeployResponse {
637
+ deploymentId: string;
638
+ url?: string;
639
+ status: string;
640
+ }
641
+
642
+ // Deployment types
643
+ export interface Deployment {
644
+ id: string;
645
+ state?: string;
646
+ active: boolean;
647
+ createdAt: string;
648
+ message?: string;
649
+ tags: string[];
650
+ }
651
+
652
+ export type DeploymentListResponse = Deployment[];
653
+
654
+ export interface DeploymentShowResponse {
655
+ id: string;
656
+ state?: string;
657
+ active: boolean;
658
+ createdAt: string;
659
+ updatedAt?: string;
660
+ message?: string;
661
+ tags: string[];
662
+ customDomains?: string[];
663
+ cloudRegion?: string;
664
+ metadata?: {
665
+ git?: {
666
+ repo?: string;
667
+ commit?: string;
668
+ message?: string;
669
+ branch?: string;
670
+ url?: string;
671
+ trigger?: string;
672
+ provider?: string;
673
+ event?: string;
674
+ buildUrl?: string;
675
+ };
676
+ build?: {
677
+ agentuity?: string;
678
+ bun?: string;
679
+ platform?: string;
680
+ arch?: string;
681
+ };
682
+ };
683
+ }
684
+
685
+ export interface DeploymentLog {
686
+ body: string;
687
+ severity: string;
688
+ timestamp: string;
689
+ spanId?: string;
690
+ traceId?: string;
691
+ serviceName?: string;
692
+ }
693
+
694
+ // Session types
695
+ export interface SessionListOptions {
696
+ count?: number;
697
+ deploymentId?: string;
698
+ agentIdentifier?: string;
699
+ success?: boolean;
700
+ devmode?: boolean;
701
+ trigger?: 'api' | 'cron' | 'webhook';
702
+ env?: string;
703
+ }
704
+
705
+ export interface Session {
706
+ id: string;
707
+ created_at: string;
708
+ success: boolean;
709
+ duration: number | null;
710
+ method: string;
711
+ url: string;
712
+ trigger: string;
713
+ env: string;
714
+ }
715
+
716
+ export type SessionListResponse = Session[];
717
+
718
+ export interface SessionGetResponse {
719
+ id: string;
720
+ created_at: string;
721
+ start_time: string;
722
+ end_time: string | null;
723
+ duration: number | null;
724
+ org_id: string;
725
+ project_id: string;
726
+ deployment_id: string;
727
+ agent_ids: string[];
728
+ trigger: string;
729
+ env: string;
730
+ devmode: boolean;
731
+ pending: boolean;
732
+ success: boolean;
733
+ error: string | null;
734
+ method: string;
735
+ url: string;
736
+ route_id: string;
737
+ thread_id: string;
738
+ agents: Array<{ name: string; identifier: string }>;
739
+ eval_runs: Array<{
740
+ id: string;
741
+ eval_id: string;
742
+ created_at: string;
743
+ pending: boolean;
744
+ success: boolean;
745
+ error: string | null;
746
+ result: string | null;
747
+ }>;
748
+ timeline?: unknown;
749
+ route?: {
750
+ id: string;
751
+ method: string;
752
+ path: string;
753
+ } | null;
754
+ }
755
+
756
+ export interface SessionLog {
757
+ body: string;
758
+ severity: string;
759
+ timestamp: string;
760
+ }
761
+
762
+ // Singleton
763
+ let _cliClient: CliClient | undefined;
764
+
765
+ export function getCliClient(): CliClient {
766
+ if (!_cliClient) {
767
+ _cliClient = new CliClient();
768
+ }
769
+ return _cliClient;
770
+ }
771
+
772
+ export function disposeCliClient(): void {
773
+ if (_cliClient) {
774
+ _cliClient.dispose();
775
+ _cliClient = undefined;
776
+ }
777
+ }