@sembix/cli 1.2.1 → 1.4.0
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.
- package/COMMANDS.md +1522 -0
- package/QUICKSTART.md +829 -0
- package/README.md +1976 -309
- package/USAGE-EXAMPLES.md +872 -0
- package/config.example.yaml +3 -0
- package/dist/commands/configure.d.ts.map +1 -1
- package/dist/commands/configure.js +172 -2
- package/dist/commands/configure.js.map +1 -1
- package/dist/commands/index.d.ts +10 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +11 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +118 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +21 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +66 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/profile-project.d.ts +14 -0
- package/dist/commands/profile-project.d.ts.map +1 -0
- package/dist/commands/profile-project.js +123 -0
- package/dist/commands/profile-project.js.map +1 -0
- package/dist/commands/profile.d.ts +26 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +177 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/commands/project.d.ts +16 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +153 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/setup.js +3 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/workflow.d.ts +91 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +1201 -0
- package/dist/commands/workflow.js.map +1 -0
- package/dist/config-schema.d.ts +26 -0
- package/dist/config-schema.d.ts.map +1 -1
- package/dist/config-schema.js +24 -1
- package/dist/config-schema.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +23 -2
- package/dist/config.js.map +1 -1
- package/dist/index.js +324 -1
- package/dist/index.js.map +1 -1
- package/dist/prompts/environment-setup.d.ts.map +1 -1
- package/dist/prompts/environment-setup.js +12 -0
- package/dist/prompts/environment-setup.js.map +1 -1
- package/dist/prompts/project-selection.d.ts +8 -0
- package/dist/prompts/project-selection.d.ts.map +1 -0
- package/dist/prompts/project-selection.js +132 -0
- package/dist/prompts/project-selection.js.map +1 -0
- package/dist/prompts/workflow-inputs.d.ts +10 -0
- package/dist/prompts/workflow-inputs.d.ts.map +1 -0
- package/dist/prompts/workflow-inputs.js +71 -0
- package/dist/prompts/workflow-inputs.js.map +1 -0
- package/dist/prompts/workflow-selection.d.ts +8 -0
- package/dist/prompts/workflow-selection.d.ts.map +1 -0
- package/dist/prompts/workflow-selection.js +147 -0
- package/dist/prompts/workflow-selection.js.map +1 -0
- package/dist/sembix-cli-1.4.0.tgz +0 -0
- package/dist/services/cognito-auth.d.ts +92 -0
- package/dist/services/cognito-auth.d.ts.map +1 -0
- package/dist/services/cognito-auth.js +319 -0
- package/dist/services/cognito-auth.js.map +1 -0
- package/dist/services/studio-api-client.d.ts +127 -0
- package/dist/services/studio-api-client.d.ts.map +1 -0
- package/dist/services/studio-api-client.js +291 -0
- package/dist/services/studio-api-client.js.map +1 -0
- package/dist/types/studio.d.ts +82 -0
- package/dist/types/studio.d.ts.map +1 -0
- package/dist/types/studio.js +7 -0
- package/dist/types/studio.js.map +1 -0
- package/dist/types.d.ts +286 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/browser-auth.d.ts +45 -0
- package/dist/utils/browser-auth.d.ts.map +1 -0
- package/dist/utils/browser-auth.js +168 -0
- package/dist/utils/browser-auth.js.map +1 -0
- package/dist/utils/cognito-auth.d.ts +3 -0
- package/dist/utils/cognito-auth.d.ts.map +1 -0
- package/dist/utils/cognito-auth.js +3 -0
- package/dist/utils/cognito-auth.js.map +1 -0
- package/dist/utils/config-file.d.ts +40 -0
- package/dist/utils/config-file.d.ts.map +1 -1
- package/dist/utils/config-file.js +158 -4
- package/dist/utils/config-file.js.map +1 -1
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +15 -2
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/environment.d.ts +22 -0
- package/dist/utils/environment.d.ts.map +1 -0
- package/dist/utils/environment.js +39 -0
- package/dist/utils/environment.js.map +1 -0
- package/dist/utils/error-handler.d.ts +53 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +174 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/fuzzy-match.d.ts +31 -0
- package/dist/utils/fuzzy-match.d.ts.map +1 -0
- package/dist/utils/fuzzy-match.js +138 -0
- package/dist/utils/fuzzy-match.js.map +1 -0
- package/dist/utils/input-parser.d.ts +14 -0
- package/dist/utils/input-parser.d.ts.map +1 -0
- package/dist/utils/input-parser.js +34 -0
- package/dist/utils/input-parser.js.map +1 -0
- package/dist/utils/output.d.ts +55 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +80 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/recent-workflows.d.ts +37 -0
- package/dist/utils/recent-workflows.d.ts.map +1 -0
- package/dist/utils/recent-workflows.js +172 -0
- package/dist/utils/recent-workflows.js.map +1 -0
- package/dist/utils/studio-api-client.d.ts +3 -0
- package/dist/utils/studio-api-client.d.ts.map +1 -0
- package/dist/utils/studio-api-client.js +3 -0
- package/dist/utils/studio-api-client.js.map +1 -0
- package/dist/utils/studio-api.d.ts +53 -0
- package/dist/utils/studio-api.d.ts.map +1 -0
- package/dist/utils/studio-api.js +102 -0
- package/dist/utils/studio-api.js.map +1 -0
- package/dist/utils/studio-config.d.ts +74 -0
- package/dist/utils/studio-config.d.ts.map +1 -0
- package/dist/utils/studio-config.js +213 -0
- package/dist/utils/studio-config.js.map +1 -0
- package/dist/utils/token-manager.d.ts +4 -0
- package/dist/utils/token-manager.d.ts.map +1 -0
- package/dist/utils/token-manager.js +3 -0
- package/dist/utils/token-manager.js.map +1 -0
- package/dist/utils/ui.d.ts +55 -1
- package/dist/utils/ui.d.ts.map +1 -1
- package/dist/utils/ui.js +151 -2
- package/dist/utils/ui.js.map +1 -1
- package/package.json +4 -1
- package/dist/__tests__/config-schema.test.d.ts +0 -2
- package/dist/__tests__/config-schema.test.d.ts.map +0 -1
- package/dist/__tests__/config-schema.test.js +0 -471
- package/dist/__tests__/config-schema.test.js.map +0 -1
- package/dist/__tests__/config.test.d.ts +0 -2
- package/dist/__tests__/config.test.d.ts.map +0 -1
- package/dist/__tests__/config.test.js +0 -75
- package/dist/__tests__/config.test.js.map +0 -1
- package/dist/__tests__/integration/configure.test.d.ts +0 -2
- package/dist/__tests__/integration/configure.test.d.ts.map +0 -1
- package/dist/__tests__/integration/configure.test.js +0 -247
- package/dist/__tests__/integration/configure.test.js.map +0 -1
- package/dist/__tests__/integration/fixtures/configs.d.ts +0 -477
- package/dist/__tests__/integration/fixtures/configs.d.ts.map +0 -1
- package/dist/__tests__/integration/fixtures/configs.js +0 -175
- package/dist/__tests__/integration/fixtures/configs.js.map +0 -1
- package/dist/__tests__/integration/helpers/cli-runner.d.ts +0 -63
- package/dist/__tests__/integration/helpers/cli-runner.d.ts.map +0 -1
- package/dist/__tests__/integration/helpers/cli-runner.js +0 -152
- package/dist/__tests__/integration/helpers/cli-runner.js.map +0 -1
- package/dist/__tests__/integration/helpers/command-runner.d.ts +0 -53
- package/dist/__tests__/integration/helpers/command-runner.d.ts.map +0 -1
- package/dist/__tests__/integration/helpers/command-runner.js +0 -117
- package/dist/__tests__/integration/helpers/command-runner.js.map +0 -1
- package/dist/__tests__/integration/studio-create.test.d.ts +0 -2
- package/dist/__tests__/integration/studio-create.test.d.ts.map +0 -1
- package/dist/__tests__/integration/studio-create.test.js +0 -209
- package/dist/__tests__/integration/studio-create.test.js.map +0 -1
- package/dist/__tests__/integration/studio-update.test.d.ts +0 -2
- package/dist/__tests__/integration/studio-update.test.d.ts.map +0 -1
- package/dist/__tests__/integration/studio-update.test.js +0 -166
- package/dist/__tests__/integration/studio-update.test.js.map +0 -1
- package/dist/commands/__tests__/configure.test.d.ts +0 -2
- package/dist/commands/__tests__/configure.test.d.ts.map +0 -1
- package/dist/commands/__tests__/configure.test.js +0 -229
- package/dist/commands/__tests__/configure.test.js.map +0 -1
- package/dist/prompts/__tests__/environment-setup.test.d.ts +0 -2
- package/dist/prompts/__tests__/environment-setup.test.d.ts.map +0 -1
- package/dist/prompts/__tests__/environment-setup.test.js +0 -206
- package/dist/prompts/__tests__/environment-setup.test.js.map +0 -1
- package/dist/prompts/__tests__/hub-integration.test.d.ts +0 -2
- package/dist/prompts/__tests__/hub-integration.test.d.ts.map +0 -1
- package/dist/prompts/__tests__/hub-integration.test.js +0 -126
- package/dist/prompts/__tests__/hub-integration.test.js.map +0 -1
- package/dist/prompts/__tests__/prompt-helpers.test.d.ts +0 -2
- package/dist/prompts/__tests__/prompt-helpers.test.d.ts.map +0 -1
- package/dist/prompts/__tests__/prompt-helpers.test.js +0 -235
- package/dist/prompts/__tests__/prompt-helpers.test.js.map +0 -1
- package/dist/sembix-cli-1.2.1.tgz +0 -0
- package/dist/utils/__tests__/config-file.test.d.ts +0 -2
- package/dist/utils/__tests__/config-file.test.d.ts.map +0 -1
- package/dist/utils/__tests__/config-file.test.js +0 -218
- package/dist/utils/__tests__/config-file.test.js.map +0 -1
- package/dist/utils/__tests__/config-loader.test.d.ts +0 -2
- package/dist/utils/__tests__/config-loader.test.d.ts.map +0 -1
- package/dist/utils/__tests__/config-loader.test.js +0 -325
- package/dist/utils/__tests__/config-loader.test.js.map +0 -1
- package/dist/utils/__tests__/github.test.d.ts +0 -2
- package/dist/utils/__tests__/github.test.d.ts.map +0 -1
- package/dist/utils/__tests__/github.test.js +0 -282
- package/dist/utils/__tests__/github.test.js.map +0 -1
- package/dist/utils/__tests__/ui.test.d.ts +0 -2
- package/dist/utils/__tests__/ui.test.d.ts.map +0 -1
- package/dist/utils/__tests__/ui.test.js +0 -256
- package/dist/utils/__tests__/ui.test.js.map +0 -1
|
@@ -0,0 +1,1201 @@
|
|
|
1
|
+
// Node.js built-ins
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
// Internal utilities
|
|
4
|
+
import { getAuthenticatedProfile } from '../utils/studio-config.js';
|
|
5
|
+
import { readProfile } from '../utils/config-file.js';
|
|
6
|
+
import { isInteractive } from '../utils/environment.js';
|
|
7
|
+
import { addRecentWorkflow } from '../utils/recent-workflows.js';
|
|
8
|
+
import * as ui from '../utils/ui.js';
|
|
9
|
+
import { getOutputFormat, outputSuccess, outputError } from '../utils/output.js';
|
|
10
|
+
// Services
|
|
11
|
+
import { StudioApiClient } from '../services/studio-api-client.js';
|
|
12
|
+
export async function workflowStartCommand(options) {
|
|
13
|
+
try {
|
|
14
|
+
// Determine output format
|
|
15
|
+
const format = getOutputFormat(options);
|
|
16
|
+
// Show UI elements only in TEXT mode
|
|
17
|
+
console.log();
|
|
18
|
+
ui.banner('Start Workflow Execution');
|
|
19
|
+
console.log();
|
|
20
|
+
// Load profile and validate authentication
|
|
21
|
+
const { profile, tokens } = await getAuthenticatedProfile(options.profile);
|
|
22
|
+
// Create API client
|
|
23
|
+
const apiClient = new StudioApiClient(profile, tokens);
|
|
24
|
+
// Handle project selection
|
|
25
|
+
let projectId = options.projectId;
|
|
26
|
+
let projectName;
|
|
27
|
+
if (!projectId) {
|
|
28
|
+
// Check if in interactive mode
|
|
29
|
+
if (!isInteractive()) {
|
|
30
|
+
// Non-interactive mode requires explicit project ID
|
|
31
|
+
const profileConfig = await readProfile(options.profile);
|
|
32
|
+
if (profileConfig?.defaultProjectId) {
|
|
33
|
+
throw new Error(`Missing required argument: --project-id\n\n` +
|
|
34
|
+
`Default project is set for profile '${profile.name}' but not being used in non-interactive mode.\n` +
|
|
35
|
+
`Use: --project-id ${profileConfig.defaultProjectId}`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw new Error(`Missing required argument: --project-id\n\n` +
|
|
39
|
+
`In non-interactive mode, you must provide explicit arguments.\n` +
|
|
40
|
+
`For CI/CD, consider setting a default project:\n` +
|
|
41
|
+
` sembix profile set-default-project ${profile.name} <project-id>\n\n` +
|
|
42
|
+
`Or provide the flag explicitly:\n` +
|
|
43
|
+
` sembix workflow start --project-id <id> --workflow-id <id>`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Interactive mode - check for default project first
|
|
47
|
+
const profileConfig = await readProfile(options.profile);
|
|
48
|
+
if (profileConfig?.defaultProjectId) {
|
|
49
|
+
projectId = profileConfig.defaultProjectId;
|
|
50
|
+
console.log(`Using default project: ${ui.highlight(projectId)}`);
|
|
51
|
+
console.log();
|
|
52
|
+
// Fetch project name for display and caching
|
|
53
|
+
try {
|
|
54
|
+
const project = await apiClient.getProject(projectId);
|
|
55
|
+
projectName = project?.name;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Silently fail - we'll use the ID
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// No default project - prompt for selection
|
|
63
|
+
console.log('No default project set. Select a project:');
|
|
64
|
+
console.log();
|
|
65
|
+
const { promptForProject } = await import('../prompts/project-selection.js');
|
|
66
|
+
const selectedProject = await promptForProject(apiClient, profile.name);
|
|
67
|
+
projectId = selectedProject.projectId;
|
|
68
|
+
projectName = selectedProject.name;
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(`Selected: ${ui.highlight(selectedProject.name)} (${projectId})`);
|
|
71
|
+
console.log();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Validate project ID
|
|
75
|
+
if (!projectId) {
|
|
76
|
+
throw new Error('Project ID is required but was not provided or selected.');
|
|
77
|
+
}
|
|
78
|
+
// Handle workflow selection
|
|
79
|
+
let workflowId = options.workflowId;
|
|
80
|
+
let workflowName = options.workflowName;
|
|
81
|
+
// Validate mutually exclusive options
|
|
82
|
+
if (workflowId && workflowName) {
|
|
83
|
+
throw new Error('Cannot specify both --workflow-id and --workflow-name. Choose one.');
|
|
84
|
+
}
|
|
85
|
+
if (!workflowId && !workflowName) {
|
|
86
|
+
// Check if in interactive mode
|
|
87
|
+
if (!isInteractive()) {
|
|
88
|
+
throw new Error(`Missing required argument: --workflow-id or --workflow-name\n\n` +
|
|
89
|
+
`In non-interactive mode, you must provide explicit arguments.\n` +
|
|
90
|
+
`Use: --workflow-id <id> or --workflow-name <name>`);
|
|
91
|
+
}
|
|
92
|
+
// Interactive mode - prompt for workflow
|
|
93
|
+
console.log('Select a workflow to start:');
|
|
94
|
+
console.log();
|
|
95
|
+
const { promptForWorkflow } = await import('../prompts/workflow-selection.js');
|
|
96
|
+
const selectedWorkflow = await promptForWorkflow(apiClient, profile.name, projectId);
|
|
97
|
+
workflowId = selectedWorkflow.workflowId;
|
|
98
|
+
workflowName = selectedWorkflow.name;
|
|
99
|
+
console.log();
|
|
100
|
+
console.log(`Selected: ${ui.highlight(selectedWorkflow.name)} (${workflowId})`);
|
|
101
|
+
console.log();
|
|
102
|
+
}
|
|
103
|
+
// Resolve workflow ID from name if needed (when provided via CLI flag)
|
|
104
|
+
if (workflowName && !workflowId) {
|
|
105
|
+
const lookupSpinner = ui.spinner(`Looking up workflow by name: ${workflowName}...`);
|
|
106
|
+
lookupSpinner.start();
|
|
107
|
+
const workflow = await apiClient.findWorkflowByName(projectId, workflowName);
|
|
108
|
+
lookupSpinner.stop();
|
|
109
|
+
if (!workflow) {
|
|
110
|
+
console.log();
|
|
111
|
+
ui.error(`Workflow with name "${workflowName}" not found in project ${projectId}.`);
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(`Use: sembix workflow list --project-id ${projectId}`);
|
|
114
|
+
console.log();
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
workflowId = workflow.workflowId;
|
|
118
|
+
workflowName = workflow.name; // Update with canonical name
|
|
119
|
+
console.log(`Found workflow: ${ui.highlight(workflow.name)} (${workflowId})`);
|
|
120
|
+
console.log();
|
|
121
|
+
}
|
|
122
|
+
// Validate workflow ID
|
|
123
|
+
if (!workflowId) {
|
|
124
|
+
throw new Error('Workflow ID is required but was not provided or selected.');
|
|
125
|
+
}
|
|
126
|
+
// Resolve parent workflow to published child
|
|
127
|
+
// The workflow lists show parent workflows, but we must start child workflows
|
|
128
|
+
console.log('Resolving to published workflow version...');
|
|
129
|
+
console.log();
|
|
130
|
+
const resolveSpinner = ui.spinner('Finding published version...');
|
|
131
|
+
resolveSpinner.start();
|
|
132
|
+
let parentWorkflowId;
|
|
133
|
+
let parentWorkflowName;
|
|
134
|
+
try {
|
|
135
|
+
const publishedWorkflow = await apiClient.resolveToPublishedChild(projectId, workflowId);
|
|
136
|
+
resolveSpinner.stop();
|
|
137
|
+
// Store the parent workflow ID for cache (so users see parent in recent list)
|
|
138
|
+
parentWorkflowId = workflowId;
|
|
139
|
+
parentWorkflowName = workflowName || publishedWorkflow.name;
|
|
140
|
+
// Update workflowId to the published child (for starting)
|
|
141
|
+
workflowId = publishedWorkflow.workflowId;
|
|
142
|
+
workflowName = publishedWorkflow.name;
|
|
143
|
+
console.log(`Using published version: ${ui.highlight(`v${publishedWorkflow.version || '?'}`)} (${workflowId})`);
|
|
144
|
+
console.log();
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
resolveSpinner.stop();
|
|
148
|
+
// If resolution fails, show clear error message
|
|
149
|
+
if (error instanceof Error) {
|
|
150
|
+
ui.error(error.message);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
// Parse input configuration
|
|
156
|
+
// Initialize with empty initialInstructions (API requires this field)
|
|
157
|
+
const startConfiguration = {
|
|
158
|
+
initialInstructions: []
|
|
159
|
+
};
|
|
160
|
+
// Check for interactive input prompting
|
|
161
|
+
// Only fetch template and prompt if:
|
|
162
|
+
// 1. No --inputs flag provided
|
|
163
|
+
// 2. Running in interactive terminal (TTY)
|
|
164
|
+
// 3. Not explicitly disabled with --no-interactive
|
|
165
|
+
if (!options.inputs && options.interactive !== false) {
|
|
166
|
+
const { isInteractive } = await import('../utils/environment.js');
|
|
167
|
+
if (isInteractive()) {
|
|
168
|
+
try {
|
|
169
|
+
// Show UI regardless of output format (this is input, not output)
|
|
170
|
+
console.error('Fetching workflow template...');
|
|
171
|
+
const workflowTemplate = await apiClient.getWorkflowTemplateForWorkflow(projectId, workflowId);
|
|
172
|
+
if (!workflowTemplate) {
|
|
173
|
+
console.error('No template found for this workflow. Starting without inputs...');
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// Find the Start step (it may not be the first step)
|
|
177
|
+
const startStep = workflowTemplate.steps?.find(step => step.name === 'Start');
|
|
178
|
+
const initialInstructions = startStep?.initialInstructions;
|
|
179
|
+
if (!initialInstructions || initialInstructions.length === 0) {
|
|
180
|
+
console.error('This workflow has no input parameters. Starting workflow...');
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// Template has inputs - prompt user directly (interactive mode already checked)
|
|
184
|
+
const { promptForWorkflowInputs } = await import('../prompts/workflow-inputs.js');
|
|
185
|
+
const inputs = await promptForWorkflowInputs(initialInstructions);
|
|
186
|
+
startConfiguration.initialInstructions = inputs;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
// Check if user cancelled (Ctrl+C)
|
|
192
|
+
if (error instanceof Error &&
|
|
193
|
+
(error.message.includes('SIGINT') ||
|
|
194
|
+
error.message.includes('force closed') ||
|
|
195
|
+
error.message.includes('User cancelled'))) {
|
|
196
|
+
console.error('\nOperation cancelled by user.');
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
// Show warning on stderr so it doesn't interfere with JSON output
|
|
200
|
+
console.error(`Warning: Could not fetch workflow template (${error instanceof Error ? error.message : 'unknown error'}). Starting without inputs...`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (options.inputs) {
|
|
205
|
+
// Support @file.json syntax or direct JSON string
|
|
206
|
+
let inputsJson;
|
|
207
|
+
if (options.inputs.startsWith('@')) {
|
|
208
|
+
const filePath = options.inputs.slice(1);
|
|
209
|
+
try {
|
|
210
|
+
inputsJson = await readFile(filePath, 'utf-8');
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
throw new Error(`Failed to read input file "${filePath}": ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
inputsJson = options.inputs;
|
|
218
|
+
}
|
|
219
|
+
// Auto-detect format and parse
|
|
220
|
+
try {
|
|
221
|
+
const { parseWorkflowInputs } = await import('../utils/input-parser.js');
|
|
222
|
+
const parsedConfig = parseWorkflowInputs(inputsJson);
|
|
223
|
+
Object.assign(startConfiguration, parsedConfig);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
throw new Error(`Failed to parse inputs: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Add optional workspace and issue metadata (nested structure)
|
|
230
|
+
if (options.workspaceId || options.workspaceName || options.issueId || options.issueKey) {
|
|
231
|
+
startConfiguration.workspaceMetadata = {};
|
|
232
|
+
if (options.workspaceId) {
|
|
233
|
+
startConfiguration.workspaceMetadata.workspaceId = options.workspaceId;
|
|
234
|
+
}
|
|
235
|
+
if (options.workspaceName) {
|
|
236
|
+
startConfiguration.workspaceMetadata.workspaceName = options.workspaceName;
|
|
237
|
+
}
|
|
238
|
+
if (options.issueId || options.issueKey) {
|
|
239
|
+
startConfiguration.workspaceMetadata.issueMetadata = {
|
|
240
|
+
issueId: options.issueId,
|
|
241
|
+
issueKey: options.issueKey
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Start workflow execution
|
|
246
|
+
const spinner = ui.spinner('Starting workflow execution...');
|
|
247
|
+
spinner.start();
|
|
248
|
+
const response = await apiClient.startWorkflow(projectId, workflowId, startConfiguration);
|
|
249
|
+
spinner.stop();
|
|
250
|
+
// Update recent workflows cache with parent workflow ID
|
|
251
|
+
// (fire and forget - don't block on cache errors)
|
|
252
|
+
if (parentWorkflowName && parentWorkflowId) {
|
|
253
|
+
addRecentWorkflow(profile.name, projectId, projectName || projectId, parentWorkflowId, // Store parent ID so users see parent in recent list
|
|
254
|
+
parentWorkflowName).catch(() => {
|
|
255
|
+
// Silently fail - cache update errors shouldn't break the command
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
// Output results based on format
|
|
259
|
+
outputSuccess(response, { format });
|
|
260
|
+
// TEXT mode: human-readable output
|
|
261
|
+
console.log();
|
|
262
|
+
ui.success('Workflow started successfully!');
|
|
263
|
+
console.log();
|
|
264
|
+
console.log(`Run ID: ${ui.highlight(response.runId)}`);
|
|
265
|
+
console.log(`Task ARN: ${ui.highlight(response.taskArn)}`);
|
|
266
|
+
console.log();
|
|
267
|
+
console.log(`Check status with: sembix workflow run status --workflow-id ${workflowId} --run-id ${response.runId}`);
|
|
268
|
+
console.log();
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
// Output error based on format
|
|
272
|
+
const format = getOutputFormat(options);
|
|
273
|
+
if (format === 'text') {
|
|
274
|
+
console.log();
|
|
275
|
+
if (error instanceof Error) {
|
|
276
|
+
ui.error(error.message);
|
|
277
|
+
// Provide actionable next steps
|
|
278
|
+
if (error.message.includes('authentication') || error.message.includes('token')) {
|
|
279
|
+
console.log();
|
|
280
|
+
console.log(`Run: sembix login${options.profile ? ` --profile ${options.profile}` : ''}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
ui.error('An unexpected error occurred');
|
|
285
|
+
console.error(error);
|
|
286
|
+
}
|
|
287
|
+
console.log();
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
outputError(error, 1);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
export async function workflowListCommand(options) {
|
|
296
|
+
try {
|
|
297
|
+
// Determine output format
|
|
298
|
+
const format = getOutputFormat(options);
|
|
299
|
+
// Show UI elements only in TEXT mode
|
|
300
|
+
console.log();
|
|
301
|
+
ui.banner('List Workflows');
|
|
302
|
+
console.log();
|
|
303
|
+
// Load profile and validate authentication
|
|
304
|
+
const { profile, tokens } = await getAuthenticatedProfile(options.profile);
|
|
305
|
+
// Create API client
|
|
306
|
+
const apiClient = new StudioApiClient(profile, tokens);
|
|
307
|
+
// Handle project ID requirement
|
|
308
|
+
let projectId = options.projectId;
|
|
309
|
+
if (!projectId) {
|
|
310
|
+
// Try to use default project from profile
|
|
311
|
+
const profileConfig = await readProfile(options.profile);
|
|
312
|
+
if (profileConfig?.defaultProjectId) {
|
|
313
|
+
projectId = profileConfig.defaultProjectId;
|
|
314
|
+
if (format === 'text') {
|
|
315
|
+
console.log(`Using default project: ${ui.highlight(projectId)}`);
|
|
316
|
+
console.log();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
// No project ID and no default - show helpful error
|
|
321
|
+
throw new Error(`Project ID is required to list workflows.\n\n` +
|
|
322
|
+
`Either:\n` +
|
|
323
|
+
` • Use: --project-id <id>\n` +
|
|
324
|
+
` • Set a default project: sembix profile set-default-project ${profile.name} <project-id>\n\n` +
|
|
325
|
+
`To see available projects: sembix project list`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Build filter params
|
|
329
|
+
const filterParams = {
|
|
330
|
+
projectId: projectId
|
|
331
|
+
};
|
|
332
|
+
if (options.workflowTemplateIds) {
|
|
333
|
+
filterParams.workflowTemplateIds = options.workflowTemplateIds;
|
|
334
|
+
}
|
|
335
|
+
if (options.parentWorkflowId) {
|
|
336
|
+
filterParams.parentWorkflowId = options.parentWorkflowId;
|
|
337
|
+
}
|
|
338
|
+
// List workflows
|
|
339
|
+
const spinner = ui.spinner('Loading workflows...');
|
|
340
|
+
spinner.start();
|
|
341
|
+
const response = await apiClient.listWorkflows(filterParams);
|
|
342
|
+
spinner.stop();
|
|
343
|
+
// Output results based on format
|
|
344
|
+
outputSuccess(response, { format });
|
|
345
|
+
// TEXT mode: human-readable output
|
|
346
|
+
console.log();
|
|
347
|
+
if (response.workflows.length === 0) {
|
|
348
|
+
ui.warning('No workflows found matching the criteria.');
|
|
349
|
+
console.log();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
ui.success(`Found ${response.workflows.length} workflow(s):`);
|
|
353
|
+
console.log();
|
|
354
|
+
for (const workflow of response.workflows) {
|
|
355
|
+
console.log(` ${ui.highlight(workflow.name)} (${workflow.workflowId})`);
|
|
356
|
+
console.log(` Project: ${projectId}`);
|
|
357
|
+
console.log(` Template: ${workflow.workflowTemplateId}`);
|
|
358
|
+
console.log(` State: ${workflow.state}`);
|
|
359
|
+
if (workflow.parentWorkflowId) {
|
|
360
|
+
console.log(` Parent: ${workflow.parentWorkflowId}`);
|
|
361
|
+
}
|
|
362
|
+
if (workflow.isPublished) {
|
|
363
|
+
console.log(` Published: Yes (v${workflow.version})`);
|
|
364
|
+
}
|
|
365
|
+
console.log(` Created: ${new Date(workflow.createdAt).toLocaleString()}`);
|
|
366
|
+
console.log();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
// Output error based on format
|
|
371
|
+
const format = getOutputFormat(options);
|
|
372
|
+
if (format === 'text') {
|
|
373
|
+
console.log();
|
|
374
|
+
if (error instanceof Error) {
|
|
375
|
+
ui.error(error.message);
|
|
376
|
+
if (error.message.includes('authentication') || error.message.includes('token')) {
|
|
377
|
+
console.log();
|
|
378
|
+
console.log(`Run: sembix login${options.profile ? ` --profile ${options.profile}` : ''}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
ui.error('An unexpected error occurred');
|
|
383
|
+
console.error(error);
|
|
384
|
+
}
|
|
385
|
+
console.log();
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
outputError(error, 1);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
export async function workflowShowCommand(options) {
|
|
394
|
+
try {
|
|
395
|
+
// Determine output format
|
|
396
|
+
const format = getOutputFormat(options);
|
|
397
|
+
// Validate required options
|
|
398
|
+
if (!options.projectId) {
|
|
399
|
+
throw new Error('Project ID is required. Use --project-id flag.');
|
|
400
|
+
}
|
|
401
|
+
if (!options.workflowId) {
|
|
402
|
+
throw new Error('Workflow ID is required. Use --workflow-id flag.');
|
|
403
|
+
}
|
|
404
|
+
// Show UI elements only in TEXT mode
|
|
405
|
+
console.log();
|
|
406
|
+
ui.banner('Workflow Details');
|
|
407
|
+
console.log();
|
|
408
|
+
// Load profile and validate authentication
|
|
409
|
+
const { profile, tokens } = await getAuthenticatedProfile(options.profile);
|
|
410
|
+
// Create API client
|
|
411
|
+
const apiClient = new StudioApiClient(profile, tokens);
|
|
412
|
+
// Get workflow
|
|
413
|
+
const spinner = ui.spinner(`Fetching workflow ${options.workflowId}...`);
|
|
414
|
+
spinner.start();
|
|
415
|
+
const workflow = await apiClient.getWorkflow(options.projectId, options.workflowId);
|
|
416
|
+
spinner.stop();
|
|
417
|
+
// Fetch template info if available
|
|
418
|
+
let template = null;
|
|
419
|
+
if (workflow.workflowTemplateId) {
|
|
420
|
+
try {
|
|
421
|
+
template = await apiClient.getWorkflowTemplate(options.projectId, workflow.workflowTemplateId);
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
// Ignore template fetch errors - workflow info is still useful
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// Output results based on format
|
|
428
|
+
outputSuccess(workflow, { format });
|
|
429
|
+
// TEXT mode: human-readable output
|
|
430
|
+
console.log();
|
|
431
|
+
console.log(`Name: ${ui.highlight(workflow.name)}`);
|
|
432
|
+
console.log(`Workflow ID: ${workflow.workflowId}`);
|
|
433
|
+
console.log(`Project ID: ${workflow.projectId}`);
|
|
434
|
+
console.log(`Template ID: ${workflow.workflowTemplateId}`);
|
|
435
|
+
console.log(`State: ${workflow.state}`);
|
|
436
|
+
console.log(`Created: ${new Date(workflow.createdAt).toLocaleString()}`);
|
|
437
|
+
console.log(`Updated: ${new Date(workflow.updatedAt).toLocaleString()}`);
|
|
438
|
+
console.log(`Creator ID: ${workflow.creatorId}`);
|
|
439
|
+
console.log(`Updated By: ${workflow.updatedBy}`);
|
|
440
|
+
if (workflow.description) {
|
|
441
|
+
console.log(`Description: ${workflow.description}`);
|
|
442
|
+
}
|
|
443
|
+
if (workflow.parentWorkflowId) {
|
|
444
|
+
console.log(`Parent: ${workflow.parentWorkflowId}`);
|
|
445
|
+
}
|
|
446
|
+
if (workflow.isPublished) {
|
|
447
|
+
console.log(`Published: Yes (v${workflow.version})`);
|
|
448
|
+
}
|
|
449
|
+
console.log();
|
|
450
|
+
// Show template information if available
|
|
451
|
+
if (template) {
|
|
452
|
+
ui.section('Template Information');
|
|
453
|
+
console.log(`Template: ${ui.highlight(template.name)}`);
|
|
454
|
+
if (template.description) {
|
|
455
|
+
console.log(`Description: ${template.description}`);
|
|
456
|
+
}
|
|
457
|
+
if (template.initialInstructions && template.initialInstructions.length > 0) {
|
|
458
|
+
console.log();
|
|
459
|
+
console.log(`Inputs: ${template.initialInstructions.length} parameter(s)`);
|
|
460
|
+
for (const instr of template.initialInstructions) {
|
|
461
|
+
const req = instr.isRequired ? '(required)' : '(optional)';
|
|
462
|
+
console.log(` - ${instr.name} ${ui.dim(req)}`);
|
|
463
|
+
}
|
|
464
|
+
console.log();
|
|
465
|
+
console.log(ui.dim('For detailed template info:'));
|
|
466
|
+
console.log(ui.code(`sembix workflow template show --project-id ${workflow.projectId} --template-id ${workflow.workflowTemplateId}`));
|
|
467
|
+
}
|
|
468
|
+
console.log();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
// Output error based on format
|
|
473
|
+
const format = getOutputFormat(options);
|
|
474
|
+
if (format === 'text') {
|
|
475
|
+
console.log();
|
|
476
|
+
if (error instanceof Error) {
|
|
477
|
+
ui.error(error.message);
|
|
478
|
+
if (error.message.includes('authentication') || error.message.includes('token')) {
|
|
479
|
+
console.log();
|
|
480
|
+
console.log(`Run: sembix login${options.profile ? ` --profile ${options.profile}` : ''}`);
|
|
481
|
+
}
|
|
482
|
+
else if (error.message.includes('404') || error.message.includes('not found')) {
|
|
483
|
+
console.log();
|
|
484
|
+
console.log('Workflow not found. Check the project ID and workflow ID.');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
ui.error('An unexpected error occurred');
|
|
489
|
+
console.error(error);
|
|
490
|
+
}
|
|
491
|
+
console.log();
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
outputError(error, 4);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
export async function workflowTemplateShowCommand(options) {
|
|
500
|
+
try {
|
|
501
|
+
// Determine output format
|
|
502
|
+
const format = getOutputFormat(options);
|
|
503
|
+
// Validate: projectId always required, plus either templateId OR workflowId
|
|
504
|
+
if (!options.projectId) {
|
|
505
|
+
throw new Error('--project-id is required');
|
|
506
|
+
}
|
|
507
|
+
if (!options.templateId && !options.workflowId) {
|
|
508
|
+
throw new Error('Either --template-id OR --workflow-id is required');
|
|
509
|
+
}
|
|
510
|
+
// Show UI elements only in TEXT mode
|
|
511
|
+
console.log();
|
|
512
|
+
ui.banner('Workflow Template Details');
|
|
513
|
+
console.log();
|
|
514
|
+
// Load profile and validate authentication
|
|
515
|
+
const { profile, tokens } = await getAuthenticatedProfile(options.profile);
|
|
516
|
+
// Create API client
|
|
517
|
+
const apiClient = new StudioApiClient(profile, tokens);
|
|
518
|
+
// Get template
|
|
519
|
+
const spinner = ui.spinner('Fetching workflow template...');
|
|
520
|
+
spinner.start();
|
|
521
|
+
let template;
|
|
522
|
+
if (options.templateId && options.projectId) {
|
|
523
|
+
template = await apiClient.getWorkflowTemplate(options.projectId, options.templateId);
|
|
524
|
+
}
|
|
525
|
+
else if (options.projectId && options.workflowId) {
|
|
526
|
+
const t = await apiClient.getWorkflowTemplateForWorkflow(options.projectId, options.workflowId);
|
|
527
|
+
if (!t) {
|
|
528
|
+
throw new Error('Workflow has no associated template');
|
|
529
|
+
}
|
|
530
|
+
template = t;
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
throw new Error('Must provide projectId with either templateId or workflowId');
|
|
534
|
+
}
|
|
535
|
+
spinner.stop();
|
|
536
|
+
// Output results based on format
|
|
537
|
+
outputSuccess(template, { format });
|
|
538
|
+
// TEXT mode: human-readable output
|
|
539
|
+
console.log();
|
|
540
|
+
console.log(`Name: ${ui.highlight(template.name)}`);
|
|
541
|
+
console.log(`Template ID: ${template.workflowTemplateId}`);
|
|
542
|
+
if (template.description) {
|
|
543
|
+
console.log(`Description: ${template.description}`);
|
|
544
|
+
}
|
|
545
|
+
if (template.category) {
|
|
546
|
+
console.log(`Category: ${template.category}`);
|
|
547
|
+
}
|
|
548
|
+
if (template.version) {
|
|
549
|
+
console.log(`Version: ${template.version}`);
|
|
550
|
+
}
|
|
551
|
+
console.log(`Created: ${new Date(template.createdAt).toLocaleString()}`);
|
|
552
|
+
console.log(`Updated: ${new Date(template.updatedAt).toLocaleString()}`);
|
|
553
|
+
if (template.tags && template.tags.length > 0) {
|
|
554
|
+
console.log(`Tags: ${template.tags.join(', ')}`);
|
|
555
|
+
}
|
|
556
|
+
console.log();
|
|
557
|
+
// Show input requirements
|
|
558
|
+
if (template.initialInstructions && template.initialInstructions.length > 0) {
|
|
559
|
+
ui.section('Input Parameters');
|
|
560
|
+
for (const instr of template.initialInstructions) {
|
|
561
|
+
const required = instr.isRequired ? ui.highlight('(required)') : ui.dim('(optional)');
|
|
562
|
+
console.log(` ${ui.highlight(instr.name)} ${required}`);
|
|
563
|
+
if (instr.description) {
|
|
564
|
+
console.log(` ${ui.dim(instr.description)}`);
|
|
565
|
+
}
|
|
566
|
+
if (instr.dataType) {
|
|
567
|
+
console.log(` Type: ${instr.dataType}`);
|
|
568
|
+
}
|
|
569
|
+
if (instr.instruction !== undefined) {
|
|
570
|
+
console.log(` Default: ${instr.instruction}`);
|
|
571
|
+
}
|
|
572
|
+
console.log();
|
|
573
|
+
}
|
|
574
|
+
console.log();
|
|
575
|
+
console.log(ui.dim('Use these parameters when starting a workflow:'));
|
|
576
|
+
console.log();
|
|
577
|
+
console.log(ui.code(`sembix workflow start --project-id <id> --workflow-id <id> \\
|
|
578
|
+
--inputs '[{"id": "${template.initialInstructions[0].id}", "instruction": "value"}]'`));
|
|
579
|
+
console.log();
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
console.log(ui.dim('This template has no input parameters.'));
|
|
583
|
+
console.log();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
catch (error) {
|
|
587
|
+
// Error handling
|
|
588
|
+
const format = getOutputFormat(options);
|
|
589
|
+
if (format === 'text') {
|
|
590
|
+
console.log();
|
|
591
|
+
if (error instanceof Error) {
|
|
592
|
+
ui.error(error.message);
|
|
593
|
+
if (error.message.includes('authentication') || error.message.includes('token')) {
|
|
594
|
+
console.log();
|
|
595
|
+
console.log(`Run: sembix login${options.profile ? ` --profile ${options.profile}` : ''}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
ui.error('An unexpected error occurred');
|
|
600
|
+
console.error(error);
|
|
601
|
+
}
|
|
602
|
+
console.log();
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
outputError(error, 1);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
export async function workflowStatusCommand(options) {
|
|
611
|
+
try {
|
|
612
|
+
// Determine output format
|
|
613
|
+
const format = getOutputFormat(options);
|
|
614
|
+
// Validate required options
|
|
615
|
+
if (!options.workflowId) {
|
|
616
|
+
throw new Error('Workflow ID is required. Use --workflow-id flag.');
|
|
617
|
+
}
|
|
618
|
+
if (!options.runId) {
|
|
619
|
+
throw new Error('Run ID is required. Use --run-id flag.');
|
|
620
|
+
}
|
|
621
|
+
// Watch mode not supported in JSON/PRETTY output
|
|
622
|
+
if (options.watch && format !== 'text') {
|
|
623
|
+
throw new Error('Watch mode (--watch) is only supported with --output text');
|
|
624
|
+
}
|
|
625
|
+
// Show UI elements only in TEXT mode
|
|
626
|
+
console.log();
|
|
627
|
+
ui.banner('Workflow Run Status');
|
|
628
|
+
console.log();
|
|
629
|
+
// Load profile and validate authentication
|
|
630
|
+
const { profile, tokens } = await getAuthenticatedProfile(options.profile);
|
|
631
|
+
// Create API client
|
|
632
|
+
const apiClient = new StudioApiClient(profile, tokens);
|
|
633
|
+
// Function to fetch and display status with optional change highlighting
|
|
634
|
+
const fetchAndDisplayStatus = async (previousRun) => {
|
|
635
|
+
const response = await apiClient.getWorkflowRun(options.workflowId, options.runId);
|
|
636
|
+
const run = response.run;
|
|
637
|
+
// Display status information
|
|
638
|
+
console.log(`Run ID: ${ui.highlight(run.runId)}`);
|
|
639
|
+
console.log(`Workflow ID: ${ui.highlight(run.workflowId)}`);
|
|
640
|
+
console.log(`Name: ${run.name || 'N/A'}`);
|
|
641
|
+
// Color-code status (with change highlighting if in watch mode)
|
|
642
|
+
let statusDisplay = run.status;
|
|
643
|
+
const statusChanged = previousRun && previousRun.status !== run.status;
|
|
644
|
+
if (run.status === 'COMPLETED') {
|
|
645
|
+
statusDisplay = statusChanged
|
|
646
|
+
? `\x1b[32m\x1b[1m${run.status}\x1b[0m` // Green + Bold
|
|
647
|
+
: `\x1b[32m${run.status}\x1b[0m`; // Green
|
|
648
|
+
}
|
|
649
|
+
else if (run.status === 'FAILED') {
|
|
650
|
+
statusDisplay = statusChanged
|
|
651
|
+
? `\x1b[31m\x1b[1m${run.status}\x1b[0m` // Red + Bold
|
|
652
|
+
: `\x1b[31m${run.status}\x1b[0m`; // Red
|
|
653
|
+
}
|
|
654
|
+
else if (run.status === 'IN_PROGRESS') {
|
|
655
|
+
statusDisplay = statusChanged
|
|
656
|
+
? `\x1b[33m\x1b[1m${run.status}\x1b[0m` // Yellow + Bold
|
|
657
|
+
: `\x1b[33m${run.status}\x1b[0m`; // Yellow
|
|
658
|
+
}
|
|
659
|
+
console.log(`Status: ${statusDisplay}`);
|
|
660
|
+
// Highlight progress if changed
|
|
661
|
+
const progressValue = run.progress || 0;
|
|
662
|
+
const progressChanged = previousRun && previousRun.progress !== progressValue;
|
|
663
|
+
const progressDisplay = progressChanged ? ui.changed(`${progressValue}%`) : `${progressValue}%`;
|
|
664
|
+
console.log(`Progress: ${progressDisplay}`);
|
|
665
|
+
console.log(`Created At: ${new Date(run.createdAt).toLocaleString()}`);
|
|
666
|
+
console.log(`Creator ID: ${run.creatorId}`);
|
|
667
|
+
// Highlight current step if changed
|
|
668
|
+
if (run.currentStepId) {
|
|
669
|
+
const stepChanged = previousRun && previousRun.currentStepId !== run.currentStepId;
|
|
670
|
+
const stepDisplay = stepChanged ? ui.changed(run.currentStepId) : run.currentStepId;
|
|
671
|
+
console.log(`Current Step: ${stepDisplay}`);
|
|
672
|
+
}
|
|
673
|
+
if (run.error) {
|
|
674
|
+
console.log(`Error: ${ui.highlight(run.error)}`);
|
|
675
|
+
}
|
|
676
|
+
// Return true if status is terminal (completed, failed, stopped)
|
|
677
|
+
return ['COMPLETED', 'FAILED', 'STOPPED'].includes(run.status);
|
|
678
|
+
};
|
|
679
|
+
if (options.watch) {
|
|
680
|
+
// Watch mode: poll every 5 seconds until terminal status
|
|
681
|
+
let previousRun = null;
|
|
682
|
+
let iteration = 0;
|
|
683
|
+
let isTerminal = false;
|
|
684
|
+
// Hide cursor to prevent flicker during updates
|
|
685
|
+
ui.hideCursor();
|
|
686
|
+
try {
|
|
687
|
+
while (!isTerminal) {
|
|
688
|
+
// Restore cursor to saved position (after first iteration)
|
|
689
|
+
if (iteration > 0) {
|
|
690
|
+
ui.restoreCursorPosition();
|
|
691
|
+
}
|
|
692
|
+
// Save cursor position here (right after banner, before dynamic content)
|
|
693
|
+
ui.saveCursorPosition();
|
|
694
|
+
// Show watch header with timestamp
|
|
695
|
+
console.log(ui.dim('Watching workflow status... (Ctrl+C to stop)'));
|
|
696
|
+
console.log(ui.dim(`Last updated: ${new Date().toLocaleTimeString()} (refreshes every 5s)`));
|
|
697
|
+
console.log();
|
|
698
|
+
// Clear everything below this point
|
|
699
|
+
ui.clearFromCursorDown();
|
|
700
|
+
// Fetch and display with change highlighting
|
|
701
|
+
const response = await apiClient.getWorkflowRun(options.workflowId, options.runId);
|
|
702
|
+
isTerminal = await fetchAndDisplayStatus(previousRun);
|
|
703
|
+
previousRun = response.run;
|
|
704
|
+
if (!isTerminal) {
|
|
705
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
706
|
+
}
|
|
707
|
+
iteration++;
|
|
708
|
+
}
|
|
709
|
+
// Show cursor again when done
|
|
710
|
+
ui.showCursor();
|
|
711
|
+
console.log();
|
|
712
|
+
ui.success('Workflow execution completed');
|
|
713
|
+
console.log();
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
// Ensure cursor is restored even on error
|
|
717
|
+
ui.showCursor();
|
|
718
|
+
throw error;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
// Single fetch
|
|
723
|
+
const spinner = ui.spinner('Fetching workflow status...');
|
|
724
|
+
spinner.start();
|
|
725
|
+
const response = await apiClient.getWorkflowRun(options.workflowId, options.runId);
|
|
726
|
+
spinner.stop();
|
|
727
|
+
// Output results based on format
|
|
728
|
+
outputSuccess(response.run, { format });
|
|
729
|
+
// TEXT mode: human-readable output
|
|
730
|
+
console.log();
|
|
731
|
+
await fetchAndDisplayStatus();
|
|
732
|
+
console.log();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
// Output error based on format
|
|
737
|
+
const format = getOutputFormat(options);
|
|
738
|
+
if (format === 'text') {
|
|
739
|
+
console.log();
|
|
740
|
+
if (error instanceof Error) {
|
|
741
|
+
ui.error(error.message);
|
|
742
|
+
if (error.message.includes('authentication') || error.message.includes('token')) {
|
|
743
|
+
console.log();
|
|
744
|
+
console.log(`Run: sembix login${options.profile ? ` --profile ${options.profile}` : ''}`);
|
|
745
|
+
}
|
|
746
|
+
else if (error.message.includes('404') || error.message.includes('not found')) {
|
|
747
|
+
console.log();
|
|
748
|
+
console.log('Workflow run not found. Check the workflow ID and run ID.');
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
ui.error('An unexpected error occurred');
|
|
753
|
+
console.error(error);
|
|
754
|
+
}
|
|
755
|
+
console.log();
|
|
756
|
+
process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
outputError(error, 4);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
export async function workflowRunListCommand(options) {
|
|
764
|
+
try {
|
|
765
|
+
// Determine output format
|
|
766
|
+
const format = getOutputFormat(options);
|
|
767
|
+
// Show UI elements only in TEXT mode
|
|
768
|
+
console.log();
|
|
769
|
+
ui.banner('List Workflow Runs');
|
|
770
|
+
console.log();
|
|
771
|
+
// Load profile and validate authentication
|
|
772
|
+
const { profile, tokens } = await getAuthenticatedProfile(options.profile);
|
|
773
|
+
// Create API client
|
|
774
|
+
const apiClient = new StudioApiClient(profile, tokens);
|
|
775
|
+
// Build search parameters
|
|
776
|
+
const searchParams = {
|
|
777
|
+
limit: options.limit || 10,
|
|
778
|
+
offset: options.offset || 0,
|
|
779
|
+
sortBy: options.sortBy || 'createdAt',
|
|
780
|
+
sortOrder: options.sortOrder || 'DESC'
|
|
781
|
+
};
|
|
782
|
+
// Default behavior: exclude subflows (only show top-level runs)
|
|
783
|
+
// Users can opt-in to see subflows with --include-subflows
|
|
784
|
+
// Or specify a specific parent with --parent-id
|
|
785
|
+
if (options.parentId !== undefined) {
|
|
786
|
+
// Explicit parent ID specified (could be 'NULL' or a specific ID)
|
|
787
|
+
searchParams.parentId = options.parentId;
|
|
788
|
+
}
|
|
789
|
+
else if (!options.includeSubflows) {
|
|
790
|
+
// Default: only show top-level runs (parent_id IS NULL)
|
|
791
|
+
searchParams.parentId = 'NULL';
|
|
792
|
+
}
|
|
793
|
+
// If includeSubflows is true, don't set parentId at all (shows everything)
|
|
794
|
+
if (options.workflowId)
|
|
795
|
+
searchParams.workflowId = options.workflowId;
|
|
796
|
+
if (options.projectId)
|
|
797
|
+
searchParams.projectId = options.projectId;
|
|
798
|
+
if (options.workflowTemplateId)
|
|
799
|
+
searchParams.workflowTemplateId = options.workflowTemplateId;
|
|
800
|
+
if (options.status)
|
|
801
|
+
searchParams.status = options.status;
|
|
802
|
+
if (options.currentUser)
|
|
803
|
+
searchParams.currentUser = true;
|
|
804
|
+
if (options.workspaceId)
|
|
805
|
+
searchParams.workspaceId = options.workspaceId;
|
|
806
|
+
if (options.workspaceName)
|
|
807
|
+
searchParams.workspaceName = options.workspaceName;
|
|
808
|
+
if (options.issueId)
|
|
809
|
+
searchParams.issueId = options.issueId;
|
|
810
|
+
if (options.issueKey)
|
|
811
|
+
searchParams.issueKey = options.issueKey;
|
|
812
|
+
// Fetch workflow runs
|
|
813
|
+
const spinner = ui.spinner('Fetching workflow runs...');
|
|
814
|
+
spinner.start();
|
|
815
|
+
const response = await apiClient.searchWorkflowRuns(searchParams);
|
|
816
|
+
spinner.stop();
|
|
817
|
+
// Output results based on format
|
|
818
|
+
outputSuccess(response, { format });
|
|
819
|
+
// TEXT mode: human-readable output
|
|
820
|
+
console.log();
|
|
821
|
+
if (response.runs.length === 0) {
|
|
822
|
+
console.log('No workflow runs found.');
|
|
823
|
+
console.log();
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
// Display table header
|
|
827
|
+
console.log('─'.repeat(120));
|
|
828
|
+
console.log(`${'Run ID'.padEnd(40)} ${'Workflow ID'.padEnd(30)} ${'Status'.padEnd(12)} ${'Progress'.padEnd(10)} ${'Created At'.padEnd(20)}`);
|
|
829
|
+
console.log('─'.repeat(120));
|
|
830
|
+
// Display each run
|
|
831
|
+
for (const run of response.runs) {
|
|
832
|
+
const runId = run.runId.padEnd(40);
|
|
833
|
+
const workflowId = (run.workflowId || 'N/A').padEnd(30);
|
|
834
|
+
// Color-code status
|
|
835
|
+
let status = run.status.padEnd(12);
|
|
836
|
+
if (run.status === 'COMPLETED') {
|
|
837
|
+
status = `\x1b[32m${run.status.padEnd(12)}\x1b[0m`;
|
|
838
|
+
}
|
|
839
|
+
else if (run.status === 'FAILED') {
|
|
840
|
+
status = `\x1b[31m${run.status.padEnd(12)}\x1b[0m`;
|
|
841
|
+
}
|
|
842
|
+
else if (run.status === 'IN_PROGRESS') {
|
|
843
|
+
status = `\x1b[33m${run.status.padEnd(12)}\x1b[0m`;
|
|
844
|
+
}
|
|
845
|
+
const progress = `${run.progress || 0}%`.padEnd(10);
|
|
846
|
+
const createdAt = new Date(run.createdAt).toLocaleString().padEnd(20);
|
|
847
|
+
console.log(`${runId} ${workflowId} ${status} ${progress} ${createdAt}`);
|
|
848
|
+
}
|
|
849
|
+
console.log('─'.repeat(120));
|
|
850
|
+
console.log();
|
|
851
|
+
// Display pagination info
|
|
852
|
+
const start = response.offset + 1;
|
|
853
|
+
const end = Math.min(response.offset + response.limit, response.totalCount);
|
|
854
|
+
console.log(`Showing ${start}-${end} of ${response.totalCount} results`);
|
|
855
|
+
console.log();
|
|
856
|
+
// Show next page hint if there are more results
|
|
857
|
+
if (end < response.totalCount) {
|
|
858
|
+
const nextOffset = response.offset + response.limit;
|
|
859
|
+
console.log(`To see more results, use: --offset ${nextOffset}`);
|
|
860
|
+
console.log();
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
catch (error) {
|
|
864
|
+
// Output error based on format
|
|
865
|
+
const format = getOutputFormat(options);
|
|
866
|
+
if (format === 'text') {
|
|
867
|
+
console.log();
|
|
868
|
+
if (error instanceof Error) {
|
|
869
|
+
ui.error(error.message);
|
|
870
|
+
if (error.message.includes('authentication') || error.message.includes('token')) {
|
|
871
|
+
console.log();
|
|
872
|
+
console.log(`Run: sembix login${options.profile ? ` --profile ${options.profile}` : ''}`);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
ui.error('An unexpected error occurred');
|
|
877
|
+
console.error(error);
|
|
878
|
+
}
|
|
879
|
+
console.log();
|
|
880
|
+
process.exit(1);
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
outputError(error, 1);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
export async function workflowStopCommand(options) {
|
|
888
|
+
try {
|
|
889
|
+
// Determine output format
|
|
890
|
+
const format = getOutputFormat(options);
|
|
891
|
+
// Validate required options
|
|
892
|
+
if (!options.projectId) {
|
|
893
|
+
throw new Error('Project ID is required. Use --project-id flag.');
|
|
894
|
+
}
|
|
895
|
+
if (!options.workflowId && !options.workflowName) {
|
|
896
|
+
throw new Error('Either --workflow-id or --workflow-name is required.');
|
|
897
|
+
}
|
|
898
|
+
if (options.workflowId && options.workflowName) {
|
|
899
|
+
throw new Error('Cannot specify both --workflow-id and --workflow-name. Choose one.');
|
|
900
|
+
}
|
|
901
|
+
if (!options.runId) {
|
|
902
|
+
throw new Error('Run ID is required. Use --run-id flag.');
|
|
903
|
+
}
|
|
904
|
+
// Show UI elements only in TEXT mode
|
|
905
|
+
console.log();
|
|
906
|
+
ui.banner('Stop Workflow Run');
|
|
907
|
+
console.log();
|
|
908
|
+
// Load profile and validate authentication
|
|
909
|
+
const { profile, tokens } = await getAuthenticatedProfile(options.profile);
|
|
910
|
+
// Create API client
|
|
911
|
+
const apiClient = new StudioApiClient(profile, tokens);
|
|
912
|
+
// Resolve workflow ID from name if needed
|
|
913
|
+
let workflowId = options.workflowId;
|
|
914
|
+
if (options.workflowName) {
|
|
915
|
+
const lookupSpinner = ui.spinner(`Looking up workflow by name: ${options.workflowName}...`);
|
|
916
|
+
lookupSpinner.start();
|
|
917
|
+
const workflow = await apiClient.findWorkflowByName(options.projectId, options.workflowName);
|
|
918
|
+
lookupSpinner.stop();
|
|
919
|
+
if (!workflow) {
|
|
920
|
+
throw new Error(`Workflow with name "${options.workflowName}" not found in project ${options.projectId}`);
|
|
921
|
+
}
|
|
922
|
+
workflowId = workflow.workflowId;
|
|
923
|
+
console.log(`Found workflow: ${ui.highlight(workflow.name)} (${workflowId})`);
|
|
924
|
+
console.log();
|
|
925
|
+
}
|
|
926
|
+
// Stop workflow run
|
|
927
|
+
const spinner = ui.spinner('Stopping workflow run...');
|
|
928
|
+
spinner.start();
|
|
929
|
+
await apiClient.stopWorkflowRun(options.projectId, workflowId, options.runId);
|
|
930
|
+
spinner.stop();
|
|
931
|
+
// Output results based on format
|
|
932
|
+
const result = {
|
|
933
|
+
success: true,
|
|
934
|
+
runId: options.runId,
|
|
935
|
+
workflowId: workflowId,
|
|
936
|
+
projectId: options.projectId
|
|
937
|
+
};
|
|
938
|
+
outputSuccess(result, { format });
|
|
939
|
+
// TEXT mode: human-readable output
|
|
940
|
+
console.log();
|
|
941
|
+
ui.success('Workflow run stopped successfully!');
|
|
942
|
+
console.log();
|
|
943
|
+
console.log(`Run ID: ${ui.highlight(options.runId)}`);
|
|
944
|
+
console.log();
|
|
945
|
+
console.log(`Check final status with: sembix workflow status --workflow-id ${workflowId} --run-id ${options.runId}`);
|
|
946
|
+
console.log();
|
|
947
|
+
}
|
|
948
|
+
catch (error) {
|
|
949
|
+
// Output error based on format
|
|
950
|
+
const format = getOutputFormat(options);
|
|
951
|
+
if (format === 'text') {
|
|
952
|
+
console.log();
|
|
953
|
+
if (error instanceof Error) {
|
|
954
|
+
ui.error(error.message);
|
|
955
|
+
if (error.message.includes('authentication') || error.message.includes('token')) {
|
|
956
|
+
console.log();
|
|
957
|
+
console.log(`Run: sembix login${options.profile ? ` --profile ${options.profile}` : ''}`);
|
|
958
|
+
}
|
|
959
|
+
else if (error.message.includes('404') || error.message.includes('not found')) {
|
|
960
|
+
console.log();
|
|
961
|
+
console.log('Workflow run not found. Check the project ID, workflow ID, and run ID.');
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
ui.error('An unexpected error occurred');
|
|
966
|
+
console.error(error);
|
|
967
|
+
}
|
|
968
|
+
console.log();
|
|
969
|
+
process.exit(1);
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
outputError(error, 4);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
export async function workflowRunShowCommand(options) {
|
|
977
|
+
try {
|
|
978
|
+
// Determine output format
|
|
979
|
+
const format = getOutputFormat(options);
|
|
980
|
+
// Validate required options
|
|
981
|
+
if (!options.workflowId) {
|
|
982
|
+
throw new Error('Workflow ID is required. Use --workflow-id flag.');
|
|
983
|
+
}
|
|
984
|
+
if (!options.runId) {
|
|
985
|
+
throw new Error('Run ID is required. Use --run-id flag.');
|
|
986
|
+
}
|
|
987
|
+
// Watch mode not supported in JSON/PRETTY output
|
|
988
|
+
if (options.watch && format !== 'text') {
|
|
989
|
+
throw new Error('Watch mode (--watch) is only supported with --output text');
|
|
990
|
+
}
|
|
991
|
+
// Show UI elements only in TEXT mode
|
|
992
|
+
console.log();
|
|
993
|
+
ui.banner('Workflow Run Details');
|
|
994
|
+
console.log();
|
|
995
|
+
// Load profile and validate authentication
|
|
996
|
+
const { profile, tokens } = await getAuthenticatedProfile(options.profile);
|
|
997
|
+
// Create API client
|
|
998
|
+
const apiClient = new StudioApiClient(profile, tokens);
|
|
999
|
+
if (options.watch) {
|
|
1000
|
+
// Watch mode: poll every 5 seconds until terminal status
|
|
1001
|
+
let previousRun = null;
|
|
1002
|
+
let iteration = 0;
|
|
1003
|
+
let isTerminal = false;
|
|
1004
|
+
// Hide cursor to prevent flicker during updates
|
|
1005
|
+
ui.hideCursor();
|
|
1006
|
+
try {
|
|
1007
|
+
while (!isTerminal) {
|
|
1008
|
+
// Restore cursor to saved position (after first iteration)
|
|
1009
|
+
if (iteration > 0) {
|
|
1010
|
+
ui.restoreCursorPosition();
|
|
1011
|
+
}
|
|
1012
|
+
// Save cursor position here (right after banner, before dynamic content)
|
|
1013
|
+
ui.saveCursorPosition();
|
|
1014
|
+
// Show watch header with timestamp
|
|
1015
|
+
console.log(ui.dim('Watching workflow run details... (Ctrl+C to stop)'));
|
|
1016
|
+
console.log(ui.dim(`Last updated: ${new Date().toLocaleTimeString()} (refreshes every 5s)`));
|
|
1017
|
+
console.log();
|
|
1018
|
+
// Clear everything below this point
|
|
1019
|
+
ui.clearFromCursorDown();
|
|
1020
|
+
// Fetch latest run
|
|
1021
|
+
const response = await apiClient.getWorkflowRun(options.workflowId, options.runId);
|
|
1022
|
+
// Display with change highlighting (with truncation for watch mode)
|
|
1023
|
+
formatWorkflowRunOutput(response.run, previousRun, true);
|
|
1024
|
+
previousRun = response.run;
|
|
1025
|
+
isTerminal = ['COMPLETED', 'FAILED', 'STOPPED'].includes(response.run.status);
|
|
1026
|
+
if (!isTerminal) {
|
|
1027
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
1028
|
+
}
|
|
1029
|
+
iteration++;
|
|
1030
|
+
}
|
|
1031
|
+
// Show cursor again when done
|
|
1032
|
+
ui.showCursor();
|
|
1033
|
+
console.log();
|
|
1034
|
+
ui.success('Workflow execution completed');
|
|
1035
|
+
console.log();
|
|
1036
|
+
}
|
|
1037
|
+
catch (error) {
|
|
1038
|
+
// Ensure cursor is restored even on error
|
|
1039
|
+
ui.showCursor();
|
|
1040
|
+
throw error;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
else {
|
|
1044
|
+
// Single fetch
|
|
1045
|
+
const spinner = ui.spinner(`Fetching workflow run ${options.runId}...`);
|
|
1046
|
+
spinner.start();
|
|
1047
|
+
const response = await apiClient.getWorkflowRun(options.workflowId, options.runId);
|
|
1048
|
+
spinner.stop();
|
|
1049
|
+
// Output results based on format
|
|
1050
|
+
outputSuccess(response, { format });
|
|
1051
|
+
// TEXT mode: human-readable output
|
|
1052
|
+
formatWorkflowRunOutput(response.run);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
catch (error) {
|
|
1056
|
+
// Output error based on format
|
|
1057
|
+
const format = getOutputFormat(options);
|
|
1058
|
+
if (format === 'text') {
|
|
1059
|
+
console.log();
|
|
1060
|
+
if (error instanceof Error) {
|
|
1061
|
+
ui.error(error.message);
|
|
1062
|
+
if (error.message.includes('authentication') || error.message.includes('token')) {
|
|
1063
|
+
console.log();
|
|
1064
|
+
console.log(`Run: sembix login${options.profile ? ` --profile ${options.profile}` : ''}`);
|
|
1065
|
+
}
|
|
1066
|
+
else if (error.message.includes('404') || error.message.includes('not found')) {
|
|
1067
|
+
console.log();
|
|
1068
|
+
console.log('Workflow run not found. Check the workflow ID and run ID.');
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
else {
|
|
1072
|
+
ui.error('An unexpected error occurred');
|
|
1073
|
+
console.error(error);
|
|
1074
|
+
}
|
|
1075
|
+
console.log();
|
|
1076
|
+
process.exit(1);
|
|
1077
|
+
}
|
|
1078
|
+
else {
|
|
1079
|
+
outputError(error, 4);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
// ============================================================================
|
|
1084
|
+
// Helper: Format Workflow Run Output
|
|
1085
|
+
// ============================================================================
|
|
1086
|
+
function formatWorkflowRunOutput(run, previousRun, watchMode = false) {
|
|
1087
|
+
console.log();
|
|
1088
|
+
ui.section('Workflow Run Details');
|
|
1089
|
+
console.log();
|
|
1090
|
+
// Basic info - highlight status if changed
|
|
1091
|
+
const statusChanged = previousRun && previousRun.status !== run.status;
|
|
1092
|
+
const statusDisplay = statusChanged ? ui.changed(run.status) : run.status;
|
|
1093
|
+
ui.table({
|
|
1094
|
+
'Run ID': run.runId,
|
|
1095
|
+
'Workflow ID': run.workflowId,
|
|
1096
|
+
'Project ID': run.projectId,
|
|
1097
|
+
'Status': statusDisplay,
|
|
1098
|
+
'Version': run.version?.toString() || 'N/A',
|
|
1099
|
+
});
|
|
1100
|
+
console.log();
|
|
1101
|
+
// MOST IMPORTANT: Summary (outcome and intent)
|
|
1102
|
+
if (run.summary) {
|
|
1103
|
+
// Highlight summary if changed
|
|
1104
|
+
const summaryChanged = previousRun && previousRun.summary !== run.summary;
|
|
1105
|
+
const sectionTitle = summaryChanged ? 'Summary (UPDATED)' : 'Summary';
|
|
1106
|
+
ui.section(sectionTitle);
|
|
1107
|
+
// Truncate summary in watch mode if it's very long
|
|
1108
|
+
const summaryToDisplay = watchMode
|
|
1109
|
+
? ui.truncateForWatch(run.summary, 500)
|
|
1110
|
+
: run.summary;
|
|
1111
|
+
console.log(summaryToDisplay);
|
|
1112
|
+
console.log();
|
|
1113
|
+
}
|
|
1114
|
+
// IMPORTANT: Final step outputs (outcome)
|
|
1115
|
+
if (run.steps && run.steps.length > 0) {
|
|
1116
|
+
// Find the last completed step with outputs
|
|
1117
|
+
const stepsWithOutputs = run.steps.filter(s => s.outputs && s.outputs.length > 0);
|
|
1118
|
+
if (stepsWithOutputs.length > 0) {
|
|
1119
|
+
const lastStepWithOutputs = stepsWithOutputs[stepsWithOutputs.length - 1];
|
|
1120
|
+
ui.section(`Outputs from "${lastStepWithOutputs.name}"`);
|
|
1121
|
+
console.log();
|
|
1122
|
+
for (const output of lastStepWithOutputs.outputs) {
|
|
1123
|
+
console.log(`${ui.highlight(output.name)}:`);
|
|
1124
|
+
// In watch mode, truncate long outputs for readability
|
|
1125
|
+
if (watchMode) {
|
|
1126
|
+
console.log(ui.formatValueForWatch(output.value, 300));
|
|
1127
|
+
}
|
|
1128
|
+
else {
|
|
1129
|
+
if (typeof output.value === 'object') {
|
|
1130
|
+
console.log(JSON.stringify(output.value, null, 2));
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
console.log(output.value);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
console.log();
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
// Extracted entities (if available)
|
|
1141
|
+
if (run.summaryExtractedEntities && Object.keys(run.summaryExtractedEntities).length > 0) {
|
|
1142
|
+
ui.section('Extracted Entities');
|
|
1143
|
+
// Truncate in watch mode
|
|
1144
|
+
if (watchMode) {
|
|
1145
|
+
console.log(ui.formatValueForWatch(run.summaryExtractedEntities, 300));
|
|
1146
|
+
}
|
|
1147
|
+
else {
|
|
1148
|
+
console.log(JSON.stringify(run.summaryExtractedEntities, null, 2));
|
|
1149
|
+
}
|
|
1150
|
+
console.log();
|
|
1151
|
+
}
|
|
1152
|
+
// Timestamps
|
|
1153
|
+
ui.section('Execution Timeline');
|
|
1154
|
+
ui.table({
|
|
1155
|
+
'Created': new Date(run.createdAt).toLocaleString(),
|
|
1156
|
+
'Started': run.startedAt ? new Date(run.startedAt).toLocaleString() : 'N/A',
|
|
1157
|
+
'Completed': run.completedAt ? new Date(run.completedAt).toLocaleString() : 'N/A',
|
|
1158
|
+
});
|
|
1159
|
+
// Progress - highlight if changed
|
|
1160
|
+
if (run.currentStepId !== undefined || run.progress !== undefined) {
|
|
1161
|
+
console.log();
|
|
1162
|
+
if (run.progress !== undefined) {
|
|
1163
|
+
const progressChanged = previousRun && previousRun.progress !== run.progress;
|
|
1164
|
+
const progressDisplay = progressChanged ? ui.changed(`${run.progress}%`) : `${run.progress}%`;
|
|
1165
|
+
console.log(`Progress: ${progressDisplay}`);
|
|
1166
|
+
}
|
|
1167
|
+
if (run.currentStepId) {
|
|
1168
|
+
const stepChanged = previousRun && previousRun.currentStepId !== run.currentStepId;
|
|
1169
|
+
const stepDisplay = stepChanged ? ui.changed(run.currentStepId) : run.currentStepId;
|
|
1170
|
+
console.log(`Current Step: ${stepDisplay}`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
// Error info (if any)
|
|
1174
|
+
if (run.error) {
|
|
1175
|
+
console.log();
|
|
1176
|
+
ui.error(`Error: ${run.error}`);
|
|
1177
|
+
}
|
|
1178
|
+
// Steps summary (overview) - highlight state changes
|
|
1179
|
+
if (run.steps && run.steps.length > 0) {
|
|
1180
|
+
console.log();
|
|
1181
|
+
ui.section(`Steps Overview (${run.steps.length})`);
|
|
1182
|
+
console.log();
|
|
1183
|
+
for (const step of run.steps) {
|
|
1184
|
+
// Check if this step's state changed
|
|
1185
|
+
const previousStep = previousRun?.steps?.find(s => s.stepId === step.stepId);
|
|
1186
|
+
const stateChanged = previousStep && previousStep.state !== step.state;
|
|
1187
|
+
const statusIcon = step.state === 'Complete' ? '✓' :
|
|
1188
|
+
step.state === 'Failed' ? '✗' :
|
|
1189
|
+
step.state === 'Pending' ? '○' : '·';
|
|
1190
|
+
const stepStateDisplay = stateChanged ? ui.changed(step.state) : step.state;
|
|
1191
|
+
console.log(` ${statusIcon} ${step.name} [${stepStateDisplay}]`);
|
|
1192
|
+
if (step.errors && step.errors.length > 0) {
|
|
1193
|
+
for (const error of step.errors) {
|
|
1194
|
+
console.log(` Error: ${ui.dim(error)}`);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
console.log();
|
|
1200
|
+
}
|
|
1201
|
+
//# sourceMappingURL=workflow.js.map
|