@output.ai/cli 0.0.0 → 0.0.1

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 CHANGED
@@ -14,6 +14,39 @@ npx @output.ai/cli
14
14
 
15
15
  ## Usage
16
16
 
17
+ ### List Workflows
18
+
19
+ ```bash
20
+ # List all available workflows (simple list, default)
21
+ output-cli workflow list
22
+
23
+ # List workflows with custom API URL
24
+ API_URL=http://localhost:3001 output-cli workflow list
25
+
26
+ # List workflows with authentication
27
+ API_AUTH_TOKEN=your-token output-cli workflow list
28
+
29
+ # Show detailed table view with all information
30
+ output-cli workflow list --format table
31
+
32
+ # Show detailed table with expanded parameter info
33
+ output-cli workflow list --format table --detailed
34
+
35
+ # List workflows in JSON format
36
+ output-cli workflow list --format json
37
+
38
+ # Filter workflows by name (partial match)
39
+ output-cli workflow list --filter simple
40
+ ```
41
+
42
+ #### Command Options
43
+
44
+ - `--format, -f` - Output format: `list` (default, simple column), `table`, or `json`
45
+ - `--detailed, -d` - Show detailed parameter information in table view
46
+ - `--filter` - Filter workflows by name (case-insensitive partial match)
47
+
48
+ The list command connects to the API server and retrieves all available workflows. By default, it displays a simple list of workflow names (like `ls`). Use `--format table` for detailed information.
49
+
17
50
  ### Generate a Workflow
18
51
 
19
52
  ```bash
@@ -44,7 +77,7 @@ The CLI creates a complete workflow structure:
44
77
  ```
45
78
  my-workflow/
46
79
  ├── index.ts # Main workflow definition
47
- ├── steps.ts # Activity/step implementations
80
+ ├── steps.ts # Activity/step implementations
48
81
  ├── types.ts # TypeScript interfaces
49
82
  ├── prompt@v1.prompt # LLM prompt template (if not skeleton)
50
83
  └── README.md # Workflow documentation
@@ -0,0 +1,272 @@
1
+ export type JSONSchemaProperties = {
2
+ [key: string]: JSONSchema;
3
+ };
4
+ export type JSONSchemaPropertyNames = {
5
+ type?: string;
6
+ };
7
+ export interface JSONSchema {
8
+ $schema?: string;
9
+ type?: string;
10
+ properties?: JSONSchemaProperties;
11
+ items?: JSONSchema;
12
+ required?: string[];
13
+ description?: string;
14
+ additionalProperties?: boolean;
15
+ propertyNames?: JSONSchemaPropertyNames;
16
+ anyOf?: JSONSchema[];
17
+ [key: string]: unknown;
18
+ }
19
+ export interface Workflow {
20
+ /** The name of the workflow */
21
+ name: string;
22
+ /** The description of the workflow */
23
+ description?: string;
24
+ inputSchema?: JSONSchema;
25
+ outputSchema?: JSONSchema;
26
+ }
27
+ export type PostWorkflowRunBody = {
28
+ /** The name of the workflow to execute */
29
+ workflowName: string;
30
+ /** The payload to send to the workflow */
31
+ input: unknown;
32
+ /** The name of the task queue to send the workflow to */
33
+ taskQueue?: string;
34
+ };
35
+ export type PostWorkflowRun200 = {
36
+ /** The workflow execution id */
37
+ workflowId?: string;
38
+ /** The output of the the workflow */
39
+ output?: unknown;
40
+ };
41
+ export type PostWorkflowStartBody = {
42
+ /** The name of the workflow to execute */
43
+ workflowName: string;
44
+ /** The payload to send to the workflow */
45
+ input: unknown;
46
+ /** The name of the task queue to send the workflow to */
47
+ taskQueue?: string;
48
+ };
49
+ export type PostWorkflowStart200 = {
50
+ /** The id of the started workflow */
51
+ workflowId?: string;
52
+ };
53
+ /**
54
+ * The workflow execution status
55
+ */
56
+ export type GetWorkflowIdStatus200Status = typeof GetWorkflowIdStatus200Status[keyof typeof GetWorkflowIdStatus200Status];
57
+ export declare const GetWorkflowIdStatus200Status: {
58
+ readonly canceled: "canceled";
59
+ readonly completed: "completed";
60
+ readonly continued_as_new: "continued_as_new";
61
+ readonly failed: "failed";
62
+ readonly running: "running";
63
+ readonly terminated: "terminated";
64
+ readonly timed_out: "timed_out";
65
+ readonly unspecified: "unspecified";
66
+ };
67
+ export type GetWorkflowIdStatus200 = {
68
+ /** The id of workflow */
69
+ workflowId?: string;
70
+ /** The workflow execution status */
71
+ status?: GetWorkflowIdStatus200Status;
72
+ /** An epoch timestamp representing when the workflow started */
73
+ startedAt?: number;
74
+ /** An epoch timestamp representing when the workflow ended */
75
+ completedAt?: number;
76
+ };
77
+ /**
78
+ * The output of workflow
79
+ */
80
+ export type GetWorkflowIdOutput200Output = {
81
+ [key: string]: unknown;
82
+ };
83
+ export type GetWorkflowIdOutput200 = {
84
+ /** The workflow execution id */
85
+ workflowId?: string;
86
+ /** The output of workflow */
87
+ output?: GetWorkflowIdOutput200Output;
88
+ };
89
+ export type GetWorkflowCatalogId200 = {
90
+ /** Each workflow available in this catalog */
91
+ workflows?: Workflow[];
92
+ };
93
+ export type GetWorkflowCatalog200 = {
94
+ /** Each workflow available in this catalog */
95
+ workflows?: Workflow[];
96
+ };
97
+ export type PostWorkflowIdFeedbackBody = {
98
+ /** The payload to send to the workflow */
99
+ payload?: unknown;
100
+ };
101
+ /**
102
+ * @summary Health check the API
103
+ */
104
+ export type getHealthResponse200 = {
105
+ data: void;
106
+ status: 200;
107
+ };
108
+ export type getHealthResponseSuccess = (getHealthResponse200) & {
109
+ headers: Headers;
110
+ };
111
+ export type getHealthResponse = (getHealthResponseSuccess);
112
+ export declare const getGetHealthUrl: () => string;
113
+ export declare const getHealth: (options?: RequestInit) => Promise<getHealthResponse>;
114
+ /**
115
+ * Executes a workflow and waits for it to complete before returning the result
116
+ * @summary Execute a workflow synchronously
117
+ */
118
+ export type postWorkflowRunResponse200 = {
119
+ data: PostWorkflowRun200;
120
+ status: 200;
121
+ };
122
+ export type postWorkflowRunResponseSuccess = (postWorkflowRunResponse200) & {
123
+ headers: Headers;
124
+ };
125
+ export type postWorkflowRunResponse = (postWorkflowRunResponseSuccess);
126
+ export declare const getPostWorkflowRunUrl: () => string;
127
+ export declare const postWorkflowRun: (postWorkflowRunBody: PostWorkflowRunBody, options?: RequestInit) => Promise<postWorkflowRunResponse>;
128
+ /**
129
+ * @summary Start a workflow asynchronously
130
+ */
131
+ export type postWorkflowStartResponse200 = {
132
+ data: PostWorkflowStart200;
133
+ status: 200;
134
+ };
135
+ export type postWorkflowStartResponseSuccess = (postWorkflowStartResponse200) & {
136
+ headers: Headers;
137
+ };
138
+ export type postWorkflowStartResponse = (postWorkflowStartResponseSuccess);
139
+ export declare const getPostWorkflowStartUrl: () => string;
140
+ export declare const postWorkflowStart: (postWorkflowStartBody: PostWorkflowStartBody, options?: RequestInit) => Promise<postWorkflowStartResponse>;
141
+ /**
142
+ * @summary Get workflow execution status
143
+ */
144
+ export type getWorkflowIdStatusResponse200 = {
145
+ data: GetWorkflowIdStatus200;
146
+ status: 200;
147
+ };
148
+ export type getWorkflowIdStatusResponse404 = {
149
+ data: void;
150
+ status: 404;
151
+ };
152
+ export type getWorkflowIdStatusResponseSuccess = (getWorkflowIdStatusResponse200) & {
153
+ headers: Headers;
154
+ };
155
+ export type getWorkflowIdStatusResponseError = (getWorkflowIdStatusResponse404) & {
156
+ headers: Headers;
157
+ };
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>;
161
+ /**
162
+ * @summary Stop a workflow execution
163
+ */
164
+ export type patchWorkflowIdStopResponse200 = {
165
+ data: void;
166
+ status: 200;
167
+ };
168
+ export type patchWorkflowIdStopResponse404 = {
169
+ data: void;
170
+ status: 404;
171
+ };
172
+ export type patchWorkflowIdStopResponseSuccess = (patchWorkflowIdStopResponse200) & {
173
+ headers: Headers;
174
+ };
175
+ export type patchWorkflowIdStopResponseError = (patchWorkflowIdStopResponse404) & {
176
+ headers: Headers;
177
+ };
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>;
181
+ /**
182
+ * @summary Return the output of a workflow
183
+ */
184
+ export type getWorkflowIdOutputResponse200 = {
185
+ data: GetWorkflowIdOutput200;
186
+ status: 200;
187
+ };
188
+ export type getWorkflowIdOutputResponse404 = {
189
+ data: void;
190
+ status: 404;
191
+ };
192
+ export type getWorkflowIdOutputResponseSuccess = (getWorkflowIdOutputResponse200) & {
193
+ headers: Headers;
194
+ };
195
+ export type getWorkflowIdOutputResponseError = (getWorkflowIdOutputResponse404) & {
196
+ headers: Headers;
197
+ };
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>;
201
+ /**
202
+ * @summary Return the trace of a workflow execution
203
+ */
204
+ export type getWorkflowIdTraceResponse200 = {
205
+ data: string[];
206
+ status: 200;
207
+ };
208
+ export type getWorkflowIdTraceResponse404 = {
209
+ data: void;
210
+ status: 404;
211
+ };
212
+ export type getWorkflowIdTraceResponseSuccess = (getWorkflowIdTraceResponse200) & {
213
+ headers: Headers;
214
+ };
215
+ export type getWorkflowIdTraceResponseError = (getWorkflowIdTraceResponse404) & {
216
+ headers: Headers;
217
+ };
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>;
221
+ /**
222
+ * @summary Get a specific workflow catalog by ID
223
+ */
224
+ export type getWorkflowCatalogIdResponse200 = {
225
+ data: GetWorkflowCatalogId200;
226
+ status: 200;
227
+ };
228
+ export type getWorkflowCatalogIdResponseSuccess = (getWorkflowCatalogIdResponse200) & {
229
+ headers: Headers;
230
+ };
231
+ export type getWorkflowCatalogIdResponse = (getWorkflowCatalogIdResponseSuccess);
232
+ export declare const getGetWorkflowCatalogIdUrl: (id: unknown) => string;
233
+ export declare const getWorkflowCatalogId: (id: unknown, options?: RequestInit) => Promise<getWorkflowCatalogIdResponse>;
234
+ /**
235
+ * @summary Get the default workflow catalog
236
+ */
237
+ export type getWorkflowCatalogResponse200 = {
238
+ data: GetWorkflowCatalog200;
239
+ status: 200;
240
+ };
241
+ export type getWorkflowCatalogResponseSuccess = (getWorkflowCatalogResponse200) & {
242
+ headers: Headers;
243
+ };
244
+ export type getWorkflowCatalogResponse = (getWorkflowCatalogResponseSuccess);
245
+ export declare const getGetWorkflowCatalogUrl: () => string;
246
+ export declare const getWorkflowCatalog: (options?: RequestInit) => Promise<getWorkflowCatalogResponse>;
247
+ /**
248
+ * @summary Send feedback to a payload
249
+ */
250
+ export type postWorkflowIdFeedbackResponse200 = {
251
+ data: void;
252
+ status: 200;
253
+ };
254
+ export type postWorkflowIdFeedbackResponseSuccess = (postWorkflowIdFeedbackResponse200) & {
255
+ headers: Headers;
256
+ };
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>;
260
+ /**
261
+ * @summary A dummy post endpoint for test only
262
+ */
263
+ export type postHeartbeatResponse204 = {
264
+ data: void;
265
+ status: 204;
266
+ };
267
+ export type postHeartbeatResponseSuccess = (postHeartbeatResponse204) & {
268
+ headers: Headers;
269
+ };
270
+ export type postHeartbeatResponse = (postHeartbeatResponseSuccess);
271
+ export declare const getPostHeartbeatUrl: () => string;
272
+ export declare const postHeartbeat: (options?: RequestInit) => Promise<postHeartbeatResponse>;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Generated by orval v7.13.0 🍺
3
+ * Do not edit manually.
4
+ * Output.ai SDK API
5
+ * API for managing and executing Temporal workflows through Flow SDK
6
+ * OpenAPI spec version: 1.0.0
7
+ */
8
+ import { customFetchInstance } from '../http_client.js';
9
+ // eslint-disable-next-line @typescript-eslint/no-redeclare
10
+ export const GetWorkflowIdStatus200Status = {
11
+ canceled: 'canceled',
12
+ completed: 'completed',
13
+ continued_as_new: 'continued_as_new',
14
+ failed: 'failed',
15
+ running: 'running',
16
+ terminated: 'terminated',
17
+ timed_out: 'timed_out',
18
+ unspecified: 'unspecified',
19
+ };
20
+ ;
21
+ export const getGetHealthUrl = () => {
22
+ return `/health`;
23
+ };
24
+ export const getHealth = async (options) => {
25
+ return customFetchInstance(getGetHealthUrl(), {
26
+ ...options,
27
+ method: 'GET'
28
+ });
29
+ };
30
+ ;
31
+ export const getPostWorkflowRunUrl = () => {
32
+ return `/workflow/run`;
33
+ };
34
+ export const postWorkflowRun = async (postWorkflowRunBody, options) => {
35
+ return customFetchInstance(getPostWorkflowRunUrl(), {
36
+ ...options,
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json', ...options?.headers },
39
+ body: JSON.stringify(postWorkflowRunBody)
40
+ });
41
+ };
42
+ ;
43
+ export const getPostWorkflowStartUrl = () => {
44
+ return `/workflow/start`;
45
+ };
46
+ export const postWorkflowStart = async (postWorkflowStartBody, options) => {
47
+ return customFetchInstance(getPostWorkflowStartUrl(), {
48
+ ...options,
49
+ method: 'POST',
50
+ headers: { 'Content-Type': 'application/json', ...options?.headers },
51
+ body: JSON.stringify(postWorkflowStartBody)
52
+ });
53
+ };
54
+ export const getGetWorkflowIdStatusUrl = (id) => {
55
+ return `/workflow/${id}/status`;
56
+ };
57
+ export const getWorkflowIdStatus = async (id, options) => {
58
+ return customFetchInstance(getGetWorkflowIdStatusUrl(id), {
59
+ ...options,
60
+ method: 'GET'
61
+ });
62
+ };
63
+ export const getPatchWorkflowIdStopUrl = (id) => {
64
+ return `/workflow/${id}/stop`;
65
+ };
66
+ export const patchWorkflowIdStop = async (id, options) => {
67
+ return customFetchInstance(getPatchWorkflowIdStopUrl(id), {
68
+ ...options,
69
+ method: 'PATCH'
70
+ });
71
+ };
72
+ export const getGetWorkflowIdOutputUrl = (id) => {
73
+ return `/workflow/${id}/output`;
74
+ };
75
+ export const getWorkflowIdOutput = async (id, options) => {
76
+ return customFetchInstance(getGetWorkflowIdOutputUrl(id), {
77
+ ...options,
78
+ method: 'GET'
79
+ });
80
+ };
81
+ export const getGetWorkflowIdTraceUrl = (id) => {
82
+ return `/workflow/${id}/trace`;
83
+ };
84
+ export const getWorkflowIdTrace = async (id, options) => {
85
+ return customFetchInstance(getGetWorkflowIdTraceUrl(id), {
86
+ ...options,
87
+ method: 'GET'
88
+ });
89
+ };
90
+ ;
91
+ export const getGetWorkflowCatalogIdUrl = (id) => {
92
+ return `/workflow/catalog/${id}`;
93
+ };
94
+ export const getWorkflowCatalogId = async (id, options) => {
95
+ return customFetchInstance(getGetWorkflowCatalogIdUrl(id), {
96
+ ...options,
97
+ method: 'GET'
98
+ });
99
+ };
100
+ ;
101
+ export const getGetWorkflowCatalogUrl = () => {
102
+ return `/workflow/catalog`;
103
+ };
104
+ export const getWorkflowCatalog = async (options) => {
105
+ return customFetchInstance(getGetWorkflowCatalogUrl(), {
106
+ ...options,
107
+ method: 'GET'
108
+ });
109
+ };
110
+ ;
111
+ export const getPostWorkflowIdFeedbackUrl = (id) => {
112
+ return `/workflow/${id}/feedback`;
113
+ };
114
+ export const postWorkflowIdFeedback = async (id, postWorkflowIdFeedbackBody, options) => {
115
+ return customFetchInstance(getPostWorkflowIdFeedbackUrl(id), {
116
+ ...options,
117
+ method: 'POST',
118
+ headers: { 'Content-Type': 'application/json', ...options?.headers },
119
+ body: JSON.stringify(postWorkflowIdFeedbackBody)
120
+ });
121
+ };
122
+ ;
123
+ export const getPostHeartbeatUrl = () => {
124
+ return `/heartbeat`;
125
+ };
126
+ export const postHeartbeat = async (options) => {
127
+ return customFetchInstance(getPostHeartbeatUrl(), {
128
+ ...options,
129
+ method: 'POST'
130
+ });
131
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Custom ky-based HTTP client for Orval-generated API
3
+ */
4
+ export declare const customFetchInstance: <T>(url: string, options: RequestInit & {
5
+ params?: Record<string, unknown>;
6
+ }) => Promise<T>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Custom ky-based HTTP client for Orval-generated API
3
+ */
4
+ import ky from 'ky';
5
+ import { config } from '../config.js';
6
+ const api = ky.create({
7
+ prefixUrl: config.apiUrl,
8
+ timeout: config.requestTimeout,
9
+ retry: {
10
+ limit: 2,
11
+ methods: ['get', 'put', 'head', 'delete', 'options', 'trace'],
12
+ statusCodes: [408, 413, 429, 500, 502, 503, 504]
13
+ },
14
+ hooks: {
15
+ beforeRequest: [
16
+ request => {
17
+ // Add auth token if available
18
+ if (config.apiToken) {
19
+ request.headers.set('Authorization', `Basic ${config.apiToken}`);
20
+ }
21
+ }
22
+ ]
23
+ }
24
+ });
25
+ const stripLeadingSlash = (url) => url.startsWith('/') ? url.slice(1) : url;
26
+ const buildKyOptions = (options) => ({
27
+ method: options.method,
28
+ headers: options.headers,
29
+ searchParams: options.params,
30
+ body: options.body && options.method !== 'GET' ? options.body : undefined
31
+ });
32
+ const wrapResponse = (response, data) => ({
33
+ data,
34
+ status: response.status,
35
+ headers: response.headers
36
+ });
37
+ export const customFetchInstance = async (url, options) => {
38
+ const response = await api(stripLeadingSlash(url), buildKyOptions(options));
39
+ const data = await response.json().catch(() => undefined);
40
+ return wrapResponse(response, data);
41
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Orval post-generation hook to fix ES module imports by adding .js extensions.
3
+ * This is necessary because the SDK uses "type": "module" in package.json,
4
+ * which requires all relative imports to have explicit .js extensions.
5
+ */
6
+ export declare function fixEsmImports(outputPath: string): Promise<void>;
7
+ /**
8
+ * Run ESLint fix on generated files to ensure they follow project standards
9
+ */
10
+ export declare function runEslintFix(): Promise<void>;
@@ -0,0 +1,33 @@
1
+ import { readFileSync, writeFileSync } from 'fs';
2
+ import { execSync } from 'child_process';
3
+ /**
4
+ * Orval post-generation hook to fix ES module imports by adding .js extensions.
5
+ * This is necessary because the SDK uses "type": "module" in package.json,
6
+ * which requires all relative imports to have explicit .js extensions.
7
+ */
8
+ export async function fixEsmImports(outputPath) {
9
+ const content = readFileSync(outputPath, 'utf8');
10
+ const fixedContent = content.replace(/from '\.\.\/http_client'/g, 'from \'../http_client.js\'');
11
+ writeFileSync(outputPath, fixedContent, 'utf8');
12
+ console.log('✅ Fixed ESM imports in Orval generated file');
13
+ }
14
+ /**
15
+ * Run ESLint fix on generated files to ensure they follow project standards
16
+ */
17
+ export async function runEslintFix() {
18
+ try {
19
+ execSync('npx eslint --fix src/api/generated/api.ts', {
20
+ cwd: process.cwd(),
21
+ stdio: 'pipe' // Suppress output
22
+ });
23
+ console.log('✅ Applied ESLint fixes to generated file');
24
+ }
25
+ catch (error) {
26
+ if (error instanceof Error && 'status' in error && error.status === 1) {
27
+ console.log('✅ Applied ESLint fixes to generated file (with warnings)');
28
+ }
29
+ else {
30
+ console.warn('⚠️ ESLint fix encountered an issue:', error instanceof Error ? error.message : error);
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,17 @@
1
+ import type { Workflow } from './generated/api.js';
2
+ interface WorkflowParameter {
3
+ name: string;
4
+ type: string;
5
+ required: boolean;
6
+ description?: string;
7
+ }
8
+ interface ParsedWorkflow {
9
+ name: string;
10
+ description?: string;
11
+ inputs: WorkflowParameter[];
12
+ outputs: WorkflowParameter[];
13
+ }
14
+ export declare function parseWorkflowDefinition(workflow: Workflow): ParsedWorkflow;
15
+ export declare function formatParameterType(param: WorkflowParameter): string;
16
+ export declare function formatParameters(params: WorkflowParameter[]): string;
17
+ export {};
@@ -0,0 +1,71 @@
1
+ // json-schema-library is a CommonJS module that doesn't properly export named exports for ESM.
2
+ // We need to use the default import pattern and destructure from it.
3
+ import pkg from 'json-schema-library';
4
+ const { compileSchema, draft07 } = pkg;
5
+ function formatType(schema) {
6
+ if (!schema) {
7
+ return 'any';
8
+ }
9
+ if (schema.type === 'array') {
10
+ const itemsType = schema.items?.type || 'any';
11
+ return `array<${itemsType}>`;
12
+ }
13
+ return schema.type || 'any';
14
+ }
15
+ function processProperties(properties, prefix = '', requiredList = []) {
16
+ return Object.entries(properties).flatMap(([key, propSchema]) => {
17
+ const prop = propSchema;
18
+ const propName = prefix ? `${prefix}.${key}` : key;
19
+ if (prop.type === 'object' && prop.properties) {
20
+ const nestedRequired = prop.required || [];
21
+ return processProperties(prop.properties, propName, nestedRequired);
22
+ }
23
+ return {
24
+ name: propName,
25
+ type: formatType(prop),
26
+ required: requiredList.includes(key),
27
+ description: prop.description
28
+ };
29
+ });
30
+ }
31
+ function extractParametersFromSchema(schema) {
32
+ if (!schema) {
33
+ return [];
34
+ }
35
+ const schemaNode = compileSchema(schema, { drafts: [draft07] });
36
+ const compiledSchema = schemaNode.schema;
37
+ if (compiledSchema.type === 'object' && compiledSchema.properties) {
38
+ const required = compiledSchema.required || [];
39
+ return processProperties(compiledSchema.properties, '', required);
40
+ }
41
+ if (compiledSchema.type) {
42
+ return [{
43
+ name: 'value',
44
+ type: compiledSchema.type,
45
+ required: true,
46
+ description: compiledSchema.description
47
+ }];
48
+ }
49
+ return [];
50
+ }
51
+ export function parseWorkflowDefinition(workflow) {
52
+ return {
53
+ name: workflow.name,
54
+ description: workflow.description,
55
+ inputs: extractParametersFromSchema(workflow.inputSchema),
56
+ outputs: extractParametersFromSchema(workflow.outputSchema)
57
+ };
58
+ }
59
+ export function formatParameterType(param) {
60
+ const typeStr = param.type;
61
+ const reqStr = param.required ? '' : '?';
62
+ return `${typeStr}${reqStr}`;
63
+ }
64
+ export function formatParameters(params) {
65
+ if (params.length === 0) {
66
+ return 'none';
67
+ }
68
+ return params
69
+ .map(p => `${p.name}: ${formatParameterType(p)}`)
70
+ .join(', ');
71
+ }
@@ -0,0 +1,21 @@
1
+ import { Command } from '@oclif/core';
2
+ import { type Workflow } from '../../api/generated/api.js';
3
+ interface WorkflowDisplay {
4
+ name: string;
5
+ description: string;
6
+ inputs: string;
7
+ outputs: string;
8
+ }
9
+ export declare function parseWorkflowForDisplay(workflow: Workflow): WorkflowDisplay;
10
+ export default class WorkflowList extends Command {
11
+ static description: string;
12
+ static examples: string[];
13
+ static flags: {
14
+ format: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
15
+ detailed: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
16
+ filter: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
17
+ };
18
+ run(): Promise<void>;
19
+ private handleError;
20
+ }
21
+ export {};
@@ -0,0 +1,169 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import Table from 'cli-table3';
3
+ import { getWorkflowCatalog } from '../../api/generated/api.js';
4
+ import { parseWorkflowDefinition, formatParameters } from '../../api/parser.js';
5
+ import { config } from '../../config.js';
6
+ const OUTPUT_FORMAT = {
7
+ LIST: 'list',
8
+ TABLE: 'table',
9
+ JSON: 'json'
10
+ };
11
+ export function parseWorkflowForDisplay(workflow) {
12
+ const parsed = parseWorkflowDefinition(workflow);
13
+ return {
14
+ name: parsed.name,
15
+ description: parsed.description || 'No description',
16
+ inputs: formatParameters(parsed.inputs),
17
+ outputs: formatParameters(parsed.outputs)
18
+ };
19
+ }
20
+ function caseInsensitiveIncludes(str, filter) {
21
+ return str.toLowerCase().includes(filter.toLowerCase());
22
+ }
23
+ function matchName(filterString) {
24
+ return workflow => {
25
+ const name = workflow.name || '';
26
+ return caseInsensitiveIncludes(name, filterString);
27
+ };
28
+ }
29
+ function sortWorkflowsByName(workflows) {
30
+ return [...workflows].sort((a, b) => {
31
+ const nameA = (a.name || '').toLowerCase();
32
+ const nameB = (b.name || '').toLowerCase();
33
+ return nameA.localeCompare(nameB);
34
+ });
35
+ }
36
+ function createWorkflowTable(workflows, detailed) {
37
+ const table = new Table({
38
+ head: ['Name', 'Description', 'Inputs', 'Outputs'],
39
+ colWidths: detailed ? [25, 40, 40, 40] : [20, 30, 25, 25],
40
+ wordWrap: true,
41
+ style: {
42
+ head: ['cyan']
43
+ }
44
+ });
45
+ const sortedWorkflows = sortWorkflowsByName(workflows);
46
+ sortedWorkflows.forEach(workflow => {
47
+ const display = parseWorkflowForDisplay(workflow);
48
+ if (detailed) {
49
+ const inputs = display.inputs.split(', ').join('\n');
50
+ const outputs = display.outputs.split(', ').join('\n');
51
+ table.push([display.name, display.description, inputs, outputs]);
52
+ }
53
+ else {
54
+ const maxLen = 22;
55
+ const inputs = display.inputs.length > maxLen ?
56
+ display.inputs.substring(0, maxLen) + '...' :
57
+ display.inputs;
58
+ const outputs = display.outputs.length > maxLen ?
59
+ display.outputs.substring(0, maxLen) + '...' :
60
+ display.outputs;
61
+ table.push([display.name, display.description, inputs, outputs]);
62
+ }
63
+ });
64
+ return table.toString();
65
+ }
66
+ function formatWorkflowsAsList(workflows) {
67
+ const sortedWorkflows = sortWorkflowsByName(workflows);
68
+ const names = sortedWorkflows.map(w => parseWorkflowForDisplay(w).name);
69
+ return `\nWorkflows:\n\n${names.map(name => `- ${name}`).join('\n')}`;
70
+ }
71
+ function formatWorkflowsAsJson(workflows) {
72
+ const output = {
73
+ workflows: workflows.map(w => {
74
+ const display = parseWorkflowForDisplay(w);
75
+ return {
76
+ name: display.name,
77
+ description: display.description,
78
+ inputs: display.inputs.split(', '),
79
+ outputs: display.outputs.split(', '),
80
+ raw: w
81
+ };
82
+ })
83
+ };
84
+ return JSON.stringify(output, null, 2);
85
+ }
86
+ function formatWorkflows(workflows, format, detailed) {
87
+ if (format === OUTPUT_FORMAT.JSON) {
88
+ return formatWorkflowsAsJson(workflows);
89
+ }
90
+ if (format === OUTPUT_FORMAT.TABLE) {
91
+ return createWorkflowTable(workflows, detailed);
92
+ }
93
+ return formatWorkflowsAsList(workflows);
94
+ }
95
+ export default class WorkflowList extends Command {
96
+ static description = 'List available workflows from the catalog';
97
+ static examples = [
98
+ '<%= config.bin %> <%= command.id %>',
99
+ '<%= config.bin %> <%= command.id %> --format table',
100
+ '<%= config.bin %> <%= command.id %> --format json',
101
+ '<%= config.bin %> <%= command.id %> --detailed',
102
+ '<%= config.bin %> <%= command.id %> --filter simple'
103
+ ];
104
+ static flags = {
105
+ format: Flags.string({
106
+ char: 'f',
107
+ description: 'Output format',
108
+ options: [OUTPUT_FORMAT.LIST, OUTPUT_FORMAT.TABLE, OUTPUT_FORMAT.JSON],
109
+ default: OUTPUT_FORMAT.LIST
110
+ }),
111
+ detailed: Flags.boolean({
112
+ char: 'd',
113
+ description: 'Show detailed parameter information',
114
+ default: false
115
+ }),
116
+ filter: Flags.string({
117
+ description: 'Filter workflows by name'
118
+ })
119
+ };
120
+ async run() {
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)`);
148
+ }
149
+ catch (error) {
150
+ this.handleError(error);
151
+ }
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 });
160
+ }
161
+ if (apiError.response?.status === 404) {
162
+ this.error('Not found.', { exit: 1 });
163
+ }
164
+ if (apiError.message) {
165
+ this.error(`Failed to fetch workflow catalog: ${apiError.message}`, { exit: 1 });
166
+ }
167
+ this.error('Failed to fetch workflow catalog: Unknown error', { exit: 1 });
168
+ }
169
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,83 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ describe('workflow list command', () => {
3
+ beforeEach(() => {
4
+ vi.clearAllMocks();
5
+ });
6
+ describe('command functionality', () => {
7
+ it('should export a valid OCLIF command', async () => {
8
+ const WorkflowList = (await import('./list.js')).default;
9
+ expect(WorkflowList).toBeDefined();
10
+ expect(WorkflowList.description).toContain('List available workflows');
11
+ expect(WorkflowList.flags).toHaveProperty('format');
12
+ expect(WorkflowList.flags).toHaveProperty('detailed');
13
+ expect(WorkflowList.flags).toHaveProperty('filter');
14
+ });
15
+ it('should have correct flag configuration', async () => {
16
+ const WorkflowList = (await import('./list.js')).default;
17
+ expect(WorkflowList.flags.format.options).toEqual(['list', 'table', 'json']);
18
+ expect(WorkflowList.flags.format.default).toBe('list');
19
+ expect(WorkflowList.flags.detailed.default).toBe(false);
20
+ });
21
+ });
22
+ });
23
+ describe('workflow list parsing', () => {
24
+ it('should parse workflow definitions correctly', async () => {
25
+ const { parseWorkflowForDisplay } = await import('./list.js');
26
+ const mockWorkflow = {
27
+ name: 'test-workflow',
28
+ description: 'A test workflow',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: {
32
+ message: { type: 'string', description: 'The message' },
33
+ count: { type: 'number', description: 'The count' }
34
+ },
35
+ required: ['message']
36
+ },
37
+ outputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ result: { type: 'string' }
41
+ }
42
+ }
43
+ };
44
+ const parsed = parseWorkflowForDisplay(mockWorkflow);
45
+ expect(parsed.name).toBe('test-workflow');
46
+ expect(parsed.description).toBe('A test workflow');
47
+ expect(parsed.inputs).toContain('message: string');
48
+ expect(parsed.inputs).toContain('count: number?');
49
+ expect(parsed.outputs).toContain('result: string?');
50
+ });
51
+ it('should handle workflows without schemas', async () => {
52
+ const { parseWorkflowForDisplay } = await import('./list.js');
53
+ const mockWorkflow = {
54
+ name: 'simple-workflow',
55
+ description: 'No parameters'
56
+ };
57
+ const parsed = parseWorkflowForDisplay(mockWorkflow);
58
+ expect(parsed.name).toBe('simple-workflow');
59
+ expect(parsed.inputs).toBe('none');
60
+ expect(parsed.outputs).toBe('none');
61
+ });
62
+ it('should format nested parameters correctly', async () => {
63
+ const { parseWorkflowForDisplay } = await import('./list.js');
64
+ const mockWorkflow = {
65
+ name: 'nested-workflow',
66
+ inputSchema: {
67
+ type: 'object',
68
+ properties: {
69
+ user: {
70
+ type: 'object',
71
+ properties: {
72
+ name: { type: 'string' },
73
+ email: { type: 'string' }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ };
79
+ const parsed = parseWorkflowForDisplay(mockWorkflow);
80
+ expect(parsed.inputs).toContain('user.name: string');
81
+ expect(parsed.inputs).toContain('user.email: string');
82
+ });
83
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * CLI configuration
3
+ */
4
+ export declare const config: {
5
+ /**
6
+ * Base URL for the Output.ai API server
7
+ * Can be overridden with API_URL environment variable
8
+ */
9
+ apiUrl: string;
10
+ /**
11
+ * API authentication token
12
+ * Set via API_AUTH_TOKEN environment variable
13
+ */
14
+ apiToken: string | undefined;
15
+ /**
16
+ * Default timeout for API requests (in milliseconds)
17
+ */
18
+ requestTimeout: number;
19
+ };
package/dist/config.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * CLI configuration
3
+ */
4
+ export const config = {
5
+ /**
6
+ * Base URL for the Output.ai API server
7
+ * Can be overridden with API_URL environment variable
8
+ */
9
+ apiUrl: process.env.API_URL || 'http://localhost:3001',
10
+ /**
11
+ * API authentication token
12
+ * Set via API_AUTH_TOKEN environment variable
13
+ */
14
+ apiToken: process.env.API_AUTH_TOKEN,
15
+ /**
16
+ * Default timeout for API requests (in milliseconds)
17
+ */
18
+ requestTimeout: 30000
19
+ };
@@ -159,8 +159,8 @@ export const llmStep = step( {
159
159
  },
160
160
  outputSchema: { type: 'string' },
161
161
  fn: async ( input: { userInput: string } ) => {
162
- const prompt = loadPrompt( 'prompt@v1', {
163
- userInput: input.userInput
162
+ const prompt = loadPrompt( 'prompt@v1', {
163
+ userInput: input.userInput
164
164
  } );
165
165
  const response = await generateText( prompt as Prompt );
166
166
  return response;
@@ -5,7 +5,7 @@ import { exampleLLMStep, processDataStep } from './steps.js';
5
5
  const inputSchema = {
6
6
  type: 'object',
7
7
  properties: {
8
- prompt: {
8
+ prompt: {
9
9
  type: 'string',
10
10
  description: 'The prompt to send to the LLM'
11
11
  },
@@ -60,7 +60,7 @@ export default workflow( {
60
60
  } );
61
61
 
62
62
  // Process data if provided, otherwise use defaults
63
- const processedData = await processDataStep(
63
+ const processedData = await processDataStep(
64
64
  input.data || { value: 42, type: 'default' }
65
65
  );
66
66
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@output.ai/cli",
3
3
  "description": "CLI for Output.ai workflow generation",
4
- "version": "0.0.0",
4
+ "version": "0.0.1",
5
5
  "author": "Ben Church",
6
6
  "bin": {
7
7
  "output-cli": "./bin/run.js"
@@ -12,14 +12,19 @@
12
12
  "@oclif/plugin-help": "^6",
13
13
  "@oclif/plugin-plugins": "^5",
14
14
  "change-case": "^5.4.4",
15
- "handlebars": "^4.7.8"
15
+ "cli-table3": "^0.6.5",
16
+ "handlebars": "^4.7.8",
17
+ "json-schema-library": "^10.2.1",
18
+ "ky": "^1.11.0"
16
19
  },
17
20
  "devDependencies": {
18
21
  "@oclif/test": "^4",
19
22
  "@types/handlebars": "^4.0.40",
20
23
  "@types/node": "^18",
21
24
  "copyfiles": "^2.4.1",
22
- "oclif": "^4"
25
+ "oclif": "^4",
26
+ "orval": "^7.13.0",
27
+ "slash": "^5.1.0"
23
28
  },
24
29
  "engines": {
25
30
  "node": ">=18.0.0"
@@ -54,7 +59,9 @@
54
59
  "repository": "sdk/cli",
55
60
  "scripts": {
56
61
  "build": "rm -rf ./dist && tsc && copyfiles -u 1 './src/templates/**/*.template' './src/templates/**/.env.template' './src/templates/**/*.prompt.template' dist",
57
- "test": "vitest run"
62
+ "test": "vitest run",
63
+ "generate:api": "orval --config ./orval.config.ts",
64
+ "prebuild": "npm run generate:api"
58
65
  },
59
66
  "types": "dist/index.d.ts"
60
67
  }