@output.ai/cli 0.4.1 → 0.4.2

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.
@@ -54,6 +54,39 @@ export interface TraceInfo {
54
54
  /** File destinations for trace data */
55
55
  destinations?: TraceInfoDestinations;
56
56
  }
57
+ /**
58
+ * Current run status
59
+ */
60
+ export type WorkflowRunInfoStatus = typeof WorkflowRunInfoStatus[keyof typeof WorkflowRunInfoStatus];
61
+ export declare const WorkflowRunInfoStatus: {
62
+ readonly running: "running";
63
+ readonly completed: "completed";
64
+ readonly failed: "failed";
65
+ readonly canceled: "canceled";
66
+ readonly terminated: "terminated";
67
+ readonly timed_out: "timed_out";
68
+ readonly continued: "continued";
69
+ };
70
+ export interface WorkflowRunInfo {
71
+ /** Unique identifier for this run */
72
+ workflowId?: string;
73
+ /** Name of the workflow definition */
74
+ workflowType?: string;
75
+ /** Current run status */
76
+ status?: WorkflowRunInfoStatus;
77
+ /** ISO 8601 timestamp of run start */
78
+ startedAt?: string;
79
+ /**
80
+ * ISO 8601 timestamp of completion, or null if still running
81
+ * @nullable
82
+ */
83
+ completedAt?: string | null;
84
+ }
85
+ export interface WorkflowRunsResponse {
86
+ runs?: WorkflowRunInfo[];
87
+ /** Total number of runs returned */
88
+ count?: number;
89
+ }
57
90
  export type PostWorkflowRunBody = {
58
91
  /** The name of the workflow to execute */
59
92
  workflowName: string;
@@ -124,6 +157,18 @@ export type GetWorkflowCatalog200 = {
124
157
  /** Each workflow available in this catalog */
125
158
  workflows?: Workflow[];
126
159
  };
160
+ export type GetWorkflowRunsParams = {
161
+ /**
162
+ * Filter by workflow type/name
163
+ */
164
+ workflowType?: string;
165
+ /**
166
+ * Maximum number of runs to return
167
+ * @minimum 1
168
+ * @maximum 1000
169
+ */
170
+ limit?: number;
171
+ };
127
172
  export type PostWorkflowIdFeedbackBody = {
128
173
  /** The payload to send to the workflow */
129
174
  payload?: unknown;
@@ -254,6 +299,27 @@ export type getWorkflowCatalogResponseSuccess = (getWorkflowCatalogResponse200)
254
299
  export type getWorkflowCatalogResponse = (getWorkflowCatalogResponseSuccess);
255
300
  export declare const getGetWorkflowCatalogUrl: () => string;
256
301
  export declare const getWorkflowCatalog: (options?: ApiRequestOptions) => Promise<getWorkflowCatalogResponse>;
302
+ /**
303
+ * Returns a list of workflow runs with optional filtering by workflow type
304
+ * @summary List workflow runs
305
+ */
306
+ export type getWorkflowRunsResponse200 = {
307
+ data: WorkflowRunsResponse;
308
+ status: 200;
309
+ };
310
+ export type getWorkflowRunsResponse400 = {
311
+ data: void;
312
+ status: 400;
313
+ };
314
+ export type getWorkflowRunsResponseSuccess = (getWorkflowRunsResponse200) & {
315
+ headers: Headers;
316
+ };
317
+ export type getWorkflowRunsResponseError = (getWorkflowRunsResponse400) & {
318
+ headers: Headers;
319
+ };
320
+ export type getWorkflowRunsResponse = (getWorkflowRunsResponseSuccess | getWorkflowRunsResponseError);
321
+ export declare const getGetWorkflowRunsUrl: (params?: GetWorkflowRunsParams) => string;
322
+ export declare const getWorkflowRuns: (params?: GetWorkflowRunsParams, options?: ApiRequestOptions) => Promise<getWorkflowRunsResponse>;
257
323
  /**
258
324
  * @summary Send feedback to a payload
259
325
  */
@@ -7,6 +7,16 @@
7
7
  */
8
8
  import { customFetchInstance } from '../http_client.js';
9
9
  // eslint-disable-next-line @typescript-eslint/no-redeclare
10
+ export const WorkflowRunInfoStatus = {
11
+ running: 'running',
12
+ completed: 'completed',
13
+ failed: 'failed',
14
+ canceled: 'canceled',
15
+ terminated: 'terminated',
16
+ timed_out: 'timed_out',
17
+ continued: 'continued',
18
+ };
19
+ // eslint-disable-next-line @typescript-eslint/no-redeclare
10
20
  export const GetWorkflowIdStatus200Status = {
11
21
  canceled: 'canceled',
12
22
  completed: 'completed',
@@ -98,6 +108,22 @@ export const getWorkflowCatalog = async (options) => {
98
108
  method: 'GET'
99
109
  });
100
110
  };
111
+ export const getGetWorkflowRunsUrl = (params) => {
112
+ const normalizedParams = new URLSearchParams();
113
+ Object.entries(params || {}).forEach(([key, value]) => {
114
+ if (value !== undefined) {
115
+ normalizedParams.append(key, value === null ? 'null' : value.toString());
116
+ }
117
+ });
118
+ const stringifiedParams = normalizedParams.toString();
119
+ return stringifiedParams.length > 0 ? `/workflow/runs?${stringifiedParams}` : `/workflow/runs`;
120
+ };
121
+ export const getWorkflowRuns = async (params, options) => {
122
+ return customFetchInstance(getGetWorkflowRunsUrl(params), {
123
+ ...options,
124
+ method: 'GET'
125
+ });
126
+ };
101
127
  ;
102
128
  export const getPostWorkflowIdFeedbackUrl = (id) => {
103
129
  return `/workflow/${id}/feedback`;
@@ -80,7 +80,6 @@ services:
80
80
  temporal:
81
81
  condition: service_healthy
82
82
  image: growthxteam/output-api:latest
83
- pull_policy: always
84
83
  networks:
85
84
  - main
86
85
  environment:
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class WorkflowRunsList extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ workflowName: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ limit: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ format: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<void>;
13
+ catch(error: Error): Promise<void>;
14
+ }
@@ -0,0 +1,104 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import Table from 'cli-table3';
3
+ import { fetchWorkflowRuns } from '#services/workflow_runs.js';
4
+ import { formatDate, formatDurationFromTimestamps } from '#utils/date_formatter.js';
5
+ import { handleApiError } from '#utils/error_handler.js';
6
+ const OUTPUT_FORMAT = {
7
+ TABLE: 'table',
8
+ JSON: 'json',
9
+ TEXT: 'text'
10
+ };
11
+ function createRunsTable(runs) {
12
+ const table = new Table({
13
+ head: ['Workflow ID', 'Type', 'Status', 'Started', 'Duration'],
14
+ colWidths: [40, 20, 12, 22, 10],
15
+ wordWrap: true,
16
+ style: {
17
+ head: ['cyan']
18
+ }
19
+ });
20
+ runs.forEach(run => {
21
+ table.push([
22
+ run.workflowId || '-',
23
+ run.workflowType || '-',
24
+ run.status || '-',
25
+ formatDate(run.startedAt),
26
+ formatDurationFromTimestamps(run.startedAt || '', run.completedAt)
27
+ ]);
28
+ });
29
+ return table.toString();
30
+ }
31
+ function formatRunsAsText(runs) {
32
+ if (runs.length === 0) {
33
+ return 'No workflow runs found.';
34
+ }
35
+ return runs.map(run => {
36
+ const duration = formatDurationFromTimestamps(run.startedAt || '', run.completedAt);
37
+ return `${run.workflowId} (${run.workflowType}) - ${run.status} [${duration}]`;
38
+ }).join('\n');
39
+ }
40
+ function formatRunsAsJson(runs) {
41
+ return JSON.stringify(runs, null, 2);
42
+ }
43
+ function formatRuns(runs, format) {
44
+ if (format === OUTPUT_FORMAT.JSON) {
45
+ return formatRunsAsJson(runs);
46
+ }
47
+ if (format === OUTPUT_FORMAT.TABLE) {
48
+ return createRunsTable(runs);
49
+ }
50
+ return formatRunsAsText(runs);
51
+ }
52
+ export default class WorkflowRunsList extends Command {
53
+ static description = 'List workflow runs with optional filtering by workflow type';
54
+ static examples = [
55
+ '<%= config.bin %> <%= command.id %>',
56
+ '<%= config.bin %> <%= command.id %> simple',
57
+ '<%= config.bin %> <%= command.id %> simple --limit 10',
58
+ '<%= config.bin %> <%= command.id %> --format json',
59
+ '<%= config.bin %> <%= command.id %> --format table'
60
+ ];
61
+ static args = {
62
+ workflowName: Args.string({
63
+ description: 'Filter by workflow type/name',
64
+ required: false
65
+ })
66
+ };
67
+ static flags = {
68
+ limit: Flags.integer({
69
+ char: 'l',
70
+ description: 'Maximum number of runs to return',
71
+ default: 100
72
+ }),
73
+ format: Flags.string({
74
+ char: 'f',
75
+ description: 'Output format',
76
+ options: [OUTPUT_FORMAT.TABLE, OUTPUT_FORMAT.JSON, OUTPUT_FORMAT.TEXT],
77
+ default: OUTPUT_FORMAT.TABLE
78
+ })
79
+ };
80
+ async run() {
81
+ const { args, flags } = await this.parse(WorkflowRunsList);
82
+ const { runs, count } = await fetchWorkflowRuns({
83
+ workflowType: args.workflowName,
84
+ limit: flags.limit
85
+ });
86
+ if (runs.length === 0) {
87
+ const filterMsg = args.workflowName ? ` for workflow type "${args.workflowName}"` : '';
88
+ this.log(`No workflow runs found${filterMsg}.`);
89
+ return;
90
+ }
91
+ const output = formatRuns(runs, flags.format);
92
+ this.log(output);
93
+ if (flags.format !== OUTPUT_FORMAT.JSON) {
94
+ const filterMsg = args.workflowName ? ` of type "${args.workflowName}"` : '';
95
+ this.log(`\nFound ${count} run(s)${filterMsg}`);
96
+ }
97
+ }
98
+ async catch(error) {
99
+ return handleApiError(error, (...args) => this.error(...args), {
100
+ 400: 'Invalid parameters provided.',
101
+ 503: 'Workflow service temporarily unavailable.'
102
+ });
103
+ }
104
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Workflow runs service for fetching workflow run data from the API
3
+ */
4
+ import { type WorkflowRunInfo } from '#api/generated/api.js';
5
+ export type WorkflowRun = WorkflowRunInfo;
6
+ export interface WorkflowRunsResult {
7
+ runs: WorkflowRun[];
8
+ count: number;
9
+ }
10
+ export interface FetchWorkflowRunsOptions {
11
+ workflowType?: string;
12
+ limit?: number;
13
+ }
14
+ export declare function fetchWorkflowRuns(options?: FetchWorkflowRunsOptions): Promise<WorkflowRunsResult>;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Workflow runs service for fetching workflow run data from the API
3
+ */
4
+ import { getWorkflowRuns } from '#api/generated/api.js';
5
+ export async function fetchWorkflowRuns(options = {}) {
6
+ const params = {};
7
+ if (options.limit) {
8
+ params.limit = options.limit;
9
+ }
10
+ if (options.workflowType) {
11
+ params.workflowType = options.workflowType;
12
+ }
13
+ const response = await getWorkflowRuns(params);
14
+ if (!response) {
15
+ throw new Error('Failed to connect to API server. Is it running?');
16
+ }
17
+ if (!response.data) {
18
+ throw new Error('API returned invalid response (missing data)');
19
+ }
20
+ return {
21
+ runs: response.data.runs || [],
22
+ count: response.data.count || 0
23
+ };
24
+ }
@@ -6,3 +6,18 @@
6
6
  * @returns Formatted duration string (e.g., "150ms", "2.50s", "3 minutes 45 seconds")
7
7
  */
8
8
  export declare function formatDuration(ms: number): string;
9
+ /**
10
+ * Format an ISO date string to a human-readable format
11
+ *
12
+ * @param isoString - ISO 8601 date string
13
+ * @returns Formatted date string (e.g., "Dec 3, 2025 10:30 AM")
14
+ */
15
+ export declare function formatDate(isoString: string | null | undefined): string;
16
+ /**
17
+ * Format a duration between two ISO timestamps
18
+ *
19
+ * @param startedAt - ISO 8601 start timestamp
20
+ * @param completedAt - ISO 8601 end timestamp (or null if still running)
21
+ * @returns Human-readable duration string (e.g., "5 seconds", "running")
22
+ */
23
+ export declare function formatDurationFromTimestamps(startedAt: string, completedAt: string | null | undefined): string;
@@ -1,4 +1,7 @@
1
- import { formatDuration as formatDurationFns, intervalToDuration } from 'date-fns';
1
+ /**
2
+ * Date and duration formatting utilities
3
+ */
4
+ import { format, formatDistanceStrict, formatDuration as formatDurationFns, intervalToDuration, parseISO } from 'date-fns';
2
5
  /**
3
6
  * Format a duration in milliseconds to a human-readable string.
4
7
  * Uses date-fns for durations >= 1 minute, custom formatting for shorter durations.
@@ -17,3 +20,30 @@ export function formatDuration(ms) {
17
20
  }
18
21
  return formatDurationFns(duration, { format: ['minutes', 'seconds'] });
19
22
  }
23
+ /**
24
+ * Format an ISO date string to a human-readable format
25
+ *
26
+ * @param isoString - ISO 8601 date string
27
+ * @returns Formatted date string (e.g., "Dec 3, 2025 10:30 AM")
28
+ */
29
+ export function formatDate(isoString) {
30
+ if (!isoString) {
31
+ return '-';
32
+ }
33
+ return format(parseISO(isoString), 'MMM d, yyyy h:mm a');
34
+ }
35
+ /**
36
+ * Format a duration between two ISO timestamps
37
+ *
38
+ * @param startedAt - ISO 8601 start timestamp
39
+ * @param completedAt - ISO 8601 end timestamp (or null if still running)
40
+ * @returns Human-readable duration string (e.g., "5 seconds", "running")
41
+ */
42
+ export function formatDurationFromTimestamps(startedAt, completedAt) {
43
+ if (!completedAt) {
44
+ return 'running';
45
+ }
46
+ const start = parseISO(startedAt);
47
+ const end = parseISO(completedAt);
48
+ return formatDistanceStrict(start, end, { addSuffix: false });
49
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@output.ai/cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "CLI for Output.ai workflow generation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",