@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.
- package/README.md +2 -28
- package/dist/api/generated/api.d.ts +35 -59
- package/dist/api/generated/api.js +4 -13
- package/dist/assets/docker/docker-compose-dev.yml +2 -2
- package/dist/commands/workflow/debug.d.ts +2 -8
- package/dist/commands/workflow/debug.js +24 -164
- package/dist/commands/workflow/debug.spec.js +36 -0
- package/dist/commands/workflow/generate.js +3 -10
- package/dist/commands/workflow/generate.spec.js +6 -4
- package/dist/commands/workflow/{output.d.ts → result.d.ts} +1 -1
- package/dist/commands/workflow/{output.js → result.js} +8 -8
- package/dist/commands/workflow/result.test.js +23 -0
- package/dist/commands/workflow/start.js +1 -1
- package/dist/services/coding_agents.js +30 -0
- package/dist/services/coding_agents.spec.js +36 -61
- package/dist/services/messages.d.ts +1 -0
- package/dist/services/messages.js +65 -1
- package/dist/services/trace_reader.d.ts +14 -0
- package/dist/services/trace_reader.js +67 -0
- package/dist/services/trace_reader.spec.d.ts +1 -0
- package/dist/services/trace_reader.spec.js +164 -0
- package/dist/services/workflow_generator.spec.d.ts +1 -0
- package/dist/services/workflow_generator.spec.js +77 -0
- package/dist/templates/agent_instructions/AGENTS.md.template +209 -19
- package/dist/templates/agent_instructions/agents/context_fetcher.md.template +82 -0
- package/dist/templates/agent_instructions/agents/prompt_writer.md.template +595 -0
- package/dist/templates/agent_instructions/agents/workflow_planner.md.template +13 -4
- package/dist/templates/agent_instructions/agents/workflow_quality.md.template +244 -0
- package/dist/templates/agent_instructions/commands/build_workflow.md.template +52 -9
- package/dist/templates/agent_instructions/commands/plan_workflow.md.template +4 -4
- package/dist/templates/project/package.json.template +2 -2
- package/dist/templates/project/src/simple/scenarios/question_ada_lovelace.json.template +3 -0
- package/dist/templates/workflow/scenarios/test_input.json.template +7 -0
- package/dist/types/trace.d.ts +161 -0
- package/dist/types/trace.js +18 -0
- package/dist/utils/date_formatter.d.ts +8 -0
- package/dist/utils/date_formatter.js +19 -0
- package/dist/utils/template.spec.js +6 -0
- package/dist/utils/trace_formatter.d.ts +11 -61
- package/dist/utils/trace_formatter.js +384 -239
- package/package.json +2 -2
- package/dist/commands/workflow/debug.test.js +0 -107
- package/dist/commands/workflow/output.test.js +0 -23
- package/dist/utils/s3_downloader.d.ts +0 -49
- package/dist/utils/s3_downloader.js +0 -154
- /package/dist/commands/workflow/{debug.test.d.ts → debug.spec.d.ts} +0 -0
- /package/dist/commands/workflow/{output.test.d.ts → result.test.d.ts} +0 -0
- /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
|
-
####
|
|
121
|
+
#### Environment Variables
|
|
143
122
|
|
|
144
|
-
|
|
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
|
-
|
|
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
|
|
115
|
+
/** The result of workflow */
|
|
107
116
|
output?: unknown;
|
|
108
|
-
|
|
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
|
|
212
|
+
* @summary Return the result of a workflow
|
|
217
213
|
*/
|
|
218
|
-
export type
|
|
219
|
-
data:
|
|
214
|
+
export type getWorkflowIdResultResponse200 = {
|
|
215
|
+
data: GetWorkflowIdResult200;
|
|
220
216
|
status: 200;
|
|
221
217
|
};
|
|
222
|
-
export type
|
|
218
|
+
export type getWorkflowIdResultResponse404 = {
|
|
223
219
|
data: void;
|
|
224
220
|
status: 404;
|
|
225
221
|
};
|
|
226
|
-
export type
|
|
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
|
|
225
|
+
export type getWorkflowIdResultResponseError = (getWorkflowIdResultResponse404) & {
|
|
250
226
|
headers: Headers;
|
|
251
227
|
};
|
|
252
|
-
export type
|
|
253
|
-
export declare const
|
|
254
|
-
export declare const
|
|
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
|
|
73
|
-
return `/workflow/${id}/
|
|
72
|
+
export const getGetWorkflowIdResultUrl = (id) => {
|
|
73
|
+
return `/workflow/${id}/result`;
|
|
74
74
|
};
|
|
75
|
-
export const
|
|
76
|
-
return customFetchInstance(
|
|
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
|
|
17
|
-
private
|
|
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 --
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
82
|
-
this.log(
|
|
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('
|
|
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('
|
|
109
|
-
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('
|
|
110
|
-
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('
|
|
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,17 +1,17 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
-
import {
|
|
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
|
|
7
|
-
static description = 'Get workflow execution
|
|
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
|
|
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(
|
|
28
|
-
this.log(`Fetching
|
|
29
|
-
const response = await
|
|
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
|
-
'
|
|
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
|
|
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
|
}
|