@llm-dev-ops/agentics-cli 1.3.7 → 1.3.9

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 (80) hide show
  1. package/dist/adapters/base-adapter.d.ts +58 -1
  2. package/dist/adapters/base-adapter.d.ts.map +1 -1
  3. package/dist/adapters/base-adapter.js +110 -0
  4. package/dist/adapters/base-adapter.js.map +1 -1
  5. package/dist/cli/index.js +992 -16
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/commands/deploy.d.ts +95 -13
  8. package/dist/commands/deploy.d.ts.map +1 -1
  9. package/dist/commands/deploy.js +420 -14
  10. package/dist/commands/deploy.js.map +1 -1
  11. package/dist/commands/erp.d.ts +302 -0
  12. package/dist/commands/erp.d.ts.map +1 -0
  13. package/dist/commands/erp.js +1064 -0
  14. package/dist/commands/erp.js.map +1 -0
  15. package/dist/commands/export.d.ts +40 -13
  16. package/dist/commands/export.d.ts.map +1 -1
  17. package/dist/commands/export.js +434 -14
  18. package/dist/commands/export.js.map +1 -1
  19. package/dist/commands/index.d.ts +20 -12
  20. package/dist/commands/index.d.ts.map +1 -1
  21. package/dist/commands/index.js +10 -6
  22. package/dist/commands/index.js.map +1 -1
  23. package/dist/commands/inspect.d.ts +84 -18
  24. package/dist/commands/inspect.d.ts.map +1 -1
  25. package/dist/commands/inspect.js +585 -131
  26. package/dist/commands/inspect.js.map +1 -1
  27. package/dist/commands/logout.d.ts +34 -0
  28. package/dist/commands/logout.d.ts.map +1 -0
  29. package/dist/commands/logout.js +73 -0
  30. package/dist/commands/logout.js.map +1 -0
  31. package/dist/commands/plan.d.ts +147 -14
  32. package/dist/commands/plan.d.ts.map +1 -1
  33. package/dist/commands/plan.js +557 -16
  34. package/dist/commands/plan.js.map +1 -1
  35. package/dist/commands/policy.d.ts +136 -0
  36. package/dist/commands/policy.d.ts.map +1 -0
  37. package/dist/commands/policy.js +460 -0
  38. package/dist/commands/policy.js.map +1 -0
  39. package/dist/commands/quantify.d.ts +131 -13
  40. package/dist/commands/quantify.d.ts.map +1 -1
  41. package/dist/commands/quantify.js +425 -14
  42. package/dist/commands/quantify.js.map +1 -1
  43. package/dist/commands/simulate.d.ts +119 -13
  44. package/dist/commands/simulate.d.ts.map +1 -1
  45. package/dist/commands/simulate.js +363 -14
  46. package/dist/commands/simulate.js.map +1 -1
  47. package/dist/commands/usage.d.ts +110 -0
  48. package/dist/commands/usage.d.ts.map +1 -0
  49. package/dist/commands/usage.js +507 -0
  50. package/dist/commands/usage.js.map +1 -0
  51. package/dist/commands/whoami.d.ts +33 -0
  52. package/dist/commands/whoami.d.ts.map +1 -0
  53. package/dist/commands/whoami.js +93 -0
  54. package/dist/commands/whoami.js.map +1 -0
  55. package/dist/config/endpoints.d.ts.map +1 -1
  56. package/dist/config/endpoints.js +16 -1
  57. package/dist/config/endpoints.js.map +1 -1
  58. package/dist/contracts/schemas/index.d.ts +4 -0
  59. package/dist/contracts/schemas/index.d.ts.map +1 -1
  60. package/dist/contracts/schemas/index.js +2 -0
  61. package/dist/contracts/schemas/index.js.map +1 -1
  62. package/dist/contracts/validator.d.ts.map +1 -1
  63. package/dist/contracts/validator.js +55 -148
  64. package/dist/contracts/validator.js.map +1 -1
  65. package/dist/modules/artifact-handoff.d.ts +5 -0
  66. package/dist/modules/artifact-handoff.d.ts.map +1 -1
  67. package/dist/modules/artifact-handoff.js +47 -11
  68. package/dist/modules/artifact-handoff.js.map +1 -1
  69. package/dist/modules/command-parser.d.ts +1 -1
  70. package/dist/modules/command-parser.d.ts.map +1 -1
  71. package/dist/modules/command-parser.js +6 -1
  72. package/dist/modules/command-parser.js.map +1 -1
  73. package/dist/modules/output-renderer.d.ts +1 -0
  74. package/dist/modules/output-renderer.d.ts.map +1 -1
  75. package/dist/modules/output-renderer.js +25 -1
  76. package/dist/modules/output-renderer.js.map +1 -1
  77. package/dist/types/index.d.ts +3 -0
  78. package/dist/types/index.d.ts.map +1 -1
  79. package/dist/types/index.js.map +1 -1
  80. package/package.json +4 -4
@@ -1,149 +1,603 @@
1
1
  /**
2
- * Inspect Command
2
+ * Inspect Command - Standalone Read-Only HTTP Command
3
3
  *
4
- * SPARC Pseudocode Reference: §2.6
4
+ * ARCHITECTURE:
5
+ * - This is a TERMINAL command with its own control flow
6
+ * - Does NOT participate in orchestration, retrieval, or execution pipelines
7
+ * - Issues direct HTTP GET requests to agentics-results-index
8
+ * - Has its own error handling and exit codes
9
+ * - No code path can fall through to simulation, planner, or execution services
5
10
  *
6
- * FLOW:
7
- * 1. Parse arguments CommandObject
8
- * 2. VALIDATE request against contract (MUST pass before network call)
9
- * 3. Retrieve simulation output via agentics-simulation-engine
10
- * 4. VALIDATE response has execution_metadata (reject if missing)
11
- * 5. Record audit entry
12
- * 6. Return SimulationOutputReference
13
- *
14
- * CONTRACT ENFORCEMENT:
15
- * - Request validation happens BEFORE any network call
16
- * - Response validation requires execution_metadata
17
- * - No bypass flags or soft-fail behavior
11
+ * ROUTING:
12
+ * - Routes EXCLUSIVELY to agentics-results-index
13
+ * - NO routing to execution, planning, or simulation services
14
+ * - Read-only operation - performs NO mutations
18
15
  */
19
16
  import { isNaturalLanguage } from '../types/index.js';
20
- import { SimulationEngineAdapter } from '../adapters/base-adapter.js';
21
- import { createOrchestrationEngine, } from '../modules/orchestration-engine.js';
22
- import { createArtifactHandoff } from '../modules/artifact-handoff.js';
23
- import { createAuditTrail } from '../audit/audit-trail.js';
24
- import { failFast, toError } from '../errors/index.js';
25
- import { loadEndpointConfig } from '../config/endpoints.js';
26
- import { validateRequest, validateResponse, RequestSchemas, ResponseSchemas, } from '../contracts/validator.js';
17
+ import { CLIError } from '../errors/index.js';
27
18
  // ============================================================================
28
- // Inspect Command Implementation
19
+ // Inspect-Specific Exit Codes
29
20
  // ============================================================================
30
- export async function executeInspectCommand(input, options) {
31
- const correlationId = options.trace_id ?? crypto.randomUUID();
32
- const startTime = Date.now();
33
- // Initialize components
34
- const engine = createOrchestrationEngine(correlationId);
35
- const handoff = createArtifactHandoff();
36
- const audit = createAuditTrail();
37
- // Create adapter
38
- const engineConfig = loadEndpointConfig('agentics-simulation-engine');
39
- const engineAdapter = new SimulationEngineAdapter(engineConfig, correlationId);
40
- // Default depth
41
- const depth = input.depth ?? 'summary';
42
- // Define orchestration stages
43
- const stages = [
44
- {
45
- name: 'retrieve-output',
46
- adapter: engineAdapter,
47
- request: {
48
- endpoint: '/v1/simulate',
49
- method: 'POST',
50
- buildBody: () => {
51
- let requestBody;
52
- if (isNaturalLanguage(input.simRef)) {
53
- // ============================================================================
54
- // CONTRACT VALIDATION - Natural language format
55
- // ============================================================================
56
- requestBody = {
57
- sim_ref: {
58
- natural_language: input.simRef.description,
59
- },
60
- depth,
61
- include_metrics: input.includeMetrics,
62
- include_traces: input.includeTraces,
63
- };
64
- }
65
- else {
66
- // ============================================================================
67
- // CONTRACT VALIDATION - Structured reference format
68
- // ============================================================================
69
- const simRef = input.simRef;
70
- requestBody = {
71
- sim_ref: {
72
- id: simRef.id,
73
- type: 'SimulationReference',
74
- repository: simRef.repository,
75
- created_at: simRef.created_at,
76
- checksum: simRef.checksum,
77
- },
78
- depth,
79
- include_metrics: input.includeMetrics,
80
- include_traces: input.includeTraces,
81
- };
82
- }
83
- // Validate request against contract BEFORE sending
84
- validateRequest(RequestSchemas.INSPECT, requestBody, correlationId);
85
- return requestBody;
86
- },
21
+ export const INSPECT_EXIT_CODES = {
22
+ SUCCESS: 0,
23
+ INPUT_ERROR: 100,
24
+ SERVICE_UNAVAILABLE: 69,
25
+ TIMEOUT: 75,
26
+ NETWORK_ERROR: 120,
27
+ UNEXPECTED_ERROR: 1,
28
+ };
29
+ // ============================================================================
30
+ // Endpoint Configuration (Standalone - No Shared Config)
31
+ // ============================================================================
32
+ /**
33
+ * Get the results-index endpoint URL.
34
+ * Uses environment variable override or production default.
35
+ */
36
+ function getResultsIndexUrl() {
37
+ const envUrl = process.env['AGENTICS_RESULTS_INDEX_URL'];
38
+ if (envUrl) {
39
+ return envUrl;
40
+ }
41
+ return 'https://agentics-results-index-1062287243982.us-central1.run.app';
42
+ }
43
+ /**
44
+ * Get the request timeout in milliseconds.
45
+ */
46
+ function getRequestTimeout() {
47
+ const envTimeout = process.env['AGENTICS_RESULTS_INDEX_TIMEOUT'];
48
+ if (envTimeout) {
49
+ const parsed = parseInt(envTimeout, 10);
50
+ if (!isNaN(parsed) && parsed > 0) {
51
+ return parsed;
52
+ }
53
+ }
54
+ return 30000; // 30 seconds default
55
+ }
56
+ // ============================================================================
57
+ // Natural Language Query Parser
58
+ // ============================================================================
59
+ /**
60
+ * Translate free-form natural language input into structured query parameters.
61
+ * Recognizes common patterns like "last production test", "latest run", etc.
62
+ */
63
+ function parseNaturalLanguageQuery(description) {
64
+ const lower = description.toLowerCase().trim();
65
+ const params = {
66
+ order: 'newest', // Default to newest-first for all queries
67
+ limit: 10, // Default limit
68
+ };
69
+ // Environment detection
70
+ if (lower.includes('production') || lower.includes('prod')) {
71
+ params.environment = 'production';
72
+ }
73
+ else if (lower.includes('staging') || lower.includes('stage')) {
74
+ params.environment = 'staging';
75
+ }
76
+ else if (lower.includes('development') || lower.includes('dev')) {
77
+ params.environment = 'development';
78
+ }
79
+ else if (lower.includes('test')) {
80
+ params.environment = 'test';
81
+ }
82
+ // Status detection
83
+ if (lower.includes('failed') || lower.includes('failure') || lower.includes('error')) {
84
+ params.status = 'failed';
85
+ }
86
+ else if (lower.includes('completed') || lower.includes('success') || lower.includes('passed')) {
87
+ params.status = 'completed';
88
+ }
89
+ else if (lower.includes('running') || lower.includes('in progress') || lower.includes('active')) {
90
+ params.status = 'running';
91
+ }
92
+ else if (lower.includes('pending') || lower.includes('queued') || lower.includes('waiting')) {
93
+ params.status = 'pending';
94
+ }
95
+ // Limit detection
96
+ if (lower.includes('last') || lower.includes('latest') || lower.includes('most recent')) {
97
+ // Check for specific numbers
98
+ const numberMatch = lower.match(/last\s+(\d+)/);
99
+ if (numberMatch && numberMatch[1]) {
100
+ params.limit = Math.min(parseInt(numberMatch[1], 10), 100);
101
+ }
102
+ else {
103
+ params.limit = 1; // "last" without number means just one
104
+ }
105
+ }
106
+ else if (lower.includes('all')) {
107
+ params.limit = 100; // Cap at 100 for safety
108
+ }
109
+ // Order detection (override default if explicitly specified)
110
+ if (lower.includes('oldest') || lower.includes('first') || lower.includes('earliest')) {
111
+ params.order = 'oldest';
112
+ }
113
+ return params;
114
+ }
115
+ // ============================================================================
116
+ // Input Validation (Standalone)
117
+ // ============================================================================
118
+ /**
119
+ * Validate query parameters. Throws InspectError on invalid input.
120
+ */
121
+ function validateQueryParams(params, correlationId) {
122
+ // Validate limit
123
+ if (params.limit !== undefined) {
124
+ if (params.limit < 1) {
125
+ throw new CLIError({
126
+ code: 'ECLI-INSPECT-001',
127
+ category: 'INPUT_ERROR',
128
+ message: 'Query limit must be at least 1',
129
+ details: { provided_limit: params.limit },
130
+ module: 'inspect',
131
+ correlationId,
132
+ recoverable: false,
133
+ exitCode: INSPECT_EXIT_CODES.INPUT_ERROR,
134
+ });
135
+ }
136
+ if (params.limit > 100) {
137
+ throw new CLIError({
138
+ code: 'ECLI-INSPECT-002',
139
+ category: 'INPUT_ERROR',
140
+ message: 'Query limit cannot exceed 100 results',
141
+ details: { provided_limit: params.limit, max_limit: 100 },
142
+ module: 'inspect',
143
+ correlationId,
144
+ recoverable: false,
145
+ exitCode: INSPECT_EXIT_CODES.INPUT_ERROR,
146
+ });
147
+ }
148
+ }
149
+ // Validate environment if provided
150
+ const validEnvironments = ['production', 'staging', 'development', 'test'];
151
+ if (params.environment && !validEnvironments.includes(params.environment)) {
152
+ throw new CLIError({
153
+ code: 'ECLI-INSPECT-003',
154
+ category: 'INPUT_ERROR',
155
+ message: `Invalid environment filter: ${params.environment}`,
156
+ details: {
157
+ provided: params.environment,
158
+ valid_options: validEnvironments,
159
+ },
160
+ module: 'inspect',
161
+ correlationId,
162
+ recoverable: false,
163
+ exitCode: INSPECT_EXIT_CODES.INPUT_ERROR,
164
+ });
165
+ }
166
+ // Validate status if provided
167
+ const validStatuses = ['completed', 'failed', 'running', 'pending'];
168
+ if (params.status && !validStatuses.includes(params.status)) {
169
+ throw new CLIError({
170
+ code: 'ECLI-INSPECT-004',
171
+ category: 'INPUT_ERROR',
172
+ message: `Invalid status filter: ${params.status}`,
173
+ details: {
174
+ provided: params.status,
175
+ valid_options: validStatuses,
176
+ },
177
+ module: 'inspect',
178
+ correlationId,
179
+ recoverable: false,
180
+ exitCode: INSPECT_EXIT_CODES.INPUT_ERROR,
181
+ });
182
+ }
183
+ // Validate order if provided
184
+ const validOrders = ['newest', 'oldest'];
185
+ if (params.order && !validOrders.includes(params.order)) {
186
+ throw new CLIError({
187
+ code: 'ECLI-INSPECT-005',
188
+ category: 'INPUT_ERROR',
189
+ message: `Invalid order: ${params.order}`,
190
+ details: {
191
+ provided: params.order,
192
+ valid_options: validOrders,
87
193
  },
88
- },
89
- ];
194
+ module: 'inspect',
195
+ correlationId,
196
+ recoverable: false,
197
+ exitCode: INSPECT_EXIT_CODES.INPUT_ERROR,
198
+ });
199
+ }
200
+ }
201
+ // ============================================================================
202
+ // Result Formatting
203
+ // ============================================================================
204
+ /**
205
+ * Format simulation results for human-readable terminal output.
206
+ */
207
+ export function formatResultsForDisplay(results) {
208
+ if (results.length === 0) {
209
+ return 'No simulation results found matching the query.';
210
+ }
211
+ const lines = [];
212
+ lines.push(`Found ${results.length} result${results.length === 1 ? '' : 's'}:`);
213
+ lines.push('');
214
+ for (const result of results) {
215
+ const statusIcon = getStatusIcon(result.status);
216
+ const duration = result.duration_ms ? `${(result.duration_ms / 1000).toFixed(1)}s` : 'N/A';
217
+ const completedAt = result.completed_at
218
+ ? new Date(result.completed_at).toLocaleString()
219
+ : 'In progress';
220
+ lines.push(`${statusIcon} ${result.id}`);
221
+ lines.push(` Environment: ${result.environment}`);
222
+ lines.push(` Status: ${result.status}`);
223
+ lines.push(` Created: ${new Date(result.created_at).toLocaleString()}`);
224
+ lines.push(` Completed: ${completedAt}`);
225
+ lines.push(` Duration: ${duration}`);
226
+ if (result.summary) {
227
+ lines.push(` Summary: ${result.summary}`);
228
+ }
229
+ lines.push('');
230
+ }
231
+ return lines.join('\n');
232
+ }
233
+ function getStatusIcon(status) {
234
+ switch (status) {
235
+ case 'completed':
236
+ return '[OK]';
237
+ case 'failed':
238
+ return '[FAIL]';
239
+ case 'running':
240
+ return '[...]';
241
+ case 'pending':
242
+ return '[WAIT]';
243
+ default:
244
+ return '[?]';
245
+ }
246
+ }
247
+ // ============================================================================
248
+ // Direct HTTP Request (No Shared Adapter)
249
+ // ============================================================================
250
+ /**
251
+ * Build the query URL for the results-index service.
252
+ */
253
+ function buildQueryUrl(baseUrl, params) {
254
+ const url = new URL('/v1/results', baseUrl);
255
+ if (params.environment) {
256
+ url.searchParams.set('environment', params.environment);
257
+ }
258
+ if (params.status) {
259
+ url.searchParams.set('status', params.status);
260
+ }
261
+ if (params.limit !== undefined) {
262
+ url.searchParams.set('limit', params.limit.toString());
263
+ }
264
+ if (params.order) {
265
+ url.searchParams.set('order', params.order);
266
+ }
267
+ if (params.id) {
268
+ url.searchParams.set('id', params.id);
269
+ }
270
+ return url.toString();
271
+ }
272
+ /**
273
+ * Execute a direct HTTP GET request to the results-index service.
274
+ * This is a standalone implementation that does not use shared adapters.
275
+ */
276
+ async function executeHttpGet(url, timeout, correlationId) {
277
+ const startTime = Date.now();
278
+ const controller = new AbortController();
279
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
90
280
  try {
91
- // Execute orchestration
92
- const result = await engine.execute(stages, options);
93
- if (!result.success) {
94
- const failedStage = result.stages.find(s => s.status === 'failed');
95
- throw new Error(failedStage?.error?.message ?? 'Output retrieval failed');
96
- }
97
- // ============================================================================
98
- // RESPONSE VALIDATION - execution_metadata REQUIRED
99
- // ============================================================================
100
- const outputStage = result.stages.find(s => s.name === 'retrieve-output');
101
- const validatedOutput = validateResponse(ResponseSchemas.SIMULATION_OUTPUT, outputStage?.response, correlationId);
102
- const outputRef = handoff.extractReference(validatedOutput);
103
- // Record audit entry
104
- const simRefId = isNaturalLanguage(input.simRef)
105
- ? `nl:${input.simRef.description.slice(0, 50)}`
106
- : input.simRef.id;
107
- audit.record({
108
- command: 'inspect',
109
- inputs: { simRef: simRefId, depth },
110
- outputs: outputRef,
111
- dependencies: [
112
- { service: 'agentics-simulation-engine', method: 'retrieve', timestamp: new Date().toISOString(), duration_ms: result.timing.total, success: true },
113
- ],
114
- duration_ms: result.timing.total,
115
- status: 'SUCCESS',
116
- trace_id: correlationId,
281
+ const response = await fetch(url, {
282
+ method: 'GET',
283
+ headers: {
284
+ 'Accept': 'application/json',
285
+ 'X-Correlation-ID': correlationId,
286
+ 'X-API-Version': 'v1',
287
+ },
288
+ signal: controller.signal,
117
289
  });
118
- return {
119
- reference: outputRef,
120
- timing: Date.now() - startTime,
121
- };
290
+ clearTimeout(timeoutId);
291
+ const timing = Date.now() - startTime;
292
+ // Parse response body
293
+ let body;
294
+ const contentType = response.headers.get('content-type') ?? '';
295
+ if (contentType.includes('application/json')) {
296
+ body = await response.json();
297
+ }
298
+ else {
299
+ body = await response.text();
300
+ }
301
+ return { body, timing, status: response.status };
122
302
  }
123
303
  catch (error) {
124
- // Record failure in audit
125
- const failedSimRefId = isNaturalLanguage(input.simRef)
126
- ? `nl:${input.simRef.description.slice(0, 50)}`
127
- : input.simRef.id;
128
- audit.record({
129
- command: 'inspect',
130
- inputs: { simRef: failedSimRefId, depth },
131
- outputs: null,
132
- dependencies: [],
133
- duration_ms: Date.now() - startTime,
134
- status: 'FAILED',
135
- error_code: error instanceof Error ? error.name : 'UNKNOWN',
136
- error_message: error instanceof Error ? error.message : 'Unknown error',
137
- trace_id: correlationId,
304
+ clearTimeout(timeoutId);
305
+ // Handle abort (timeout)
306
+ if (error instanceof Error && error.name === 'AbortError') {
307
+ throw new CLIError({
308
+ code: 'ECLI-INSPECT-TIMEOUT',
309
+ category: 'NETWORK_ERROR',
310
+ message: `Request timed out after ${timeout}ms`,
311
+ details: { timeout_ms: timeout },
312
+ module: 'inspect',
313
+ correlationId,
314
+ recoverable: true,
315
+ exitCode: INSPECT_EXIT_CODES.TIMEOUT,
316
+ });
317
+ }
318
+ // Handle network errors
319
+ if (error instanceof TypeError) {
320
+ throw new CLIError({
321
+ code: 'ECLI-INSPECT-NETWORK',
322
+ category: 'NETWORK_ERROR',
323
+ message: 'Unable to connect to the results service',
324
+ details: { hint: 'Check your network connection and try again.' },
325
+ module: 'inspect',
326
+ correlationId,
327
+ recoverable: true,
328
+ exitCode: INSPECT_EXIT_CODES.NETWORK_ERROR,
329
+ });
330
+ }
331
+ // Re-throw CLIErrors
332
+ if (error instanceof CLIError) {
333
+ throw error;
334
+ }
335
+ // Wrap unknown errors
336
+ throw new CLIError({
337
+ code: 'ECLI-INSPECT-UNKNOWN',
338
+ category: 'INTERNAL_ERROR',
339
+ message: error instanceof Error ? error.message : 'An unexpected error occurred',
340
+ module: 'inspect',
341
+ correlationId,
342
+ recoverable: false,
343
+ exitCode: INSPECT_EXIT_CODES.UNEXPECTED_ERROR,
344
+ });
345
+ }
346
+ }
347
+ // ============================================================================
348
+ // Inspect Command Implementation (Standalone Terminal Command)
349
+ // ============================================================================
350
+ /**
351
+ * Execute the inspect command.
352
+ *
353
+ * This is a STANDALONE command that:
354
+ * - Does NOT use shared orchestration, retrieval, or execution helpers
355
+ * - Issues direct HTTP GET requests to agentics-results-index
356
+ * - Has its own error handling with inspect-specific exit codes
357
+ * - Cannot fall through to any other service
358
+ */
359
+ export async function executeInspectCommand(input, options) {
360
+ const correlationId = options.trace_id ?? crypto.randomUUID();
361
+ const startTime = Date.now();
362
+ // Get endpoint configuration (standalone - no shared config loader)
363
+ const baseUrl = getResultsIndexUrl();
364
+ const timeout = options.timeout ?? getRequestTimeout();
365
+ // Build query parameters from input
366
+ let queryParams;
367
+ if (isNaturalLanguage(input.simRef)) {
368
+ // Translate natural language to structured query
369
+ const nlInput = input.simRef;
370
+ queryParams = parseNaturalLanguageQuery(nlInput.description);
371
+ }
372
+ else {
373
+ // Structured input - extract ID if present
374
+ const structuredInput = input.simRef;
375
+ queryParams = {
376
+ id: structuredInput.id,
377
+ order: 'newest',
378
+ limit: 1,
379
+ };
380
+ }
381
+ // Validate query parameters (throws on error - no fall-through)
382
+ validateQueryParams(queryParams, correlationId);
383
+ // Build request URL
384
+ const requestUrl = buildQueryUrl(baseUrl, queryParams);
385
+ // Execute direct HTTP GET request (no shared adapter)
386
+ const response = await executeHttpGet(requestUrl, timeout, correlationId);
387
+ // Handle non-2xx responses
388
+ if (response.status >= 400) {
389
+ // Extract error message from response body if available
390
+ let errorMessage = 'The results service returned an error';
391
+ if (typeof response.body === 'object' && response.body !== null) {
392
+ const body = response.body;
393
+ if (typeof body['message'] === 'string') {
394
+ errorMessage = body['message'];
395
+ }
396
+ else if (typeof body['error'] === 'string') {
397
+ errorMessage = body['error'];
398
+ }
399
+ }
400
+ if (response.status === 503 || response.status === 502 || response.status === 504) {
401
+ throw new CLIError({
402
+ code: 'ECLI-INSPECT-SERVICE-UNAVAILABLE',
403
+ category: 'NETWORK_ERROR',
404
+ message: 'The results service is currently unavailable. Please try again later.',
405
+ details: { hint: 'The service may be temporarily down for maintenance.' },
406
+ module: 'inspect',
407
+ correlationId,
408
+ recoverable: true,
409
+ exitCode: INSPECT_EXIT_CODES.SERVICE_UNAVAILABLE,
410
+ });
411
+ }
412
+ throw new CLIError({
413
+ code: `ECLI-INSPECT-HTTP-${response.status}`,
414
+ category: response.status >= 500 ? 'NETWORK_ERROR' : 'INPUT_ERROR',
415
+ message: errorMessage,
416
+ details: { http_status: response.status },
417
+ module: 'inspect',
418
+ correlationId,
419
+ recoverable: response.status >= 500,
420
+ exitCode: response.status >= 500
421
+ ? INSPECT_EXIT_CODES.SERVICE_UNAVAILABLE
422
+ : INSPECT_EXIT_CODES.INPUT_ERROR,
138
423
  });
139
- // Fail fast
140
- failFast({
141
- command: 'inspect',
142
- phase: 'retrieval',
143
- upstreamError: toError(error),
144
- inputs: input,
424
+ }
425
+ // Extract results from response body
426
+ const responseBody = response.body;
427
+ // Handle various response formats (newest-first is enforced by the service)
428
+ const results = responseBody.results ??
429
+ responseBody.data ??
430
+ responseBody.items ??
431
+ (Array.isArray(responseBody) ? responseBody : []);
432
+ // Return result - command terminates here
433
+ return {
434
+ results,
435
+ query: queryParams,
436
+ count: results.length,
437
+ timing: Date.now() - startTime,
438
+ };
439
+ }
440
+ // ============================================================================
441
+ // Inspect Latest Subcommand Implementation
442
+ // ============================================================================
443
+ /**
444
+ * Execute the 'inspect latest' subcommand.
445
+ * Returns the most recent simulation result matching the filters.
446
+ */
447
+ export async function executeInspectLatestCommand(input, options) {
448
+ const correlationId = options.trace_id ?? crypto.randomUUID();
449
+ const startTime = Date.now();
450
+ // Get endpoint configuration
451
+ const baseUrl = getResultsIndexUrl();
452
+ const timeout = options.timeout ?? getRequestTimeout();
453
+ // Build query for latest result
454
+ const queryParams = {
455
+ order: 'newest',
456
+ limit: 1,
457
+ environment: input.environment,
458
+ status: input.status,
459
+ };
460
+ // Validate query parameters
461
+ validateQueryParams(queryParams, correlationId);
462
+ // Build request URL
463
+ const requestUrl = buildQueryUrl(baseUrl, queryParams);
464
+ // Execute HTTP GET request
465
+ const response = await executeHttpGet(requestUrl, timeout, correlationId);
466
+ // Handle non-2xx responses
467
+ if (response.status >= 400) {
468
+ let errorMessage = 'The results service returned an error';
469
+ if (typeof response.body === 'object' && response.body !== null) {
470
+ const body = response.body;
471
+ if (typeof body['message'] === 'string') {
472
+ errorMessage = body['message'];
473
+ }
474
+ else if (typeof body['error'] === 'string') {
475
+ errorMessage = body['error'];
476
+ }
477
+ }
478
+ if (response.status === 503 || response.status === 502 || response.status === 504) {
479
+ throw new CLIError({
480
+ code: 'ECLI-INSPECT-SERVICE-UNAVAILABLE',
481
+ category: 'NETWORK_ERROR',
482
+ message: 'The results service is currently unavailable. Please try again later.',
483
+ details: { hint: 'The service may be temporarily down for maintenance.' },
484
+ module: 'inspect',
485
+ correlationId,
486
+ recoverable: true,
487
+ exitCode: INSPECT_EXIT_CODES.SERVICE_UNAVAILABLE,
488
+ });
489
+ }
490
+ throw new CLIError({
491
+ code: `ECLI-INSPECT-HTTP-${response.status}`,
492
+ category: response.status >= 500 ? 'NETWORK_ERROR' : 'INPUT_ERROR',
493
+ message: errorMessage,
494
+ details: { http_status: response.status },
495
+ module: 'inspect',
496
+ correlationId,
497
+ recoverable: response.status >= 500,
498
+ exitCode: response.status >= 500
499
+ ? INSPECT_EXIT_CODES.SERVICE_UNAVAILABLE
500
+ : INSPECT_EXIT_CODES.INPUT_ERROR,
501
+ });
502
+ }
503
+ // Extract results from response body
504
+ const responseBody = response.body;
505
+ const results = responseBody.results ??
506
+ responseBody.data ??
507
+ responseBody.items ??
508
+ (Array.isArray(responseBody) ? responseBody : []);
509
+ const latestResult = results.length > 0 ? results[0] : null;
510
+ return {
511
+ result: latestResult ?? null,
512
+ found: latestResult !== null,
513
+ timing: Date.now() - startTime,
514
+ };
515
+ }
516
+ // ============================================================================
517
+ // Inspect Run Subcommand Implementation
518
+ // ============================================================================
519
+ /**
520
+ * Execute the 'inspect run <id>' subcommand.
521
+ * Returns a specific simulation result by ID.
522
+ */
523
+ export async function executeInspectRunCommand(input, options) {
524
+ const correlationId = options.trace_id ?? crypto.randomUUID();
525
+ const startTime = Date.now();
526
+ // Validate ID
527
+ if (!input.id || typeof input.id !== 'string' || input.id.trim() === '') {
528
+ throw new CLIError({
529
+ code: 'ECLI-INSPECT-INVALID-ID',
530
+ category: 'INPUT_ERROR',
531
+ message: 'Run ID is required',
532
+ details: { provided: input.id },
533
+ module: 'inspect',
534
+ correlationId,
535
+ recoverable: false,
536
+ exitCode: INSPECT_EXIT_CODES.INPUT_ERROR,
537
+ });
538
+ }
539
+ // Get endpoint configuration
540
+ const baseUrl = getResultsIndexUrl();
541
+ const timeout = options.timeout ?? getRequestTimeout();
542
+ // Build query for specific ID
543
+ const queryParams = {
544
+ id: input.id.trim(),
545
+ limit: 1,
546
+ };
547
+ // Validate query parameters
548
+ validateQueryParams(queryParams, correlationId);
549
+ // Build request URL
550
+ const requestUrl = buildQueryUrl(baseUrl, queryParams);
551
+ // Execute HTTP GET request
552
+ const response = await executeHttpGet(requestUrl, timeout, correlationId);
553
+ // Handle non-2xx responses
554
+ if (response.status >= 400) {
555
+ let errorMessage = 'The results service returned an error';
556
+ if (typeof response.body === 'object' && response.body !== null) {
557
+ const body = response.body;
558
+ if (typeof body['message'] === 'string') {
559
+ errorMessage = body['message'];
560
+ }
561
+ else if (typeof body['error'] === 'string') {
562
+ errorMessage = body['error'];
563
+ }
564
+ }
565
+ if (response.status === 503 || response.status === 502 || response.status === 504) {
566
+ throw new CLIError({
567
+ code: 'ECLI-INSPECT-SERVICE-UNAVAILABLE',
568
+ category: 'NETWORK_ERROR',
569
+ message: 'The results service is currently unavailable. Please try again later.',
570
+ details: { hint: 'The service may be temporarily down for maintenance.' },
571
+ module: 'inspect',
572
+ correlationId,
573
+ recoverable: true,
574
+ exitCode: INSPECT_EXIT_CODES.SERVICE_UNAVAILABLE,
575
+ });
576
+ }
577
+ throw new CLIError({
578
+ code: `ECLI-INSPECT-HTTP-${response.status}`,
579
+ category: response.status >= 500 ? 'NETWORK_ERROR' : 'INPUT_ERROR',
580
+ message: errorMessage,
581
+ details: { http_status: response.status },
582
+ module: 'inspect',
145
583
  correlationId,
584
+ recoverable: response.status >= 500,
585
+ exitCode: response.status >= 500
586
+ ? INSPECT_EXIT_CODES.SERVICE_UNAVAILABLE
587
+ : INSPECT_EXIT_CODES.INPUT_ERROR,
146
588
  });
147
589
  }
590
+ // Extract results from response body
591
+ const responseBody = response.body;
592
+ const results = responseBody.results ??
593
+ responseBody.data ??
594
+ responseBody.items ??
595
+ (Array.isArray(responseBody) ? responseBody : []);
596
+ const matchingResult = results.find(r => r.id === input.id) ?? (results.length > 0 ? results[0] : null);
597
+ return {
598
+ result: matchingResult ?? null,
599
+ found: matchingResult !== null,
600
+ timing: Date.now() - startTime,
601
+ };
148
602
  }
149
603
  //# sourceMappingURL=inspect.js.map