@output.ai/cli 0.3.1-dev.pr156.0 → 0.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.
Files changed (48) hide show
  1. package/README.md +2 -28
  2. package/dist/api/generated/api.d.ts +35 -59
  3. package/dist/api/generated/api.js +4 -13
  4. package/dist/assets/docker/docker-compose-dev.yml +2 -2
  5. package/dist/commands/workflow/debug.d.ts +2 -8
  6. package/dist/commands/workflow/debug.js +24 -164
  7. package/dist/commands/workflow/debug.spec.js +36 -0
  8. package/dist/commands/workflow/generate.js +3 -10
  9. package/dist/commands/workflow/generate.spec.js +6 -4
  10. package/dist/commands/workflow/{output.d.ts → result.d.ts} +1 -1
  11. package/dist/commands/workflow/{output.js → result.js} +8 -8
  12. package/dist/commands/workflow/result.test.js +23 -0
  13. package/dist/commands/workflow/start.js +1 -1
  14. package/dist/services/coding_agents.js +30 -0
  15. package/dist/services/coding_agents.spec.js +36 -61
  16. package/dist/services/messages.d.ts +1 -0
  17. package/dist/services/messages.js +65 -1
  18. package/dist/services/trace_reader.d.ts +14 -0
  19. package/dist/services/trace_reader.js +67 -0
  20. package/dist/services/trace_reader.spec.d.ts +1 -0
  21. package/dist/services/trace_reader.spec.js +164 -0
  22. package/dist/services/workflow_generator.spec.d.ts +1 -0
  23. package/dist/services/workflow_generator.spec.js +77 -0
  24. package/dist/templates/agent_instructions/AGENTS.md.template +209 -19
  25. package/dist/templates/agent_instructions/agents/context_fetcher.md.template +82 -0
  26. package/dist/templates/agent_instructions/agents/prompt_writer.md.template +595 -0
  27. package/dist/templates/agent_instructions/agents/workflow_planner.md.template +13 -4
  28. package/dist/templates/agent_instructions/agents/workflow_quality.md.template +244 -0
  29. package/dist/templates/agent_instructions/commands/build_workflow.md.template +52 -9
  30. package/dist/templates/agent_instructions/commands/plan_workflow.md.template +4 -4
  31. package/dist/templates/project/package.json.template +2 -2
  32. package/dist/templates/project/src/simple/scenarios/question_ada_lovelace.json.template +3 -0
  33. package/dist/templates/workflow/scenarios/test_input.json.template +7 -0
  34. package/dist/types/trace.d.ts +161 -0
  35. package/dist/types/trace.js +18 -0
  36. package/dist/utils/date_formatter.d.ts +8 -0
  37. package/dist/utils/date_formatter.js +19 -0
  38. package/dist/utils/template.spec.js +6 -0
  39. package/dist/utils/trace_formatter.d.ts +11 -61
  40. package/dist/utils/trace_formatter.js +384 -239
  41. package/package.json +2 -2
  42. package/dist/commands/workflow/debug.test.js +0 -107
  43. package/dist/commands/workflow/output.test.js +0 -23
  44. package/dist/utils/s3_downloader.d.ts +0 -49
  45. package/dist/utils/s3_downloader.js +0 -154
  46. /package/dist/commands/workflow/{debug.test.d.ts → debug.spec.d.ts} +0 -0
  47. /package/dist/commands/workflow/{output.test.d.ts → result.test.d.ts} +0 -0
  48. /package/dist/templates/workflow/{prompt@v1.prompt.template → prompts/prompt@v1.prompt.template} +0 -0
package/README.md CHANGED
@@ -103,15 +103,6 @@ output workflow debug <workflowId>
103
103
 
104
104
  # Display trace in JSON format
105
105
  output workflow debug <workflowId> --format json
106
-
107
- # Prefer remote (S3) trace over local
108
- output workflow debug <workflowId> --remote
109
-
110
- # Save trace to a custom directory
111
- output workflow debug <workflowId> --download-dir ./my-traces
112
-
113
- # Force re-download of remote traces (bypass cache)
114
- output workflow debug <workflowId> --remote --force-download
115
106
  ```
116
107
 
117
108
  #### What It Does
@@ -123,30 +114,13 @@ The `debug` command retrieves and displays detailed execution traces for debuggi
123
114
  - Error details and stack traces
124
115
  - Performance metrics and durations
125
116
 
126
- #### Trace Sources
127
-
128
- The command automatically handles both local and remote traces:
129
- - **Local traces**: Stored on your machine when `TRACE_LOCAL_ON=true`
130
- - **Remote traces**: Stored in S3 when `TRACE_REMOTE_ON=true` (requires AWS credentials)
131
-
132
- By default, it tries local traces first for faster access, then falls back to remote if needed.
133
-
134
117
  #### Command Options
135
118
 
136
119
  - `--format, -f` - Output format: `text` (default, human-readable) or `json` (raw trace data)
137
- - `--remote, -r` - Prefer remote S3 trace even if local exists
138
- - `--download-dir, -d` - Directory for saving downloaded traces (default: `.output/traces`)
139
- - `--open, -o` - Save trace to file for viewing in external JSON viewer
140
- - `--force-download` - Force re-download from S3, bypassing local cache
141
120
 
142
- #### AWS Configuration
121
+ #### Environment Variables
143
122
 
144
- For remote traces, configure AWS credentials:
145
- ```bash
146
- export AWS_ACCESS_KEY_ID=your-access-key
147
- export AWS_SECRET_ACCESS_KEY=your-secret-key
148
- export AWS_REGION=us-east-1 # or your region
149
- ```
123
+ - `HOST_TRACE_PATH` - When running in Docker, this maps the container's trace log path to the host filesystem path. Set this to your project's `logs` directory (e.g., `${PWD}/logs`). Required for trace files to be accessible from the CLI when workflows run in containers.
150
124
 
151
125
  ### Generate a Workflow
152
126
 
@@ -32,6 +32,28 @@ export interface Workflow {
32
32
  inputSchema?: JSONSchema;
33
33
  outputSchema?: JSONSchema;
34
34
  }
35
+ /**
36
+ * File destinations for trace data
37
+ */
38
+ export type TraceInfoDestinations = {
39
+ /**
40
+ * Absolute path to local trace file, or null if not saved locally
41
+ * @nullable
42
+ */
43
+ local: string | null;
44
+ /**
45
+ * Remote trace location (e.g., S3 URI), or null if not saved remotely
46
+ * @nullable
47
+ */
48
+ remote: string | null;
49
+ };
50
+ /**
51
+ * An object with information about the trace generated by the execution
52
+ */
53
+ export interface TraceInfo {
54
+ /** File destinations for trace data */
55
+ destinations?: TraceInfoDestinations;
56
+ }
35
57
  export type PostWorkflowRunBody = {
36
58
  /** The name of the workflow to execute */
37
59
  workflowName: string;
@@ -42,19 +64,12 @@ export type PostWorkflowRunBody = {
42
64
  /** The name of the task queue to send the workflow to */
43
65
  taskQueue?: string;
44
66
  };
45
- /**
46
- * An object with information about the trace generated by the execution
47
- */
48
- export type PostWorkflowRun200Trace = {
49
- [key: string]: unknown;
50
- };
51
67
  export type PostWorkflowRun200 = {
52
68
  /** The workflow execution id */
53
69
  workflowId?: string;
54
70
  /** The output of the workflow */
55
71
  output?: unknown;
56
- /** An object with information about the trace generated by the execution */
57
- trace?: PostWorkflowRun200Trace;
72
+ trace?: TraceInfo;
58
73
  };
59
74
  export type PostWorkflowStartBody = {
60
75
  /** The name of the workflow to execute */
@@ -94,31 +109,12 @@ export type GetWorkflowIdStatus200 = {
94
109
  /** An epoch timestamp representing when the workflow ended */
95
110
  completedAt?: number;
96
111
  };
97
- /**
98
- * An object with information about the trace generated by the execution
99
- */
100
- export type GetWorkflowIdOutput200Trace = {
101
- [key: string]: unknown;
102
- };
103
- export type GetWorkflowIdOutput200 = {
112
+ export type GetWorkflowIdResult200 = {
104
113
  /** The workflow execution id */
105
114
  workflowId?: string;
106
- /** The output of workflow */
115
+ /** The result of workflow */
107
116
  output?: unknown;
108
- /** An object with information about the trace generated by the execution */
109
- trace?: GetWorkflowIdOutput200Trace;
110
- };
111
- /**
112
- * The trace tree object containing execution details
113
- */
114
- export type GetWorkflowIdTraceLog200 = {
115
- [key: string]: unknown;
116
- };
117
- export type GetWorkflowIdTraceLog404 = {
118
- /** Error type */
119
- error?: string;
120
- /** Detailed error message */
121
- message?: string;
117
+ trace?: TraceInfo;
122
118
  };
123
119
  export type GetWorkflowCatalogId200 = {
124
120
  /** Each workflow available in this catalog */
@@ -213,45 +209,25 @@ export type patchWorkflowIdStopResponse = (patchWorkflowIdStopResponseSuccess |
213
209
  export declare const getPatchWorkflowIdStopUrl: (id: string) => string;
214
210
  export declare const patchWorkflowIdStop: (id: string, options?: ApiRequestOptions) => Promise<patchWorkflowIdStopResponse>;
215
211
  /**
216
- * @summary Return the output of a workflow
212
+ * @summary Return the result of a workflow
217
213
  */
218
- export type getWorkflowIdOutputResponse200 = {
219
- data: GetWorkflowIdOutput200;
214
+ export type getWorkflowIdResultResponse200 = {
215
+ data: GetWorkflowIdResult200;
220
216
  status: 200;
221
217
  };
222
- export type getWorkflowIdOutputResponse404 = {
218
+ export type getWorkflowIdResultResponse404 = {
223
219
  data: void;
224
220
  status: 404;
225
221
  };
226
- export type getWorkflowIdOutputResponseSuccess = (getWorkflowIdOutputResponse200) & {
227
- headers: Headers;
228
- };
229
- export type getWorkflowIdOutputResponseError = (getWorkflowIdOutputResponse404) & {
230
- headers: Headers;
231
- };
232
- export type getWorkflowIdOutputResponse = (getWorkflowIdOutputResponseSuccess | getWorkflowIdOutputResponseError);
233
- export declare const getGetWorkflowIdOutputUrl: (id: string) => string;
234
- export declare const getWorkflowIdOutput: (id: string, options?: ApiRequestOptions) => Promise<getWorkflowIdOutputResponse>;
235
- /**
236
- * @summary Get the trace log contents from a workflow execution
237
- */
238
- export type getWorkflowIdTraceLogResponse200 = {
239
- data: GetWorkflowIdTraceLog200;
240
- status: 200;
241
- };
242
- export type getWorkflowIdTraceLogResponse404 = {
243
- data: GetWorkflowIdTraceLog404;
244
- status: 404;
245
- };
246
- export type getWorkflowIdTraceLogResponseSuccess = (getWorkflowIdTraceLogResponse200) & {
222
+ export type getWorkflowIdResultResponseSuccess = (getWorkflowIdResultResponse200) & {
247
223
  headers: Headers;
248
224
  };
249
- export type getWorkflowIdTraceLogResponseError = (getWorkflowIdTraceLogResponse404) & {
225
+ export type getWorkflowIdResultResponseError = (getWorkflowIdResultResponse404) & {
250
226
  headers: Headers;
251
227
  };
252
- export type getWorkflowIdTraceLogResponse = (getWorkflowIdTraceLogResponseSuccess | getWorkflowIdTraceLogResponseError);
253
- export declare const getGetWorkflowIdTraceLogUrl: (id: string) => string;
254
- export declare const getWorkflowIdTraceLog: (id: string, options?: ApiRequestOptions) => Promise<getWorkflowIdTraceLogResponse>;
228
+ export type getWorkflowIdResultResponse = (getWorkflowIdResultResponseSuccess | getWorkflowIdResultResponseError);
229
+ export declare const getGetWorkflowIdResultUrl: (id: string) => string;
230
+ export declare const getWorkflowIdResult: (id: string, options?: ApiRequestOptions) => Promise<getWorkflowIdResultResponse>;
255
231
  /**
256
232
  * @summary Get a specific workflow catalog by ID
257
233
  */
@@ -69,20 +69,11 @@ export const patchWorkflowIdStop = async (id, options) => {
69
69
  method: 'PATCH'
70
70
  });
71
71
  };
72
- export const getGetWorkflowIdOutputUrl = (id) => {
73
- return `/workflow/${id}/output`;
72
+ export const getGetWorkflowIdResultUrl = (id) => {
73
+ return `/workflow/${id}/result`;
74
74
  };
75
- export const getWorkflowIdOutput = async (id, options) => {
76
- return customFetchInstance(getGetWorkflowIdOutputUrl(id), {
77
- ...options,
78
- method: 'GET'
79
- });
80
- };
81
- export const getGetWorkflowIdTraceLogUrl = (id) => {
82
- return `/workflow/${id}/trace_log`;
83
- };
84
- export const getWorkflowIdTraceLog = async (id, options) => {
85
- return customFetchInstance(getGetWorkflowIdTraceLogUrl(id), {
75
+ export const getWorkflowIdResult = async (id, options) => {
76
+ return customFetchInstance(getGetWorkflowIdResultUrl(id), {
86
77
  ...options,
87
78
  method: 'GET'
88
79
  });
@@ -80,6 +80,7 @@ services:
80
80
  temporal:
81
81
  condition: service_healthy
82
82
  image: growthxteam/output-api:latest
83
+ pull_policy: always
83
84
  networks:
84
85
  - main
85
86
  environment:
@@ -87,8 +88,6 @@ services:
87
88
  - CATALOG_ID=main
88
89
  - TEMPORAL_ADDRESS=temporal:7233
89
90
  - NODE_ENV=development
90
- volumes:
91
- - ./logs:/app/logs:ro
92
91
  ports:
93
92
  - '3001:3001'
94
93
 
@@ -106,6 +105,7 @@ services:
106
105
  - REDIS_URL=redis://redis:6379
107
106
  - TEMPORAL_ADDRESS=temporal:7233
108
107
  - TRACE_LOCAL_ON=true
108
+ - HOST_TRACE_PATH=${PWD}/logs
109
109
  command: npm run start-worker
110
110
  working_dir: /app
111
111
  volumes:
@@ -7,15 +7,9 @@ export default class WorkflowDebug extends Command {
7
7
  };
8
8
  static flags: {
9
9
  format: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
10
- remote: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
- 'download-dir': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
12
- open: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
- 'force-download': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
14
10
  };
15
11
  run(): Promise<void>;
16
- private getLocalTrace;
17
- private getRemoteTrace;
18
- private openInViewer;
19
- private formatDuration;
12
+ private outputJson;
13
+ private displayTextTrace;
20
14
  catch(error: Error): Promise<void>;
21
15
  }
@@ -1,21 +1,14 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
- import * as fs from 'node:fs/promises';
3
- import * as path from 'node:path';
4
- import { existsSync } from 'node:fs';
5
- import { getWorkflowIdOutput } from '#api/generated/api.js';
6
2
  import { OUTPUT_FORMAT } from '#utils/constants.js';
3
+ import { displayDebugTree } from '#utils/trace_formatter.js';
4
+ import { getTrace } from '#services/trace_reader.js';
7
5
  import { handleApiError } from '#utils/error_handler.js';
8
- import { s3Downloader } from '#utils/s3_downloader.js';
9
- import { traceFormatter } from '#utils/trace_formatter.js';
10
- // Note: 'open' dependency is optional for --open flag functionality
11
6
  export default class WorkflowDebug extends Command {
12
7
  static description = 'Get and display workflow execution trace for debugging';
13
8
  static examples = [
14
9
  '<%= config.bin %> <%= command.id %> wf-12345',
15
10
  '<%= config.bin %> <%= command.id %> wf-12345 --format json',
16
- '<%= config.bin %> <%= command.id %> wf-12345 --remote',
17
- '<%= config.bin %> <%= command.id %> wf-12345 --open',
18
- '<%= config.bin %> <%= command.id %> wf-12345 --download-dir ./my-traces'
11
+ '<%= config.bin %> <%= command.id %> wf-12345 --format text'
19
12
  ];
20
13
  static args = {
21
14
  workflowId: Args.string({
@@ -29,169 +22,36 @@ export default class WorkflowDebug extends Command {
29
22
  description: 'Output format',
30
23
  options: [OUTPUT_FORMAT.JSON, OUTPUT_FORMAT.TEXT],
31
24
  default: OUTPUT_FORMAT.TEXT
32
- }),
33
- remote: Flags.boolean({
34
- char: 'r',
35
- description: 'Prefer remote (S3) trace over local',
36
- default: false
37
- }),
38
- 'download-dir': Flags.string({
39
- char: 'd',
40
- description: 'Directory to save downloaded remote traces',
41
- default: path.join(process.cwd(), '.output', 'traces')
42
- }),
43
- open: Flags.boolean({
44
- char: 'o',
45
- description: 'Open trace file in default viewer/browser',
46
- default: false
47
- }),
48
- 'force-download': Flags.boolean({
49
- description: 'Force re-download of remote traces (bypass cache)',
50
- default: false
51
25
  })
52
26
  };
53
27
  async run() {
54
28
  const { args, flags } = await this.parse(WorkflowDebug);
55
- this.log(`Fetching debug information for workflow: ${args.workflowId}...`);
56
- // Get workflow output which includes trace destinations
57
- const response = await getWorkflowIdOutput(args.workflowId);
58
- if (!response || !response.data) {
59
- this.error('API returned invalid response', { exit: 1 });
60
- }
61
- const { trace } = response.data;
62
- const typedTrace = trace;
63
- if (!typedTrace || !typedTrace.destinations) {
64
- this.error('No trace information available for this workflow', { exit: 1 });
65
- }
66
- const { local: localPath, remote: remotePath } = typedTrace.destinations;
67
- // Determine which trace to use based on flags and availability
68
- // eslint-disable-next-line no-restricted-syntax
69
- let traceContent = null;
70
- // eslint-disable-next-line no-restricted-syntax
71
- let traceSource = '';
72
- if (flags.remote && remotePath) {
73
- // User prefers remote trace
74
- traceContent = await this.getRemoteTrace(remotePath, flags);
75
- traceSource = 'remote';
76
- }
77
- else if (localPath) {
78
- // Try local first
79
- traceContent = await this.getLocalTrace(localPath);
80
- traceSource = 'local';
81
- // If local not found but remote available, try remote
82
- if (!traceContent && remotePath && !flags.remote) {
83
- this.log('Local trace not found, trying remote...');
84
- traceContent = await this.getRemoteTrace(remotePath, flags);
85
- traceSource = 'remote';
86
- }
87
- }
88
- else if (remotePath) {
89
- // Only remote available
90
- traceContent = await this.getRemoteTrace(remotePath, flags);
91
- traceSource = 'remote';
92
- }
93
- if (!traceContent) {
94
- this.error('No trace file could be retrieved. The workflow may still be running or trace files may have been deleted.', { exit: 1 });
95
- }
96
- // Parse and validate trace JSON
97
- // eslint-disable-next-line init-declarations, no-restricted-syntax, @typescript-eslint/no-explicit-any
98
- let traceData;
99
- try {
100
- traceData = JSON.parse(traceContent);
101
- }
102
- catch {
103
- this.error('Invalid trace file format: could not parse JSON', { exit: 1 });
104
- }
105
- // Get summary statistics
106
- const summary = traceFormatter.getSummary(traceData);
107
- // Display source information
108
- this.log(`\nTrace source: ${traceSource}`);
109
- this.log(`Total events: ${summary.totalEvents}`);
110
- this.log(`Total steps: ${summary.totalSteps}`);
111
- this.log(`Total activities: ${summary.totalActivities}`);
112
- if (summary.totalDuration > 0) {
113
- this.log(`Total duration: ${this.formatDuration(summary.totalDuration)}`);
114
- }
115
- if (summary.hasErrors) {
116
- this.log('⚠️ Workflow contains errors');
117
- }
118
- this.log('');
119
- // Format and display trace
120
- const formattedOutput = traceFormatter.format(traceData, flags.format);
121
- this.log(formattedOutput);
122
- // Open in viewer if requested
123
- if (flags.open) {
124
- await this.openInViewer(traceContent, args.workflowId, flags['download-dir']);
125
- }
29
+ const isJsonFormat = flags.format === OUTPUT_FORMAT.JSON;
30
+ if (!isJsonFormat) {
31
+ this.log(`Fetching debug information for workflow: ${args.workflowId}...`);
32
+ }
33
+ const traceData = await getTrace(args.workflowId);
34
+ // Output based on format
35
+ if (isJsonFormat) {
36
+ this.outputJson(traceData);
37
+ return;
38
+ }
39
+ // Display text format
40
+ this.displayTextTrace(traceData);
126
41
  }
127
- async getLocalTrace(localPath) {
128
- try {
129
- // Check if file exists
130
- if (!existsSync(localPath)) {
131
- return null;
132
- }
133
- // Read file content
134
- const content = await fs.readFile(localPath, 'utf-8');
135
- return content;
136
- }
137
- catch (error) {
138
- this.warn(`Failed to read local trace file: ${error instanceof Error ? error.message : 'Unknown error'}`);
139
- return null;
140
- }
141
- }
142
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
- async getRemoteTrace(remotePath, flags) {
144
- try {
145
- // Check if S3 downloader is available
146
- if (!s3Downloader.isAvailable()) {
147
- this.error('AWS credentials not configured. Please set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables to access remote traces.', { exit: 1 });
148
- }
149
- this.log('Downloading trace from S3...');
150
- // Download with cache support
151
- const content = await s3Downloader.download(remotePath, {
152
- forceDownload: flags['force-download']
153
- });
154
- if (!flags['force-download']) {
155
- this.log('(Using cached version if available)');
156
- }
157
- return content;
158
- }
159
- catch (error) {
160
- this.warn(`Failed to download remote trace: ${error instanceof Error ? error.message : 'Unknown error'}`);
161
- return null;
162
- }
42
+ outputJson(data) {
43
+ this.log(JSON.stringify(data, null, 2));
163
44
  }
164
- async openInViewer(content, workflowId, downloadDir) {
165
- try {
166
- // Save to temporary file
167
- const tempDir = downloadDir;
168
- await fs.mkdir(tempDir, { recursive: true });
169
- const tempFile = path.join(tempDir, `${workflowId}_debug.json`);
170
- await fs.writeFile(tempFile, content, 'utf-8');
171
- this.log(`\nTrace file saved to: ${tempFile}`);
172
- this.log('You can open this file in your preferred JSON viewer or text editor.');
173
- // Note: To automatically open files, install the 'open' package and uncomment below:
174
- // await open(tempFile);
175
- }
176
- catch (error) {
177
- this.warn(`Failed to save file: ${error instanceof Error ? error.message : 'Unknown error'}`);
178
- }
179
- }
180
- formatDuration(ms) {
181
- if (ms < 1000) {
182
- return `${ms}ms`;
183
- }
184
- if (ms < 60000) {
185
- return `${(ms / 1000).toFixed(2)}s`;
186
- }
187
- const minutes = Math.floor(ms / 60000);
188
- const seconds = ((ms % 60000) / 1000).toFixed(0);
189
- return `${minutes}m ${seconds}s`;
45
+ displayTextTrace(traceData) {
46
+ this.log('\nTrace Log:');
47
+ this.log('─'.repeat(80));
48
+ this.log(displayDebugTree(traceData));
49
+ this.log('\n' + '─'.repeat(80));
50
+ this.log('Tip: Use --format json for the full untruncated trace');
190
51
  }
191
52
  async catch(error) {
192
53
  return handleApiError(error, (...args) => this.error(...args), {
193
- 404: 'Workflow not found. Check the workflow ID.',
194
- 500: 'Server error. The workflow may still be running or the trace may not be available yet.'
54
+ 404: 'Workflow not found or trace not available. Check the workflow ID.'
195
55
  });
196
56
  }
197
57
  }
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ // Mock the TraceReader service
3
+ vi.mock('../../services/trace_reader.js', () => ({
4
+ findTraceFile: vi.fn(),
5
+ readTraceFile: vi.fn(),
6
+ getTrace: vi.fn()
7
+ }));
8
+ // Mock the utilities
9
+ vi.mock('../../utils/trace_formatter.js', () => ({
10
+ displayDebugTree: vi.fn()
11
+ }));
12
+ describe('workflow debug command', () => {
13
+ beforeEach(() => {
14
+ vi.clearAllMocks();
15
+ });
16
+ describe('command definition', () => {
17
+ it('should export a valid OCLIF command', async () => {
18
+ const WorkflowDebug = (await import('./debug.js')).default;
19
+ expect(WorkflowDebug).toBeDefined();
20
+ expect(WorkflowDebug.description).toContain('Get and display workflow execution trace for debugging');
21
+ expect(WorkflowDebug.args).toHaveProperty('workflowId');
22
+ expect(WorkflowDebug.flags).toHaveProperty('format');
23
+ });
24
+ it('should have correct flag configuration', async () => {
25
+ const WorkflowDebug = (await import('./debug.js')).default;
26
+ // Format flag
27
+ expect(WorkflowDebug.flags.format.options).toEqual(['json', 'text']);
28
+ expect(WorkflowDebug.flags.format.default).toBe('text');
29
+ });
30
+ it('should have correct examples', async () => {
31
+ const WorkflowDebug = (await import('./debug.js')).default;
32
+ expect(WorkflowDebug.examples).toBeDefined();
33
+ expect(WorkflowDebug.examples.length).toBeGreaterThan(0);
34
+ });
35
+ });
36
+ });
@@ -2,6 +2,7 @@ import { Args, Command, Flags, ux } from '@oclif/core';
2
2
  import { generateWorkflow } from '#services/workflow_generator.js';
3
3
  import { buildWorkflow, buildWorkflowInteractiveLoop } from '#services/workflow_builder.js';
4
4
  import { ensureOutputAIStructure } from '#services/coding_agents.js';
5
+ import { getWorkflowGenerateSuccessMessage } from '#services/messages.js';
5
6
  import { DEFAULT_OUTPUT_DIRS } from '#utils/paths.js';
6
7
  import path from 'node:path';
7
8
  export default class Generate extends Command {
@@ -78,15 +79,7 @@ export default class Generate extends Command {
78
79
  }
79
80
  }
80
81
  displaySuccess(result) {
81
- this.log(`\n[SUCCESS] Workflow "${result.workflowName}" created successfully!`);
82
- this.log(`\nLocation: ${result.targetDir}`);
83
- this.log('\n-- Next steps --');
84
- this.log(` 1. cd ${result.targetDir}`);
85
- this.log(' 2. Edit the workflow files to customize your implementation');
86
- this.log(' 3. If using @output.ai/llm, configure your .env file with LLM provider credentials:');
87
- this.log(' - ANTHROPIC_API_KEY for Claude');
88
- this.log(' - OPENAI_API_KEY for OpenAI');
89
- this.log(' 4. Build and run with the worker');
90
- this.log('\nCheck the README.md for workflow-specific documentation.');
82
+ const message = getWorkflowGenerateSuccessMessage(result.workflowName, result.targetDir, result.filesCreated);
83
+ this.log(message);
91
84
  }
92
85
  }
@@ -46,7 +46,8 @@ describe('Generate Command', () => {
46
46
  skeleton: true,
47
47
  force: false
48
48
  });
49
- expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('[SUCCESS] Workflow "test-workflow" created successfully!'));
49
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('SUCCESS!'));
50
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('test-workflow'));
50
51
  });
51
52
  it('should require skeleton flag and reject without it', async () => {
52
53
  const cmd = createCommand();
@@ -105,9 +106,10 @@ describe('Generate Command', () => {
105
106
  filesCreated: ['index.ts', 'steps.ts', 'types.ts']
106
107
  });
107
108
  await cmd.run();
108
- expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('[SUCCESS] Workflow "my-workflow" created successfully!'));
109
- expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Location: /custom/path/my-workflow'));
110
- expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('-- Next steps --'));
109
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('SUCCESS!'));
110
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('my-workflow'));
111
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('/custom/path/my-workflow'));
112
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('NEXT STEPS'));
111
113
  });
112
114
  });
113
115
  });
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class WorkflowOutput extends Command {
2
+ export default class WorkflowResult extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -1,17 +1,17 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
- import { getWorkflowIdOutput } from '#api/generated/api.js';
2
+ import { getWorkflowIdResult } from '#api/generated/api.js';
3
3
  import { OUTPUT_FORMAT } from '#utils/constants.js';
4
4
  import { formatOutput } from '#utils/output_formatter.js';
5
5
  import { handleApiError } from '#utils/error_handler.js';
6
- export default class WorkflowOutput extends Command {
7
- static description = 'Get workflow execution output';
6
+ export default class WorkflowResult extends Command {
7
+ static description = 'Get workflow execution result';
8
8
  static examples = [
9
9
  '<%= config.bin %> <%= command.id %> wf-12345',
10
10
  '<%= config.bin %> <%= command.id %> wf-12345 --format json'
11
11
  ];
12
12
  static args = {
13
13
  workflowId: Args.string({
14
- description: 'The workflow ID to get output for',
14
+ description: 'The workflow ID to get result for',
15
15
  required: true
16
16
  })
17
17
  };
@@ -24,9 +24,9 @@ export default class WorkflowOutput extends Command {
24
24
  })
25
25
  };
26
26
  async run() {
27
- const { args, flags } = await this.parse(WorkflowOutput);
28
- this.log(`Fetching output for workflow: ${args.workflowId}...`);
29
- const response = await getWorkflowIdOutput(args.workflowId);
27
+ const { args, flags } = await this.parse(WorkflowResult);
28
+ this.log(`Fetching result for workflow: ${args.workflowId}...`);
29
+ const response = await getWorkflowIdResult(args.workflowId);
30
30
  if (!response || !response.data) {
31
31
  this.error('API returned invalid response', { exit: 1 });
32
32
  }
@@ -34,7 +34,7 @@ export default class WorkflowOutput extends Command {
34
34
  const lines = [
35
35
  `Workflow ID: ${result.workflowId || 'unknown'}`,
36
36
  '',
37
- 'Output:',
37
+ 'Result:',
38
38
  JSON.stringify(result.output, null, 2)
39
39
  ];
40
40
  return lines.join('\n');
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ vi.mock('../../api/generated/api.js', () => ({
3
+ getWorkflowIdResult: vi.fn()
4
+ }));
5
+ describe('workflow result command', () => {
6
+ beforeEach(() => {
7
+ vi.clearAllMocks();
8
+ });
9
+ describe('command definition', () => {
10
+ it('should export a valid OCLIF command', async () => {
11
+ const WorkflowResult = (await import('./result.js')).default;
12
+ expect(WorkflowResult).toBeDefined();
13
+ expect(WorkflowResult.description).toContain('Get workflow execution result');
14
+ expect(WorkflowResult.args).toHaveProperty('workflowId');
15
+ expect(WorkflowResult.flags).toHaveProperty('format');
16
+ });
17
+ it('should have correct flag configuration', async () => {
18
+ const WorkflowResult = (await import('./result.js')).default;
19
+ expect(WorkflowResult.flags.format.options).toEqual(['json', 'text']);
20
+ expect(WorkflowResult.flags.format.default).toBe('text');
21
+ });
22
+ });
23
+ });
@@ -45,7 +45,7 @@ export default class WorkflowStart extends Command {
45
45
  `Workflow ID: ${result.workflowId || 'unknown'}`,
46
46
  '',
47
47
  `Use "workflow status ${result.workflowId || '<workflow-id>'}" to check the workflow status`,
48
- `Use "workflow output ${result.workflowId || '<workflow-id>'}" to get the workflow output when complete`
48
+ `Use "workflow result ${result.workflowId || '<workflow-id>'}" to get the workflow result when complete`
49
49
  ].join('\n');
50
50
  this.log(`\n${output}`);
51
51
  }