@outputai/cli 0.3.2-next.5e221e8.0 → 0.3.3-dev.422151e.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 (94) hide show
  1. package/dist/assets/docker/docker-compose-dev.yml +1 -1
  2. package/dist/commands/dev/index.js +48 -13
  3. package/dist/commands/dev/index.spec.js +1 -2
  4. package/dist/components/status_icon.js +1 -1
  5. package/dist/generated/framework_version.json +1 -1
  6. package/dist/services/docker.js +0 -1
  7. package/dist/templates/project/src/workflows/blog_evaluator/prompts/signal_noise@v1.prompt.template +2 -1
  8. package/dist/templates/workflow/README.md.template +2 -1
  9. package/dist/templates/workflow/prompts/example@v1.prompt.template +2 -1
  10. package/dist/utils/paths.d.ts +11 -0
  11. package/dist/utils/paths.js +14 -0
  12. package/dist/utils/scenario_resolver.js +3 -2
  13. package/dist/views/dev/chrome/divider.d.ts +8 -0
  14. package/dist/views/dev/chrome/divider.js +16 -0
  15. package/dist/views/dev/chrome/footer.d.ts +11 -0
  16. package/dist/views/dev/chrome/footer.js +10 -0
  17. package/dist/views/dev/chrome/header.d.ts +21 -0
  18. package/dist/views/dev/chrome/header.js +74 -0
  19. package/dist/views/dev/chrome/header.spec.d.ts +1 -0
  20. package/dist/views/dev/chrome/header.spec.js +50 -0
  21. package/dist/views/dev/chrome/loading_spinner.d.ts +9 -0
  22. package/dist/views/dev/chrome/loading_spinner.js +9 -0
  23. package/dist/views/dev/chrome/palette.d.ts +16 -0
  24. package/dist/views/dev/chrome/palette.js +16 -0
  25. package/dist/views/dev/chrome/search_bar.d.ts +5 -0
  26. package/dist/views/dev/chrome/search_bar.js +35 -0
  27. package/dist/views/dev/chrome/selection_indicator.d.ts +14 -0
  28. package/dist/views/dev/chrome/selection_indicator.js +13 -0
  29. package/dist/views/dev/chrome/tab_bar.d.ts +5 -0
  30. package/dist/views/dev/chrome/tab_bar.js +4 -0
  31. package/dist/views/dev/chrome/toasts.d.ts +2 -0
  32. package/dist/views/dev/chrome/toasts.js +40 -0
  33. package/dist/views/dev/components/master_detail_panel.d.ts +21 -0
  34. package/dist/views/dev/components/master_detail_panel.js +18 -0
  35. package/dist/views/{dev.d.ts → dev/dev_app.d.ts} +1 -0
  36. package/dist/views/dev/dev_app.js +146 -0
  37. package/dist/views/dev/hooks/use_docker_logs.d.ts +7 -0
  38. package/dist/views/dev/hooks/use_docker_logs.js +69 -0
  39. package/dist/views/dev/hooks/use_poll.d.ts +16 -0
  40. package/dist/views/dev/hooks/use_poll.js +95 -0
  41. package/dist/views/dev/hooks/use_run_detail.d.ts +21 -0
  42. package/dist/views/dev/hooks/use_run_detail.js +153 -0
  43. package/dist/views/dev/hooks/use_run_detail.spec.d.ts +1 -0
  44. package/dist/views/dev/hooks/use_run_detail.spec.js +86 -0
  45. package/dist/views/dev/hooks/use_workflow_catalog.d.ts +2 -0
  46. package/dist/views/dev/hooks/use_workflow_catalog.js +21 -0
  47. package/dist/views/dev/modals/expanded_json_modal.d.ts +2 -0
  48. package/dist/views/dev/modals/expanded_json_modal.js +44 -0
  49. package/dist/views/dev/modals/run_modal.d.ts +4 -0
  50. package/dist/views/dev/modals/run_modal.js +213 -0
  51. package/dist/views/dev/panels/help_panel.d.ts +2 -0
  52. package/dist/views/dev/panels/help_panel.js +53 -0
  53. package/dist/views/dev/panels/run_detail_view.d.ts +5 -0
  54. package/dist/views/dev/panels/run_detail_view.js +112 -0
  55. package/dist/views/dev/panels/runs_panel.d.ts +8 -0
  56. package/dist/views/dev/panels/runs_panel.js +204 -0
  57. package/dist/views/dev/panels/runs_panel.spec.d.ts +1 -0
  58. package/dist/views/dev/panels/runs_panel.spec.js +82 -0
  59. package/dist/views/dev/panels/services_panel.d.ts +14 -0
  60. package/dist/views/dev/panels/services_panel.js +155 -0
  61. package/dist/views/dev/panels/services_panel.spec.d.ts +1 -0
  62. package/dist/views/dev/panels/services_panel.spec.js +28 -0
  63. package/dist/views/dev/panels/workflows_panel.d.ts +7 -0
  64. package/dist/views/dev/panels/workflows_panel.js +111 -0
  65. package/dist/views/dev/services/docker_control.d.ts +5 -0
  66. package/dist/views/dev/services/docker_control.js +25 -0
  67. package/dist/views/dev/services/run_workflow.d.ts +10 -0
  68. package/dist/views/dev/services/run_workflow.js +14 -0
  69. package/dist/views/dev/services/scenario_io.d.ts +2 -0
  70. package/dist/views/dev/services/scenario_io.js +37 -0
  71. package/dist/views/dev/state/ui_state.d.ts +60 -0
  72. package/dist/views/dev/state/ui_state.js +64 -0
  73. package/dist/views/dev/utils/constants.d.ts +17 -0
  74. package/dist/views/dev/utils/constants.js +17 -0
  75. package/dist/views/dev/utils/json_editor.d.ts +21 -0
  76. package/dist/views/dev/utils/json_editor.js +117 -0
  77. package/dist/views/dev/utils/json_editor.spec.d.ts +1 -0
  78. package/dist/views/dev/utils/json_editor.spec.js +57 -0
  79. package/dist/views/dev/utils/json_render.d.ts +15 -0
  80. package/dist/views/dev/utils/json_render.js +77 -0
  81. package/dist/views/dev/utils/json_render.spec.d.ts +1 -0
  82. package/dist/views/dev/utils/json_render.spec.js +65 -0
  83. package/dist/views/dev/utils/panel_helpers.d.ts +16 -0
  84. package/dist/views/dev/utils/panel_helpers.js +32 -0
  85. package/dist/views/dev/utils/panel_helpers.spec.d.ts +1 -0
  86. package/dist/views/dev/utils/panel_helpers.spec.js +47 -0
  87. package/package.json +5 -5
  88. package/dist/components/command_footer.d.ts +0 -8
  89. package/dist/components/command_footer.js +0 -4
  90. package/dist/components/workflow_summary.d.ts +0 -10
  91. package/dist/components/workflow_summary.js +0 -4
  92. package/dist/views/dev.js +0 -187
  93. package/dist/views/workflow/list.d.ts +0 -6
  94. package/dist/views/workflow/list.js +0 -129
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { truncate, formatStartedShort, computeWindowStart } from './panel_helpers.js';
3
+ describe('truncate', () => {
4
+ it('returns the input unchanged when shorter than max', () => {
5
+ expect(truncate('short', 10)).toBe('short');
6
+ });
7
+ it('returns the input unchanged when exactly equal to max', () => {
8
+ expect(truncate('abcde', 5)).toBe('abcde');
9
+ });
10
+ it('replaces the last character with an ellipsis when too long', () => {
11
+ expect(truncate('abcdefgh', 5)).toBe('abcd…');
12
+ });
13
+ it('handles empty input', () => {
14
+ expect(truncate('', 4)).toBe('');
15
+ });
16
+ });
17
+ describe('formatStartedShort', () => {
18
+ it('returns `-` when the input is undefined', () => {
19
+ expect(formatStartedShort(undefined)).toBe('-');
20
+ });
21
+ it('returns `-` when the input is empty', () => {
22
+ expect(formatStartedShort('')).toBe('-');
23
+ });
24
+ it('returns `-` when the input is unparseable', () => {
25
+ expect(formatStartedShort('not-an-iso')).toBe('-');
26
+ });
27
+ it('formats a valid ISO timestamp into `MMM d HH:mm`', () => {
28
+ expect(formatStartedShort('2026-04-28T18:56:53Z')).toMatch(/^Apr 28 \d{2}:\d{2}$/);
29
+ });
30
+ });
31
+ describe('computeWindowStart', () => {
32
+ it('centres the selected row in the viewport when possible', () => {
33
+ expect(computeWindowStart(10, 30, 8)).toBe(6);
34
+ });
35
+ it('clamps to 0 when the selected row is near the top', () => {
36
+ expect(computeWindowStart(1, 30, 8)).toBe(0);
37
+ });
38
+ it('clamps so the window never runs off the end', () => {
39
+ expect(computeWindowStart(28, 30, 8)).toBe(22);
40
+ });
41
+ it('returns 0 when the list is shorter than the viewport', () => {
42
+ expect(computeWindowStart(2, 5, 8)).toBe(0);
43
+ });
44
+ it('returns 0 for an empty list', () => {
45
+ expect(computeWindowStart(0, 0, 8)).toBe(0);
46
+ });
47
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/cli",
3
- "version": "0.3.2-next.5e221e8.0",
3
+ "version": "0.3.3-dev.422151e.0",
4
4
  "description": "CLI for Output.ai workflow generation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,7 +27,7 @@
27
27
  "debug": "4.4.3",
28
28
  "dotenv": "17.4.2",
29
29
  "handlebars": "4.7.9",
30
- "ink": "6.8.0",
30
+ "ink": "7.0.1",
31
31
  "ink-spinner": "5.0.0",
32
32
  "js-yaml": "4.1.1",
33
33
  "json-schema-library": "11.4.0",
@@ -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.3.2-next.5e221e8.0",
40
- "@outputai/evals": "0.3.2-next.5e221e8.0",
41
- "@outputai/llm": "0.3.2-next.5e221e8.0"
39
+ "@outputai/credentials": "0.3.3-dev.422151e.0",
40
+ "@outputai/llm": "0.3.3-dev.422151e.0",
41
+ "@outputai/evals": "0.3.3-dev.422151e.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/cli-progress": "3.11.6",
@@ -1,8 +0,0 @@
1
- import React from 'react';
2
- export interface CommandHint {
3
- key: string;
4
- label: string;
5
- }
6
- export declare const CommandFooter: React.FC<{
7
- hints: CommandHint[];
8
- }>;
@@ -1,4 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { Box, Text } from 'ink';
4
- export const CommandFooter = ({ hints }) => (_jsx(Box, { marginTop: 1, children: hints.map((hint, i) => (_jsxs(React.Fragment, { children: [i > 0 && _jsx(Text, { dimColor: true, children: ' | ' }), _jsx(Text, { dimColor: true, children: '(' }), _jsx(Text, { dimColor: true, bold: true, children: hint.key }), _jsx(Text, { dimColor: true, children: ')' }), _jsx(Text, { dimColor: true, children: ` ${hint.label}` })] }, hint.key))) }));
@@ -1,10 +0,0 @@
1
- import React from 'react';
2
- export interface WorkflowSummary {
3
- running: number;
4
- completed: number;
5
- failed: number;
6
- total: number;
7
- }
8
- export declare const WorkflowSummarySection: React.FC<{
9
- summary: WorkflowSummary;
10
- }>;
@@ -1,4 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
3
- import { statusColor } from '#components/status_icon.js';
4
- export const WorkflowSummarySection = ({ summary }) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCCB Workflows" }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: statusColor('running'), children: [summary.running, " running"] }), _jsx(Text, { children: ", " }), _jsxs(Text, { color: statusColor('failed'), children: [summary.failed, " failed"] }), _jsx(Text, { children: ", " }), _jsxs(Text, { color: statusColor('completed'), children: [summary.completed, " complete"] })] })] }));
package/dist/views/dev.js DELETED
@@ -1,187 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState, useEffect, useRef } from 'react';
3
- import { Box, Text, Static, useApp, useInput } from 'ink';
4
- import Spinner from 'ink-spinner';
5
- import { getServiceStatus, isServiceHealthy, isServiceFailed, SERVICE_HEALTH } from '#services/docker.js';
6
- import { config } from '#config.js';
7
- import { fetchWorkflowRuns } from '#services/workflow_runs.js';
8
- import { openUrl } from '#utils/open_url.js';
9
- import { StatusIcon } from '#components/status_icon.js';
10
- import { WorkflowSummarySection } from '#components/workflow_summary.js';
11
- import { CommandFooter } from '#components/command_footer.js';
12
- import { WorkflowListView } from '#views/workflow/list.js';
13
- const POLL_INTERVAL_MS = 2000;
14
- const HEALTH_TIMEOUT_MS = 120_000;
15
- const resolveServiceStatus = (service) => service.health === SERVICE_HEALTH.NONE ? service.state : service.health;
16
- const fetchServices = async (dockerComposePath) => {
17
- try {
18
- return await getServiceStatus(dockerComposePath);
19
- }
20
- catch {
21
- return null;
22
- }
23
- };
24
- const usePoll = (enabled, onTick) => {
25
- const onTickRef = useRef(onTick);
26
- onTickRef.current = onTick;
27
- useEffect(() => {
28
- const state = {
29
- active: true,
30
- timeout: undefined
31
- };
32
- const run = async () => {
33
- if (!state.active) {
34
- return;
35
- }
36
- const result = await onTickRef.current();
37
- if (!state.active || result === 'done') {
38
- return;
39
- }
40
- state.timeout = setTimeout(run, POLL_INTERVAL_MS);
41
- };
42
- if (enabled) {
43
- void run();
44
- }
45
- return () => {
46
- state.active = false;
47
- clearTimeout(state.timeout);
48
- };
49
- }, [enabled]);
50
- };
51
- const useHealthPolling = (dockerComposePath, enabled, callbacks) => {
52
- const callbacksRef = useRef(callbacks);
53
- callbacksRef.current = callbacks;
54
- const startTimeRef = useRef(Date.now());
55
- usePoll(enabled, async () => {
56
- if (Date.now() - startTimeRef.current > HEALTH_TIMEOUT_MS) {
57
- callbacksRef.current.onTimeout();
58
- return 'done';
59
- }
60
- const svcs = await fetchServices(dockerComposePath);
61
- if (svcs === null) {
62
- return 'continue';
63
- }
64
- callbacksRef.current.onServices(svcs);
65
- if (svcs.length > 0 && svcs.every(isServiceHealthy)) {
66
- callbacksRef.current.onAllHealthy(svcs);
67
- return 'done';
68
- }
69
- if (svcs.length > 0 && svcs.find(isServiceFailed)) {
70
- callbacksRef.current.onFailure(svcs);
71
- return 'done';
72
- }
73
- return 'continue';
74
- });
75
- };
76
- const useStatusRefresh = (dockerComposePath, enabled, onServices) => {
77
- const onServicesRef = useRef(onServices);
78
- onServicesRef.current = onServices;
79
- usePoll(enabled, async () => {
80
- const svcs = await fetchServices(dockerComposePath);
81
- if (svcs !== null) {
82
- onServicesRef.current(svcs);
83
- }
84
- return 'continue';
85
- });
86
- };
87
- const useWorkflowPolling = (enabled, onRuns) => {
88
- const onRunsRef = useRef(onRuns);
89
- onRunsRef.current = onRuns;
90
- usePoll(enabled, async () => {
91
- try {
92
- const { runs } = await fetchWorkflowRuns({ limit: 100 });
93
- onRunsRef.current(runs);
94
- }
95
- catch {
96
- // API may not be ready yet
97
- }
98
- return 'continue';
99
- });
100
- };
101
- const useMainViewInput = (isActive, callbacks) => {
102
- const callbacksRef = useRef(callbacks);
103
- callbacksRef.current = callbacks;
104
- useInput(input => {
105
- if (input === 'o') {
106
- callbacksRef.current.onOpenTemporal();
107
- }
108
- if (input === 'w') {
109
- callbacksRef.current.onOpenWorkflows();
110
- }
111
- }, { isActive });
112
- };
113
- const useCtrlC = (onCleanup) => {
114
- const { exit } = useApp();
115
- const isExitingRef = useRef(false);
116
- useInput((input, key) => {
117
- if (key.ctrl && input === 'c' && !isExitingRef.current) {
118
- isExitingRef.current = true;
119
- void onCleanup().then(() => exit()).catch(err => exit(err instanceof Error ? err : new Error(String(err))));
120
- }
121
- });
122
- };
123
- const ServiceRow = ({ service }) => {
124
- const status = resolveServiceStatus(service);
125
- const ports = service.ports.length ? service.ports.join(', ') : '-';
126
- return (_jsxs(Box, { children: [_jsx(Box, { width: 3, children: _jsx(StatusIcon, { status: status }) }), _jsx(Box, { width: 16, children: _jsx(Text, { children: service.name }) }), _jsx(Text, { dimColor: true, children: status.padEnd(10) }), _jsx(Text, { dimColor: true, children: ports })] }));
127
- };
128
- const FailureWarning = ({ services }) => {
129
- const failed = services.filter(isServiceFailed);
130
- if (failed.length === 0) {
131
- return null;
132
- }
133
- const failedNames = failed.map(s => s.name).join(', ');
134
- const hasWorker = failed.some(s => s.name.toLowerCase().includes('worker'));
135
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { children: _jsx(Text, { backgroundColor: "red", color: "white", bold: true, children: " \u26A0\uFE0F SERVICE FAILURE DETECTED " }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "red", bold: true, children: "Failed services: " }), _jsx(Text, { children: failedNames })] }), hasWorker ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "yellow", bold: true, children: "\u26A1 The worker is not running!" }), _jsx(Text, { color: "yellow", children: ' Workflows will fail until the worker is restarted.' }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsxs(Text, { children: ["\uD83D\uDD0D Check the logs with: ", _jsx(Text, { color: "magenta", children: "docker compose logs worker" })] }) }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsxs(Text, { children: ["\uD83D\uDD27 If you just updated ", _jsx(Text, { italic: true, children: "@outputai/cli" }), ", try: ", _jsx(Text, { color: "magenta", children: "output fix" })] }) })] })) : (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["\uD83D\uDD0D Check the logs with: ", _jsx(Text, { color: "magenta", children: "docker compose logs <service-name>" })] }) }))] }));
136
- };
137
- const DevSuccessMessage = ({ services }) => {
138
- const divider = '─'.repeat(80);
139
- const sortedNames = services.map(s => s.name).sort().join('|');
140
- const logsCommand = `docker compose -p ${config.dockerServiceName} logs -f <${sortedNames}>`;
141
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { dimColor: true, children: divider }) }), _jsxs(Box, { children: [_jsx(Text, { color: "green", bold: true, children: '✅ SUCCESS! ' }), _jsx(Text, { bold: true, children: "Development services are running" })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { dimColor: true, children: divider }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "\uD83D\uDC33 SERVICES" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { color: "white", children: 'Temporal UI: ' }), _jsx(Text, { color: "cyan", children: config.temporalUiUrl })] }), _jsxs(Box, { children: [_jsx(Text, { color: "white", children: 'API Server: ' }), _jsxs(Text, { color: "yellow", children: ["localhost:", config.ports.api] })] })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { dimColor: true, children: divider }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "\uD83D\uDE80 RUN A WORKFLOW" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsx(Text, { color: "white", children: "In a new terminal, execute:" }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "cyan", children: "npx output workflow run blog_evaluator paulgraham_hwh" }) })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { dimColor: true, children: divider }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "\u26A1 USEFUL COMMANDS" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { color: "white", children: 'Open Temporal UI: ' }), _jsxs(Text, { color: "cyan", children: ["open ", config.temporalUiUrl] })] }), _jsxs(Box, { children: [_jsx(Text, { color: "white", children: 'View logs: ' }), _jsx(Text, { color: "cyan", children: logsCommand })] }), _jsxs(Box, { children: [_jsx(Text, { color: "white", children: 'Stop services: ' }), _jsx(Text, { color: "cyan", children: "Press Ctrl+C" })] })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { dimColor: true, children: divider }) }), _jsx(Text, { dimColor: true, children: "\uD83D\uDCA1 Tip: The Temporal UI lets you monitor workflow executions in real-time" })] }));
142
- };
143
- const WaitingView = ({ services }) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Waiting for services to become healthy..." })] }), services.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: services.map(s => _jsx(ServiceRow, { service: s }, s.name)) }))] }));
144
- const RunningView = ({ services, workflowSummary }) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "\uD83D\uDCCA Service Status" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: services.map(s => _jsx(ServiceRow, { service: s }, s.name)) }), _jsx(FailureWarning, { services: services }), workflowSummary && _jsx(WorkflowSummarySection, { summary: workflowSummary }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "cyan", children: '🌐 Temporal UI: ' }), _jsx(Text, { bold: true, children: config.temporalUiUrl })] }), _jsx(CommandFooter, { hints: [
145
- { key: 'o', label: 'open ui' },
146
- { key: 'w', label: 'view workflow runs' },
147
- { key: 'ctrl+c', label: 'stop' }
148
- ] })] }));
149
- const MainDevView = ({ phase, services, workflowSummary }) => {
150
- if (phase === 'waiting') {
151
- return _jsx(WaitingView, { services: services });
152
- }
153
- return _jsx(RunningView, { services: services, workflowSummary: workflowSummary });
154
- };
155
- export const DevApp = ({ dockerComposePath, onCleanup }) => {
156
- const { exit } = useApp();
157
- const [phase, setPhase] = useState('waiting');
158
- const [services, setServices] = useState([]);
159
- const [successItems, setSuccessItems] = useState([]);
160
- const [activeView, setActiveView] = useState('main');
161
- const [workflowRuns, setWorkflowRuns] = useState([]);
162
- useHealthPolling(dockerComposePath, phase === 'waiting', {
163
- onServices: setServices,
164
- onAllHealthy: svcs => {
165
- setSuccessItems([{ id: 'success', services: svcs }]);
166
- setPhase('running');
167
- },
168
- onFailure: () => {
169
- setPhase('failed');
170
- },
171
- onTimeout: () => exit(new Error('Timeout waiting for services to become healthy'))
172
- });
173
- useStatusRefresh(dockerComposePath, phase === 'running', setServices);
174
- useWorkflowPolling(phase === 'running' || phase === 'failed', setWorkflowRuns);
175
- useMainViewInput(activeView === 'main' && phase !== 'waiting', {
176
- onOpenTemporal: () => openUrl(config.temporalUiUrl),
177
- onOpenWorkflows: () => setActiveView('workflows')
178
- });
179
- useCtrlC(onCleanup);
180
- const workflowSummary = workflowRuns.length > 0 ? {
181
- running: workflowRuns.filter(r => r.status === 'running').length,
182
- completed: workflowRuns.filter(r => r.status === 'completed').length,
183
- failed: workflowRuns.filter(r => r.status === 'failed').length,
184
- total: workflowRuns.length
185
- } : null;
186
- return (_jsxs(_Fragment, { children: [_jsx(Static, { items: successItems, children: item => _jsx(DevSuccessMessage, { services: item.services }, item.id) }), _jsxs(Box, { flexDirection: "column", children: [activeView === 'main' && (_jsx(MainDevView, { phase: phase, services: services, workflowSummary: workflowSummary })), activeView === 'workflows' && (_jsx(WorkflowListView, { runs: workflowRuns, onBack: () => setActiveView('main') }))] })] }));
187
- };
@@ -1,6 +0,0 @@
1
- import React from 'react';
2
- import type { WorkflowRun } from '#services/workflow_runs.js';
3
- export declare const WorkflowListView: React.FC<{
4
- runs: WorkflowRun[];
5
- onBack: () => void;
6
- }>;
@@ -1,129 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect, useRef, useMemo } from 'react';
3
- import { Box, Text, useInput } from 'ink';
4
- import Spinner from 'ink-spinner';
5
- import { getWorkflowIdRunsRidResult } from '#api/generated/api.js';
6
- import { StatusIcon, statusColor } from '#components/status_icon.js';
7
- import { elapsedMs, formatDurationCompact } from '#utils/date_formatter.js';
8
- import { CommandFooter } from '#components/command_footer.js';
9
- import { openUrl } from '#utils/open_url.js';
10
- import { config } from '#config.js';
11
- const VISIBLE_ROWS = 15;
12
- const STATUS_ORDER = {
13
- running: 0,
14
- failed: 1,
15
- timed_out: 2,
16
- terminated: 3,
17
- canceled: 4,
18
- continued: 5,
19
- completed: 6
20
- };
21
- const sortRuns = (runs) => [...runs].sort((a, b) => {
22
- const statusDiff = (STATUS_ORDER[a.status ?? ''] ?? Infinity) - (STATUS_ORDER[b.status ?? ''] ?? Infinity);
23
- if (statusDiff !== 0) {
24
- return statusDiff;
25
- }
26
- const aTime = a.startedAt ? new Date(a.startedAt).getTime() : 0;
27
- const bTime = b.startedAt ? new Date(b.startedAt).getTime() : 0;
28
- return bTime - aTime;
29
- });
30
- const truncate = (str, max) => str.length > max ? str.slice(0, max - 1) + '…' : str;
31
- const COL = {
32
- indicator: 2,
33
- icon: 3,
34
- status: 12,
35
- type: 20,
36
- id: 32,
37
- duration: 10
38
- };
39
- const WorkflowRow = ({ run, selected }) => {
40
- const status = run.status ?? 'running';
41
- const color = statusColor(status);
42
- const duration = run.startedAt ? formatDurationCompact(elapsedMs(run.startedAt, run.completedAt)) : '-';
43
- return (_jsxs(Box, { children: [_jsx(Box, { width: COL.indicator, children: _jsx(Text, { color: selected ? 'cyan' : undefined, bold: selected, children: selected ? '▸' : ' ' }) }), _jsx(Box, { width: COL.icon, children: _jsx(StatusIcon, { status: status }) }), _jsx(Box, { width: COL.status, children: _jsx(Text, { color: color, children: status }) }), _jsx(Box, { width: COL.type, children: _jsx(Text, { bold: selected, children: truncate(run.workflowType ?? '-', COL.type - 2) }) }), _jsx(Box, { width: COL.id, children: _jsx(Text, { dimColor: !selected, children: truncate(run.workflowId ?? '-', COL.id - 2) }) }), _jsx(Box, { width: COL.duration, justifyContent: "flex-end", children: _jsx(Text, { dimColor: true, children: duration }) })] }));
44
- };
45
- const HeaderRow = () => (_jsxs(Box, { children: [_jsx(Box, { width: COL.indicator, children: _jsx(Text, { children: " " }) }), _jsx(Box, { width: COL.icon, children: _jsx(Text, { children: " " }) }), _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: "WORKFLOW ID" }) }), _jsx(Box, { width: COL.duration, justifyContent: "flex-end", children: _jsx(Text, { dimColor: true, bold: true, children: "DURATION" }) })] }));
46
- const WorkflowDetailPane = ({ detail, loading }) => {
47
- if (loading) {
48
- return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Loading details..." })] }));
49
- }
50
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Status: " }), _jsx(Text, { color: statusColor(detail.status ?? ''), children: detail.status })] }), detail.error && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: "red", children: "Error:" }), _jsx(Text, { color: "red", children: truncate(detail.error, 300) })] })), detail.output !== undefined && detail.output !== null && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Output:" }), _jsx(Text, { children: truncate(JSON.stringify(detail.output, null, 2), 400) })] }))] }));
51
- };
52
- export const WorkflowListView = ({ runs, onBack }) => {
53
- const [selectedIndex, setSelectedIndex] = useState(0);
54
- const [detail, setDetail] = useState(null);
55
- const [detailLoading, setDetailLoading] = useState(false);
56
- const cacheRef = useRef(new Map());
57
- const fetchIdRef = useRef(0);
58
- const sortedRuns = useMemo(() => sortRuns(runs), [runs]);
59
- const clampedIndex = Math.min(selectedIndex, Math.max(0, sortedRuns.length - 1));
60
- const selectedRun = sortedRuns[clampedIndex];
61
- const selectedWorkflowId = selectedRun?.workflowId;
62
- const selectedRunId = selectedRun?.runId;
63
- useEffect(() => {
64
- if (clampedIndex !== selectedIndex) {
65
- setSelectedIndex(clampedIndex);
66
- }
67
- }, [clampedIndex, selectedIndex]);
68
- useEffect(() => {
69
- if (!selectedWorkflowId || !selectedRunId) {
70
- setDetail(null);
71
- return;
72
- }
73
- const cacheKey = `${selectedWorkflowId}:${selectedRunId}`;
74
- const cached = cacheRef.current.get(cacheKey);
75
- if (cached) {
76
- setDetail(cached);
77
- setDetailLoading(false);
78
- return;
79
- }
80
- const currentFetchId = ++fetchIdRef.current;
81
- setDetailLoading(true);
82
- getWorkflowIdRunsRidResult(selectedWorkflowId, selectedRunId)
83
- .then(response => {
84
- if (fetchIdRef.current !== currentFetchId) {
85
- return;
86
- }
87
- const data = response.data;
88
- cacheRef.current.set(cacheKey, data);
89
- setDetail(data);
90
- setDetailLoading(false);
91
- })
92
- .catch(() => {
93
- if (fetchIdRef.current !== currentFetchId) {
94
- return;
95
- }
96
- setDetail(null);
97
- setDetailLoading(false);
98
- });
99
- }, [selectedWorkflowId, selectedRunId]);
100
- useInput((input, key) => {
101
- if (key.upArrow) {
102
- setSelectedIndex(i => Math.max(0, i - 1));
103
- }
104
- else if (key.downArrow) {
105
- setSelectedIndex(i => Math.min(sortedRuns.length - 1, i + 1));
106
- }
107
- else if (key.escape || input === 'q') {
108
- onBack();
109
- }
110
- else if (input === 'o' && selectedWorkflowId) {
111
- openUrl(`${config.temporalUiUrl}/namespaces/default/workflows/${selectedWorkflowId}`);
112
- }
113
- });
114
- const windowStart = useMemo(() => {
115
- const half = Math.floor(VISIBLE_ROWS / 2);
116
- const start = Math.max(0, clampedIndex - half);
117
- const maxStart = Math.max(0, sortedRuns.length - VISIBLE_ROWS);
118
- return Math.min(start, maxStart);
119
- }, [clampedIndex, sortedRuns.length]);
120
- const visibleRuns = sortedRuns.slice(windowStart, windowStart + VISIBLE_ROWS);
121
- if (sortedRuns.length === 0) {
122
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Workflow Runs" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "No workflow runs found." }) }), _jsx(CommandFooter, { hints: [{ key: 'q', label: 'back' }] })] }));
123
- }
124
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: ["Workflow Runs (", sortedRuns.length, ")"] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(HeaderRow, {}), windowStart > 0 && _jsxs(Text, { dimColor: true, children: [" \u2191 ", windowStart, " more above"] }), visibleRuns.map((run, i) => (_jsx(WorkflowRow, { run: run, selected: windowStart + i === clampedIndex }, `${run.workflowId}-${run.startedAt}-${windowStart + i}`))), windowStart + VISIBLE_ROWS < sortedRuns.length && (_jsxs(Text, { dimColor: true, children: [" \u2193 ", sortedRuns.length - windowStart - VISIBLE_ROWS, " more below"] }))] }), detail && _jsx(WorkflowDetailPane, { detail: detail, loading: detailLoading }), detailLoading && !detail && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Loading details..." })] })), _jsx(CommandFooter, { hints: [
125
- { key: '↑/↓', label: 'navigate' },
126
- { key: 'o', label: 'open in temporal' },
127
- { key: 'q', label: 'back' }
128
- ] })] }));
129
- };