@output.ai/cli 0.0.2 → 0.0.4
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 +11 -11
- package/dist/api/generated/api.d.ts +13 -13
- package/dist/api/generated/api.js +1 -1
- package/dist/commands/agents/init.js +11 -1
- package/dist/commands/agents/init.spec.js +52 -0
- package/dist/commands/workflow/generate.js +0 -1
- package/dist/commands/workflow/generate.spec.js +0 -6
- package/dist/commands/workflow/list.d.ts +1 -1
- package/dist/commands/workflow/list.js +26 -42
- package/dist/commands/workflow/output.d.ts +13 -0
- package/dist/commands/workflow/output.js +49 -0
- package/dist/commands/workflow/output.test.d.ts +1 -0
- package/dist/commands/workflow/output.test.js +23 -0
- package/dist/commands/workflow/run.d.ts +15 -0
- package/dist/commands/workflow/run.js +66 -0
- package/dist/commands/workflow/run.test.d.ts +1 -0
- package/dist/commands/workflow/run.test.js +26 -0
- package/dist/commands/workflow/start.d.ts +14 -0
- package/dist/commands/workflow/start.js +57 -0
- package/dist/commands/workflow/start.test.d.ts +1 -0
- package/dist/commands/workflow/start.test.js +23 -0
- package/dist/commands/workflow/status.d.ts +13 -0
- package/dist/commands/workflow/status.js +56 -0
- package/dist/commands/workflow/status.test.d.ts +1 -0
- package/dist/commands/workflow/status.test.js +33 -0
- package/dist/commands/workflow/stop.d.ts +10 -0
- package/dist/commands/workflow/stop.js +31 -0
- package/dist/commands/workflow/stop.test.d.ts +1 -0
- package/dist/commands/workflow/stop.test.js +17 -0
- package/dist/templates/workflow/README.md.template +2 -2
- package/dist/utils/constants.d.ts +5 -0
- package/dist/utils/constants.js +4 -0
- package/dist/utils/error_handler.d.ts +8 -0
- package/dist/utils/error_handler.js +25 -0
- package/dist/utils/input_parser.d.ts +1 -0
- package/dist/utils/input_parser.js +19 -0
- package/dist/utils/output_formatter.d.ts +2 -0
- package/dist/utils/output_formatter.js +11 -0
- package/package.json +20 -30
package/README.md
CHANGED
|
@@ -18,25 +18,25 @@ npx @output.ai/cli
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
# List all available workflows (simple list, default)
|
|
21
|
-
output
|
|
21
|
+
output workflow list
|
|
22
22
|
|
|
23
23
|
# List workflows with custom API URL
|
|
24
|
-
API_URL=http://localhost:3001 output
|
|
24
|
+
API_URL=http://localhost:3001 output workflow list
|
|
25
25
|
|
|
26
26
|
# List workflows with authentication
|
|
27
|
-
API_AUTH_TOKEN=your-token output
|
|
27
|
+
API_AUTH_TOKEN=your-token output workflow list
|
|
28
28
|
|
|
29
29
|
# Show detailed table view with all information
|
|
30
|
-
output
|
|
30
|
+
output workflow list --format table
|
|
31
31
|
|
|
32
32
|
# Show detailed table with expanded parameter info
|
|
33
|
-
output
|
|
33
|
+
output workflow list --format table --detailed
|
|
34
34
|
|
|
35
35
|
# List workflows in JSON format
|
|
36
|
-
output
|
|
36
|
+
output workflow list --format json
|
|
37
37
|
|
|
38
38
|
# Filter workflows by name (partial match)
|
|
39
|
-
output
|
|
39
|
+
output workflow list --filter simple
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
#### Command Options
|
|
@@ -51,16 +51,16 @@ The list command connects to the API server and retrieves all available workflow
|
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
53
|
# Generate a complete workflow with example steps
|
|
54
|
-
output
|
|
54
|
+
output workflow generate my-workflow --description "My awesome workflow"
|
|
55
55
|
|
|
56
56
|
# Generate a minimal skeleton workflow
|
|
57
|
-
output
|
|
57
|
+
output workflow generate my-workflow --skeleton
|
|
58
58
|
|
|
59
59
|
# Generate in a specific directory
|
|
60
|
-
output
|
|
60
|
+
output workflow generate my-workflow --output-dir ./src/workflows
|
|
61
61
|
|
|
62
62
|
# Force overwrite existing workflow
|
|
63
|
-
output
|
|
63
|
+
output workflow generate my-workflow --force
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
#### Command Options
|
|
@@ -35,7 +35,7 @@ export type PostWorkflowRunBody = {
|
|
|
35
35
|
export type PostWorkflowRun200 = {
|
|
36
36
|
/** The workflow execution id */
|
|
37
37
|
workflowId?: string;
|
|
38
|
-
/** The output of the
|
|
38
|
+
/** The output of the workflow */
|
|
39
39
|
output?: unknown;
|
|
40
40
|
};
|
|
41
41
|
export type PostWorkflowStartBody = {
|
|
@@ -156,8 +156,8 @@ export type getWorkflowIdStatusResponseError = (getWorkflowIdStatusResponse404)
|
|
|
156
156
|
headers: Headers;
|
|
157
157
|
};
|
|
158
158
|
export type getWorkflowIdStatusResponse = (getWorkflowIdStatusResponseSuccess | getWorkflowIdStatusResponseError);
|
|
159
|
-
export declare const getGetWorkflowIdStatusUrl: (id:
|
|
160
|
-
export declare const getWorkflowIdStatus: (id:
|
|
159
|
+
export declare const getGetWorkflowIdStatusUrl: (id: string) => string;
|
|
160
|
+
export declare const getWorkflowIdStatus: (id: string, options?: RequestInit) => Promise<getWorkflowIdStatusResponse>;
|
|
161
161
|
/**
|
|
162
162
|
* @summary Stop a workflow execution
|
|
163
163
|
*/
|
|
@@ -176,8 +176,8 @@ export type patchWorkflowIdStopResponseError = (patchWorkflowIdStopResponse404)
|
|
|
176
176
|
headers: Headers;
|
|
177
177
|
};
|
|
178
178
|
export type patchWorkflowIdStopResponse = (patchWorkflowIdStopResponseSuccess | patchWorkflowIdStopResponseError);
|
|
179
|
-
export declare const getPatchWorkflowIdStopUrl: (id:
|
|
180
|
-
export declare const patchWorkflowIdStop: (id:
|
|
179
|
+
export declare const getPatchWorkflowIdStopUrl: (id: string) => string;
|
|
180
|
+
export declare const patchWorkflowIdStop: (id: string, options?: RequestInit) => Promise<patchWorkflowIdStopResponse>;
|
|
181
181
|
/**
|
|
182
182
|
* @summary Return the output of a workflow
|
|
183
183
|
*/
|
|
@@ -196,8 +196,8 @@ export type getWorkflowIdOutputResponseError = (getWorkflowIdOutputResponse404)
|
|
|
196
196
|
headers: Headers;
|
|
197
197
|
};
|
|
198
198
|
export type getWorkflowIdOutputResponse = (getWorkflowIdOutputResponseSuccess | getWorkflowIdOutputResponseError);
|
|
199
|
-
export declare const getGetWorkflowIdOutputUrl: (id:
|
|
200
|
-
export declare const getWorkflowIdOutput: (id:
|
|
199
|
+
export declare const getGetWorkflowIdOutputUrl: (id: string) => string;
|
|
200
|
+
export declare const getWorkflowIdOutput: (id: string, options?: RequestInit) => Promise<getWorkflowIdOutputResponse>;
|
|
201
201
|
/**
|
|
202
202
|
* @summary Return the trace of a workflow execution
|
|
203
203
|
*/
|
|
@@ -216,8 +216,8 @@ export type getWorkflowIdTraceResponseError = (getWorkflowIdTraceResponse404) &
|
|
|
216
216
|
headers: Headers;
|
|
217
217
|
};
|
|
218
218
|
export type getWorkflowIdTraceResponse = (getWorkflowIdTraceResponseSuccess | getWorkflowIdTraceResponseError);
|
|
219
|
-
export declare const getGetWorkflowIdTraceUrl: (id:
|
|
220
|
-
export declare const getWorkflowIdTrace: (id:
|
|
219
|
+
export declare const getGetWorkflowIdTraceUrl: (id: string) => string;
|
|
220
|
+
export declare const getWorkflowIdTrace: (id: string, options?: RequestInit) => Promise<getWorkflowIdTraceResponse>;
|
|
221
221
|
/**
|
|
222
222
|
* @summary Get a specific workflow catalog by ID
|
|
223
223
|
*/
|
|
@@ -229,8 +229,8 @@ export type getWorkflowCatalogIdResponseSuccess = (getWorkflowCatalogIdResponse2
|
|
|
229
229
|
headers: Headers;
|
|
230
230
|
};
|
|
231
231
|
export type getWorkflowCatalogIdResponse = (getWorkflowCatalogIdResponseSuccess);
|
|
232
|
-
export declare const getGetWorkflowCatalogIdUrl: (id:
|
|
233
|
-
export declare const getWorkflowCatalogId: (id:
|
|
232
|
+
export declare const getGetWorkflowCatalogIdUrl: (id: string) => string;
|
|
233
|
+
export declare const getWorkflowCatalogId: (id: string, options?: RequestInit) => Promise<getWorkflowCatalogIdResponse>;
|
|
234
234
|
/**
|
|
235
235
|
* @summary Get the default workflow catalog
|
|
236
236
|
*/
|
|
@@ -255,8 +255,8 @@ export type postWorkflowIdFeedbackResponseSuccess = (postWorkflowIdFeedbackRespo
|
|
|
255
255
|
headers: Headers;
|
|
256
256
|
};
|
|
257
257
|
export type postWorkflowIdFeedbackResponse = (postWorkflowIdFeedbackResponseSuccess);
|
|
258
|
-
export declare const getPostWorkflowIdFeedbackUrl: (id:
|
|
259
|
-
export declare const postWorkflowIdFeedback: (id:
|
|
258
|
+
export declare const getPostWorkflowIdFeedbackUrl: (id: string) => string;
|
|
259
|
+
export declare const postWorkflowIdFeedback: (id: string, postWorkflowIdFeedbackBody: PostWorkflowIdFeedbackBody, options?: RequestInit) => Promise<postWorkflowIdFeedbackResponse>;
|
|
260
260
|
/**
|
|
261
261
|
* @summary A dummy post endpoint for test only
|
|
262
262
|
*/
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Generated by orval v7.13.0 🍺
|
|
3
3
|
* Do not edit manually.
|
|
4
4
|
* Output.ai SDK API
|
|
5
|
-
* API for managing and executing Temporal workflows through
|
|
5
|
+
* API for managing and executing Temporal workflows through Output SDK
|
|
6
6
|
* OpenAPI spec version: 1.0.0
|
|
7
7
|
*/
|
|
8
8
|
import { customFetchInstance } from '../http_client.js';
|
|
@@ -22,6 +22,16 @@ const AGENT_CONFIGS = {
|
|
|
22
22
|
type: 'template',
|
|
23
23
|
from: 'commands/plan_workflow.md.template',
|
|
24
24
|
to: '.outputai/commands/plan_workflow.md'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'template',
|
|
28
|
+
from: 'meta/pre_flight.md.template',
|
|
29
|
+
to: '.outputai/meta/pre_flight.md'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'template',
|
|
33
|
+
from: 'meta/post_flight.md.template',
|
|
34
|
+
to: '.outputai/meta/post_flight.md'
|
|
25
35
|
}
|
|
26
36
|
]
|
|
27
37
|
},
|
|
@@ -77,7 +87,7 @@ export default class Init extends Command {
|
|
|
77
87
|
this.log('✅ Agent configuration initialized successfully!');
|
|
78
88
|
this.log('');
|
|
79
89
|
this.log('Created:');
|
|
80
|
-
this.log(' • .outputai/ directory with agent and
|
|
90
|
+
this.log(' • .outputai/ directory with agent, command, and meta configurations');
|
|
81
91
|
this.log(' • .claude/ directory with symlinks for Claude Code integration');
|
|
82
92
|
this.log('');
|
|
83
93
|
this.log('Claude Code will automatically detect and use these configurations.');
|
|
@@ -86,6 +86,7 @@ describe('agents init', () => {
|
|
|
86
86
|
expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.outputai'), expect.objectContaining({ recursive: true }));
|
|
87
87
|
expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.outputai/agents'), expect.objectContaining({ recursive: true }));
|
|
88
88
|
expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.outputai/commands'), expect.objectContaining({ recursive: true }));
|
|
89
|
+
expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('.outputai/meta'), expect.objectContaining({ recursive: true }));
|
|
89
90
|
});
|
|
90
91
|
it('should create .claude directory structure', async () => {
|
|
91
92
|
const mockMkdir = vi.mocked(fs.mkdir);
|
|
@@ -133,6 +134,19 @@ describe('agents init', () => {
|
|
|
133
134
|
expect(mockWriteFile).toHaveBeenCalledWith(file, expect.any(String), 'utf-8');
|
|
134
135
|
}
|
|
135
136
|
});
|
|
137
|
+
it('should create all meta configuration files', async () => {
|
|
138
|
+
const mockWriteFile = vi.mocked(fs.writeFile);
|
|
139
|
+
const cmd = createTestCommand();
|
|
140
|
+
cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
|
|
141
|
+
await cmd.run();
|
|
142
|
+
const metaFiles = [
|
|
143
|
+
'.outputai/meta/pre_flight.md',
|
|
144
|
+
'.outputai/meta/post_flight.md'
|
|
145
|
+
];
|
|
146
|
+
for (const file of metaFiles) {
|
|
147
|
+
expect(mockWriteFile).toHaveBeenCalledWith(file, expect.any(String), 'utf-8');
|
|
148
|
+
}
|
|
149
|
+
});
|
|
136
150
|
});
|
|
137
151
|
describe('symlink creation', () => {
|
|
138
152
|
it('should create symlink from CLAUDE.md to .outputai/AGENTS.md', async () => {
|
|
@@ -203,6 +217,44 @@ describe('agents init', () => {
|
|
|
203
217
|
expect(cmd.error).not.toHaveBeenCalled();
|
|
204
218
|
});
|
|
205
219
|
});
|
|
220
|
+
describe('complete initialization', () => {
|
|
221
|
+
it('should create all required files and directories', async () => {
|
|
222
|
+
const mockMkdir = vi.mocked(fs.mkdir);
|
|
223
|
+
const mockWriteFile = vi.mocked(fs.writeFile);
|
|
224
|
+
const mockSymlink = vi.mocked(fs.symlink);
|
|
225
|
+
const cmd = createTestCommand();
|
|
226
|
+
cmd.parse.mockResolvedValue({ flags: { 'agent-provider': 'claude-code', force: false }, args: {} });
|
|
227
|
+
await cmd.run();
|
|
228
|
+
// Verify all expected directories are created
|
|
229
|
+
const expectedDirs = [
|
|
230
|
+
'.outputai',
|
|
231
|
+
'.outputai/agents',
|
|
232
|
+
'.outputai/commands',
|
|
233
|
+
'.outputai/meta',
|
|
234
|
+
'.claude',
|
|
235
|
+
'.claude/agents',
|
|
236
|
+
'.claude/commands'
|
|
237
|
+
];
|
|
238
|
+
for (const dir of expectedDirs) {
|
|
239
|
+
expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining(dir), expect.objectContaining({ recursive: true }));
|
|
240
|
+
}
|
|
241
|
+
// Verify all expected template files are created
|
|
242
|
+
const expectedFiles = [
|
|
243
|
+
'.outputai/AGENTS.md',
|
|
244
|
+
'.outputai/agents/workflow_planner.md',
|
|
245
|
+
'.outputai/commands/plan_workflow.md',
|
|
246
|
+
'.outputai/meta/pre_flight.md',
|
|
247
|
+
'.outputai/meta/post_flight.md'
|
|
248
|
+
];
|
|
249
|
+
for (const file of expectedFiles) {
|
|
250
|
+
expect(mockWriteFile).toHaveBeenCalledWith(file, expect.any(String), 'utf-8');
|
|
251
|
+
}
|
|
252
|
+
// Verify symlinks are created
|
|
253
|
+
expect(mockSymlink).toHaveBeenCalledWith('.outputai/AGENTS.md', 'CLAUDE.md');
|
|
254
|
+
expect(mockSymlink).toHaveBeenCalledWith(expect.stringContaining('.outputai/agents/workflow_planner.md'), '.claude/agents/workflow_planner.md');
|
|
255
|
+
expect(mockSymlink).toHaveBeenCalledWith(expect.stringContaining('.outputai/commands/plan_workflow.md'), '.claude/commands/plan_workflow.md');
|
|
256
|
+
});
|
|
257
|
+
});
|
|
206
258
|
describe('force flag', () => {
|
|
207
259
|
it('should overwrite existing files when force flag is set', async () => {
|
|
208
260
|
const mockWriteFile = vi.mocked(fs.writeFile);
|
|
@@ -38,7 +38,6 @@ export default class Generate extends Command {
|
|
|
38
38
|
};
|
|
39
39
|
async run() {
|
|
40
40
|
const { args, flags } = await this.parse(Generate);
|
|
41
|
-
// Check if skeleton flag is required
|
|
42
41
|
if (!flags.skeleton) {
|
|
43
42
|
this.error('Full workflow generation not implemented yet. Please use --skeleton flag');
|
|
44
43
|
}
|
|
@@ -3,32 +3,27 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
|
3
3
|
import Generate from './generate.js';
|
|
4
4
|
import { generateWorkflow } from '../../services/workflow_generator.js';
|
|
5
5
|
import { InvalidNameError, WorkflowExistsError } from '../../types/errors.js';
|
|
6
|
-
// Mock the generateWorkflow function
|
|
7
6
|
vi.mock('../../services/workflow_generator.js');
|
|
8
7
|
describe('Generate Command', () => {
|
|
9
8
|
let mockGenerateWorkflow;
|
|
10
9
|
let logSpy;
|
|
11
10
|
const createCommand = () => {
|
|
12
11
|
const cmd = new Generate([], {});
|
|
13
|
-
// Mock OCLIF methods
|
|
14
12
|
cmd.log = vi.fn();
|
|
15
13
|
cmd.error = vi.fn((message) => {
|
|
16
14
|
throw new Error(message);
|
|
17
15
|
});
|
|
18
|
-
// Mock parse method
|
|
19
16
|
cmd.parse = vi.fn();
|
|
20
17
|
logSpy = cmd.log;
|
|
21
18
|
return cmd;
|
|
22
19
|
};
|
|
23
20
|
beforeEach(() => {
|
|
24
21
|
vi.clearAllMocks();
|
|
25
|
-
// Mock generateWorkflow function
|
|
26
22
|
mockGenerateWorkflow = vi.mocked(generateWorkflow);
|
|
27
23
|
});
|
|
28
24
|
describe('successful workflow generation', () => {
|
|
29
25
|
it('should generate workflow with skeleton flag', async () => {
|
|
30
26
|
const cmd = createCommand();
|
|
31
|
-
// Mock parse return
|
|
32
27
|
cmd.parse.mockResolvedValue({
|
|
33
28
|
args: { name: 'test-workflow' },
|
|
34
29
|
flags: {
|
|
@@ -38,7 +33,6 @@ describe('Generate Command', () => {
|
|
|
38
33
|
force: false
|
|
39
34
|
}
|
|
40
35
|
});
|
|
41
|
-
// Mock successful generation
|
|
42
36
|
mockGenerateWorkflow.mockResolvedValue({
|
|
43
37
|
workflowName: 'test-workflow',
|
|
44
38
|
targetDir: '/tmp/test-workflow',
|
|
@@ -16,6 +16,6 @@ export default class WorkflowList extends Command {
|
|
|
16
16
|
filter: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
17
17
|
};
|
|
18
18
|
run(): Promise<void>;
|
|
19
|
-
|
|
19
|
+
catch(error: Error): Promise<void>;
|
|
20
20
|
}
|
|
21
21
|
export {};
|
|
@@ -2,7 +2,7 @@ import { Command, Flags } from '@oclif/core';
|
|
|
2
2
|
import Table from 'cli-table3';
|
|
3
3
|
import { getWorkflowCatalog } from '../../api/generated/api.js';
|
|
4
4
|
import { parseWorkflowDefinition, formatParameters } from '../../api/parser.js';
|
|
5
|
-
import {
|
|
5
|
+
import { handleApiError } from '../../utils/error_handler.js';
|
|
6
6
|
const OUTPUT_FORMAT = {
|
|
7
7
|
LIST: 'list',
|
|
8
8
|
TABLE: 'table',
|
|
@@ -119,51 +119,35 @@ export default class WorkflowList extends Command {
|
|
|
119
119
|
};
|
|
120
120
|
async run() {
|
|
121
121
|
const { flags } = await this.parse(WorkflowList);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.error('Failed to connect to API server. Is it running?', { exit: 1 });
|
|
127
|
-
}
|
|
128
|
-
if (!response.data) {
|
|
129
|
-
this.error('API returned invalid response (missing data)', { exit: 1 });
|
|
130
|
-
}
|
|
131
|
-
if (!response.data.workflows) {
|
|
132
|
-
this.error('API returned invalid response (missing workflows)', { exit: 1 });
|
|
133
|
-
}
|
|
134
|
-
if (response.data.workflows.length === 0) {
|
|
135
|
-
this.log('No workflows found in catalog.');
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const workflows = flags.filter ?
|
|
139
|
-
response.data.workflows.filter(matchName(flags.filter)) :
|
|
140
|
-
response.data.workflows;
|
|
141
|
-
if (workflows.length === 0 && flags.filter) {
|
|
142
|
-
this.log(`No workflows matching filter: ${flags.filter}`);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const output = formatWorkflows(workflows, flags.format, flags.detailed);
|
|
146
|
-
this.log(output);
|
|
147
|
-
this.log(`\nFound ${workflows.length} workflow(s)`);
|
|
122
|
+
this.log('Fetching workflow catalog...');
|
|
123
|
+
const response = await getWorkflowCatalog();
|
|
124
|
+
if (!response) {
|
|
125
|
+
this.error('Failed to connect to API server. Is it running?', { exit: 1 });
|
|
148
126
|
}
|
|
149
|
-
|
|
150
|
-
this.
|
|
127
|
+
if (!response.data) {
|
|
128
|
+
this.error('API returned invalid response (missing data)', { exit: 1 });
|
|
151
129
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const apiError = error;
|
|
155
|
-
if (apiError.code === 'ECONNREFUSED') {
|
|
156
|
-
this.error(`Connection refused to ${config.apiUrl}`, { exit: 1 });
|
|
157
|
-
}
|
|
158
|
-
if (apiError.response?.status === 401) {
|
|
159
|
-
this.error('Authentication failed.', { exit: 1 });
|
|
130
|
+
if (!response.data.workflows) {
|
|
131
|
+
this.error('API returned invalid response (missing workflows)', { exit: 1 });
|
|
160
132
|
}
|
|
161
|
-
if (
|
|
162
|
-
this.
|
|
133
|
+
if (response.data.workflows.length === 0) {
|
|
134
|
+
this.log('No workflows found in catalog.');
|
|
135
|
+
return;
|
|
163
136
|
}
|
|
164
|
-
|
|
165
|
-
|
|
137
|
+
const workflows = flags.filter ?
|
|
138
|
+
response.data.workflows.filter(matchName(flags.filter)) :
|
|
139
|
+
response.data.workflows;
|
|
140
|
+
if (workflows.length === 0 && flags.filter) {
|
|
141
|
+
this.log(`No workflows matching filter: ${flags.filter}`);
|
|
142
|
+
return;
|
|
166
143
|
}
|
|
167
|
-
|
|
144
|
+
const output = formatWorkflows(workflows, flags.format, flags.detailed);
|
|
145
|
+
this.log(output);
|
|
146
|
+
this.log(`\nFound ${workflows.length} workflow(s)`);
|
|
147
|
+
}
|
|
148
|
+
async catch(error) {
|
|
149
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
150
|
+
404: 'Catalog not found.'
|
|
151
|
+
});
|
|
168
152
|
}
|
|
169
153
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class WorkflowOutput extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
workflowId: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
format: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
catch(error: Error): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getWorkflowIdOutput } from '../../api/generated/api.js';
|
|
3
|
+
import { OUTPUT_FORMAT } from '../../utils/constants.js';
|
|
4
|
+
import { formatOutput } from '../../utils/output_formatter.js';
|
|
5
|
+
import { handleApiError } from '../../utils/error_handler.js';
|
|
6
|
+
export default class WorkflowOutput extends Command {
|
|
7
|
+
static description = 'Get workflow execution output';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> wf-12345',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> wf-12345 --format json'
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
workflowId: Args.string({
|
|
14
|
+
description: 'The workflow ID to get output for',
|
|
15
|
+
required: true
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
format: Flags.string({
|
|
20
|
+
char: 'f',
|
|
21
|
+
description: 'Output format',
|
|
22
|
+
options: [OUTPUT_FORMAT.JSON, OUTPUT_FORMAT.TEXT],
|
|
23
|
+
default: OUTPUT_FORMAT.TEXT
|
|
24
|
+
})
|
|
25
|
+
};
|
|
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);
|
|
30
|
+
if (!response || !response.data) {
|
|
31
|
+
this.error('API returned invalid response', { exit: 1 });
|
|
32
|
+
}
|
|
33
|
+
const output = formatOutput(response.data, flags.format, (result) => {
|
|
34
|
+
const lines = [
|
|
35
|
+
`Workflow ID: ${result.workflowId || 'unknown'}`,
|
|
36
|
+
'',
|
|
37
|
+
'Output:',
|
|
38
|
+
JSON.stringify(result.output, null, 2)
|
|
39
|
+
];
|
|
40
|
+
return lines.join('\n');
|
|
41
|
+
});
|
|
42
|
+
this.log(`\n${output}`);
|
|
43
|
+
}
|
|
44
|
+
async catch(error) {
|
|
45
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
46
|
+
404: 'Workflow not found. Check the workflow ID.'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('../../api/generated/api.js', () => ({
|
|
3
|
+
getWorkflowIdOutput: vi.fn()
|
|
4
|
+
}));
|
|
5
|
+
describe('workflow output command', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
describe('command definition', () => {
|
|
10
|
+
it('should export a valid OCLIF command', async () => {
|
|
11
|
+
const WorkflowOutput = (await import('./output.js')).default;
|
|
12
|
+
expect(WorkflowOutput).toBeDefined();
|
|
13
|
+
expect(WorkflowOutput.description).toContain('Get workflow execution output');
|
|
14
|
+
expect(WorkflowOutput.args).toHaveProperty('workflowId');
|
|
15
|
+
expect(WorkflowOutput.flags).toHaveProperty('format');
|
|
16
|
+
});
|
|
17
|
+
it('should have correct flag configuration', async () => {
|
|
18
|
+
const WorkflowOutput = (await import('./output.js')).default;
|
|
19
|
+
expect(WorkflowOutput.flags.format.options).toEqual(['json', 'text']);
|
|
20
|
+
expect(WorkflowOutput.flags.format.default).toBe('text');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class WorkflowRun extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
workflowName: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
input: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
'task-queue': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
+
format: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
catch(error: Error): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { postWorkflowRun } from '../../api/generated/api.js';
|
|
3
|
+
import { OUTPUT_FORMAT } from '../../utils/constants.js';
|
|
4
|
+
import { parseInputFlag } from '../../utils/input_parser.js';
|
|
5
|
+
import { formatOutput } from '../../utils/output_formatter.js';
|
|
6
|
+
import { handleApiError } from '../../utils/error_handler.js';
|
|
7
|
+
export default class WorkflowRun extends Command {
|
|
8
|
+
static description = 'Execute a workflow synchronously and wait for completion';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"values":[1,2,3]}\'',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> simple --input input.json',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"key":"value"}\' --format json',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"key":"value"}\' --task-queue my-queue'
|
|
14
|
+
];
|
|
15
|
+
static args = {
|
|
16
|
+
workflowName: Args.string({
|
|
17
|
+
description: 'Name of the workflow to execute',
|
|
18
|
+
required: true
|
|
19
|
+
})
|
|
20
|
+
};
|
|
21
|
+
static flags = {
|
|
22
|
+
input: Flags.string({
|
|
23
|
+
char: 'i',
|
|
24
|
+
description: 'Workflow input as JSON string or file path',
|
|
25
|
+
required: true
|
|
26
|
+
}),
|
|
27
|
+
'task-queue': Flags.string({
|
|
28
|
+
char: 'q',
|
|
29
|
+
description: 'Task queue name for workflow execution'
|
|
30
|
+
}),
|
|
31
|
+
format: Flags.string({
|
|
32
|
+
char: 'f',
|
|
33
|
+
description: 'Output format',
|
|
34
|
+
options: [OUTPUT_FORMAT.JSON, OUTPUT_FORMAT.TEXT],
|
|
35
|
+
default: OUTPUT_FORMAT.TEXT
|
|
36
|
+
})
|
|
37
|
+
};
|
|
38
|
+
async run() {
|
|
39
|
+
const { args, flags } = await this.parse(WorkflowRun);
|
|
40
|
+
const input = parseInputFlag(flags.input);
|
|
41
|
+
this.log(`Executing workflow: ${args.workflowName}...`);
|
|
42
|
+
const response = await postWorkflowRun({
|
|
43
|
+
workflowName: args.workflowName,
|
|
44
|
+
input,
|
|
45
|
+
taskQueue: flags['task-queue']
|
|
46
|
+
});
|
|
47
|
+
if (!response || !response.data) {
|
|
48
|
+
this.error('API returned invalid response', { exit: 1 });
|
|
49
|
+
}
|
|
50
|
+
const output = formatOutput(response.data, flags.format, (result) => {
|
|
51
|
+
const lines = [
|
|
52
|
+
`Workflow ID: ${result.workflowId || 'unknown'}`,
|
|
53
|
+
'',
|
|
54
|
+
'Output:',
|
|
55
|
+
JSON.stringify(result.output, null, 2)
|
|
56
|
+
];
|
|
57
|
+
return lines.join('\n');
|
|
58
|
+
});
|
|
59
|
+
this.log(`\n${output}`);
|
|
60
|
+
}
|
|
61
|
+
async catch(error) {
|
|
62
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
63
|
+
404: 'Workflow not found. Check the workflow name.'
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('../../api/generated/api.js', () => ({
|
|
3
|
+
postWorkflowRun: vi.fn()
|
|
4
|
+
}));
|
|
5
|
+
describe('workflow run command', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
describe('command definition', () => {
|
|
10
|
+
it('should export a valid OCLIF command', async () => {
|
|
11
|
+
const WorkflowRun = (await import('./run.js')).default;
|
|
12
|
+
expect(WorkflowRun).toBeDefined();
|
|
13
|
+
expect(WorkflowRun.description).toContain('Execute a workflow');
|
|
14
|
+
expect(WorkflowRun.args).toHaveProperty('workflowName');
|
|
15
|
+
expect(WorkflowRun.flags).toHaveProperty('input');
|
|
16
|
+
expect(WorkflowRun.flags).toHaveProperty('format');
|
|
17
|
+
expect(WorkflowRun.flags).toHaveProperty('task-queue');
|
|
18
|
+
});
|
|
19
|
+
it('should have correct flag configuration', async () => {
|
|
20
|
+
const WorkflowRun = (await import('./run.js')).default;
|
|
21
|
+
expect(WorkflowRun.flags.format.options).toEqual(['json', 'text']);
|
|
22
|
+
expect(WorkflowRun.flags.format.default).toBe('text');
|
|
23
|
+
expect(WorkflowRun.flags.input.required).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class WorkflowStart extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
workflowName: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
input: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
'task-queue': import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
catch(error: Error): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { postWorkflowStart } from '../../api/generated/api.js';
|
|
3
|
+
import { parseInputFlag } from '../../utils/input_parser.js';
|
|
4
|
+
import { handleApiError } from '../../utils/error_handler.js';
|
|
5
|
+
export default class WorkflowStart extends Command {
|
|
6
|
+
static description = 'Start a workflow asynchronously without waiting for completion';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"values":[1,2,3]}\'',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> simple --input input.json',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> simple --input \'{"key":"value"}\' --task-queue my-queue'
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
workflowName: Args.string({
|
|
14
|
+
description: 'Name of the workflow to start',
|
|
15
|
+
required: true
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
input: Flags.string({
|
|
20
|
+
char: 'i',
|
|
21
|
+
description: 'Workflow input as JSON string or file path',
|
|
22
|
+
required: true
|
|
23
|
+
}),
|
|
24
|
+
'task-queue': Flags.string({
|
|
25
|
+
char: 'q',
|
|
26
|
+
description: 'Task queue name for workflow execution'
|
|
27
|
+
})
|
|
28
|
+
};
|
|
29
|
+
async run() {
|
|
30
|
+
const { args, flags } = await this.parse(WorkflowStart);
|
|
31
|
+
const input = parseInputFlag(flags.input);
|
|
32
|
+
this.log(`Starting workflow: ${args.workflowName}...`);
|
|
33
|
+
const response = await postWorkflowStart({
|
|
34
|
+
workflowName: args.workflowName,
|
|
35
|
+
input,
|
|
36
|
+
taskQueue: flags['task-queue']
|
|
37
|
+
});
|
|
38
|
+
if (!response || !response.data) {
|
|
39
|
+
this.error('API returned invalid response', { exit: 1 });
|
|
40
|
+
}
|
|
41
|
+
const result = response.data;
|
|
42
|
+
const output = [
|
|
43
|
+
'Workflow started successfully',
|
|
44
|
+
'',
|
|
45
|
+
`Workflow ID: ${result.workflowId || 'unknown'}`,
|
|
46
|
+
'',
|
|
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`
|
|
49
|
+
].join('\n');
|
|
50
|
+
this.log(`\n${output}`);
|
|
51
|
+
}
|
|
52
|
+
async catch(error) {
|
|
53
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
54
|
+
404: 'Workflow not found. Check the workflow name.'
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('../../api/generated/api.js', () => ({
|
|
3
|
+
postWorkflowStart: vi.fn()
|
|
4
|
+
}));
|
|
5
|
+
describe('workflow start command', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
describe('command definition', () => {
|
|
10
|
+
it('should export a valid OCLIF command', async () => {
|
|
11
|
+
const WorkflowStart = (await import('./start.js')).default;
|
|
12
|
+
expect(WorkflowStart).toBeDefined();
|
|
13
|
+
expect(WorkflowStart.description).toContain('Start a workflow');
|
|
14
|
+
expect(WorkflowStart.args).toHaveProperty('workflowName');
|
|
15
|
+
expect(WorkflowStart.flags).toHaveProperty('input');
|
|
16
|
+
expect(WorkflowStart.flags).toHaveProperty('task-queue');
|
|
17
|
+
});
|
|
18
|
+
it('should have correct flag configuration', async () => {
|
|
19
|
+
const WorkflowStart = (await import('./start.js')).default;
|
|
20
|
+
expect(WorkflowStart.flags.input.required).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class WorkflowStatus extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
workflowId: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
format: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
catch(error: Error): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getWorkflowIdStatus } from '../../api/generated/api.js';
|
|
3
|
+
import { OUTPUT_FORMAT } from '../../utils/constants.js';
|
|
4
|
+
import { formatOutput } from '../../utils/output_formatter.js';
|
|
5
|
+
import { handleApiError } from '../../utils/error_handler.js';
|
|
6
|
+
export default class WorkflowStatus extends Command {
|
|
7
|
+
static description = 'Get workflow execution status';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> wf-12345',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> wf-12345 --format json'
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
workflowId: Args.string({
|
|
14
|
+
description: 'The workflow ID to check status for',
|
|
15
|
+
required: true
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
format: Flags.string({
|
|
20
|
+
char: 'f',
|
|
21
|
+
description: 'Output format',
|
|
22
|
+
options: [OUTPUT_FORMAT.JSON, OUTPUT_FORMAT.TEXT],
|
|
23
|
+
default: OUTPUT_FORMAT.TEXT
|
|
24
|
+
})
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { args, flags } = await this.parse(WorkflowStatus);
|
|
28
|
+
this.log(`Fetching status for workflow: ${args.workflowId}...`);
|
|
29
|
+
const response = await getWorkflowIdStatus(args.workflowId);
|
|
30
|
+
if (!response || !response.data) {
|
|
31
|
+
this.error('API returned invalid response', { exit: 1 });
|
|
32
|
+
}
|
|
33
|
+
const output = formatOutput(response.data, flags.format, (result) => {
|
|
34
|
+
const lines = [
|
|
35
|
+
`Workflow ID: ${result.workflowId || 'unknown'}`,
|
|
36
|
+
`Status: ${result.status || 'unknown'}`,
|
|
37
|
+
''
|
|
38
|
+
];
|
|
39
|
+
if (result.startedAt) {
|
|
40
|
+
const startDate = new Date(result.startedAt);
|
|
41
|
+
lines.push(`Started At: ${startDate.toISOString()}`);
|
|
42
|
+
}
|
|
43
|
+
if (result.completedAt) {
|
|
44
|
+
const completedDate = new Date(result.completedAt);
|
|
45
|
+
lines.push(`Completed At: ${completedDate.toISOString()}`);
|
|
46
|
+
}
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
});
|
|
49
|
+
this.log(`\n${output}`);
|
|
50
|
+
}
|
|
51
|
+
async catch(error) {
|
|
52
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
53
|
+
404: 'Workflow not found. Check the workflow ID.'
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('../../api/generated/api.js', () => ({
|
|
3
|
+
getWorkflowIdStatus: vi.fn(),
|
|
4
|
+
GetWorkflowIdStatus200Status: {
|
|
5
|
+
canceled: 'canceled',
|
|
6
|
+
completed: 'completed',
|
|
7
|
+
continued_as_new: 'continued_as_new',
|
|
8
|
+
failed: 'failed',
|
|
9
|
+
running: 'running',
|
|
10
|
+
terminated: 'terminated',
|
|
11
|
+
timed_out: 'timed_out',
|
|
12
|
+
unspecified: 'unspecified'
|
|
13
|
+
}
|
|
14
|
+
}));
|
|
15
|
+
describe('workflow status command', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
describe('command definition', () => {
|
|
20
|
+
it('should export a valid OCLIF command', async () => {
|
|
21
|
+
const WorkflowStatus = (await import('./status.js')).default;
|
|
22
|
+
expect(WorkflowStatus).toBeDefined();
|
|
23
|
+
expect(WorkflowStatus.description).toContain('Get workflow execution status');
|
|
24
|
+
expect(WorkflowStatus.args).toHaveProperty('workflowId');
|
|
25
|
+
expect(WorkflowStatus.flags).toHaveProperty('format');
|
|
26
|
+
});
|
|
27
|
+
it('should have correct flag configuration', async () => {
|
|
28
|
+
const WorkflowStatus = (await import('./status.js')).default;
|
|
29
|
+
expect(WorkflowStatus.flags.format.options).toEqual(['json', 'text']);
|
|
30
|
+
expect(WorkflowStatus.flags.format.default).toBe('text');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class WorkflowStop extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
workflowId: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
catch(error: Error): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { patchWorkflowIdStop } from '../../api/generated/api.js';
|
|
3
|
+
import { handleApiError } from '../../utils/error_handler.js';
|
|
4
|
+
export default class WorkflowStop extends Command {
|
|
5
|
+
static description = 'Stop a workflow execution';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %> wf-12345'
|
|
8
|
+
];
|
|
9
|
+
static args = {
|
|
10
|
+
workflowId: Args.string({
|
|
11
|
+
description: 'The workflow ID to stop',
|
|
12
|
+
required: true
|
|
13
|
+
})
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { args } = await this.parse(WorkflowStop);
|
|
17
|
+
this.log(`Stopping workflow: ${args.workflowId}...`);
|
|
18
|
+
await patchWorkflowIdStop(args.workflowId);
|
|
19
|
+
const output = [
|
|
20
|
+
'Workflow stopped successfully',
|
|
21
|
+
'',
|
|
22
|
+
`Workflow ID: ${args.workflowId}`
|
|
23
|
+
].join('\n');
|
|
24
|
+
this.log(`\n${output}`);
|
|
25
|
+
}
|
|
26
|
+
async catch(error) {
|
|
27
|
+
return handleApiError(error, (...args) => this.error(...args), {
|
|
28
|
+
404: 'Workflow not found. Check the workflow ID.'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('../../api/generated/api.js', () => ({
|
|
3
|
+
patchWorkflowIdStop: vi.fn()
|
|
4
|
+
}));
|
|
5
|
+
describe('workflow stop command', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.clearAllMocks();
|
|
8
|
+
});
|
|
9
|
+
describe('command definition', () => {
|
|
10
|
+
it('should export a valid OCLIF command', async () => {
|
|
11
|
+
const WorkflowStop = (await import('./stop.js')).default;
|
|
12
|
+
expect(WorkflowStop).toBeDefined();
|
|
13
|
+
expect(WorkflowStop.description).toContain('Stop a workflow execution');
|
|
14
|
+
expect(WorkflowStop.args).toHaveProperty('workflowId');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -192,7 +192,7 @@ Please provide a helpful response.
|
|
|
192
192
|
To test your workflow:
|
|
193
193
|
|
|
194
194
|
1. Build the parent project containing this workflow
|
|
195
|
-
2. Start the
|
|
195
|
+
2. Start the Output worker
|
|
196
196
|
3. Execute the workflow using the Output SDK API
|
|
197
197
|
|
|
198
198
|
Example execution:
|
|
@@ -210,6 +210,6 @@ curl -X POST http://localhost:3001/workflow \
|
|
|
210
210
|
|
|
211
211
|
## Resources
|
|
212
212
|
|
|
213
|
-
- [Output SDK Documentation](https://github.com/growthxai/
|
|
213
|
+
- [Output SDK Documentation](https://github.com/growthxai/output-sdk)
|
|
214
214
|
- [Temporal Documentation](https://docs.temporal.io)
|
|
215
215
|
- [AI SDK Documentation](https://sdk.vercel.ai/docs)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type ErrorOverrides = {
|
|
2
|
+
[statusCode: number]: string;
|
|
3
|
+
ECONNREFUSED?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function handleApiError(error: unknown, errorFn: (...args: [message: string, options: {
|
|
6
|
+
exit: number;
|
|
7
|
+
}]) => never, overrides?: ErrorOverrides): never;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { config } from '../config.js';
|
|
2
|
+
const DEFAULT_MESSAGES = {
|
|
3
|
+
ECONNREFUSED: `Connection refused to ${config.apiUrl}. Is the API server running?`,
|
|
4
|
+
401: 'Authentication failed. Check your API_AUTH_TOKEN.',
|
|
5
|
+
404: 'Resource not found.',
|
|
6
|
+
UNKNOWN: 'An unknown error occurred.'
|
|
7
|
+
};
|
|
8
|
+
export function handleApiError(error, errorFn, overrides = {}) {
|
|
9
|
+
const apiError = error;
|
|
10
|
+
const errorMessages = { ...DEFAULT_MESSAGES, ...overrides };
|
|
11
|
+
if (apiError.code === 'ECONNREFUSED') {
|
|
12
|
+
return errorFn(errorMessages.ECONNREFUSED, { exit: 1 });
|
|
13
|
+
}
|
|
14
|
+
if (apiError.response?.status) {
|
|
15
|
+
const status = apiError.response.status;
|
|
16
|
+
const message = errorMessages[status];
|
|
17
|
+
if (message) {
|
|
18
|
+
return errorFn(message, { exit: 1 });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (apiError.message) {
|
|
22
|
+
return errorFn(apiError.message, { exit: 1 });
|
|
23
|
+
}
|
|
24
|
+
return errorFn(errorMessages.UNKNOWN, { exit: 1 });
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseInputFlag(input: string): unknown;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
export function parseInputFlag(input) {
|
|
3
|
+
try {
|
|
4
|
+
return JSON.parse(input);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
try {
|
|
8
|
+
const fileContent = readFileSync(input, 'utf-8');
|
|
9
|
+
return JSON.parse(fileContent);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
const err = error;
|
|
13
|
+
if (err.code === 'ENOENT') {
|
|
14
|
+
throw new Error(`Input file not found: ${input}`);
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`Invalid JSON input: ${err.message}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { OUTPUT_FORMAT } from './constants.js';
|
|
2
|
+
export function formatOutput(result, format, textFormatter = result => JSON.stringify(result, null, 2)) {
|
|
3
|
+
switch (format) {
|
|
4
|
+
case OUTPUT_FORMAT.JSON:
|
|
5
|
+
return JSON.stringify(result, null, 2);
|
|
6
|
+
case OUTPUT_FORMAT.TEXT:
|
|
7
|
+
return textFormatter(result);
|
|
8
|
+
default:
|
|
9
|
+
return textFormatter(result);
|
|
10
|
+
}
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@output.ai/cli",
|
|
3
|
+
"version": "0.0.4",
|
|
3
4
|
"description": "CLI for Output.ai workflow generation",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"./bin",
|
|
10
|
+
"./dist",
|
|
11
|
+
"./oclif.manifest.json"
|
|
12
|
+
],
|
|
6
13
|
"bin": {
|
|
7
|
-
"output
|
|
14
|
+
"output": "./bin/run.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "rm -rf ./dist && tsc && copyfiles -u 1 './src/templates/**/*.template' './src/templates/**/.env.template' './src/templates/**/*.prompt.template' dist",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"generate:api": "orval --config ./orval.config.ts",
|
|
20
|
+
"prebuild": "npm run generate:api"
|
|
8
21
|
},
|
|
9
|
-
"bugs": "https://github.com/growthxai/flow-sdk/issues",
|
|
10
22
|
"dependencies": {
|
|
11
23
|
"@oclif/core": "^4",
|
|
12
24
|
"@oclif/plugin-help": "^6",
|
|
@@ -26,24 +38,10 @@
|
|
|
26
38
|
"orval": "^7.13.0",
|
|
27
39
|
"slash": "^5.1.0"
|
|
28
40
|
},
|
|
29
|
-
"
|
|
30
|
-
"node": ">=18.0.0"
|
|
31
|
-
},
|
|
32
|
-
"files": [
|
|
33
|
-
"./bin",
|
|
34
|
-
"./dist",
|
|
35
|
-
"./oclif.manifest.json"
|
|
36
|
-
],
|
|
37
|
-
"homepage": "https://github.com/growthxai/flow-sdk",
|
|
38
|
-
"keywords": [
|
|
39
|
-
"oclif"
|
|
40
|
-
],
|
|
41
|
-
"license": "MIT",
|
|
42
|
-
"main": "dist/index.js",
|
|
43
|
-
"type": "module",
|
|
41
|
+
"license": "UNLICENSED",
|
|
44
42
|
"oclif": {
|
|
45
|
-
"bin": "output
|
|
46
|
-
"dirname": "output
|
|
43
|
+
"bin": "output",
|
|
44
|
+
"dirname": "output",
|
|
47
45
|
"commands": "./dist/commands",
|
|
48
46
|
"plugins": [
|
|
49
47
|
"@oclif/plugin-help",
|
|
@@ -63,13 +61,5 @@
|
|
|
63
61
|
}
|
|
64
62
|
}
|
|
65
63
|
}
|
|
66
|
-
}
|
|
67
|
-
"repository": "sdk/cli",
|
|
68
|
-
"scripts": {
|
|
69
|
-
"build": "rm -rf ./dist && tsc && copyfiles -u 1 './src/templates/**/*.template' './src/templates/**/.env.template' './src/templates/**/*.prompt.template' dist",
|
|
70
|
-
"test": "vitest run",
|
|
71
|
-
"generate:api": "orval --config ./orval.config.ts",
|
|
72
|
-
"prebuild": "npm run generate:api"
|
|
73
|
-
},
|
|
74
|
-
"types": "dist/index.d.ts"
|
|
64
|
+
}
|
|
75
65
|
}
|