@outputai/cli 0.10.1-next.6deba0f.0 → 0.10.1-next.74e6a3d.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.
Files changed (45) hide show
  1. package/dist/api/generated/api.d.ts +81 -26
  2. package/dist/api/generated/api.js +7 -4
  3. package/dist/assets/docker/docker-compose-dev.yml +2 -2
  4. package/dist/commands/workflow/monitor.d.ts +5 -20
  5. package/dist/commands/workflow/monitor.js +20 -182
  6. package/dist/commands/workflow/monitor.spec.js +82 -3
  7. package/dist/commands/workflow/result.js +2 -2
  8. package/dist/commands/workflow/result.spec.js +65 -1
  9. package/dist/commands/workflow/run.js +2 -2
  10. package/dist/commands/workflow/run.spec.js +30 -3
  11. package/dist/commands/workflow/start.d.ts +4 -0
  12. package/dist/commands/workflow/start.js +95 -10
  13. package/dist/commands/workflow/start.spec.js +252 -0
  14. package/dist/commands/workflow/status.spec.js +1 -1
  15. package/dist/commands/workflow/{test_eval.d.ts → test.d.ts} +0 -1
  16. package/dist/commands/workflow/{test_eval.js → test.js} +0 -1
  17. package/dist/commands/workflow/{test_eval.spec.js → test.spec.js} +4 -4
  18. package/dist/generated/framework_version.json +1 -1
  19. package/dist/services/monitor_stream.d.ts +62 -0
  20. package/dist/services/monitor_stream.js +285 -0
  21. package/dist/services/monitor_stream.spec.d.ts +1 -0
  22. package/dist/services/monitor_stream.spec.js +285 -0
  23. package/dist/services/workflow_history.js +2 -2
  24. package/dist/templates/agent_instructions/CLAUDE.md.template +4 -2
  25. package/dist/templates/project/README.md.template +3 -1
  26. package/dist/templates/project/package.json.template +2 -2
  27. package/dist/utils/error_handler.d.ts +10 -0
  28. package/dist/utils/error_handler.js +14 -0
  29. package/dist/utils/error_handler.spec.d.ts +1 -0
  30. package/dist/utils/error_handler.spec.js +62 -0
  31. package/dist/utils/format_workflow_result.d.ts +15 -3
  32. package/dist/utils/format_workflow_result.js +39 -6
  33. package/dist/utils/format_workflow_result.spec.js +39 -6
  34. package/dist/utils/monitor_flags.d.ts +35 -0
  35. package/dist/utils/monitor_flags.js +76 -0
  36. package/dist/utils/normalize_workflow_status.d.ts +4 -3
  37. package/dist/utils/normalize_workflow_status.js +12 -3
  38. package/dist/utils/normalize_workflow_status.spec.js +3 -0
  39. package/dist/views/dev/components/workflow_status.js +1 -1
  40. package/dist/views/dev/hooks/use_run_detail.js +4 -4
  41. package/dist/views/dev/hooks/use_run_detail.spec.js +1 -1
  42. package/dist/views/dev/panels/runs_panel.js +2 -2
  43. package/oclif.manifest.json +44 -7
  44. package/package.json +5 -5
  45. /package/dist/commands/workflow/{test_eval.spec.d.ts → test.spec.d.ts} +0 -0
@@ -0,0 +1,76 @@
1
+ import { Flags } from '@oclif/core';
2
+ /**
3
+ * The stream's defaults when the tuning flags are not given. Caller-side, not a
4
+ * property of the loop: `streamWorkflowUpdates` takes all three as required
5
+ * options and never falls back. Single-sourced because only `workflow monitor`
6
+ * can hand them to oclif — `workflow start --monitor` has to apply them itself
7
+ * (see `gatedMonitorStreamFlags`), and a literal repeated on that side would
8
+ * drift the moment one of these changes.
9
+ */
10
+ export const MONITOR_DEFAULTS = {
11
+ interval: 2500,
12
+ color: true,
13
+ includePayloads: false
14
+ };
15
+ /**
16
+ * Descriptions and constraints for the three flags that tune
17
+ * `streamWorkflowUpdates`, single-sourced so `workflow monitor` and
18
+ * `workflow start --monitor` can't drift apart on help text — the interval
19
+ * caveat in particular is easy to lose in a copy-paste.
20
+ */
21
+ const PAYLOADS_DESCRIPTION = 'Include decoded step input/output payloads';
22
+ const COLOR_DESCRIPTION = 'Colorize status output (use --no-color to disable)';
23
+ const INTERVAL_DESCRIPTION = 'Poll interval in milliseconds. Once a resumed poll is long-polling ' +
24
+ 'server-side for new events, this also bounds how long that block may last (capped at the ' +
25
+ 'server\'s configured max), so an idle workflow\'s update cadence is roughly twice this value ' +
26
+ '(the long-poll bound, then this sleep) rather than a much longer, separate server default.';
27
+ /** For a command that always monitors, where oclif can apply the defaults itself. */
28
+ export function monitorStreamFlags() {
29
+ return {
30
+ 'include-payloads': Flags.boolean({
31
+ description: PAYLOADS_DESCRIPTION,
32
+ default: MONITOR_DEFAULTS.includePayloads
33
+ }),
34
+ interval: Flags.integer({
35
+ description: INTERVAL_DESCRIPTION,
36
+ default: MONITOR_DEFAULTS.interval,
37
+ min: 1
38
+ }),
39
+ color: Flags.boolean({
40
+ description: COLOR_DESCRIPTION,
41
+ default: MONITOR_DEFAULTS.color,
42
+ allowNo: true
43
+ })
44
+ };
45
+ }
46
+ /**
47
+ * For a command where monitoring is opt-in (`workflow start --monitor`).
48
+ *
49
+ * These carry no oclif `default` on purpose: a defaulted flag counts as present
50
+ * (`validateFlags` runs a relationship check whenever the parsed value is not
51
+ * `undefined`, and `validateDependsOn` has no `setFromDefault` exemption the way
52
+ * `validateExclusive` does), which triggers its own `dependsOn` check and fails
53
+ * every invocation that omits `--monitor` — including a plain `workflow start`.
54
+ * The caller applies the defaults in `run()`, so the interval's default is
55
+ * spelled out in the help text rather than rendered by oclif.
56
+ */
57
+ export function gatedMonitorStreamFlags(gatedBy) {
58
+ const gate = { dependsOn: [gatedBy], helpGroup: 'MONITOR' };
59
+ const requires = ` (requires --${gatedBy})`;
60
+ return {
61
+ 'include-payloads': Flags.boolean({
62
+ description: `${PAYLOADS_DESCRIPTION}${requires}`,
63
+ ...gate
64
+ }),
65
+ interval: Flags.integer({
66
+ description: `${INTERVAL_DESCRIPTION}${requires} [default: ${MONITOR_DEFAULTS.interval}]`,
67
+ min: 1,
68
+ ...gate
69
+ }),
70
+ color: Flags.boolean({
71
+ description: `${COLOR_DESCRIPTION}${requires}`,
72
+ allowNo: true,
73
+ ...gate
74
+ })
75
+ };
76
+ }
@@ -1,8 +1,9 @@
1
1
  /**
2
- * Temporary compatibility for API responses produced before CONTINUED_AS_NEW
3
- * was exposed as `continued_as_new`.
2
+ * Normalizes statuses from earlier API contracts.
3
+ *
4
+ * This can be removed after Aug, 2026
4
5
  *
5
6
  * @param status - Workflow status from the API
6
7
  * @returns Normalized workflow status
7
8
  */
8
- export declare const normalizeWorkflowStatus: <T extends string | null | undefined>(status: T) => T | "continued_as_new";
9
+ export declare const normalizeWorkflowStatus: <T extends string | null | undefined>(status: T) => T | "continued_as_new" | "cancelled";
@@ -1,8 +1,17 @@
1
1
  /**
2
- * Temporary compatibility for API responses produced before CONTINUED_AS_NEW
3
- * was exposed as `continued_as_new`.
2
+ * Normalizes statuses from earlier API contracts.
3
+ *
4
+ * This can be removed after Aug, 2026
4
5
  *
5
6
  * @param status - Workflow status from the API
6
7
  * @returns Normalized workflow status
7
8
  */
8
- export const normalizeWorkflowStatus = (status) => status === 'continued' ? 'continued_as_new' : status;
9
+ export const normalizeWorkflowStatus = (status) => {
10
+ if (status === 'continued') {
11
+ return 'continued_as_new';
12
+ }
13
+ if (status === 'canceled') {
14
+ return 'cancelled';
15
+ }
16
+ return status;
17
+ };
@@ -4,6 +4,9 @@ describe('normalizeWorkflowStatus', () => {
4
4
  it('temporarily maps continued to continued_as_new', () => {
5
5
  expect(normalizeWorkflowStatus('continued')).toBe('continued_as_new');
6
6
  });
7
+ it('maps the previous canceled spelling to cancelled', () => {
8
+ expect(normalizeWorkflowStatus('canceled')).toBe('cancelled');
9
+ });
7
10
  it('leaves other statuses and nullish values unchanged', () => {
8
11
  expect(normalizeWorkflowStatus('completed')).toBe('completed');
9
12
  expect(normalizeWorkflowStatus('continued_as_new')).toBe('continued_as_new');
@@ -4,7 +4,7 @@ const WORKFLOW_STATUS_MAP = {
4
4
  running: { icon: '●', color: 'yellow' },
5
5
  completed: { icon: '●', color: 'green' },
6
6
  failed: { icon: '✗', color: 'red' },
7
- canceled: { icon: '○', color: 'gray' },
7
+ cancelled: { icon: '○', color: 'gray' },
8
8
  terminated: { icon: '✗', color: 'gray' },
9
9
  timed_out: { icon: '✗', color: 'red' },
10
10
  continued_as_new: { icon: '↻', color: 'blue' }
@@ -2,7 +2,7 @@ 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
4
  import { normalizeWorkflowStatus } from '#utils/normalize_workflow_status.js';
5
- import { TERMINAL_STATUSES } from '#utils/format_workflow_result.js';
5
+ import { isTerminalStatus } from '#utils/format_workflow_result.js';
6
6
  import { createBoundedCache } from '#views/dev/utils/bounded_cache.js';
7
7
  const EMPTY_DETAIL = {
8
8
  result: null,
@@ -73,7 +73,7 @@ const readTraceLog = async (source) => {
73
73
  return JSON.parse(content);
74
74
  };
75
75
  // Run detail and trace fetches are best-effort. Many statuses (in-progress,
76
- // failed, canceled) don't have a fully-formed result or trace available at
76
+ // failed, cancelled) don't have a fully-formed result or trace available at
77
77
  // any given moment, and that's expected — the caller falls back to
78
78
  // EMPTY_DETAIL and the UI renders whatever's there. Swallow everything.
79
79
  const fetchTrace = async (workflowId, runId) => {
@@ -104,8 +104,8 @@ const fetchResult = async (workflowId, runId) => {
104
104
  };
105
105
  // The cache is intentionally only populated for terminal statuses — partial results
106
106
  // from a still-running workflow would otherwise stick and stall the UI when the run
107
- // eventually finishes. `TERMINAL_STATUSES` is shared with `workflow monitor`.
108
- export const isTerminalRunStatus = (status) => Boolean(status && TERMINAL_STATUSES.has(status));
107
+ // eventually finishes. `isTerminalStatus` is shared with `workflow monitor`.
108
+ export const isTerminalRunStatus = (status) => isTerminalStatus(status) !== undefined;
109
109
  export const useRunDetail = (workflowId, runId, status) => {
110
110
  const [detail, setDetail] = useState(EMPTY_DETAIL);
111
111
  const fetchIdRef = useRef(0);
@@ -4,7 +4,7 @@ describe('isTerminalRunStatus', () => {
4
4
  it('returns true for completed states', () => {
5
5
  expect(isTerminalRunStatus('completed')).toBe(true);
6
6
  expect(isTerminalRunStatus('failed')).toBe(true);
7
- expect(isTerminalRunStatus('canceled')).toBe(true);
7
+ expect(isTerminalRunStatus('cancelled')).toBe(true);
8
8
  expect(isTerminalRunStatus('terminated')).toBe(true);
9
9
  expect(isTerminalRunStatus('timed_out')).toBe(true);
10
10
  });
@@ -21,7 +21,7 @@ const STATUS_ORDER = {
21
21
  failed: 1,
22
22
  timed_out: 2,
23
23
  terminated: 3,
24
- canceled: 4,
24
+ cancelled: 4,
25
25
  continued_as_new: 5,
26
26
  completed: 6
27
27
  };
@@ -110,7 +110,7 @@ const DetailPane = ({ run, pane, rows }) => {
110
110
  }
111
111
  return _jsx(Text, { dimColor: true, children: "\u2014" });
112
112
  }
113
- if (activePane === 'output' && hasJsonValue(pane.error)) {
113
+ if (activePane === 'output' && typeof pane.error === 'string') {
114
114
  const lines = String(pane.error).split('\n').slice(0, tabContentRows);
115
115
  return (_jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { color: "red", wrap: "truncate-end", children: line }, i))) }));
116
116
  }
@@ -1236,6 +1236,7 @@
1236
1236
  "<%= config.bin %> <%= command.id %> simple basic_input",
1237
1237
  "<%= config.bin %> <%= command.id %> simple --input '{\"values\":[1,2,3]}'",
1238
1238
  "<%= config.bin %> <%= command.id %> simple --input input.json",
1239
+ "<%= config.bin %> <%= command.id %> simple --input input.json --monitor",
1239
1240
  "<%= config.bin %> <%= command.id %> simple --input '{\"key\":\"value\"}' --catalog my-catalog",
1240
1241
  "<%= config.bin %> <%= command.id %> simple --json"
1241
1242
  ],
@@ -1271,6 +1272,44 @@
1271
1272
  "hasDynamicHelp": false,
1272
1273
  "multiple": false,
1273
1274
  "type": "option"
1275
+ },
1276
+ "monitor": {
1277
+ "char": "m",
1278
+ "description": "After starting, attach and stream status updates until the workflow ends (Ctrl+C detaches; the workflow keeps running). Cannot be combined with --json",
1279
+ "name": "monitor",
1280
+ "allowNo": false,
1281
+ "type": "boolean"
1282
+ },
1283
+ "include-payloads": {
1284
+ "dependsOn": [
1285
+ "monitor"
1286
+ ],
1287
+ "description": "Include decoded step input/output payloads (requires --monitor)",
1288
+ "helpGroup": "MONITOR",
1289
+ "name": "include-payloads",
1290
+ "allowNo": false,
1291
+ "type": "boolean"
1292
+ },
1293
+ "interval": {
1294
+ "dependsOn": [
1295
+ "monitor"
1296
+ ],
1297
+ "description": "Poll interval in milliseconds. Once a resumed poll is long-polling server-side for new events, this also bounds how long that block may last (capped at the server's configured max), so an idle workflow's update cadence is roughly twice this value (the long-poll bound, then this sleep) rather than a much longer, separate server default. (requires --monitor) [default: 2500]",
1298
+ "helpGroup": "MONITOR",
1299
+ "name": "interval",
1300
+ "hasDynamicHelp": false,
1301
+ "multiple": false,
1302
+ "type": "option"
1303
+ },
1304
+ "color": {
1305
+ "dependsOn": [
1306
+ "monitor"
1307
+ ],
1308
+ "description": "Colorize status output (use --no-color to disable) (requires --monitor)",
1309
+ "helpGroup": "MONITOR",
1310
+ "name": "color",
1311
+ "allowNo": true,
1312
+ "type": "boolean"
1274
1313
  }
1275
1314
  },
1276
1315
  "hasDynamicHelp": false,
@@ -1398,10 +1437,8 @@
1398
1437
  "terminate.js"
1399
1438
  ]
1400
1439
  },
1401
- "workflow:test_eval": {
1402
- "aliases": [
1403
- "workflow:test"
1404
- ],
1440
+ "workflow:test": {
1441
+ "aliases": [],
1405
1442
  "args": {
1406
1443
  "workflowName": {
1407
1444
  "description": "Name of the workflow to test",
@@ -1470,7 +1507,7 @@
1470
1507
  },
1471
1508
  "hasDynamicHelp": false,
1472
1509
  "hiddenAliases": [],
1473
- "id": "workflow:test_eval",
1510
+ "id": "workflow:test",
1474
1511
  "pluginAlias": "@outputai/cli",
1475
1512
  "pluginName": "@outputai/cli",
1476
1513
  "pluginType": "core",
@@ -1481,7 +1518,7 @@
1481
1518
  "dist",
1482
1519
  "commands",
1483
1520
  "workflow",
1484
- "test_eval.js"
1521
+ "test.js"
1485
1522
  ]
1486
1523
  },
1487
1524
  "workflow:dataset:generate": {
@@ -1715,5 +1752,5 @@
1715
1752
  ]
1716
1753
  }
1717
1754
  },
1718
- "version": "0.10.1-next.6deba0f.0"
1755
+ "version": "0.10.1-next.74e6a3d.0"
1719
1756
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/cli",
3
- "version": "0.10.1-next.6deba0f.0",
3
+ "version": "0.10.1-next.74e6a3d.0",
4
4
  "description": "CLI for Output.ai workflow generation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,15 +30,15 @@
30
30
  "handlebars": "4.7.9",
31
31
  "ink": "7.0.1",
32
32
  "ink-spinner": "5.0.0",
33
- "js-yaml": "4.3.0",
33
+ "js-yaml": "4.3.1",
34
34
  "json-schema-library": "11.4.0",
35
35
  "ky": "2.0.2",
36
36
  "react": "19.2.5",
37
37
  "semver": "7.7.4",
38
38
  "undici": "8.9.0",
39
- "@outputai/credentials": "0.10.1-next.6deba0f.0",
40
- "@outputai/evals": "0.10.1-next.6deba0f.0",
41
- "@outputai/llm": "0.10.1-next.6deba0f.0"
39
+ "@outputai/evals": "0.10.1-next.74e6a3d.0",
40
+ "@outputai/credentials": "0.10.1-next.74e6a3d.0",
41
+ "@outputai/llm": "0.10.1-next.74e6a3d.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/cli-progress": "3.11.6",