@output.ai/cli 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +11 -11
  2. package/dist/api/generated/api.d.ts +13 -13
  3. package/dist/api/generated/api.js +1 -1
  4. package/dist/commands/workflow/generate.js +0 -1
  5. package/dist/commands/workflow/generate.spec.js +0 -6
  6. package/dist/commands/workflow/list.d.ts +1 -1
  7. package/dist/commands/workflow/list.js +26 -42
  8. package/dist/commands/workflow/output.d.ts +13 -0
  9. package/dist/commands/workflow/output.js +49 -0
  10. package/dist/commands/workflow/output.test.d.ts +1 -0
  11. package/dist/commands/workflow/output.test.js +23 -0
  12. package/dist/commands/workflow/run.d.ts +15 -0
  13. package/dist/commands/workflow/run.js +66 -0
  14. package/dist/commands/workflow/run.test.d.ts +1 -0
  15. package/dist/commands/workflow/run.test.js +26 -0
  16. package/dist/commands/workflow/start.d.ts +14 -0
  17. package/dist/commands/workflow/start.js +57 -0
  18. package/dist/commands/workflow/start.test.d.ts +1 -0
  19. package/dist/commands/workflow/start.test.js +23 -0
  20. package/dist/commands/workflow/status.d.ts +13 -0
  21. package/dist/commands/workflow/status.js +56 -0
  22. package/dist/commands/workflow/status.test.d.ts +1 -0
  23. package/dist/commands/workflow/status.test.js +33 -0
  24. package/dist/commands/workflow/stop.d.ts +10 -0
  25. package/dist/commands/workflow/stop.js +31 -0
  26. package/dist/commands/workflow/stop.test.d.ts +1 -0
  27. package/dist/commands/workflow/stop.test.js +17 -0
  28. package/dist/templates/workflow/README.md.template +2 -2
  29. package/dist/utils/constants.d.ts +5 -0
  30. package/dist/utils/constants.js +4 -0
  31. package/dist/utils/error_handler.d.ts +8 -0
  32. package/dist/utils/error_handler.js +25 -0
  33. package/dist/utils/input_parser.d.ts +1 -0
  34. package/dist/utils/input_parser.js +19 -0
  35. package/dist/utils/output_formatter.d.ts +2 -0
  36. package/dist/utils/output_formatter.js +11 -0
  37. 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-cli workflow list
21
+ output workflow list
22
22
 
23
23
  # List workflows with custom API URL
24
- API_URL=http://localhost:3001 output-cli workflow list
24
+ API_URL=http://localhost:3001 output workflow list
25
25
 
26
26
  # List workflows with authentication
27
- API_AUTH_TOKEN=your-token output-cli workflow list
27
+ API_AUTH_TOKEN=your-token output workflow list
28
28
 
29
29
  # Show detailed table view with all information
30
- output-cli workflow list --format table
30
+ output workflow list --format table
31
31
 
32
32
  # Show detailed table with expanded parameter info
33
- output-cli workflow list --format table --detailed
33
+ output workflow list --format table --detailed
34
34
 
35
35
  # List workflows in JSON format
36
- output-cli workflow list --format json
36
+ output workflow list --format json
37
37
 
38
38
  # Filter workflows by name (partial match)
39
- output-cli workflow list --filter simple
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-cli workflow generate my-workflow --description "My awesome workflow"
54
+ output workflow generate my-workflow --description "My awesome workflow"
55
55
 
56
56
  # Generate a minimal skeleton workflow
57
- output-cli workflow generate my-workflow --skeleton
57
+ output workflow generate my-workflow --skeleton
58
58
 
59
59
  # Generate in a specific directory
60
- output-cli workflow generate my-workflow --output-dir ./src/workflows
60
+ output workflow generate my-workflow --output-dir ./src/workflows
61
61
 
62
62
  # Force overwrite existing workflow
63
- output-cli workflow generate my-workflow --force
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 the workflow */
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: unknown) => string;
160
- export declare const getWorkflowIdStatus: (id: unknown, options?: RequestInit) => Promise<getWorkflowIdStatusResponse>;
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: unknown) => string;
180
- export declare const patchWorkflowIdStop: (id: unknown, options?: RequestInit) => Promise<patchWorkflowIdStopResponse>;
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: unknown) => string;
200
- export declare const getWorkflowIdOutput: (id: unknown, options?: RequestInit) => Promise<getWorkflowIdOutputResponse>;
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: unknown) => string;
220
- export declare const getWorkflowIdTrace: (id: unknown, options?: RequestInit) => Promise<getWorkflowIdTraceResponse>;
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: unknown) => string;
233
- export declare const getWorkflowCatalogId: (id: unknown, options?: RequestInit) => Promise<getWorkflowCatalogIdResponse>;
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: unknown) => string;
259
- export declare const postWorkflowIdFeedback: (id: unknown, postWorkflowIdFeedbackBody: PostWorkflowIdFeedbackBody, options?: RequestInit) => Promise<postWorkflowIdFeedbackResponse>;
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 Flow SDK
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';
@@ -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
- private handleError;
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 { config } from '../../config.js';
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
- try {
123
- this.log('Fetching workflow catalog...');
124
- const response = await getWorkflowCatalog();
125
- if (!response) {
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
- catch (error) {
150
- this.handleError(error);
127
+ if (!response.data) {
128
+ this.error('API returned invalid response (missing data)', { exit: 1 });
151
129
  }
152
- }
153
- handleError(error) {
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 (apiError.response?.status === 404) {
162
- this.error('Not found.', { exit: 1 });
133
+ if (response.data.workflows.length === 0) {
134
+ this.log('No workflows found in catalog.');
135
+ return;
163
136
  }
164
- if (apiError.message) {
165
- this.error(`Failed to fetch workflow catalog: ${apiError.message}`, { exit: 1 });
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
- this.error('Failed to fetch workflow catalog: Unknown error', { exit: 1 });
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 Flow worker
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/flow-sdk)
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,5 @@
1
+ export declare const OUTPUT_FORMAT: {
2
+ readonly JSON: "json";
3
+ readonly TEXT: "text";
4
+ };
5
+ export type OutputFormat = typeof OUTPUT_FORMAT[keyof typeof OUTPUT_FORMAT];
@@ -0,0 +1,4 @@
1
+ export const OUTPUT_FORMAT = {
2
+ JSON: 'json',
3
+ TEXT: 'text'
4
+ };
@@ -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,2 @@
1
+ import { OutputFormat } from './constants.js';
2
+ export declare function formatOutput<T>(result: T, format: OutputFormat, textFormatter?: (result: T) => string): string;
@@ -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.3",
3
4
  "description": "CLI for Output.ai workflow generation",
4
- "version": "0.0.2",
5
- "author": "Ben Church",
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-cli": "./bin/run.js"
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
- "engines": {
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-cli",
46
- "dirname": "output-cli",
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
  }