@output.ai/cli 0.3.0-dev.pr156.c8e7f40 → 0.3.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 +0 -53
- package/dist/api/generated/api.d.ts +2 -34
- package/dist/assets/docker/docker-compose-dev.yml +0 -1
- package/dist/commands/workflow/generate.js +3 -10
- package/dist/commands/workflow/generate.spec.js +6 -4
- 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/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/utils/template.spec.js +6 -0
- package/package.json +1 -2
- package/dist/commands/workflow/debug.d.ts +0 -15
- package/dist/commands/workflow/debug.js +0 -55
- package/dist/commands/workflow/debug.test.js +0 -75
- package/dist/services/trace_reader.d.ts +0 -8
- package/dist/services/trace_reader.js +0 -51
- package/dist/services/trace_reader.test.d.ts +0 -1
- package/dist/services/trace_reader.test.js +0 -163
- package/dist/utils/trace_formatter.d.ts +0 -27
- package/dist/utils/trace_formatter.js +0 -465
- /package/dist/{commands/workflow/debug.test.d.ts → services/workflow_generator.spec.d.ts} +0 -0
- /package/dist/templates/workflow/{prompt@v1.prompt.template → prompts/prompt@v1.prompt.template} +0 -0
package/README.md
CHANGED
|
@@ -95,59 +95,6 @@ output workflow list --filter simple
|
|
|
95
95
|
|
|
96
96
|
The list command connects to the API server and retrieves all available workflows. By default, it displays a simple list of workflow names (like `ls`). Use `--format table` for detailed information.
|
|
97
97
|
|
|
98
|
-
### Debug a Workflow Execution
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
# Display trace information for a workflow run
|
|
102
|
-
output workflow debug <workflowId>
|
|
103
|
-
|
|
104
|
-
# Display trace in JSON format
|
|
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
|
-
```
|
|
116
|
-
|
|
117
|
-
#### What It Does
|
|
118
|
-
|
|
119
|
-
The `debug` command retrieves and displays detailed execution traces for debugging workflow runs. It shows:
|
|
120
|
-
- Complete execution timeline with all events
|
|
121
|
-
- Hierarchical execution tree showing workflow structure
|
|
122
|
-
- Step and activity inputs/outputs
|
|
123
|
-
- Error details and stack traces
|
|
124
|
-
- Performance metrics and durations
|
|
125
|
-
|
|
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
|
-
#### Command Options
|
|
135
|
-
|
|
136
|
-
- `--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
|
-
|
|
142
|
-
#### AWS Configuration
|
|
143
|
-
|
|
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
|
-
```
|
|
150
|
-
|
|
151
98
|
### Generate a Workflow
|
|
152
99
|
|
|
153
100
|
```bash
|
|
@@ -42,27 +42,11 @@ export type PostWorkflowRunBody = {
|
|
|
42
42
|
/** The name of the task queue to send the workflow to */
|
|
43
43
|
taskQueue?: string;
|
|
44
44
|
};
|
|
45
|
-
/**
|
|
46
|
-
* File destinations for trace data
|
|
47
|
-
*/
|
|
48
|
-
export type PostWorkflowRun200TraceDestinations = {
|
|
49
|
-
/**
|
|
50
|
-
* Absolute path to local trace file, or null if not saved locally
|
|
51
|
-
* @nullable
|
|
52
|
-
*/
|
|
53
|
-
local: string | null;
|
|
54
|
-
/**
|
|
55
|
-
* Remote trace location (e.g., S3 URI), or null if not saved remotely
|
|
56
|
-
* @nullable
|
|
57
|
-
*/
|
|
58
|
-
remote: string | null;
|
|
59
|
-
};
|
|
60
45
|
/**
|
|
61
46
|
* An object with information about the trace generated by the execution
|
|
62
47
|
*/
|
|
63
48
|
export type PostWorkflowRun200Trace = {
|
|
64
|
-
|
|
65
|
-
destinations?: PostWorkflowRun200TraceDestinations;
|
|
49
|
+
[key: string]: unknown;
|
|
66
50
|
};
|
|
67
51
|
export type PostWorkflowRun200 = {
|
|
68
52
|
/** The workflow execution id */
|
|
@@ -110,27 +94,11 @@ export type GetWorkflowIdStatus200 = {
|
|
|
110
94
|
/** An epoch timestamp representing when the workflow ended */
|
|
111
95
|
completedAt?: number;
|
|
112
96
|
};
|
|
113
|
-
/**
|
|
114
|
-
* File destinations for trace data
|
|
115
|
-
*/
|
|
116
|
-
export type GetWorkflowIdOutput200TraceDestinations = {
|
|
117
|
-
/**
|
|
118
|
-
* Absolute path to local trace file, or null if not saved locally
|
|
119
|
-
* @nullable
|
|
120
|
-
*/
|
|
121
|
-
local: string | null;
|
|
122
|
-
/**
|
|
123
|
-
* Remote trace location (e.g., S3 URI), or null if not saved remotely
|
|
124
|
-
* @nullable
|
|
125
|
-
*/
|
|
126
|
-
remote: string | null;
|
|
127
|
-
};
|
|
128
97
|
/**
|
|
129
98
|
* An object with information about the trace generated by the execution
|
|
130
99
|
*/
|
|
131
100
|
export type GetWorkflowIdOutput200Trace = {
|
|
132
|
-
|
|
133
|
-
destinations?: GetWorkflowIdOutput200TraceDestinations;
|
|
101
|
+
[key: string]: unknown;
|
|
134
102
|
};
|
|
135
103
|
export type GetWorkflowIdOutput200 = {
|
|
136
104
|
/** The workflow execution id */
|
|
@@ -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
|
});
|
|
@@ -29,6 +29,21 @@ export const AGENT_CONFIGS = {
|
|
|
29
29
|
from: 'agents/workflow_planner.md.template',
|
|
30
30
|
to: `${AGENT_CONFIG_DIR}/agents/workflow_planner.md`
|
|
31
31
|
},
|
|
32
|
+
{
|
|
33
|
+
type: 'template',
|
|
34
|
+
from: 'agents/workflow_quality.md.template',
|
|
35
|
+
to: `${AGENT_CONFIG_DIR}/agents/workflow_quality.md`
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'template',
|
|
39
|
+
from: 'agents/context_fetcher.md.template',
|
|
40
|
+
to: `${AGENT_CONFIG_DIR}/agents/context_fetcher.md`
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'template',
|
|
44
|
+
from: 'agents/prompt_writer.md.template',
|
|
45
|
+
to: `${AGENT_CONFIG_DIR}/agents/prompt_writer.md`
|
|
46
|
+
},
|
|
32
47
|
{
|
|
33
48
|
type: 'template',
|
|
34
49
|
from: 'commands/plan_workflow.md.template',
|
|
@@ -65,6 +80,21 @@ export const AGENT_CONFIGS = {
|
|
|
65
80
|
from: `${AGENT_CONFIG_DIR}/agents/workflow_planner.md`,
|
|
66
81
|
to: '.claude/agents/workflow_planner.md'
|
|
67
82
|
},
|
|
83
|
+
{
|
|
84
|
+
type: 'symlink',
|
|
85
|
+
from: `${AGENT_CONFIG_DIR}/agents/workflow_quality.md`,
|
|
86
|
+
to: '.claude/agents/workflow_quality.md'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'symlink',
|
|
90
|
+
from: `${AGENT_CONFIG_DIR}/agents/context_fetcher.md`,
|
|
91
|
+
to: '.claude/agents/context_fetcher.md'
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'symlink',
|
|
95
|
+
from: `${AGENT_CONFIG_DIR}/agents/prompt_writer.md`,
|
|
96
|
+
to: '.claude/agents/prompt_writer.md'
|
|
97
|
+
},
|
|
68
98
|
{
|
|
69
99
|
type: 'symlink',
|
|
70
100
|
from: `${AGENT_CONFIG_DIR}/commands/plan_workflow.md`,
|
|
@@ -39,11 +39,11 @@ describe('coding_agents service', () => {
|
|
|
39
39
|
const expectedCount = AGENT_CONFIGS.outputai.mappings.length +
|
|
40
40
|
AGENT_CONFIGS['claude-code'].mappings.length;
|
|
41
41
|
expect(files.length).toBe(expectedCount);
|
|
42
|
-
expect(files.length).toBe(
|
|
42
|
+
expect(files.length).toBe(16);
|
|
43
43
|
});
|
|
44
44
|
it('should have outputai files with .outputai prefix', () => {
|
|
45
45
|
const files = getRequiredFiles();
|
|
46
|
-
const outputaiFiles = files.slice(0,
|
|
46
|
+
const outputaiFiles = files.slice(0, 9);
|
|
47
47
|
outputaiFiles.forEach(file => {
|
|
48
48
|
expect(file).toMatch(/^\.outputai\//);
|
|
49
49
|
});
|
|
@@ -77,12 +77,18 @@ describe('coding_agents service', () => {
|
|
|
77
77
|
missingFiles: [
|
|
78
78
|
'.outputai/AGENTS.md',
|
|
79
79
|
'.outputai/agents/workflow_planner.md',
|
|
80
|
+
'.outputai/agents/workflow_quality.md',
|
|
81
|
+
'.outputai/agents/context_fetcher.md',
|
|
82
|
+
'.outputai/agents/prompt_writer.md',
|
|
80
83
|
'.outputai/commands/plan_workflow.md',
|
|
81
84
|
'.outputai/commands/build_workflow.md',
|
|
82
85
|
'.outputai/meta/pre_flight.md',
|
|
83
86
|
'.outputai/meta/post_flight.md',
|
|
84
87
|
'CLAUDE.md',
|
|
85
88
|
'.claude/agents/workflow_planner.md',
|
|
89
|
+
'.claude/agents/workflow_quality.md',
|
|
90
|
+
'.claude/agents/context_fetcher.md',
|
|
91
|
+
'.claude/agents/prompt_writer.md',
|
|
86
92
|
'.claude/commands/plan_workflow.md',
|
|
87
93
|
'.claude/commands/build_workflow.md'
|
|
88
94
|
],
|
|
@@ -98,54 +104,21 @@ describe('coding_agents service', () => {
|
|
|
98
104
|
missingFiles: [],
|
|
99
105
|
isComplete: true
|
|
100
106
|
});
|
|
101
|
-
expect(access).toHaveBeenCalledTimes(
|
|
107
|
+
expect(access).toHaveBeenCalledTimes(17); // dir + 9 outputai + 7 claude-code
|
|
102
108
|
});
|
|
103
109
|
it('should return missing files when some files do not exist', async () => {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
// Call 3: .outputai/agents/workflow_planner.md missing
|
|
117
|
-
if (callNum === 3) {
|
|
118
|
-
throw { code: 'ENOENT' };
|
|
119
|
-
}
|
|
120
|
-
// Call 4: .outputai/commands/plan_workflow.md exists
|
|
121
|
-
if (callNum === 4) {
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
124
|
-
// Call 5: .outputai/commands/build_workflow.md exists
|
|
125
|
-
if (callNum === 5) {
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
128
|
-
// Call 6: .outputai/meta/pre_flight.md exists
|
|
129
|
-
if (callNum === 6) {
|
|
130
|
-
return undefined;
|
|
131
|
-
}
|
|
132
|
-
// Call 7: .outputai/meta/post_flight.md exists
|
|
133
|
-
if (callNum === 7) {
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
// Call 8: CLAUDE.md exists
|
|
137
|
-
if (callNum === 8) {
|
|
138
|
-
return undefined;
|
|
139
|
-
}
|
|
140
|
-
// Call 9: .claude/agents/workflow_planner.md missing
|
|
141
|
-
if (callNum === 9) {
|
|
142
|
-
throw { code: 'ENOENT' };
|
|
143
|
-
}
|
|
144
|
-
// Call 10: .claude/commands/plan_workflow.md exists
|
|
145
|
-
if (callNum === 10) {
|
|
146
|
-
return undefined;
|
|
110
|
+
const missingFiles = new Set([
|
|
111
|
+
'.outputai/agents/workflow_planner.md',
|
|
112
|
+
'.claude/agents/workflow_planner.md'
|
|
113
|
+
]);
|
|
114
|
+
vi.mocked(access).mockImplementation(async (path) => {
|
|
115
|
+
const pathStr = path.toString();
|
|
116
|
+
// Check if this path ends with any of the missing files
|
|
117
|
+
for (const missing of missingFiles) {
|
|
118
|
+
if (pathStr.endsWith(missing)) {
|
|
119
|
+
throw { code: 'ENOENT' };
|
|
120
|
+
}
|
|
147
121
|
}
|
|
148
|
-
// Call 11: .claude/commands/build_workflow.md exists
|
|
149
122
|
return undefined;
|
|
150
123
|
});
|
|
151
124
|
const result = await checkAgentStructure('/test/project');
|
|
@@ -159,18 +132,20 @@ describe('coding_agents service', () => {
|
|
|
159
132
|
});
|
|
160
133
|
});
|
|
161
134
|
it('should check all required files even when directory exists', async () => {
|
|
162
|
-
|
|
163
|
-
.
|
|
164
|
-
.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
135
|
+
const missingFiles = new Set([
|
|
136
|
+
'.outputai/agents/workflow_planner.md',
|
|
137
|
+
'.outputai/commands/plan_workflow.md',
|
|
138
|
+
'.claude/commands/plan_workflow.md'
|
|
139
|
+
]);
|
|
140
|
+
vi.mocked(access).mockImplementation(async (path) => {
|
|
141
|
+
const pathStr = path.toString();
|
|
142
|
+
for (const missing of missingFiles) {
|
|
143
|
+
if (pathStr.endsWith(missing)) {
|
|
144
|
+
throw { code: 'ENOENT' };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
});
|
|
174
149
|
const result = await checkAgentStructure('/test/project');
|
|
175
150
|
expect(result.dirExists).toBe(true);
|
|
176
151
|
expect(result.missingFiles).toEqual([
|
|
@@ -207,8 +182,8 @@ describe('coding_agents service', () => {
|
|
|
207
182
|
});
|
|
208
183
|
// Should create outputai files (6 templates)
|
|
209
184
|
expect(fs.writeFile).toHaveBeenCalledWith(expect.stringContaining('AGENTS.md'), expect.any(String), 'utf-8');
|
|
210
|
-
// Should create symlinks (
|
|
211
|
-
expect(fs.symlink).toHaveBeenCalledTimes(
|
|
185
|
+
// Should create symlinks (7 symlinks for claude-code)
|
|
186
|
+
expect(fs.symlink).toHaveBeenCalledTimes(7);
|
|
212
187
|
});
|
|
213
188
|
it('should skip existing files when force is false', async () => {
|
|
214
189
|
// Mock some files exist
|
|
@@ -3,3 +3,4 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare const getEjectSuccessMessage: (destPath: string, outputFile: string, binName: string) => string;
|
|
5
5
|
export declare const getProjectSuccessMessage: (folderName: string, installSuccess: boolean, envConfigured?: boolean) => string;
|
|
6
|
+
export declare const getWorkflowGenerateSuccessMessage: (workflowName: string, targetDir: string, filesCreated: string[]) => string;
|
|
@@ -176,7 +176,7 @@ export const getProjectSuccessMessage = (folderName, installSuccess, envConfigur
|
|
|
176
176
|
note: 'Launches Temporal, Redis, PostgreSQL, API, Worker, and UI'
|
|
177
177
|
}, {
|
|
178
178
|
step: 'Run example workflow',
|
|
179
|
-
command: 'output workflow run simple --input
|
|
179
|
+
command: 'output workflow run simple --input src/simple/scenarios/question_ada_lovelace.json',
|
|
180
180
|
note: 'Execute in a new terminal after services are running'
|
|
181
181
|
}, {
|
|
182
182
|
step: 'Monitor workflows',
|
|
@@ -231,3 +231,67 @@ ${ux.colorize('dim', ' with AI assistance.')}
|
|
|
231
231
|
${ux.colorize('green', ux.colorize('bold', 'Happy building with Output SDK! 🚀'))}
|
|
232
232
|
`;
|
|
233
233
|
};
|
|
234
|
+
export const getWorkflowGenerateSuccessMessage = (workflowName, targetDir, filesCreated) => {
|
|
235
|
+
const divider = ux.colorize('dim', '─'.repeat(80));
|
|
236
|
+
const bulletPoint = ux.colorize('green', '▸');
|
|
237
|
+
const formattedFiles = filesCreated.map(file => {
|
|
238
|
+
return ` ${bulletPoint} ${formatPath(file)}`;
|
|
239
|
+
}).join('\n');
|
|
240
|
+
const steps = [
|
|
241
|
+
{
|
|
242
|
+
step: 'Navigate to workflow directory',
|
|
243
|
+
command: `cd ${targetDir}`
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
step: 'Edit workflow files',
|
|
247
|
+
note: 'Customize workflow.ts, steps.ts, and prompts to match your requirements'
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
step: 'Configure environment',
|
|
251
|
+
command: 'Edit .env file',
|
|
252
|
+
note: 'Add your LLM provider credentials (ANTHROPIC_API_KEY or OPENAI_API_KEY)'
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
step: 'Test with example scenario',
|
|
256
|
+
command: `output workflow run ${workflowName} --input ${targetDir}/scenarios/test_input.json`,
|
|
257
|
+
note: 'Run after starting services with "output dev"'
|
|
258
|
+
}
|
|
259
|
+
];
|
|
260
|
+
const formattedSteps = steps.map((item, index) => {
|
|
261
|
+
const stepNumber = ux.colorize('dim', `${index + 1}.`);
|
|
262
|
+
const stepText = ux.colorize('white', item.step);
|
|
263
|
+
const command = item.command ? `\n ${bulletPoint} ${formatCommand(item.command)}` : '';
|
|
264
|
+
const note = item.note ? `\n ${ux.colorize('dim', ` ${item.note}`)}` : '';
|
|
265
|
+
return ` ${stepNumber} ${stepText}${command}${note}`;
|
|
266
|
+
}).join('\n\n');
|
|
267
|
+
return `
|
|
268
|
+
${divider}
|
|
269
|
+
|
|
270
|
+
${ux.colorize('bold', ux.colorize('green', '✅ SUCCESS!'))} ${ux.colorize('bold', `Workflow "${workflowName}" created`)}
|
|
271
|
+
|
|
272
|
+
${divider}
|
|
273
|
+
|
|
274
|
+
${createSectionHeader('WORKFLOW DETAILS', '📁')}
|
|
275
|
+
|
|
276
|
+
${bulletPoint} ${ux.colorize('white', 'Name:')} ${formatPath(workflowName)}
|
|
277
|
+
${bulletPoint} ${ux.colorize('white', 'Location:')} ${formatPath(targetDir)}
|
|
278
|
+
|
|
279
|
+
${divider}
|
|
280
|
+
|
|
281
|
+
${createSectionHeader('FILES CREATED', '📄')}
|
|
282
|
+
|
|
283
|
+
${formattedFiles}
|
|
284
|
+
|
|
285
|
+
${divider}
|
|
286
|
+
|
|
287
|
+
${createSectionHeader('NEXT STEPS', '🚀')}
|
|
288
|
+
|
|
289
|
+
${formattedSteps}
|
|
290
|
+
|
|
291
|
+
${divider}
|
|
292
|
+
|
|
293
|
+
${ux.colorize('dim', '💡 Tip: Check the README.md in your workflow directory for detailed documentation.')}
|
|
294
|
+
|
|
295
|
+
${ux.colorize('green', ux.colorize('bold', 'Happy building! 🛠️'))}
|
|
296
|
+
`;
|
|
297
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* eslint-disable no-restricted-syntax, init-declarations */
|
|
2
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import * as os from 'node:os';
|
|
6
|
+
import { generateWorkflow } from './workflow_generator.js';
|
|
7
|
+
describe('Workflow Generator', () => {
|
|
8
|
+
let tempDir;
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'workflow-gen-test-'));
|
|
11
|
+
});
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
14
|
+
});
|
|
15
|
+
describe('skeleton generation', () => {
|
|
16
|
+
it('should create prompts folder with prompt template', async () => {
|
|
17
|
+
const result = await generateWorkflow({
|
|
18
|
+
name: 'testWorkflow',
|
|
19
|
+
description: 'Test workflow',
|
|
20
|
+
outputDir: tempDir,
|
|
21
|
+
skeleton: true,
|
|
22
|
+
force: false
|
|
23
|
+
});
|
|
24
|
+
const promptPath = path.join(result.targetDir, 'prompts', 'prompt@v1.prompt');
|
|
25
|
+
const promptExists = await fs.access(promptPath).then(() => true).catch(() => false);
|
|
26
|
+
expect(promptExists).toBe(true);
|
|
27
|
+
expect(result.filesCreated).toContain('prompts/prompt@v1.prompt');
|
|
28
|
+
});
|
|
29
|
+
it('should create scenarios folder with test_input.json', async () => {
|
|
30
|
+
const result = await generateWorkflow({
|
|
31
|
+
name: 'testWorkflow',
|
|
32
|
+
description: 'Test workflow',
|
|
33
|
+
outputDir: tempDir,
|
|
34
|
+
skeleton: true,
|
|
35
|
+
force: false
|
|
36
|
+
});
|
|
37
|
+
const scenarioPath = path.join(result.targetDir, 'scenarios', 'test_input.json');
|
|
38
|
+
const scenarioExists = await fs.access(scenarioPath).then(() => true).catch(() => false);
|
|
39
|
+
expect(scenarioExists).toBe(true);
|
|
40
|
+
expect(result.filesCreated).toContain('scenarios/test_input.json');
|
|
41
|
+
});
|
|
42
|
+
it('should create valid JSON in scenario file', async () => {
|
|
43
|
+
const result = await generateWorkflow({
|
|
44
|
+
name: 'testWorkflow',
|
|
45
|
+
description: 'Test workflow',
|
|
46
|
+
outputDir: tempDir,
|
|
47
|
+
skeleton: true,
|
|
48
|
+
force: false
|
|
49
|
+
});
|
|
50
|
+
const scenarioPath = path.join(result.targetDir, 'scenarios', 'test_input.json');
|
|
51
|
+
const content = await fs.readFile(scenarioPath, 'utf-8');
|
|
52
|
+
const parsed = JSON.parse(content);
|
|
53
|
+
expect(parsed).toHaveProperty('prompt');
|
|
54
|
+
expect(parsed).toHaveProperty('data');
|
|
55
|
+
});
|
|
56
|
+
it('should create all expected skeleton files', async () => {
|
|
57
|
+
const result = await generateWorkflow({
|
|
58
|
+
name: 'testWorkflow',
|
|
59
|
+
description: 'Test workflow',
|
|
60
|
+
outputDir: tempDir,
|
|
61
|
+
skeleton: true,
|
|
62
|
+
force: false
|
|
63
|
+
});
|
|
64
|
+
const expectedFiles = [
|
|
65
|
+
'workflow.ts',
|
|
66
|
+
'steps.ts',
|
|
67
|
+
'README.md',
|
|
68
|
+
'.env',
|
|
69
|
+
'prompts/prompt@v1.prompt',
|
|
70
|
+
'scenarios/test_input.json'
|
|
71
|
+
];
|
|
72
|
+
for (const file of expectedFiles) {
|
|
73
|
+
expect(result.filesCreated).toContain(file);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|