@outputai/cli 0.7.1-next.de30052.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -157,7 +157,7 @@ export declare const WorkflowRunInfoStatus: {
157
157
  readonly canceled: "canceled";
158
158
  readonly terminated: "terminated";
159
159
  readonly timed_out: "timed_out";
160
- readonly continued: "continued";
160
+ readonly continued_as_new: "continued_as_new";
161
161
  };
162
162
  export interface WorkflowRunInfo {
163
163
  /** Unique identifier for this run */
@@ -207,13 +207,6 @@ export interface WorkflowStatusResponse {
207
207
  /** An epoch timestamp representing when the workflow ended */
208
208
  completedAt?: number;
209
209
  }
210
- /**
211
- * Convenience totals aggregated from LLM and http usage and cost
212
- * @nullable
213
- */
214
- export type WorkflowResultResponseAggregations = {
215
- [key: string]: unknown;
216
- } | null;
217
210
  /**
218
211
  * The workflow execution status
219
212
  */
@@ -224,7 +217,7 @@ export declare const WorkflowResultResponseStatus: {
224
217
  readonly canceled: "canceled";
225
218
  readonly terminated: "terminated";
226
219
  readonly timed_out: "timed_out";
227
- readonly continued: "continued";
220
+ readonly continued_as_new: "continued_as_new";
228
221
  };
229
222
  /**
230
223
  * Structured failure details if the workflow failed, null otherwise
@@ -269,11 +262,6 @@ export interface WorkflowResultResponse {
269
262
  /** The result of workflow, null if workflow failed */
270
263
  output?: unknown;
271
264
  trace?: TraceInfo;
272
- /**
273
- * Convenience totals aggregated from LLM and http usage and cost
274
- * @nullable
275
- */
276
- aggregations?: WorkflowResultResponseAggregations;
277
265
  /** The workflow execution status */
278
266
  status?: WorkflowResultResponseStatus;
279
267
  /**
@@ -541,6 +529,26 @@ export type getHealthResponseSuccess = (getHealthResponse200) & {
541
529
  export type getHealthResponse = (getHealthResponseSuccess);
542
530
  export declare const getGetHealthUrl: () => string;
543
531
  export declare const getHealth: (options?: ApiRequestOptions) => Promise<getHealthResponse>;
532
+ /**
533
+ * @summary Check if the API is ready to answer requests
534
+ */
535
+ export type getReadyResponse200 = {
536
+ data: void;
537
+ status: 200;
538
+ };
539
+ export type getReadyResponse503 = {
540
+ data: void;
541
+ status: 503;
542
+ };
543
+ export type getReadyResponseSuccess = (getReadyResponse200) & {
544
+ headers: Headers;
545
+ };
546
+ export type getReadyResponseError = (getReadyResponse503) & {
547
+ headers: Headers;
548
+ };
549
+ export type getReadyResponse = (getReadyResponseSuccess | getReadyResponseError);
550
+ export declare const getGetReadyUrl: () => string;
551
+ export declare const getReady: (options?: ApiRequestOptions) => Promise<getReadyResponse>;
544
552
  /**
545
553
  * Executes a workflow and waits for it to complete before returning the result
546
554
  * @summary Execute a workflow synchronously
@@ -22,7 +22,7 @@ export const WorkflowRunInfoStatus = {
22
22
  canceled: 'canceled',
23
23
  terminated: 'terminated',
24
24
  timed_out: 'timed_out',
25
- continued: 'continued',
25
+ continued_as_new: 'continued_as_new',
26
26
  };
27
27
  export const WorkflowStatusResponseStatus = {
28
28
  canceled: 'canceled',
@@ -40,7 +40,7 @@ export const WorkflowResultResponseStatus = {
40
40
  canceled: 'canceled',
41
41
  terminated: 'terminated',
42
42
  timed_out: 'timed_out',
43
- continued: 'continued',
43
+ continued_as_new: 'continued_as_new',
44
44
  };
45
45
  ;
46
46
  export const getGetHealthUrl = () => {
@@ -52,6 +52,15 @@ export const getHealth = async (options) => {
52
52
  method: 'GET'
53
53
  });
54
54
  };
55
+ export const getGetReadyUrl = () => {
56
+ return `/ready`;
57
+ };
58
+ export const getReady = async (options) => {
59
+ return customFetchInstance(getGetReadyUrl(), {
60
+ ...options,
61
+ method: 'GET'
62
+ });
63
+ };
55
64
  export const getPostWorkflowRunUrl = () => {
56
65
  return `/workflow/run`;
57
66
  };
@@ -80,7 +80,7 @@ services:
80
80
  condition: service_healthy
81
81
  worker:
82
82
  condition: service_healthy
83
- image: outputai/api:${OUTPUT_API_VERSION:-0.7.1-next.de30052.0}
83
+ image: outputai/api:${OUTPUT_API_VERSION:-0.8.0}
84
84
  init: true
85
85
  networks:
86
86
  - main
@@ -3,6 +3,7 @@ import { getWorkflowIdStatus } from '#api/generated/api.js';
3
3
  import { OUTPUT_FORMAT } from '#utils/constants.js';
4
4
  import { formatOutput } from '#utils/output_formatter.js';
5
5
  import { handleApiError } from '#utils/error_handler.js';
6
+ import { normalizeWorkflowStatus } from '#utils/normalize_workflow_status.js';
6
7
  export default class WorkflowStatus extends Command {
7
8
  static description = 'Get workflow execution status';
8
9
  static examples = [
@@ -30,7 +31,11 @@ export default class WorkflowStatus extends Command {
30
31
  if (!response || !response.data) {
31
32
  this.error('API returned invalid response', { exit: 1 });
32
33
  }
33
- const data = response.data;
34
+ const rawData = response.data;
35
+ const data = {
36
+ ...rawData,
37
+ status: normalizeWorkflowStatus(rawData.status)
38
+ };
34
39
  const output = formatOutput(data, flags.format, (result) => {
35
40
  const lines = [
36
41
  `Workflow ID: ${result.workflowId || 'unknown'}`,
@@ -1,3 +1,3 @@
1
1
  {
2
- "framework": "0.7.1-next.de30052.0"
2
+ "framework": "0.8.0"
3
3
  }
@@ -19,7 +19,7 @@ describe('fix package', () => {
19
19
  expect(plan.scriptsToRemove.map(r => r.key).sort()).toEqual([...legacyScripts].sort());
20
20
  expect(plan.hasChanges).toBe(true);
21
21
  expect(plan.scriptsToReplace).toEqual([]);
22
- expect(plan.scriptsToAdd).toHaveLength(6);
22
+ expect(plan.scriptsToAdd).toHaveLength(7);
23
23
  applyFix(plan);
24
24
  const next = JSON.parse(await fs.readFile(path.join(tmpDir, 'package.json'), 'utf-8'));
25
25
  expect(next.scripts['dev']).toBeUndefined();
@@ -42,10 +42,11 @@ describe('fix package', () => {
42
42
  expect(plan.scriptsToReplace).toEqual([
43
43
  { key: 'output:dev', before: 'bad-dev-command', after: 'output dev' }
44
44
  ]);
45
- expect(plan.scriptsToAdd).toHaveLength(5);
45
+ expect(plan.scriptsToAdd).toHaveLength(6);
46
46
  expect(plan.scriptsToAdd.map(a => a.key).sort()).toEqual([
47
47
  'output:worker',
48
48
  'output:worker:build',
49
+ 'output:worker:check',
49
50
  'output:worker:install',
50
51
  'output:worker:start',
51
52
  'output:worker:watch'
@@ -2,6 +2,7 @@
2
2
  * Workflow runs service for fetching workflow run data from the API
3
3
  */
4
4
  import { getWorkflowRuns } from '#api/generated/api.js';
5
+ import { normalizeWorkflowStatus } from '#utils/normalize_workflow_status.js';
5
6
  export async function fetchWorkflowRuns(options = {}) {
6
7
  const params = {};
7
8
  if (options.limit) {
@@ -21,8 +22,12 @@ export async function fetchWorkflowRuns(options = {}) {
21
22
  throw new Error('API returned invalid response (missing data)');
22
23
  }
23
24
  const data = response.data;
25
+ const runs = (data.runs || []).map(run => ({
26
+ ...run,
27
+ status: normalizeWorkflowStatus(run.status)
28
+ }));
24
29
  return {
25
- runs: data.runs || [],
30
+ runs,
26
31
  count: data.count || 0
27
32
  };
28
33
  }
@@ -16,6 +16,7 @@ claude plugin install outputai@outputai --scope project
16
16
  ```bash
17
17
  npm run output:dev # Start dev environment (worker + Temporal)
18
18
  npm run output:worker:build # Build TypeScript to dist/
19
+ npm run output:worker:check # Optional: bundle-check workflows for bad imports (node: built-ins)
19
20
  npm run output:worker:watch # Build + restart on file changes
20
21
  npm run output:worker # Install, build, and start worker
21
22
  ```
@@ -100,3 +100,35 @@ Monitor workflow execution and system status in the Temporal UI:
100
100
  ```bash
101
101
  open http://localhost:8080
102
102
  ```
103
+
104
+ ## Checking Workflows for Bad Imports
105
+
106
+ Temporal bundles your workflows before they run. If a workflow — or anything it
107
+ transitively imports — pulls in a `node:` built-in (e.g. `node:fs`), the bundle fails
108
+ **at worker startup**, not at `tsc` build time. The optional bundle check reproduces
109
+ that bundling so you catch it early:
110
+
111
+ ```bash
112
+ npm run output:worker:build # compile to dist/
113
+ npm run output:worker:check # bundle-check workflows (runs output-worker --check)
114
+ ```
115
+
116
+ It exits non-zero and names the offending module when a workflow can't be bundled.
117
+
118
+ To gate merges, wire it into your CI — for example, GitHub Actions:
119
+
120
+ ```yaml
121
+ # .github/workflows/output-check.yml
122
+ name: Output checks
123
+ on: [pull_request]
124
+ jobs:
125
+ workflows:
126
+ runs-on: ubuntu-latest
127
+ steps:
128
+ - uses: actions/checkout@v4
129
+ - uses: actions/setup-node@v4
130
+ with: { node-version: 24 }
131
+ - run: npm ci
132
+ - run: npm run output:worker:build
133
+ - run: npm run output:worker:check
134
+ ```
@@ -8,6 +8,7 @@
8
8
  "output:worker:install": "npm install",
9
9
  "output:worker:build": "rm -rf dist/* && tsc -p ./ && output-copy-assets",
10
10
  "output:worker:start": "output-worker",
11
+ "output:worker:check": "output-worker --check",
11
12
  "output:worker": "npm run output:worker:install && npm run output:worker:build && npm run output:worker:start",
12
13
  "output:worker:watch": "npx nodemon --watch src --watch package.json --ext ts,js,json,prompt,md --ignore 'dist/**' --ignore '**/*.spec.*' --ignore '**/*.test.*' --exec 'npm run output:worker'",
13
14
  "output:dev": "output dev"
@@ -1,15 +1,17 @@
1
+ import { normalizeWorkflowStatus } from './normalize_workflow_status.js';
1
2
  export const ERROR_STATUSES = new Set(['failed', 'canceled', 'terminated', 'timed_out']);
2
3
  export function formatWorkflowResult(result) {
4
+ const status = normalizeWorkflowStatus(result.status);
3
5
  const lines = [
4
6
  `Workflow ID: ${result.workflowId || 'unknown'}`,
5
7
  ''
6
8
  ];
7
- if (result.status === 'completed') {
9
+ if (status === 'completed') {
8
10
  lines.push('Output:');
9
11
  lines.push(JSON.stringify(result.output, null, 2));
10
12
  }
11
13
  else {
12
- lines.push(`Status: ${result.status || 'unknown'}`);
14
+ lines.push(`Status: ${status || 'unknown'}`);
13
15
  if (result.error) {
14
16
  lines.push(`Error: ${result.error}`);
15
17
  }
@@ -46,16 +46,26 @@ describe('formatWorkflowResult', () => {
46
46
  expect(result).toContain('Status: canceled');
47
47
  expect(result).toContain('Error: Workflow was canceled');
48
48
  });
49
- it('should display status without error line for continued workflows', () => {
49
+ it('should display status without error line for continued_as_new workflows', () => {
50
50
  const result = formatWorkflowResult({
51
51
  workflowId: 'wf-cont',
52
- status: 'continued',
52
+ status: 'continued_as_new',
53
53
  output: null,
54
54
  error: null
55
55
  });
56
- expect(result).toContain('Status: continued');
56
+ expect(result).toContain('Status: continued_as_new');
57
57
  expect(result).not.toContain('Error:');
58
58
  });
59
+ it('temporarily normalizes legacy continued status to continued_as_new', () => {
60
+ const legacyResult = {
61
+ workflowId: 'wf-cont',
62
+ status: 'continued',
63
+ output: null,
64
+ error: null
65
+ };
66
+ const result = formatWorkflowResult(legacyResult);
67
+ expect(result).toContain('Status: continued_as_new');
68
+ });
59
69
  it('should omit error line when error is null on failed workflow', () => {
60
70
  const result = formatWorkflowResult({
61
71
  workflowId: 'wf-789',
@@ -71,13 +81,11 @@ describe('formatWorkflowResult', () => {
71
81
  workflowId: 'wf-456',
72
82
  status: 'failed',
73
83
  output: null,
74
- aggregations: { cost: { total: 0.4 }, tokens: { total: 20 }, httpRequests: { total: 0 } },
75
84
  error: 'Activity task failed'
76
85
  };
77
86
  const output = formatOutput(data, 'json', formatWorkflowResult);
78
87
  const parsed = JSON.parse(output);
79
88
  expect(parsed.status).toBe('failed');
80
- expect(parsed.aggregations).toEqual({ cost: { total: 0.4 }, tokens: { total: 20 }, httpRequests: { total: 0 } });
81
89
  expect(parsed.error).toBe('Activity task failed');
82
90
  });
83
91
  });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Temporary compatibility for API responses produced before CONTINUED_AS_NEW
3
+ * was exposed as `continued_as_new`.
4
+ *
5
+ * @param status - Workflow status from the API
6
+ * @returns Normalized workflow status
7
+ */
8
+ export declare const normalizeWorkflowStatus: <T extends string | null | undefined>(status: T) => T | "continued_as_new";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Temporary compatibility for API responses produced before CONTINUED_AS_NEW
3
+ * was exposed as `continued_as_new`.
4
+ *
5
+ * @param status - Workflow status from the API
6
+ * @returns Normalized workflow status
7
+ */
8
+ export const normalizeWorkflowStatus = (status) => status === 'continued' ? 'continued_as_new' : status;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { normalizeWorkflowStatus } from './normalize_workflow_status.js';
3
+ describe('normalizeWorkflowStatus', () => {
4
+ it('temporarily maps continued to continued_as_new', () => {
5
+ expect(normalizeWorkflowStatus('continued')).toBe('continued_as_new');
6
+ });
7
+ it('leaves other statuses and nullish values unchanged', () => {
8
+ expect(normalizeWorkflowStatus('completed')).toBe('completed');
9
+ expect(normalizeWorkflowStatus('continued_as_new')).toBe('continued_as_new');
10
+ expect(normalizeWorkflowStatus(null)).toBeNull();
11
+ expect(normalizeWorkflowStatus(undefined)).toBeUndefined();
12
+ });
13
+ });
@@ -7,7 +7,7 @@ const WORKFLOW_STATUS_MAP = {
7
7
  canceled: { icon: '○', color: 'gray' },
8
8
  terminated: { icon: '✗', color: 'gray' },
9
9
  timed_out: { icon: '✗', color: 'red' },
10
- continued: { icon: '↻', color: 'blue' }
10
+ continued_as_new: { icon: '↻', color: 'blue' }
11
11
  };
12
12
  const DEFAULT_DISPLAY = { icon: '?', color: 'white' };
13
13
  export const resolveWorkflowStatus = (status) => WORKFLOW_STATUS_MAP[status] ?? DEFAULT_DISPLAY;
@@ -1,6 +1,7 @@
1
1
  import { useEffect, useRef, useState } from 'react';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { getWorkflowIdResult, getWorkflowIdRunsRidResult, getWorkflowIdTraceLog, getWorkflowIdRunsRidTraceLog } from '#api/generated/api.js';
4
+ import { normalizeWorkflowStatus } from '#utils/normalize_workflow_status.js';
4
5
  const EMPTY_DETAIL = {
5
6
  result: null,
6
7
  trace: null,
@@ -88,7 +89,11 @@ const fetchResult = async (workflowId, runId) => {
88
89
  const response = runId ?
89
90
  await getWorkflowIdRunsRidResult(workflowId, runId) :
90
91
  await getWorkflowIdResult(workflowId);
91
- return response.data;
92
+ const data = response.data;
93
+ return {
94
+ ...data,
95
+ status: normalizeWorkflowStatus(data.status)
96
+ };
92
97
  }
93
98
  catch {
94
99
  return null;
@@ -10,7 +10,7 @@ describe('isTerminalRunStatus', () => {
10
10
  });
11
11
  it('returns false for in-progress states', () => {
12
12
  expect(isTerminalRunStatus('running')).toBe(false);
13
- expect(isTerminalRunStatus('continued')).toBe(false);
13
+ expect(isTerminalRunStatus('continued_as_new')).toBe(false);
14
14
  });
15
15
  it('returns false for nullish input', () => {
16
16
  expect(isTerminalRunStatus(null)).toBe(false);
@@ -22,7 +22,7 @@ const STATUS_ORDER = {
22
22
  timed_out: 2,
23
23
  terminated: 3,
24
24
  canceled: 4,
25
- continued: 5,
25
+ continued_as_new: 5,
26
26
  completed: 6
27
27
  };
28
28
  const sortRuns = (runs) => [...runs].sort((a, b) => {
@@ -60,10 +60,9 @@ const COL = {
60
60
  const RUN_INFO_TABS = [
61
61
  { id: 'status', label: 'Status' },
62
62
  { id: 'input', label: 'Input' },
63
- { id: 'output', label: 'Output' },
64
- { id: 'aggregations', label: 'Aggregations' }
63
+ { id: 'output', label: 'Output' }
65
64
  ];
66
- const RUN_INFO_TAB_ORDER = ['status', 'input', 'output', 'aggregations'];
65
+ const RUN_INFO_TAB_ORDER = ['status', 'input', 'output'];
67
66
  const HeaderRow = () => (_jsxs(Box, { children: [_jsx(Box, { width: COL.indicator, children: _jsx(Text, { children: "\u00A0" }) }), _jsx(Box, { width: COL.icon, children: _jsx(Text, { children: "\u00A0" }) }), _jsx(Box, { width: COL.status, children: _jsx(Text, { dimColor: true, bold: true, children: "STATUS" }) }), _jsx(Box, { width: COL.type, children: _jsx(Text, { dimColor: true, bold: true, children: "TYPE" }) }), _jsx(Box, { width: COL.id, children: _jsx(Text, { dimColor: true, bold: true, children: "ID" }) }), _jsx(Box, { width: COL.duration, justifyContent: "flex-end", children: _jsx(Text, { dimColor: true, bold: true, children: "DURATION" }) }), _jsx(Box, { width: COL.started, marginLeft: 2, children: _jsx(Text, { dimColor: true, bold: true, children: "STARTED" }) })] }));
68
67
  const RunRow = ({ run, selected }) => {
69
68
  const status = run.status ?? 'running';
@@ -86,10 +85,8 @@ const runPaneValue = (run, pane, activePane) => {
86
85
  if (activePane === 'input') {
87
86
  return pane.input;
88
87
  }
89
- if (activePane === 'output') {
90
- return pane.error ?? pane.output;
91
- }
92
- return pane.aggregations;
88
+ // 'output'
89
+ return pane.error ?? pane.output;
93
90
  };
94
91
  const DetailPane = ({ run, pane, rows }) => {
95
92
  const ui = useUiState();
@@ -148,7 +145,6 @@ export const RunsPanel = ({ runs, height }) => {
148
145
  input: result?.input,
149
146
  output: result?.output,
150
147
  error: result?.error,
151
- aggregations: result?.aggregations,
152
148
  status: result?.status ?? selectedRun.status ?? 'unknown',
153
149
  loading
154
150
  } : null;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  export type Tab = 'workflows' | 'runs' | 'services' | 'help';
3
3
  export declare const TAB_ORDER: Tab[];
4
4
  export declare const TAB_LABELS: Record<Tab, string>;
5
- export type RunListPaneTab = 'status' | 'input' | 'output' | 'aggregations';
5
+ export type RunListPaneTab = 'status' | 'input' | 'output';
6
6
  export type RunStepPaneTab = 'input' | 'output' | 'meta';
7
7
  export type RunsView = 'list' | 'detail';
8
8
  export interface Selection {
@@ -1441,5 +1441,5 @@
1441
1441
  ]
1442
1442
  }
1443
1443
  },
1444
- "version": "0.7.1-next.de30052.0"
1444
+ "version": "0.8.0"
1445
1445
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/cli",
3
- "version": "0.7.1-next.de30052.0",
3
+ "version": "0.8.0",
4
4
  "description": "CLI for Output.ai workflow generation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,9 +36,9 @@
36
36
  "semver": "7.7.4",
37
37
  "undici": "8.1.0",
38
38
  "yaml": "^2.8.3",
39
- "@outputai/credentials": "0.7.1-next.de30052.0",
40
- "@outputai/evals": "0.7.1-next.de30052.0",
41
- "@outputai/llm": "0.7.1-next.de30052.0"
39
+ "@outputai/credentials": "0.8.0",
40
+ "@outputai/llm": "0.8.0",
41
+ "@outputai/evals": "0.8.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/cli-progress": "3.11.6",