@sembix/cli 1.3.0 → 1.4.1

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 (204) hide show
  1. package/COMMANDS.md +1522 -0
  2. package/QUICKSTART.md +829 -0
  3. package/README.md +1949 -285
  4. package/USAGE-EXAMPLES.md +872 -0
  5. package/dist/commands/configure.d.ts.map +1 -1
  6. package/dist/commands/configure.js +172 -2
  7. package/dist/commands/configure.js.map +1 -1
  8. package/dist/commands/index.d.ts +10 -0
  9. package/dist/commands/index.d.ts.map +1 -0
  10. package/dist/commands/index.js +11 -0
  11. package/dist/commands/index.js.map +1 -0
  12. package/dist/commands/login.d.ts +19 -0
  13. package/dist/commands/login.d.ts.map +1 -0
  14. package/dist/commands/login.js +118 -0
  15. package/dist/commands/login.js.map +1 -0
  16. package/dist/commands/logout.d.ts +21 -0
  17. package/dist/commands/logout.d.ts.map +1 -0
  18. package/dist/commands/logout.js +66 -0
  19. package/dist/commands/logout.js.map +1 -0
  20. package/dist/commands/profile-project.d.ts +14 -0
  21. package/dist/commands/profile-project.d.ts.map +1 -0
  22. package/dist/commands/profile-project.js +123 -0
  23. package/dist/commands/profile-project.js.map +1 -0
  24. package/dist/commands/profile.d.ts +26 -0
  25. package/dist/commands/profile.d.ts.map +1 -0
  26. package/dist/commands/profile.js +177 -0
  27. package/dist/commands/profile.js.map +1 -0
  28. package/dist/commands/project.d.ts +16 -0
  29. package/dist/commands/project.d.ts.map +1 -0
  30. package/dist/commands/project.js +153 -0
  31. package/dist/commands/project.js.map +1 -0
  32. package/dist/commands/setup.js +1 -1
  33. package/dist/commands/setup.js.map +1 -1
  34. package/dist/commands/update.js +2 -2
  35. package/dist/commands/update.js.map +1 -1
  36. package/dist/commands/workflow.d.ts +91 -0
  37. package/dist/commands/workflow.d.ts.map +1 -0
  38. package/dist/commands/workflow.js +1201 -0
  39. package/dist/commands/workflow.js.map +1 -0
  40. package/dist/config-schema.d.ts +23 -0
  41. package/dist/config-schema.d.ts.map +1 -1
  42. package/dist/config-schema.js +21 -1
  43. package/dist/config-schema.js.map +1 -1
  44. package/dist/config.d.ts +4 -0
  45. package/dist/config.d.ts.map +1 -1
  46. package/dist/config.js +23 -2
  47. package/dist/config.js.map +1 -1
  48. package/dist/index.js +318 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/prompts/project-selection.d.ts +8 -0
  51. package/dist/prompts/project-selection.d.ts.map +1 -0
  52. package/dist/prompts/project-selection.js +132 -0
  53. package/dist/prompts/project-selection.js.map +1 -0
  54. package/dist/prompts/workflow-inputs.d.ts +10 -0
  55. package/dist/prompts/workflow-inputs.d.ts.map +1 -0
  56. package/dist/prompts/workflow-inputs.js +71 -0
  57. package/dist/prompts/workflow-inputs.js.map +1 -0
  58. package/dist/prompts/workflow-selection.d.ts +8 -0
  59. package/dist/prompts/workflow-selection.d.ts.map +1 -0
  60. package/dist/prompts/workflow-selection.js +147 -0
  61. package/dist/prompts/workflow-selection.js.map +1 -0
  62. package/dist/sembix-cli-1.4.1.tgz +0 -0
  63. package/dist/services/cognito-auth.d.ts +92 -0
  64. package/dist/services/cognito-auth.d.ts.map +1 -0
  65. package/dist/services/cognito-auth.js +319 -0
  66. package/dist/services/cognito-auth.js.map +1 -0
  67. package/dist/services/studio-api-client.d.ts +127 -0
  68. package/dist/services/studio-api-client.d.ts.map +1 -0
  69. package/dist/services/studio-api-client.js +291 -0
  70. package/dist/services/studio-api-client.js.map +1 -0
  71. package/dist/types/studio.d.ts +82 -0
  72. package/dist/types/studio.d.ts.map +1 -0
  73. package/dist/types/studio.js +7 -0
  74. package/dist/types/studio.js.map +1 -0
  75. package/dist/types.d.ts +283 -0
  76. package/dist/types.d.ts.map +1 -1
  77. package/dist/types.js +5 -0
  78. package/dist/types.js.map +1 -1
  79. package/dist/utils/browser-auth.d.ts +45 -0
  80. package/dist/utils/browser-auth.d.ts.map +1 -0
  81. package/dist/utils/browser-auth.js +168 -0
  82. package/dist/utils/browser-auth.js.map +1 -0
  83. package/dist/utils/cognito-auth.d.ts +3 -0
  84. package/dist/utils/cognito-auth.d.ts.map +1 -0
  85. package/dist/utils/cognito-auth.js +3 -0
  86. package/dist/utils/cognito-auth.js.map +1 -0
  87. package/dist/utils/config-file.d.ts +40 -0
  88. package/dist/utils/config-file.d.ts.map +1 -1
  89. package/dist/utils/config-file.js +158 -4
  90. package/dist/utils/config-file.js.map +1 -1
  91. package/dist/utils/environment.d.ts +22 -0
  92. package/dist/utils/environment.d.ts.map +1 -0
  93. package/dist/utils/environment.js +39 -0
  94. package/dist/utils/environment.js.map +1 -0
  95. package/dist/utils/error-handler.d.ts +53 -0
  96. package/dist/utils/error-handler.d.ts.map +1 -0
  97. package/dist/utils/error-handler.js +174 -0
  98. package/dist/utils/error-handler.js.map +1 -0
  99. package/dist/utils/fuzzy-match.d.ts +31 -0
  100. package/dist/utils/fuzzy-match.d.ts.map +1 -0
  101. package/dist/utils/fuzzy-match.js +138 -0
  102. package/dist/utils/fuzzy-match.js.map +1 -0
  103. package/dist/utils/github.d.ts +2 -2
  104. package/dist/utils/github.d.ts.map +1 -1
  105. package/dist/utils/github.js +30 -10
  106. package/dist/utils/github.js.map +1 -1
  107. package/dist/utils/input-parser.d.ts +14 -0
  108. package/dist/utils/input-parser.d.ts.map +1 -0
  109. package/dist/utils/input-parser.js +34 -0
  110. package/dist/utils/input-parser.js.map +1 -0
  111. package/dist/utils/output.d.ts +55 -0
  112. package/dist/utils/output.d.ts.map +1 -0
  113. package/dist/utils/output.js +80 -0
  114. package/dist/utils/output.js.map +1 -0
  115. package/dist/utils/recent-workflows.d.ts +37 -0
  116. package/dist/utils/recent-workflows.d.ts.map +1 -0
  117. package/dist/utils/recent-workflows.js +172 -0
  118. package/dist/utils/recent-workflows.js.map +1 -0
  119. package/dist/utils/studio-api-client.d.ts +3 -0
  120. package/dist/utils/studio-api-client.d.ts.map +1 -0
  121. package/dist/utils/studio-api-client.js +3 -0
  122. package/dist/utils/studio-api-client.js.map +1 -0
  123. package/dist/utils/studio-api.d.ts +53 -0
  124. package/dist/utils/studio-api.d.ts.map +1 -0
  125. package/dist/utils/studio-api.js +102 -0
  126. package/dist/utils/studio-api.js.map +1 -0
  127. package/dist/utils/studio-config.d.ts +74 -0
  128. package/dist/utils/studio-config.d.ts.map +1 -0
  129. package/dist/utils/studio-config.js +213 -0
  130. package/dist/utils/studio-config.js.map +1 -0
  131. package/dist/utils/token-manager.d.ts +4 -0
  132. package/dist/utils/token-manager.d.ts.map +1 -0
  133. package/dist/utils/token-manager.js +3 -0
  134. package/dist/utils/token-manager.js.map +1 -0
  135. package/dist/utils/ui.d.ts +55 -1
  136. package/dist/utils/ui.d.ts.map +1 -1
  137. package/dist/utils/ui.js +151 -2
  138. package/dist/utils/ui.js.map +1 -1
  139. package/package.json +4 -1
  140. package/dist/__tests__/config-schema.test.d.ts +0 -2
  141. package/dist/__tests__/config-schema.test.d.ts.map +0 -1
  142. package/dist/__tests__/config-schema.test.js +0 -471
  143. package/dist/__tests__/config-schema.test.js.map +0 -1
  144. package/dist/__tests__/config.test.d.ts +0 -2
  145. package/dist/__tests__/config.test.d.ts.map +0 -1
  146. package/dist/__tests__/config.test.js +0 -75
  147. package/dist/__tests__/config.test.js.map +0 -1
  148. package/dist/__tests__/integration/configure.test.d.ts +0 -2
  149. package/dist/__tests__/integration/configure.test.d.ts.map +0 -1
  150. package/dist/__tests__/integration/configure.test.js +0 -247
  151. package/dist/__tests__/integration/configure.test.js.map +0 -1
  152. package/dist/__tests__/integration/fixtures/configs.d.ts +0 -477
  153. package/dist/__tests__/integration/fixtures/configs.d.ts.map +0 -1
  154. package/dist/__tests__/integration/fixtures/configs.js +0 -175
  155. package/dist/__tests__/integration/fixtures/configs.js.map +0 -1
  156. package/dist/__tests__/integration/helpers/cli-runner.d.ts +0 -63
  157. package/dist/__tests__/integration/helpers/cli-runner.d.ts.map +0 -1
  158. package/dist/__tests__/integration/helpers/cli-runner.js +0 -152
  159. package/dist/__tests__/integration/helpers/cli-runner.js.map +0 -1
  160. package/dist/__tests__/integration/helpers/command-runner.d.ts +0 -53
  161. package/dist/__tests__/integration/helpers/command-runner.d.ts.map +0 -1
  162. package/dist/__tests__/integration/helpers/command-runner.js +0 -117
  163. package/dist/__tests__/integration/helpers/command-runner.js.map +0 -1
  164. package/dist/__tests__/integration/studio-create.test.d.ts +0 -2
  165. package/dist/__tests__/integration/studio-create.test.d.ts.map +0 -1
  166. package/dist/__tests__/integration/studio-create.test.js +0 -209
  167. package/dist/__tests__/integration/studio-create.test.js.map +0 -1
  168. package/dist/__tests__/integration/studio-update.test.d.ts +0 -2
  169. package/dist/__tests__/integration/studio-update.test.d.ts.map +0 -1
  170. package/dist/__tests__/integration/studio-update.test.js +0 -166
  171. package/dist/__tests__/integration/studio-update.test.js.map +0 -1
  172. package/dist/commands/__tests__/configure.test.d.ts +0 -2
  173. package/dist/commands/__tests__/configure.test.d.ts.map +0 -1
  174. package/dist/commands/__tests__/configure.test.js +0 -229
  175. package/dist/commands/__tests__/configure.test.js.map +0 -1
  176. package/dist/prompts/__tests__/environment-setup.test.d.ts +0 -2
  177. package/dist/prompts/__tests__/environment-setup.test.d.ts.map +0 -1
  178. package/dist/prompts/__tests__/environment-setup.test.js +0 -206
  179. package/dist/prompts/__tests__/environment-setup.test.js.map +0 -1
  180. package/dist/prompts/__tests__/hub-integration.test.d.ts +0 -2
  181. package/dist/prompts/__tests__/hub-integration.test.d.ts.map +0 -1
  182. package/dist/prompts/__tests__/hub-integration.test.js +0 -126
  183. package/dist/prompts/__tests__/hub-integration.test.js.map +0 -1
  184. package/dist/prompts/__tests__/prompt-helpers.test.d.ts +0 -2
  185. package/dist/prompts/__tests__/prompt-helpers.test.d.ts.map +0 -1
  186. package/dist/prompts/__tests__/prompt-helpers.test.js +0 -235
  187. package/dist/prompts/__tests__/prompt-helpers.test.js.map +0 -1
  188. package/dist/sembix-cli-1.3.0.tgz +0 -0
  189. package/dist/utils/__tests__/config-file.test.d.ts +0 -2
  190. package/dist/utils/__tests__/config-file.test.d.ts.map +0 -1
  191. package/dist/utils/__tests__/config-file.test.js +0 -218
  192. package/dist/utils/__tests__/config-file.test.js.map +0 -1
  193. package/dist/utils/__tests__/config-loader.test.d.ts +0 -2
  194. package/dist/utils/__tests__/config-loader.test.d.ts.map +0 -1
  195. package/dist/utils/__tests__/config-loader.test.js +0 -325
  196. package/dist/utils/__tests__/config-loader.test.js.map +0 -1
  197. package/dist/utils/__tests__/github.test.d.ts +0 -2
  198. package/dist/utils/__tests__/github.test.d.ts.map +0 -1
  199. package/dist/utils/__tests__/github.test.js +0 -282
  200. package/dist/utils/__tests__/github.test.js.map +0 -1
  201. package/dist/utils/__tests__/ui.test.d.ts +0 -2
  202. package/dist/utils/__tests__/ui.test.d.ts.map +0 -1
  203. package/dist/utils/__tests__/ui.test.js +0 -256
  204. 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