@output.ai/cli 0.0.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 +55 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/workflow/generate.d.ts +16 -0
- package/dist/commands/workflow/generate.js +74 -0
- package/dist/commands/workflow/generate.spec.d.ts +1 -0
- package/dist/commands/workflow/generate.spec.js +119 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.spec.d.ts +1 -0
- package/dist/index.spec.js +6 -0
- package/dist/services/template_processor.d.ts +14 -0
- package/dist/services/template_processor.js +42 -0
- package/dist/services/workflow_generator.d.ts +5 -0
- package/dist/services/workflow_generator.js +40 -0
- package/dist/templates/workflow/.env.template +7 -0
- package/dist/templates/workflow/README.md.template +215 -0
- package/dist/templates/workflow/prompt@v1.prompt.template +13 -0
- package/dist/templates/workflow/steps.ts.template +77 -0
- package/dist/templates/workflow/workflow.ts.template +73 -0
- package/dist/types/errors.d.ts +24 -0
- package/dist/types/errors.js +35 -0
- package/dist/types/generator.d.ts +26 -0
- package/dist/types/generator.js +1 -0
- package/dist/utils/paths.d.ts +24 -0
- package/dist/utils/paths.js +35 -0
- package/dist/utils/template.d.ts +9 -0
- package/dist/utils/template.js +30 -0
- package/dist/utils/template.spec.d.ts +1 -0
- package/dist/utils/template.spec.js +71 -0
- package/dist/utils/validation.d.ts +13 -0
- package/dist/utils/validation.js +25 -0
- package/dist/utils/validation.spec.d.ts +1 -0
- package/dist/utils/validation.spec.js +137 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @output.ai/cli
|
|
2
|
+
|
|
3
|
+
CLI tool for generating Output.ai workflows.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install globally from the CLI package
|
|
9
|
+
cd sdk/cli && npm link
|
|
10
|
+
|
|
11
|
+
# Or run directly from the monorepo
|
|
12
|
+
npx @output.ai/cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Generate a Workflow
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Generate a complete workflow with example steps
|
|
21
|
+
output-cli workflow generate my-workflow --description "My awesome workflow"
|
|
22
|
+
|
|
23
|
+
# Generate a minimal skeleton workflow
|
|
24
|
+
output-cli workflow generate my-workflow --skeleton
|
|
25
|
+
|
|
26
|
+
# Generate in a specific directory
|
|
27
|
+
output-cli workflow generate my-workflow --output-dir ./src/workflows
|
|
28
|
+
|
|
29
|
+
# Force overwrite existing workflow
|
|
30
|
+
output-cli workflow generate my-workflow --force
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### Command Options
|
|
34
|
+
|
|
35
|
+
- `--description, -d` - Description of the workflow
|
|
36
|
+
- `--skeleton, -s` - Generate minimal skeleton without example steps
|
|
37
|
+
- `--output-dir, -o` - Output directory (default: `output-workflows/src`)
|
|
38
|
+
- `--force, -f` - Overwrite existing directory
|
|
39
|
+
|
|
40
|
+
#### Generated Structure
|
|
41
|
+
|
|
42
|
+
The CLI creates a complete workflow structure:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
my-workflow/
|
|
46
|
+
├── index.ts # Main workflow definition
|
|
47
|
+
├── steps.ts # Activity/step implementations
|
|
48
|
+
├── types.ts # TypeScript interfaces
|
|
49
|
+
├── prompt@v1.prompt # LLM prompt template (if not skeleton)
|
|
50
|
+
└── README.md # Workflow documentation
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## About
|
|
54
|
+
|
|
55
|
+
Built with [OCLIF](https://oclif.io) - see their documentation for advanced CLI features, plugins, and configuration options.
|
package/bin/dev.cmd
ADDED
package/bin/dev.js
ADDED
package/bin/run.cmd
ADDED
package/bin/run.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Generate extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
skeleton: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
7
|
+
description: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
'output-dir': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
force: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
static args: {
|
|
12
|
+
name: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
private displaySuccess;
|
|
16
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { generateWorkflow } from '../../services/workflow_generator.js';
|
|
3
|
+
import { DEFAULT_OUTPUT_DIRS } from '../../utils/paths.js';
|
|
4
|
+
export default class Generate extends Command {
|
|
5
|
+
static description = 'Generate a new Flow SDK workflow';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %> my-workflow',
|
|
8
|
+
'<%= config.bin %> <%= command.id %> my-workflow --skeleton',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> data-processing --description "Process and transform data"'
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
skeleton: Flags.boolean({
|
|
13
|
+
char: 's',
|
|
14
|
+
description: 'Generate minimal skeleton workflow without example steps',
|
|
15
|
+
default: false
|
|
16
|
+
}),
|
|
17
|
+
description: Flags.string({
|
|
18
|
+
char: 'd',
|
|
19
|
+
description: 'Description of the workflow',
|
|
20
|
+
required: false
|
|
21
|
+
}),
|
|
22
|
+
'output-dir': Flags.string({
|
|
23
|
+
char: 'o',
|
|
24
|
+
description: 'Output directory for the workflow',
|
|
25
|
+
default: DEFAULT_OUTPUT_DIRS.workflows
|
|
26
|
+
}),
|
|
27
|
+
force: Flags.boolean({
|
|
28
|
+
char: 'f',
|
|
29
|
+
description: 'Overwrite existing directory',
|
|
30
|
+
default: false
|
|
31
|
+
})
|
|
32
|
+
};
|
|
33
|
+
static args = {
|
|
34
|
+
name: Args.string({
|
|
35
|
+
required: true,
|
|
36
|
+
description: 'Name of the workflow to generate'
|
|
37
|
+
})
|
|
38
|
+
};
|
|
39
|
+
async run() {
|
|
40
|
+
const { args, flags } = await this.parse(Generate);
|
|
41
|
+
// Check if skeleton flag is required
|
|
42
|
+
if (!flags.skeleton) {
|
|
43
|
+
this.error('Full workflow generation not implemented yet. Please use --skeleton flag');
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const result = await generateWorkflow({
|
|
47
|
+
name: args.name,
|
|
48
|
+
description: flags.description,
|
|
49
|
+
outputDir: flags['output-dir'],
|
|
50
|
+
skeleton: flags.skeleton,
|
|
51
|
+
force: flags.force
|
|
52
|
+
});
|
|
53
|
+
this.displaySuccess(result);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error instanceof Error) {
|
|
57
|
+
this.error(error.message);
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
displaySuccess(result) {
|
|
63
|
+
this.log(`\n[SUCCESS] Workflow "${result.workflowName}" created successfully!`);
|
|
64
|
+
this.log(`\nLocation: ${result.targetDir}`);
|
|
65
|
+
this.log('\n-- Next steps --');
|
|
66
|
+
this.log(` 1. cd ${result.targetDir}`);
|
|
67
|
+
this.log(' 2. Edit the workflow files to customize your implementation');
|
|
68
|
+
this.log(' 3. If using @output.ai/llm, configure your .env file with LLM provider credentials:');
|
|
69
|
+
this.log(' - ANTHROPIC_API_KEY for Claude');
|
|
70
|
+
this.log(' - OPENAI_API_KEY for OpenAI');
|
|
71
|
+
this.log(' 4. Build and run with the worker');
|
|
72
|
+
this.log('\nCheck the README.md for workflow-specific documentation.');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* eslint-disable no-restricted-syntax, @typescript-eslint/no-explicit-any, init-declarations */
|
|
2
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
3
|
+
import Generate from './generate.js';
|
|
4
|
+
import { generateWorkflow } from '../../services/workflow_generator.js';
|
|
5
|
+
import { InvalidNameError, WorkflowExistsError } from '../../types/errors.js';
|
|
6
|
+
// Mock the generateWorkflow function
|
|
7
|
+
vi.mock('../../services/workflow_generator.js');
|
|
8
|
+
describe('Generate Command', () => {
|
|
9
|
+
let mockGenerateWorkflow;
|
|
10
|
+
let logSpy;
|
|
11
|
+
const createCommand = () => {
|
|
12
|
+
const cmd = new Generate([], {});
|
|
13
|
+
// Mock OCLIF methods
|
|
14
|
+
cmd.log = vi.fn();
|
|
15
|
+
cmd.error = vi.fn((message) => {
|
|
16
|
+
throw new Error(message);
|
|
17
|
+
});
|
|
18
|
+
// Mock parse method
|
|
19
|
+
cmd.parse = vi.fn();
|
|
20
|
+
logSpy = cmd.log;
|
|
21
|
+
return cmd;
|
|
22
|
+
};
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
// Mock generateWorkflow function
|
|
26
|
+
mockGenerateWorkflow = vi.mocked(generateWorkflow);
|
|
27
|
+
});
|
|
28
|
+
describe('successful workflow generation', () => {
|
|
29
|
+
it('should generate workflow with skeleton flag', async () => {
|
|
30
|
+
const cmd = createCommand();
|
|
31
|
+
// Mock parse return
|
|
32
|
+
cmd.parse.mockResolvedValue({
|
|
33
|
+
args: { name: 'test-workflow' },
|
|
34
|
+
flags: {
|
|
35
|
+
skeleton: true,
|
|
36
|
+
description: 'Test description',
|
|
37
|
+
'output-dir': '/tmp',
|
|
38
|
+
force: false
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// Mock successful generation
|
|
42
|
+
mockGenerateWorkflow.mockResolvedValue({
|
|
43
|
+
workflowName: 'test-workflow',
|
|
44
|
+
targetDir: '/tmp/test-workflow',
|
|
45
|
+
filesCreated: ['index.ts', 'steps.ts', 'types.ts']
|
|
46
|
+
});
|
|
47
|
+
await cmd.run();
|
|
48
|
+
expect(mockGenerateWorkflow).toHaveBeenCalledWith({
|
|
49
|
+
name: 'test-workflow',
|
|
50
|
+
description: 'Test description',
|
|
51
|
+
outputDir: '/tmp',
|
|
52
|
+
skeleton: true,
|
|
53
|
+
force: false
|
|
54
|
+
});
|
|
55
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('[SUCCESS] Workflow "test-workflow" created successfully!'));
|
|
56
|
+
});
|
|
57
|
+
it('should require skeleton flag and reject without it', async () => {
|
|
58
|
+
const cmd = createCommand();
|
|
59
|
+
cmd.parse.mockResolvedValue({
|
|
60
|
+
args: { name: 'test-workflow' },
|
|
61
|
+
flags: {
|
|
62
|
+
skeleton: false,
|
|
63
|
+
'output-dir': '/tmp',
|
|
64
|
+
force: false
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
await expect(cmd.run()).rejects.toThrow('Full workflow generation not implemented yet. Please use --skeleton flag');
|
|
68
|
+
expect(mockGenerateWorkflow).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('error handling', () => {
|
|
72
|
+
it('should handle invalid name error', async () => {
|
|
73
|
+
const cmd = createCommand();
|
|
74
|
+
cmd.parse.mockResolvedValue({
|
|
75
|
+
args: { name: 'invalid name' },
|
|
76
|
+
flags: { 'output-dir': '/tmp', skeleton: true, force: false }
|
|
77
|
+
});
|
|
78
|
+
mockGenerateWorkflow.mockRejectedValue(new InvalidNameError('invalid name'));
|
|
79
|
+
await expect(cmd.run()).rejects.toThrow(/Invalid workflow name/i);
|
|
80
|
+
});
|
|
81
|
+
it('should handle workflow exists error', async () => {
|
|
82
|
+
const cmd = createCommand();
|
|
83
|
+
cmd.parse.mockResolvedValue({
|
|
84
|
+
args: { name: 'existing-workflow' },
|
|
85
|
+
flags: { 'output-dir': '/tmp', skeleton: true, force: false }
|
|
86
|
+
});
|
|
87
|
+
mockGenerateWorkflow.mockRejectedValue(new WorkflowExistsError('existing-workflow', '/tmp/existing-workflow'));
|
|
88
|
+
await expect(cmd.run()).rejects.toThrow(/already exists/i);
|
|
89
|
+
});
|
|
90
|
+
it('should re-throw non-CLI errors', async () => {
|
|
91
|
+
const cmd = createCommand();
|
|
92
|
+
cmd.parse.mockResolvedValue({
|
|
93
|
+
args: { name: 'test-workflow' },
|
|
94
|
+
flags: { 'output-dir': '/tmp', skeleton: true, force: false }
|
|
95
|
+
});
|
|
96
|
+
const systemError = new Error('System error');
|
|
97
|
+
mockGenerateWorkflow.mockRejectedValue(systemError);
|
|
98
|
+
await expect(cmd.run()).rejects.toThrow(systemError);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('success display', () => {
|
|
102
|
+
it('should display correct success message and next steps', async () => {
|
|
103
|
+
const cmd = createCommand();
|
|
104
|
+
cmd.parse.mockResolvedValue({
|
|
105
|
+
args: { name: 'my-workflow' },
|
|
106
|
+
flags: { 'output-dir': '/custom/path', skeleton: true, force: false }
|
|
107
|
+
});
|
|
108
|
+
mockGenerateWorkflow.mockResolvedValue({
|
|
109
|
+
workflowName: 'my-workflow',
|
|
110
|
+
targetDir: '/custom/path/my-workflow',
|
|
111
|
+
filesCreated: ['index.ts', 'steps.ts', 'types.ts']
|
|
112
|
+
});
|
|
113
|
+
await cmd.run();
|
|
114
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('[SUCCESS] Workflow "my-workflow" created successfully!'));
|
|
115
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Location: /custom/path/my-workflow'));
|
|
116
|
+
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('-- Next steps --'));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TemplateFile } from '../types/generator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Get list of template files from a directory
|
|
4
|
+
* Automatically discovers all .template files and derives output names
|
|
5
|
+
*/
|
|
6
|
+
export declare function getTemplateFiles(templatesDir: string): Promise<TemplateFile[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Process a single template file
|
|
9
|
+
*/
|
|
10
|
+
export declare function processTemplateFile(templateFile: TemplateFile, targetDir: string, variables: Record<string, string>): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Process all template files
|
|
13
|
+
*/
|
|
14
|
+
export declare function processAllTemplates(templateFiles: TemplateFile[], targetDir: string, variables: Record<string, string>): Promise<string[]>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { processTemplate } from '../utils/template.js';
|
|
4
|
+
const TEMPLATE_EXTENSION = '.template';
|
|
5
|
+
function isTemplateFile(file) {
|
|
6
|
+
return file.endsWith(TEMPLATE_EXTENSION);
|
|
7
|
+
}
|
|
8
|
+
function fileToTemplateFile(file, templatesDir) {
|
|
9
|
+
return {
|
|
10
|
+
name: file,
|
|
11
|
+
path: path.join(templatesDir, file),
|
|
12
|
+
outputName: file.replace(TEMPLATE_EXTENSION, '')
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get list of template files from a directory
|
|
17
|
+
* Automatically discovers all .template files and derives output names
|
|
18
|
+
*/
|
|
19
|
+
export async function getTemplateFiles(templatesDir) {
|
|
20
|
+
const files = await fs.readdir(templatesDir);
|
|
21
|
+
return files
|
|
22
|
+
.filter(isTemplateFile)
|
|
23
|
+
.map(file => fileToTemplateFile(file, templatesDir));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Process a single template file
|
|
27
|
+
*/
|
|
28
|
+
export async function processTemplateFile(templateFile, targetDir, variables) {
|
|
29
|
+
const templateContent = await fs.readFile(templateFile.path, 'utf-8');
|
|
30
|
+
const processedContent = processTemplate(templateContent, variables);
|
|
31
|
+
const outputPath = path.join(targetDir, templateFile.outputName);
|
|
32
|
+
await fs.writeFile(outputPath, processedContent, 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Process all template files
|
|
36
|
+
*/
|
|
37
|
+
export async function processAllTemplates(templateFiles, targetDir, variables) {
|
|
38
|
+
return Promise.all(templateFiles.map(async (templateFile) => {
|
|
39
|
+
await processTemplateFile(templateFile, targetDir, variables);
|
|
40
|
+
return templateFile.outputName;
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import { WorkflowExistsError } from '../types/errors.js';
|
|
3
|
+
import { createTargetDir, getTemplateDir } from '../utils/paths.js';
|
|
4
|
+
import { validateWorkflowName, validateOutputDirectory } from '../utils/validation.js';
|
|
5
|
+
import { prepareTemplateVariables } from '../utils/template.js';
|
|
6
|
+
import { getTemplateFiles, processAllTemplates } from './template_processor.js';
|
|
7
|
+
/**
|
|
8
|
+
* Validate the generation configuration
|
|
9
|
+
*/
|
|
10
|
+
function validateConfig(config) {
|
|
11
|
+
validateWorkflowName(config.name);
|
|
12
|
+
validateOutputDirectory(config.outputDir);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if target directory exists and handle accordingly
|
|
16
|
+
*/
|
|
17
|
+
import * as fsSync from 'node:fs';
|
|
18
|
+
async function checkTargetDirectory(targetDir, force) {
|
|
19
|
+
if (fsSync.existsSync(targetDir) && !force) {
|
|
20
|
+
throw new WorkflowExistsError('', targetDir);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Generate a new workflow
|
|
25
|
+
*/
|
|
26
|
+
export async function generateWorkflow(config) {
|
|
27
|
+
validateConfig(config);
|
|
28
|
+
const targetDir = createTargetDir(config.outputDir, config.name);
|
|
29
|
+
const templatesDir = getTemplateDir('workflow');
|
|
30
|
+
await checkTargetDirectory(targetDir, config.force);
|
|
31
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
32
|
+
const variables = prepareTemplateVariables(config.name, config.description || '');
|
|
33
|
+
const templateFiles = await getTemplateFiles(templatesDir);
|
|
34
|
+
const filesCreated = await processAllTemplates(templateFiles, targetDir, variables);
|
|
35
|
+
return {
|
|
36
|
+
workflowName: config.name,
|
|
37
|
+
targetDir,
|
|
38
|
+
filesCreated
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# {{WorkflowName}} Workflow
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This workflow was generated using the Flow SDK CLI. It provides a starting point for building Temporal-based workflows with LLM integration.
|
|
8
|
+
|
|
9
|
+
## Files
|
|
10
|
+
|
|
11
|
+
- `workflow.ts` - Main workflow definition with input/output schemas
|
|
12
|
+
- `steps.ts` - Activity/step definitions with input/output schemas
|
|
13
|
+
- `prompt@v1.prompt` - Example LLM prompt template
|
|
14
|
+
- `.env` - Environment variables for API keys and configuration
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
### Environment Variables
|
|
19
|
+
|
|
20
|
+
Before running the workflow, configure your API keys in the `.env` file:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Required for LLM functionality
|
|
24
|
+
OPENAI_API_KEY=your_openai_api_key_here
|
|
25
|
+
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Workflow Input
|
|
31
|
+
|
|
32
|
+
The workflow expects input matching the input schema defined in `workflow.ts`.
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
```typescript
|
|
36
|
+
{
|
|
37
|
+
prompt: "Tell me about workflows",
|
|
38
|
+
data: {
|
|
39
|
+
value: 42,
|
|
40
|
+
type: "example"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Workflow Output
|
|
46
|
+
|
|
47
|
+
The workflow returns output matching the output schema defined in `workflow.ts`.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
```typescript
|
|
51
|
+
{
|
|
52
|
+
llmResponse: "Generated text from LLM",
|
|
53
|
+
processedData: {
|
|
54
|
+
processed: true,
|
|
55
|
+
timestamp: "2024-01-01T00:00:00Z",
|
|
56
|
+
data: { value: 42, type: "example" }
|
|
57
|
+
},
|
|
58
|
+
message: "Workflow completed successfully"
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
### Workflow Structure
|
|
65
|
+
|
|
66
|
+
The workflow follows the new Flow SDK conventions:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { workflow } from '@output.ai/core';
|
|
70
|
+
import { myStep, anotherStep } from './steps.js';
|
|
71
|
+
|
|
72
|
+
const inputSchema = {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
// Define your input properties
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const outputSchema = {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
// Define your output properties
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export default workflow( {
|
|
87
|
+
name: 'workflowName',
|
|
88
|
+
description: 'Workflow description',
|
|
89
|
+
inputSchema,
|
|
90
|
+
outputSchema,
|
|
91
|
+
fn: async ( input ) => {
|
|
92
|
+
// Call steps directly
|
|
93
|
+
const result = await myStep( input );
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
} );
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Adding New Steps
|
|
100
|
+
|
|
101
|
+
1. Define new steps in `steps.ts` with schemas:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { step } from '@output.ai/core';
|
|
105
|
+
|
|
106
|
+
const inputSchema = {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
value: { type: 'number' }
|
|
110
|
+
},
|
|
111
|
+
required: ['value']
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const outputSchema = {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
result: { type: 'string' }
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const myStep = step( {
|
|
122
|
+
name: 'myStep',
|
|
123
|
+
description: 'Description of what this step does',
|
|
124
|
+
inputSchema,
|
|
125
|
+
outputSchema,
|
|
126
|
+
fn: async ( input: { value: number } ) => {
|
|
127
|
+
// Step implementation
|
|
128
|
+
return { result: `Processed ${input.value}` };
|
|
129
|
+
}
|
|
130
|
+
} );
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
2. Import and use the step in your workflow (`workflow.ts`):
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { myStep } from './steps.js';
|
|
137
|
+
|
|
138
|
+
// Inside workflow fn:
|
|
139
|
+
const result = await myStep( { value: 42 } );
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Using LLM in Steps
|
|
143
|
+
|
|
144
|
+
The template includes an example of using LLM with prompts:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { loadPrompt } from '@output.ai/prompt';
|
|
148
|
+
import { generateText } from '@output.ai/llm';
|
|
149
|
+
import type { Prompt } from '@output.ai/llm';
|
|
150
|
+
|
|
151
|
+
export const llmStep = step( {
|
|
152
|
+
name: 'llmStep',
|
|
153
|
+
description: 'Generate text using LLM',
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: {
|
|
157
|
+
userInput: { type: 'string' }
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
outputSchema: { type: 'string' },
|
|
161
|
+
fn: async ( input: { userInput: string } ) => {
|
|
162
|
+
const prompt = loadPrompt( 'prompt@v1', {
|
|
163
|
+
userInput: input.userInput
|
|
164
|
+
} );
|
|
165
|
+
const response = await generateText( prompt as Prompt );
|
|
166
|
+
return response;
|
|
167
|
+
}
|
|
168
|
+
} );
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Creating Prompt Templates
|
|
172
|
+
|
|
173
|
+
Create new prompt files following the pattern:
|
|
174
|
+
- File naming: `promptName@v1.prompt`
|
|
175
|
+
- Include YAML frontmatter with provider and model
|
|
176
|
+
- Use LiquidJS syntax for variables: `{{ variableName }}`
|
|
177
|
+
|
|
178
|
+
Example prompt file:
|
|
179
|
+
```
|
|
180
|
+
---
|
|
181
|
+
provider: anthropic
|
|
182
|
+
model: claude-3-5-sonnet-latest
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
{{ userInput }}
|
|
186
|
+
|
|
187
|
+
Please provide a helpful response.
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Testing
|
|
191
|
+
|
|
192
|
+
To test your workflow:
|
|
193
|
+
|
|
194
|
+
1. Build the parent project containing this workflow
|
|
195
|
+
2. Start the Flow worker
|
|
196
|
+
3. Execute the workflow using the Flow SDK API
|
|
197
|
+
|
|
198
|
+
Example execution:
|
|
199
|
+
```bash
|
|
200
|
+
curl -X POST http://localhost:3001/workflow \
|
|
201
|
+
-H "Content-Type: application/json" \
|
|
202
|
+
-d '{
|
|
203
|
+
"workflowName": "{{workflowName}}",
|
|
204
|
+
"input": {
|
|
205
|
+
"prompt": "Tell me about workflows",
|
|
206
|
+
"data": { "value": 42, "type": "example" }
|
|
207
|
+
}
|
|
208
|
+
}'
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Resources
|
|
212
|
+
|
|
213
|
+
- [Flow SDK Documentation](https://github.com/growthxai/flow-sdk)
|
|
214
|
+
- [Temporal Documentation](https://docs.temporal.io)
|
|
215
|
+
- [AI SDK Documentation](https://sdk.vercel.ai/docs)
|